Merge remote-tracking branch 'origin/4.18' into 4.19
diff --git a/.asf.yaml b/.asf.yaml
index a0ba915..8ce43df 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -27,15 +27,21 @@
     - infrastructure
     - java
     - python
-    - hosting
     - kvm
+    - libvirt
     - vsphere
+    - vmware
     - xenserver
     - xcp-ng
+    - orchestration
+    - virtualization
+    - virtual-machine
+    - kubernetes
 
   features:
     wiki: true
     issues: true
+    discussions: true
     projects: true
 
   enabled_merge_buttons:
@@ -45,5 +51,19 @@
 
   collaborators:
     - acs-robot
+    - kiranchavala
+    - rajujith
+    - alexandremattioli
+    - vishesh92
+    - GaOrtiga
+    - SadiJr
+    - winterhazel
+    - rp-
 
   protected_branches: ~
+
+notifications:
+  commits:      commits@cloudstack.apache.org
+  issues:       commits@cloudstack.apache.org
+  pullrequests: commits@cloudstack.apache.org
+  discussions:  users@cloudstack.apache.org
diff --git a/.github/linters/.flake8 b/.github/linters/.flake8
index 6a2235d..f250719 100644
--- a/.github/linters/.flake8
+++ b/.github/linters/.flake8
@@ -15,11 +15,17 @@
 # specific language governing permissions and limitations
 # under the License.
 
+# E112 Expected an indented block
+# E113 Unexpected indentation
+# E133 Closing bracket is missing indentation
 # E223 Tab before operator
 # E224 Tab after operator
+# E227 Missing whitespace around bitwise or shift operator
 # E242 Tab after ','
 # E273 Tab after keyword
 # E274 Tab before keyword
+# E742 Do not define classes named 'I', 'O', or 'l'
+# E743 Do not define functions named 'I', 'O', or 'l'
 # E901 SyntaxError or IndentationError
 # E902 IOError
 # W291 Trailing whitespace
@@ -28,4 +34,7 @@
 # W391 Blank line at end of file
 
 [flake8]
-select = E223,E224,E242,E273,E274,E901,E902,W291,W292,W293,W391
+exclude =
+    .git,
+    venv
+select = E112,E113,E133,E223,E224,E227,E242,E273,E274,E742,E743,E901,E902,W291,W292,W293,W391
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c90c37c..dc2bd9d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,11 +23,14 @@
   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
+permissions:
+  contents: read
+
 jobs:
   build:
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Set up JDK 11
         uses: actions/setup-java@v4
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 087e257..63f1085 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,6 +23,9 @@
   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
+permissions:
+  contents: read
+
 jobs:
   build:
     if: github.repository == 'apache/cloudstack'
@@ -59,7 +62,8 @@
                   smoke/test_domain_network_offerings
                   smoke/test_domain_service_offerings
                   smoke/test_domain_vpc_offerings",
-                "smoke/test_dynamicroles
+                "smoke/test_cluster_drs
+                  smoke/test_dynamicroles
                   smoke/test_enable_account_settings_for_domain
                   smoke/test_enable_role_based_users_in_projects
                   smoke/test_events_resource
@@ -80,7 +84,8 @@
                   smoke/test_metrics_api
                   smoke/test_migration
                   smoke/test_multipleips_per_nic
-                  smoke/test_nested_virtualization",
+                  smoke/test_nested_virtualization
+                  smoke/test_set_sourcenat",
                 "smoke/test_network
                   smoke/test_network_acl
                   smoke/test_network_ipv6
@@ -88,6 +93,7 @@
                   smoke/test_nic
                   smoke/test_nic_adapter_type
                   smoke/test_non_contigiousvlan
+                  smoke/test_object_stores
                   smoke/test_outofbandmanagement
                   smoke/test_outofbandmanagement_nestedplugin
                   smoke/test_over_provisioning
@@ -106,7 +112,8 @@
                   smoke/test_reset_configuration_settings
                   smoke/test_reset_vm_on_reboot
                   smoke/test_resource_accounting
-                  smoke/test_resource_detail",
+                  smoke/test_resource_detail
+                  smoke/test_global_acls",
                 "smoke/test_router_dhcphosts
                   smoke/test_router_dns
                   smoke/test_router_dnsservice
@@ -124,6 +131,7 @@
                   smoke/test_usage
                   smoke/test_usage_events
                   smoke/test_vm_deployment_planner
+                  smoke/test_vm_schedule
                   smoke/test_vm_life_cycle
                   smoke/test_vm_lifecycle_unmanage_import
                   smoke/test_vm_snapshot_kvm
@@ -186,11 +194,20 @@
                   component/test_vpc_network
                   component/test_vpc_offerings
                   component/test_vpc_routers
-                  component/test_vpn_users",
-                "component/test_vpc_network_lbrules" ]
+                  component/test_vpn_users
+                  component/test_vpc_network_lbrules",
+                "smoke/test_list_accounts
+                  smoke/test_list_disk_offerings
+                  smoke/test_list_domains
+                  smoke/test_list_hosts
+                  smoke/test_list_service_offerings
+                  smoke/test_list_storage_pools
+                  smoke/test_list_volumes"]
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
 
       - name: Set up JDK
         uses: actions/setup-java@v4
@@ -215,6 +232,11 @@
         run: |
           python3 -m pip install --user --upgrade urllib3 lxml paramiko nose texttable ipmisim pyopenssl pycrypto mock flask netaddr pylint pycodestyle six astroid
 
+      - 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
@@ -247,16 +269,23 @@
           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:MaxPermSize=800m -Djava.security.egd=file:/dev/urandom"
+          export MAVEN_OPTS="-Xmx4096m -XX:MaxPermSize=800m -Djava.security.egd=file:/dev/urandom  -javaagent:jacoco/lib/jacocoagent.jar=address=*,port=36320,output=tcpserver"
           echo -e "\nStarting simulator"
           set +e
           mvn -Dsimulator -Dorg.eclipse.jetty.annotations.maxWait=120 -pl :cloud-client-ui jetty:run 2>&1 > /tmp/jetty-log || true &
           while ! nc -vzw 5 localhost 8096 2>&1 > /dev/null; do grep Exception /tmp/jetty-log; sleep 10; done
           set -e
           echo -e "\nStarting Advanced Zone DataCenter deployment"
-          python3 tools/marvin/marvin/deployDataCenter.py -i setup/dev/advanced.cfg 2>&1 || true
+          python3 tools/marvin/marvin/deployDataCenter.py -i setup/dev/advdualzone.cfg 2>&1 || true
 
       - name: Run Integration Tests with Simulator
         run: |
@@ -269,10 +298,12 @@
           TESTS=($(echo $TESTS | tr -d '\n' | tr -s ' '))
           for suite in "${TESTS[@]}" ; do
             echo -e "Currently running test: $suite\n"
-            time nosetests-3.4 --with-xunit --xunit-file=integration-test-results/$suite.xml --with-marvin --marvin-config=setup/dev/advanced.cfg test/integration/$suite.py -s -a tags=advanced,required_hardware=false --zone=Sandbox-simulator --hypervisor=simulator || true ;
+            time nosetests-3.4 --with-xunit --xunit-file=integration-test-results/$suite.xml --with-marvin --marvin-config=setup/dev/advdualzone.cfg test/integration/$suite.py -s -a tags=advanced,required_hardware=false --zone=zim1 --hypervisor=simulator || true ;
           done
 
           echo -e "Stopping Simulator, integration tests run completed\n"
+          java -jar jacoco/lib/jacococli.jar dump --address localhost --port 36320 --destfile jacoco-it.exec
+          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 {} \;
 
@@ -280,3 +311,11 @@
         run: |
           echo -e "Simulator CI Test Results: (only failures listed)\n"
           python3 ./tools/marvin/xunit-reader.py integration-test-results/
+
+      - uses: codecov/codecov-action@v3
+        with:
+          files: jacoco-coverage.xml
+          fail_ci_if_error: true
+          flags: simulator-marvin-tests
+          verbose: true
+          name: codecov
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index fcbcf9b..f7b28fd 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -32,7 +32,9 @@
     name: codecov
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
 
       - name: Set up JDK11
         uses: actions/setup-java@v4
@@ -51,5 +53,6 @@
         with:
           files: ./client/target/site/jacoco-aggregate/jacoco.xml
           fail_ci_if_error: true
+          flags: unit-tests
           verbose: true
           name: codecov
diff --git a/.github/workflows/docker-cloudstack-simulator.yml b/.github/workflows/docker-cloudstack-simulator.yml
index ac6a6bf..21a09d0 100644
--- a/.github/workflows/docker-cloudstack-simulator.yml
+++ b/.github/workflows/docker-cloudstack-simulator.yml
@@ -47,7 +47,7 @@
       - name: Set Docker repository name
         run: echo "DOCKER_REPOSITORY=apache" >> $GITHUB_ENV
 
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Set ACS version
         run: echo "ACS_VERSION=$(grep '<version>' pom.xml | head -2 | tail -1 | cut -d'>' -f2 |cut -d'<' -f1)" >> $GITHUB_ENV
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 3dcddb2..784df0c 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -27,31 +27,12 @@
   cancel-in-progress: true
 
 jobs:
-  build:
-    permissions:
-      contents: read  # for actions/checkout to fetch code
-      statuses: write  # for github/super-linter to mark status of each linter run
-    name: Super-Linter Check
-    runs-on: ubuntu-22.04
-    steps:
-      - name: Checkout Code
-        uses: actions/checkout@v3
-        with:
-          # Full git history is needed to get a proper list of changed files within `super-linter`
-          fetch-depth: 0
-      - name: SuperLinter
-        uses: github/super-linter/slim@v4.9.6
-        env:
-          DEFAULT_BRANCH: main
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          VALIDATE_ALL_CODEBASE: false
-          VALIDATE_PYTHON_FLAKE8: true
   pre-commit:
     name: Run pre-commit
     runs-on: ubuntu-22.04
     steps:
       - name: Check Out
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Install
         run: |
           python -m pip install --upgrade pip
diff --git a/.github/workflows/main-sonar-check.yml b/.github/workflows/main-sonar-check.yml
index a651daa..66bb109 100644
--- a/.github/workflows/main-sonar-check.yml
+++ b/.github/workflows/main-sonar-check.yml
@@ -22,13 +22,17 @@
     branches:
       - main
 
+permissions:
+  contents: read # to fetch code (actions/checkout)
+  pull-requests: write # for sonar to comment on pull-request
+
 jobs:
   build:
     if: github.repository == 'apache/cloudstack'
     name: Main Sonar JaCoCo Build
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
diff --git a/.github/workflows/rat.yml b/.github/workflows/rat.yml
index 70b29ee..b8f83de 100644
--- a/.github/workflows/rat.yml
+++ b/.github/workflows/rat.yml
@@ -23,11 +23,14 @@
   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
+permissions:
+  contents: read
+
 jobs:
   build:
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - name: Set up JDK 11
         uses: actions/setup-java@v4
         with:
diff --git a/.github/workflows/sonar-check.yml b/.github/workflows/sonar-check.yml
index 652c6b2..2ebcf1fb 100644
--- a/.github/workflows/sonar-check.yml
+++ b/.github/workflows/sonar-check.yml
@@ -20,7 +20,8 @@
 on: [pull_request]
 
 permissions:
-  contents: read
+  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 }}
@@ -32,7 +33,7 @@
     name: Sonar JaCoCo Coverage
     runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           ref: "refs/pull/${{ github.event.number }}/merge"
           fetch-depth: 0
diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml
index 80e0807..4d89977 100644
--- a/.github/workflows/ui.yml
+++ b/.github/workflows/ui.yml
@@ -23,12 +23,15 @@
   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
+permissions:
+  contents: read
+
 jobs:
   build:
     runs-on: ubuntu-22.04
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Set up Node
         uses: actions/setup-node@v3
@@ -51,3 +54,12 @@
           npm run build
           npm run lint
           npm run test:unit
+
+      - uses: codecov/codecov-action@v3
+        with:
+          working-directory: ui
+          files: ./coverage/lcov.info
+          fail_ci_if_error: true
+          flags: uitests
+          verbose: true
+          name: codecov
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 773507c..0848a12 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -19,7 +19,7 @@
 default_language_version:
   # force all unspecified Python hooks to run python3
   python: python3
-minimum_pre_commit_version: "2.18.0"
+minimum_pre_commit_version: "2.17.0"
 repos:
   - repo: meta
     hooks:
@@ -31,13 +31,44 @@
       #- id: check-added-large-files
       - id: check-case-conflict
       #- id: check-executables-have-shebangs
-      #- id: check-merge-conflict
+      - id: check-merge-conflict
+      - id: check-symlinks
       - id: check-vcs-permalinks
       #- id: check-yaml
-      #- id: detect-private-key
+      - id: destroyed-symlinks
+      - id: detect-private-key
+        exclude: >
+          (?x)
+          ^scripts/vm/systemvm/id_rsa\.cloud$|
+          ^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$|
+          ^server/src/test/resources/certs/root_chain\.key$|
+          ^server/src/test/resources/certs/rsa_ca_signed\.key$|
+          ^server/src/test/resources/certs/rsa_self_signed_with_pwd\.key$|
+          ^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$
       - id: end-of-file-fixer
-        files: \.(java|md|py|txt|yaml|yml)$
+        exclude: \.vhd$
       #- id: fix-byte-order-marker
       - id: mixed-line-ending
-        files: \.(java|md|py|txt|yaml|yml)$
+        exclude: \.(cs|xml)$
       # - id: trailing-whitespace
+  - repo: https://github.com/pycqa/flake8
+    rev: 6.1.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/.*$
diff --git a/CHANGES.md b/CHANGES.md
index 6d6a268..ef498f8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,7 +2,7 @@
 Apache CloudStack CHANGES
 =========================
 
-Full release notes for each release are located in the project's documentation [website](http://docs.cloudstack.apache.org/projects/cloudstack-release-notes)
+Full release notes for each release are located in the project's documentation [website](https://docs.cloudstack.apache.org/en/latest/releasenotes/index.html)
 
 Version 4.5.0
 -------------
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ec3bb87..cdfbfe7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,7 +42,7 @@
 
 On your computer, follow these steps to setup a local repository for working on ACS:
 
-``` bash
+```bash
 $ git clone https://github.com/YOUR_ACCOUNT/cloudstack.git
 $ cd cloudstack
 $ git remote add upstream https://github.com/apache/cloudstack.git
@@ -60,7 +60,7 @@
 
 It is best practice to create a new branch each time you want to contribute to the project and only track the changes for that pull request in this branch.
 
-``` bash
+```bash
 $ git checkout -b feature_x
    (make your changes)
 $ git status
@@ -82,7 +82,7 @@
 2. Synchronize your local `main` branch with the `upstream/main` so you have all the latest changes from the project
 3. Rebase the latest project code into your `feature_x` branch so it is up-to-date with the upstream code
 
-``` bash
+```bash
 $ git checkout main
 $ git fetch upstream
 $ git rebase upstream/main
@@ -102,7 +102,7 @@
 
 > **IMPORTANT:** Make sure you have rebased your `feature_x` branch to include the latest code from `upstream/main` _before_ you do this.
 
-``` bash
+```bash
 $ git push origin main
 $ git push origin feature_x
 ```
@@ -128,7 +128,7 @@
 
 You can delete these deprecated branches with the following:
 
-``` bash
+```bash
 $ git checkout main
 $ git branch -D feature_x
 $ git push origin :feature_x
diff --git a/LICENSE b/LICENSE
index 48d8526..8be7d80 100644
--- a/LICENSE
+++ b/LICENSE
@@ -624,4 +624,3 @@
         from The Apache Software Foundation  http://www.apache.org/ 
             EasySSLProtocolSocketFactory.java 
             EasyX509TrustManager.java 
-
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
index 16345c1..8293a22 100644
--- a/PULL_REQUEST_TEMPLATE.md
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -10,10 +10,10 @@
 <!-- For addressing multiple issues/PRs, use multiple "Fixes: #<id>" -->
 <!-- Fixes: # -->
 
-<!--- ********************************************************************************* -->
-<!--- NOTE: AUTOMATATION USES THE DESCRIPTIONS TO SET LABELS AND PRODUCE DOCUMENTATION. -->
+<!--- ******************************************************************************* -->
+<!--- NOTE: AUTOMATION USES THE DESCRIPTIONS TO SET LABELS AND PRODUCE DOCUMENTATION. -->
 <!--- PLEASE PUT AN 'X' in only **ONE** box -->
-<!--- ********************************************************************************* -->
+<!--- ******************************************************************************* -->
 
 ### Types of changes
 
@@ -22,6 +22,7 @@
 - [ ] 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
 
 ### Feature/Enhancement Scale or Bug Severity
 
@@ -43,8 +44,12 @@
 
 
 ### How Has This Been Tested?
+
 <!-- Please describe in detail how you tested your changes. -->
 <!-- Include details of your testing environment, and the tests you ran to -->
+
+#### How did you try to break this feature and the system with this change?
+
 <!-- see how your change affects other areas of the code, etc. -->
 
 
diff --git a/README.md b/README.md
index e56857e..e193913 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # 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](tools/logo/apache_cloudstack.png)
+[![Apache CloudStack](tools/logo/apache_cloudstack.png)](https://cloudstack.apache.org/)
 
 Apache CloudStack is open source software designed to deploy and manage large
 networks of virtual machines, as a highly available, highly scalable
@@ -62,7 +62,7 @@
 ## Documentation
 
 * [Project Documentation](https://docs.cloudstack.apache.org)
-* [Release notes](https://docs.cloudstack.apache.org/projects/cloudstack-release-notes)
+* [Release notes](https://docs.cloudstack.apache.org/en/latest/releasenotes/index.html)
 * Developer [wiki](https://cwiki.apache.org/confluence/display/CLOUDSTACK/Home)
 * Design [documents](https://cwiki.apache.org/confluence/display/CLOUDSTACK/Design)
 * API [documentation](https://cloudstack.apache.org/api.html)
@@ -83,6 +83,13 @@
 to Apache CloudStack. We need folks to help with documentation, translation,
 promotion etc. See our contribution [page](http://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)
+to our GitHub repos. This allows you to use project GitHub with ability to report
+issue with tags, and be assigned to issues and PRs. This is done via the .asf.yaml
+file in this repo.
+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
@@ -135,7 +142,7 @@
 reside may have restrictions on the import, possession, use, and/or re-export to another
 country, of encryption software. BEFORE using any encryption software, please check your
 country's laws, regulations and policies concerning the import, possession, or use, and
-re-export of encryption software, to see if this is permitted. See http://www.wassenaar.org/
+re-export of encryption software, to see if this is permitted. See [The Wassenaar Arrangement](http://www.wassenaar.org/) 
 for more information.
 
 The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has
diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties
index 27c0e387..e600e8f 100644
--- a/agent/conf/agent.properties
+++ b/agent/conf/agent.properties
@@ -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
@@ -279,6 +279,11 @@
 # If this parameter is used, property host.overcommit.mem.mb must be set to 0.
 #host.reserved.mem.mb=1024
 
+# Number of CPU cores to subtract from advertised available cores.
+# These are reserved for system activity, or otherwise share host CPU resources with
+# CloudStack VM allocation.
+# host.reserved.cpu.count = 0
+
 # The model of Watchdog timer to present to the Guest.
 # For all models refer to the libvirt documentation.
 #vm.watchdog.model=i6300esb
@@ -373,6 +378,10 @@
 # If the time is exceeded shutdown will be forced.
 #stop.script.timeout=120
 
+# Time (in seconds) to wait for scripts to complete.
+# This is currently used only while checking if the host supports UEFI.
+#agent.script.timeout=60
+
 # Definition of VMs video model type.
 #vm.video.hardware=
 
@@ -398,3 +407,26 @@
 
 # The number of iothreads. There should be only 1 or 2 IOThreads per VM CPU (default is 1). The recommended number of iothreads is 1
 # iothreads=1
+
+# The path of an executable file/script for host health check for CloudStack to Auto Disable/Enable the host
+# depending on the return value of the file/script
+# agent.health.check.script.path=
+
+# Time interval (in milliseconds) between KVM heartbeats.
+# kvm.heartbeat.update.frequency=60000
+
+# Number of maximum tries to KVM heartbeats.
+# kvm.heartbeat.update.max.tries=5
+
+# Time amount (in milliseconds) for the KVM heartbeat retry sleep.
+# kvm.heartbeat.update.retry.sleep=10000
+
+# Timeout (in milliseconds) of the KVM heartbeat checker.
+# kvm.heartbeat.checker.timeout=360000
+
+# Instance Conversion from Vmware to KVM through virt-v2v. Enable verbose mode
+# virtv2v.verbose.enabled=false
+
+# If set to "true", the agent will register for libvirt domain events, allowing for immediate updates on crashed or
+# unexpectedly stopped. Experimental, requires agent restart.
+# libvirt.events.enabled=false
diff --git a/agent/pom.xml b/agent/pom.xml
index 407d6ef..178ff0f 100644
--- a/agent/pom.xml
+++ b/agent/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java
index e855893..9e0ee74 100644
--- a/agent/src/main/java/com/cloud/agent/Agent.java
+++ b/agent/src/main/java/com/cloud/agent/Agent.java
@@ -40,6 +40,8 @@
 
 import javax.naming.ConfigurationException;
 
+import com.cloud.resource.AgentStatusUpdater;
+import com.cloud.resource.ResourceStatusUpdater;
 import com.cloud.agent.api.PingAnswer;
 import com.cloud.utils.NumbersUtil;
 import org.apache.cloudstack.agent.lb.SetupMSListAnswer;
@@ -101,7 +103,7 @@
  *         For more configuration options, see the individual types.
  *
  **/
-public class Agent implements HandlerFactory, IAgentControl {
+public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater {
     protected static Logger s_logger = Logger.getLogger(Agent.class);
 
     public enum ExitStatus {
@@ -410,6 +412,20 @@
         }
     }
 
+    public void triggerUpdate() {
+        PingCommand command = _resource.getCurrentStatus(getId());
+        command.setOutOfBand(true);
+        s_logger.debug("Sending out of band ping");
+
+        final Request request = new Request(_id, -1, command, false);
+        request.setSequence(getNextSequence());
+        try {
+            _link.send(request.toBytes());
+        } catch (final ClosedChannelException e) {
+            s_logger.warn("Unable to send ping update: " + request.toString());
+        }
+    }
+
     protected void cancelTasks() {
         synchronized (_watchList) {
             for (final WatchTask task : _watchList) {
@@ -462,6 +478,10 @@
             } catch (final ClosedChannelException e) {
                 s_logger.warn("Unable to send request: " + request.toString());
             }
+
+            if (_resource instanceof ResourceStatusUpdater) {
+                ((ResourceStatusUpdater) _resource).registerStatusUpdater(this);
+            }
         }
     }
 
diff --git a/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java b/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java
index a1db88c..87610c2 100644
--- a/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java
+++ b/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java
@@ -92,11 +92,14 @@
             file = new File(path);
             try {
                 if (!file.createNewFile()) {
-                    s_logger.error("Unable to create _file: " + file.getAbsolutePath());
+                    s_logger.error(String.format("Unable to create _file: %s", file.getAbsolutePath()));
                     return false;
                 }
             } catch (IOException e) {
-                s_logger.error("Unable to create _file: " + file.getAbsolutePath(), e);
+                s_logger.error(String.format("Unable to create file: %s", file.getAbsolutePath()));
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug(String.format("IOException while trying to create file: %s", file.getAbsolutePath()), e);
+                }
                 return false;
             }
         }
diff --git a/agent/src/main/java/com/cloud/agent/dhcp/DhcpProtocolParserServer.java b/agent/src/main/java/com/cloud/agent/dhcp/DhcpProtocolParserServer.java
index 5d75acf..0ee9fd6 100644
--- a/agent/src/main/java/com/cloud/agent/dhcp/DhcpProtocolParserServer.java
+++ b/agent/src/main/java/com/cloud/agent/dhcp/DhcpProtocolParserServer.java
@@ -52,7 +52,6 @@
                     byte[] buf = new byte[bufferSize];
                     DatagramPacket dgp = new DatagramPacket(buf, buf.length);
                     dhcpSocket.receive(dgp);
-                    // _executor.execute(new DhcpPacketParser(buf));
                 }
             } catch (IOException e) {
                 s_logger.debug(e.getMessage());
diff --git a/agent/src/main/java/com/cloud/agent/dhcp/DhcpSnooper.java b/agent/src/main/java/com/cloud/agent/dhcp/DhcpSnooper.java
deleted file mode 100644
index 67718d5..0000000
--- a/agent/src/main/java/com/cloud/agent/dhcp/DhcpSnooper.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-package com.cloud.agent.dhcp;
-
-import java.net.InetAddress;
-import java.util.List;
-import java.util.Map;
-
-import com.cloud.utils.Pair;
-import com.cloud.utils.component.Adapter;
-
-public interface DhcpSnooper extends Adapter {
-
-    public InetAddress getIPAddr(String macAddr, String vmName);
-
-    public InetAddress getDhcpServerIP();
-
-    public void cleanup(String macAddr, String vmName);
-
-    public Map<String, InetAddress> syncIpAddr();
-
-    @Override
-    public boolean stop();
-
-    public void initializeMacTable(List<Pair<String, String>> macVmNameList);
-
-}
diff --git a/agent/src/main/java/com/cloud/agent/dhcp/FakeDhcpSnooper.java b/agent/src/main/java/com/cloud/agent/dhcp/FakeDhcpSnooper.java
deleted file mode 100644
index c42a5af..0000000
--- a/agent/src/main/java/com/cloud/agent/dhcp/FakeDhcpSnooper.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-package com.cloud.agent.dhcp;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import javax.naming.ConfigurationException;
-
-import org.apache.log4j.Logger;
-
-import com.cloud.utils.Pair;
-import com.cloud.utils.net.NetUtils;
-
-public class FakeDhcpSnooper implements DhcpSnooper {
-    private static final Logger s_logger = Logger.getLogger(FakeDhcpSnooper.class);
-    private Queue<String> _ipAddresses = new ConcurrentLinkedQueue<String>();
-    private Map<String, String> _macIpMap = new ConcurrentHashMap<String, String>();
-    private Map<String, InetAddress> _vmIpMap = new ConcurrentHashMap<String, InetAddress>();
-
-    @Override
-    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
-        String guestIpRange = (String)params.get("guest.ip.range");
-        if (guestIpRange != null) {
-            String[] guestIps = guestIpRange.split("-");
-            if (guestIps.length == 2) {
-                long start = NetUtils.ip2Long(guestIps[0]);
-                long end = NetUtils.ip2Long(guestIps[1]);
-                while (start <= end) {
-                    _ipAddresses.offer(NetUtils.long2Ip(start++));
-                }
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean start() {
-        return true;
-    }
-
-    @Override
-    public String getName() {
-        return "FakeDhcpSnooper";
-    }
-
-    @Override
-    public InetAddress getIPAddr(String macAddr, String vmName) {
-        String ipAddr = _ipAddresses.poll();
-        if (ipAddr == null) {
-            s_logger.warn("No ip addresses left in queue");
-            return null;
-        }
-        try {
-            InetAddress inetAddr = InetAddress.getByName(ipAddr);
-            _macIpMap.put(macAddr.toLowerCase(), ipAddr);
-            _vmIpMap.put(vmName, inetAddr);
-            s_logger.info("Got ip address " + ipAddr + " for vm " + vmName + " mac=" + macAddr.toLowerCase());
-            return inetAddr;
-        } catch (UnknownHostException e) {
-            s_logger.warn("Failed to get InetAddress for " + ipAddr);
-            return null;
-        }
-    }
-
-    @Override
-    public void cleanup(String macAddr, String vmName) {
-        try {
-            if (macAddr == null) {
-                return;
-            }
-            InetAddress inetAddr = _vmIpMap.remove(vmName);
-            String ipAddr = inetAddr.getHostName();
-            for (Map.Entry<String, String> entry : _macIpMap.entrySet()) {
-                if (entry.getValue().equalsIgnoreCase(ipAddr)) {
-                    macAddr = entry.getKey();
-                    break;
-                }
-            }
-            ipAddr = _macIpMap.remove(macAddr);
-
-            s_logger.info("Cleaning up for mac address: " + macAddr + " ip=" + ipAddr + " inetAddr=" + inetAddr);
-            if (ipAddr != null) {
-                _ipAddresses.offer(ipAddr);
-            }
-        } catch (Exception e) {
-            s_logger.debug("Failed to cleanup: " + e.toString());
-        }
-    }
-
-    @Override
-    public Map<String, InetAddress> syncIpAddr() {
-        return _vmIpMap;
-    }
-
-    @Override
-    public boolean stop() {
-        return false;
-    }
-
-    @Override
-    public void initializeMacTable(List<Pair<String, String>> macVmNameList) {
-
-    }
-
-    @Override
-    public InetAddress getDhcpServerIP() {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
-    public void setName(String name) {
-        // TODO Auto-generated method stub
-
-    }
-
-    @Override
-    public void setConfigParams(Map<String, Object> params) {
-        // TODO Auto-generated method stub
-
-    }
-
-    @Override
-    public Map<String, Object> getConfigParams() {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
-    public int getRunLevel() {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
-    @Override
-    public void setRunLevel(int level) {
-        // TODO Auto-generated method stub
-
-    }
-
-}
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 84a66d7..24a09ae 100644
--- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
+++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
@@ -314,6 +314,9 @@
      */
     public static final Property<String> OPENVSWITCH_DPDK_OVS_PATH = new Property<>("openvswitch.dpdk.ovs.path", null, String.class);
 
+    public static final Property<String> HEALTH_CHECK_SCRIPT_PATH =
+            new Property<>("agent.health.check.script.path", null, String.class);
+
     /**
      * Sets the hypervisor type.<br>
      * Possible values: kvm | lxc <br>
@@ -502,6 +505,15 @@
     public static final Property<Integer> HOST_RESERVED_MEM_MB = new Property<>("host.reserved.mem.mb", 1024);
 
     /**
+     * How many host CPUs to reserve for non-allocation.<br>
+     * This can be used to set aside CPU cores on the host for other tasks, such as running hyperconverged storage<br>
+     * processes, etc.
+     * Data type: Integer.<br>
+     * Default value: <code>0</code>
+     */
+    public static final Property<Integer> HOST_RESERVED_CPU_CORE_COUNT = new Property<>("host.reserved.cpu.count", 0);
+
+    /**
      * The model of Watchdog timer to present to the Guest.<br>
      * For all models refer to the libvirt documentation.<br>
      * Data type: String.<br>
@@ -529,10 +541,10 @@
     /**
      * Heartbeat update timeout (in ms).<br>
      * Depending on the use case, this timeout might need increasing/decreasing.<br>
-     * Data type: Integer.<br>
-     * Default value: <code>60000</code>
+     * Data type: Long.<br>
+     * Default value: <code>60000L</code>
      */
-    public static final Property<Integer> HEARTBEAT_UPDATE_TIMEOUT = new Property<>("heartbeat.update.timeout", 60000);
+    public static final Property<Long> HEARTBEAT_UPDATE_TIMEOUT = new Property<>("heartbeat.update.timeout", 60000L);
 
     /**
      * The timeout (in seconds) to retrieve the target's domain ID when migrating a VM with KVM. <br>
@@ -648,6 +660,14 @@
     public static final Property<Integer> STOP_SCRIPT_TIMEOUT = new Property<>("stop.script.timeout", 120);
 
     /**
+     * Time (in seconds) to wait for scripts to complete.<br>
+     * This is currently used only while checking if the host supports UEFI.<br>
+     * Data type: Integer.<br>
+     * Default value: <code>60</code>
+     */
+    public static final Property<Integer> AGENT_SCRIPT_TIMEOUT = new Property<>("agent.script.timeout", 60);
+
+    /**
      * Definition of VMs video model type.<br>
      * Data type: String.<br>
      * Default value: <code>null</code>
@@ -676,6 +696,13 @@
     public static final Property<Boolean> DEVELOPER = new Property<>("developer", false);
 
     /**
+     * If set to "true", the agent will register for libvirt domain events, allowing for immediate updates on crashed or unexpectedly
+     * stopped VMs. Experimental, requires agent restart.
+     * Default value: <code>false</code>
+     */
+    public static final Property<Boolean> LIBVIRT_EVENTS_ENABLED = new Property<>("libvirt.events.enabled", false);
+
+    /**
      * Can only be used if developer = true. This property is used to define the local bridge name and private network name.<br>
      * Data type: String.<br>
      * Default value: <code>null</code>
@@ -724,6 +751,13 @@
     public static final Property<Integer> IOTHREADS = new Property<>("iothreads", 1);
 
     /**
+     * Enable verbose mode for virt-v2v Instance Conversion from Vmware to KVM
+     * Data type: Boolean.<br>
+     * Default value: <code>false</code>
+     */
+    public static final Property<Boolean> VIRTV2V_VERBOSE_ENABLED = new Property<>("virtv2v.verbose.enabled", false);
+
+    /**
      * BGP controll CIDR
      * Data type: String.<br>
      * Default value: <code>169.254.0.0/16</code>
@@ -731,6 +765,38 @@
     public static final Property<String> CONTROL_CIDR = new Property<>("control.cidr", "169.254.0.0/16");
 
     /**
+     * Time interval (in milliseconds) between KVM heartbeats. <br>
+     * This property is for KVM only.
+     * Data type: Long.<br>
+     * Default value: <code>60000l</code>
+     */
+    public static final Property<Long> KVM_HEARTBEAT_UPDATE_FREQUENCY = new Property<>("kvm.heartbeat.update.frequency", 60000L);
+
+    /**
+     * Number of maximum tries to KVM heartbeats. <br>
+     * This property is for KVM only.
+     * Data type: Long.<br>
+     * Default value: <code>5l</code>
+     */
+    public static final Property<Long> KVM_HEARTBEAT_UPDATE_MAX_TRIES = new Property<>("kvm.heartbeat.update.max.tries", 5L);
+
+    /**
+     * Time amount (in milliseconds) for the KVM heartbeat retry sleep. <br>
+     * This property is for KVM only.
+     * Data type: Long.<br>
+     * Default value: <code>10000l</code>
+     */
+    public static final Property<Long> KVM_HEARTBEAT_UPDATE_RETRY_SLEEP = new Property<>("kvm.heartbeat.update.retry.sleep", 10000L);
+
+    /**
+     * Timeout (in milliseconds) of the KVM heartbeat checker. <br>
+     * This property is for KVM only.
+     * Data type: Long.<br>
+     * Default value: <code>360000l</code>
+     */
+    public static final Property<Long> KVM_HEARTBEAT_CHECKER_TIMEOUT = new Property<>("kvm.heartbeat.checker.timeout", 360000L);
+
+    /**
      * Keystore passphrase
      * Data type: String.<br>
      * Default value: <code>null</code>
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 5b04f57..5412c34 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
@@ -83,16 +83,16 @@
 public class ConsoleProxyResource extends ServerResourceBase implements ServerResource {
     static final Logger s_logger = Logger.getLogger(ConsoleProxyResource.class);
 
-    private final Properties _properties = new Properties();
-    private Thread _consoleProxyMain = null;
+    private final Properties properties = new Properties();
+    private Thread consoleProxyMain = null;
 
-    long _proxyVmId;
-    int _proxyPort;
+    long proxyVmId;
+    int proxyPort;
 
-    String _localgw;
-    String _eth1ip;
-    String _eth1mask;
-    String _pubIp;
+    String localGateway;
+    String eth1Ip;
+    String eth1Mask;
+    String publicIp;
 
     @Override
     public Answer executeRequest(final Command cmd) {
@@ -203,10 +203,10 @@
     public synchronized StartupCommand[] initialize() {
         final StartupProxyCommand cmd = new StartupProxyCommand();
         fillNetworkInformation(cmd);
-        cmd.setProxyPort(_proxyPort);
-        cmd.setProxyVmId(_proxyVmId);
-        if (_pubIp != null)
-            cmd.setPublicIpAddress(_pubIp);
+        cmd.setProxyPort(proxyPort);
+        cmd.setProxyVmId(proxyVmId);
+        if (publicIp != null)
+            cmd.setPublicIpAddress(publicIp);
         return new StartupCommand[] {cmd};
     }
 
@@ -221,10 +221,10 @@
 
     @Override
     public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
-        _localgw = (String)params.get("localgw");
-        _eth1mask = (String)params.get("eth1mask");
-        _eth1ip = (String)params.get("eth1ip");
-        if (_eth1ip != null) {
+        localGateway = (String)params.get("localgw");
+        eth1Mask = (String)params.get("eth1mask");
+        eth1Ip = (String)params.get("eth1ip");
+        if (eth1Ip != null) {
             params.put("private.network.device", "eth1");
         } else {
             s_logger.info("eth1ip parameter has not been configured, assuming that we are not inside a system vm");
@@ -240,40 +240,40 @@
         super.configure(name, params);
 
         for (Map.Entry<String, Object> entry : params.entrySet()) {
-            _properties.put(entry.getKey(), entry.getValue());
+            properties.put(entry.getKey(), entry.getValue());
         }
 
         String value = (String)params.get("premium");
         if (value != null && value.equals("premium"))
-            _proxyPort = 443;
+            proxyPort = 443;
         else {
             value = (String)params.get("consoleproxy.httpListenPort");
-            _proxyPort = NumbersUtil.parseInt(value, 80);
+            proxyPort = NumbersUtil.parseInt(value, 80);
         }
 
         value = (String)params.get("proxy_vm");
-        _proxyVmId = NumbersUtil.parseLong(value, 0);
+        proxyVmId = NumbersUtil.parseLong(value, 0);
 
-        if (_localgw != null) {
+        if (localGateway != null) {
             String mgmtHosts = (String)params.get("host");
-            if (_eth1ip != null) {
+            if (eth1Ip != null) {
                 for (final String mgmtHost : mgmtHosts.split(",")) {
-                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
+                    addRouteToInternalIpOrCidr(localGateway, eth1Ip, eth1Mask, mgmtHost);
                 }
                 String internalDns1 = (String) params.get("internaldns1");
                 if (internalDns1 == null) {
                     s_logger.warn("No DNS entry found during configuration of ConsoleProxy");
                 } else {
-                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, internalDns1);
+                    addRouteToInternalIpOrCidr(localGateway, eth1Ip, eth1Mask, internalDns1);
                 }
                 String internalDns2 = (String) params.get("internaldns2");
                 if (internalDns2 != null) {
-                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, internalDns2);
+                    addRouteToInternalIpOrCidr(localGateway, eth1Ip, eth1Mask, internalDns2);
                 }
             }
         }
 
-        _pubIp = (String)params.get("public.ip");
+        publicIp = (String)params.get("public.ip");
 
         value = (String)params.get("disable_rp_filter");
         if (value != null && value.equalsIgnoreCase("true")) {
@@ -281,7 +281,7 @@
         }
 
         if (s_logger.isInfoEnabled())
-            s_logger.info("Receive proxyVmId in ConsoleProxyResource configuration as " + _proxyVmId);
+            s_logger.info("Receive proxyVmId in ConsoleProxyResource configuration as " + proxyVmId);
 
         return true;
     }
@@ -301,7 +301,7 @@
             if (eth1ip != null && eth1mask != null) {
                 inSameSubnet = NetUtils.sameSubnet(eth1ip, destIpOrCidr, eth1mask);
             } else {
-                s_logger.warn("addRouteToInternalIp: unable to determine same subnet: _eth1ip=" + eth1ip + ", dest ip=" + destIpOrCidr + ", _eth1mask=" + eth1mask);
+                s_logger.warn("addRouteToInternalIp: unable to determine same subnet: eth1ip=" + eth1ip + ", dest ip=" + destIpOrCidr + ", eth1mask=" + eth1mask);
             }
         } else {
             inSameSubnet = NetUtils.isNetworkAWithinNetworkB(destIpOrCidr, NetUtils.ipAndNetMaskToCidr(eth1ip, eth1mask));
@@ -327,15 +327,15 @@
 
     @Override
     public String getName() {
-        return _name;
+        return name;
     }
 
     private void launchConsoleProxy(final byte[] ksBits, final String ksPassword, final String encryptorPassword, final Boolean isSourceIpCheckEnabled) {
         final Object resource = this;
         s_logger.info("Building class loader for com.cloud.consoleproxy.ConsoleProxy");
-        if (_consoleProxyMain == null) {
+        if (consoleProxyMain == null) {
             s_logger.info("Running com.cloud.consoleproxy.ConsoleProxy with encryptor password=" + encryptorPassword);
-            _consoleProxyMain = new Thread(new ManagedContextRunnable() {
+            consoleProxyMain = new Thread(new ManagedContextRunnable() {
                 @Override
                 protected void runInContext() {
                     try {
@@ -343,7 +343,7 @@
                         try {
                             s_logger.info("Invoke startWithContext()");
                             Method method = consoleProxyClazz.getMethod("startWithContext", Properties.class, Object.class, byte[].class, String.class, String.class, Boolean.class);
-                            method.invoke(null, _properties, resource, ksBits, ksPassword, encryptorPassword, isSourceIpCheckEnabled);
+                            method.invoke(null, properties, resource, ksBits, ksPassword, encryptorPassword, isSourceIpCheckEnabled);
                         } catch (SecurityException e) {
                             s_logger.error("Unable to launch console proxy due to SecurityException", e);
                             System.exit(ExitStatus.Error.value());
@@ -366,8 +366,8 @@
                     }
                 }
             }, "Console-Proxy-Main");
-            _consoleProxyMain.setDaemon(true);
-            _consoleProxyMain.start();
+            consoleProxyMain.setDaemon(true);
+            consoleProxyMain.start();
         } else {
             s_logger.info("com.cloud.consoleproxy.ConsoleProxy is already running");
 
@@ -430,27 +430,27 @@
     }
 
     public void reportLoadInfo(String gsonLoadInfo) {
-        ConsoleProxyLoadReportCommand cmd = new ConsoleProxyLoadReportCommand(_proxyVmId, gsonLoadInfo);
+        ConsoleProxyLoadReportCommand cmd = new ConsoleProxyLoadReportCommand(proxyVmId, gsonLoadInfo);
         try {
             getAgentControl().postRequest(cmd);
 
             if (s_logger.isDebugEnabled())
-                s_logger.debug("Report proxy load info, proxy : " + _proxyVmId + ", load: " + gsonLoadInfo);
+                s_logger.debug("Report proxy load info, proxy : " + proxyVmId + ", load: " + gsonLoadInfo);
         } catch (AgentControlChannelException e) {
             s_logger.error("Unable to send out load info due to " + e.getMessage(), e);
         }
     }
 
     public void ensureRoute(String address) {
-        if (_localgw != null) {
+        if (localGateway != null) {
             if (s_logger.isDebugEnabled())
-                s_logger.debug("Ensure route for " + address + " via " + _localgw);
+                s_logger.debug("Ensure route for " + address + " via " + localGateway);
 
             // this method won't be called in high frequency, serialize access
             // to script execution
             synchronized (this) {
                 try {
-                    addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, address);
+                    addRouteToInternalIpOrCidr(localGateway, eth1Ip, eth1Mask, address);
                 } catch (Throwable e) {
                     s_logger.warn("Unexpected exception while adding internal route to " + address, e);
                 }
diff --git a/agent/src/test/java/com/cloud/agent/AgentShellTest.java b/agent/src/test/java/com/cloud/agent/AgentShellTest.java
index 27bc3dc..f715177 100644
--- a/agent/src/test/java/com/cloud/agent/AgentShellTest.java
+++ b/agent/src/test/java/com/cloud/agent/AgentShellTest.java
@@ -26,20 +26,21 @@
 
 import com.cloud.agent.properties.AgentProperties;
 import com.cloud.agent.properties.AgentPropertiesFileHandler;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
 import com.cloud.utils.StringUtils;
 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.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class AgentShellTest {
 
     @InjectMocks
@@ -58,6 +59,18 @@
     @Mock
     UUID uuidMock;
 
+    MockedStatic<AgentPropertiesFileHandler> agentPropertiesFileHandlerMocked;
+
+    @Before
+    public void setUp() throws Exception {
+         agentPropertiesFileHandlerMocked = Mockito.mockStatic(AgentPropertiesFileHandler.class, Mockito.CALLS_REAL_METHODS);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        agentPropertiesFileHandlerMocked.close();
+    }
+
     @Test
     public void parseCommand() throws ConfigurationException {
         AgentShell shell = new AgentShell();
@@ -106,44 +119,35 @@
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getPortOrWorkersTestValueIsNullGetFromProperty() {
         int expected = 195;
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
 
         int result = agentShellSpy.getPortOrWorkers(null, propertyIntegerMock);
         Assert.assertEquals(expected, result);
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getPortOrWorkersTestValueIsNotAValidIntegerReturnDefaultFromProperty() {
         int expected = 42;
-
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
         Mockito.doReturn(expected).when(propertyIntegerMock).getDefaultValue();
 
         int result = agentShellSpy.getPortOrWorkers("test", propertyIntegerMock);
         Assert.assertEquals(expected, result);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.never());
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()), Mockito.never());
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getPortOrWorkersTestValueIsAValidIntegerReturnValue() {
         int expected = 42;
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
         Mockito.doReturn(79).when(propertyIntegerMock).getDefaultValue();
 
         int result = agentShellSpy.getPortOrWorkers(String.valueOf(expected), propertyIntegerMock);
         Assert.assertEquals(expected, result);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.never());
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()), Mockito.never());
     }
 
     @Test
@@ -183,41 +187,34 @@
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getZoneOrPodTestValueIsNullAndPropertyStartsAndEndsWithAtSignReturnPropertyDefaultValue() {
         String expected = "default";
-
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn("test");
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn("test");
 
         Mockito.doReturn(true).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
         Mockito.doReturn(expected).when(propertyStringMock).getDefaultValue();
 
         String result = agentShellSpy.getZoneOrPod(null, propertyStringMock);
         Assert.assertEquals(expected, result);
+
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getZoneOrPodTestValueIsNullAndPropertyDoesNotStartAndEndWithAtSignReturnPropertyDefaultValue() {
         String expected = "test";
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
 
         Mockito.doReturn(false).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
-        Mockito.doReturn("default").when(propertyStringMock).getDefaultValue();
 
         String result = agentShellSpy.getZoneOrPod(null, propertyStringMock);
         Assert.assertEquals(expected, result);
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getZoneOrPodTestValueIsNotNullAndStartsAndEndsWithAtSignReturnPropertyDefaultValue() {
         String expected = "default";
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
 
         Mockito.doReturn(true).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
         Mockito.doReturn(expected).when(propertyStringMock).getDefaultValue();
@@ -225,25 +222,20 @@
         String result = agentShellSpy.getZoneOrPod("test", propertyStringMock);
         Assert.assertEquals(expected, result);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.never());
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()), Mockito.never());
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getZoneOrPodTestValueIsNotNullAndDoesNotStartAndEndWithAtSignReturnPropertyDefaultValue() {
         String expected = "test";
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
 
         Mockito.doReturn(false).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
-        Mockito.doReturn("default").when(propertyStringMock).getDefaultValue();
 
         String result = agentShellSpy.getZoneOrPod(expected, propertyStringMock);
         Assert.assertEquals(expected, result);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.never());
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()), Mockito.never());
     }
 
     @Test
@@ -255,12 +247,10 @@
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getGuidTestGuidIsNullReturnProperty() throws ConfigurationException {
         String expected = "test";
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
 
         String result = agentShellSpy.getGuid(null);
 
@@ -268,34 +258,34 @@
     }
 
     @Test
-    @PrepareForTest({AgentShell.class, AgentPropertiesFileHandler.class})
     public void getGuidTestGuidAndPropertyAreNullIsDeveloperGenerateNewUuid() throws ConfigurationException {
         String expected = "test";
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class, UUID.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(null, true);
-        PowerMockito.when(UUID.randomUUID()).thenReturn(uuidMock);
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(null, true);
+        MockedStatic<UUID> uuidMocked = Mockito.mockStatic(UUID.class);
+        uuidMocked.when(() -> UUID.randomUUID()).thenReturn(uuidMock);
         Mockito.doReturn(expected).when(uuidMock).toString();
 
         String result = agentShellSpy.getGuid(null);
 
         Assert.assertEquals(expected, result);
+        uuidMocked.close();
     }
 
     @Test(expected = ConfigurationException.class)
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void getGuidTestGuidAndPropertyAreNullIsNotDeveloperThrowConfigurationException() throws ConfigurationException {
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(null, false);
+
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(null, false);
 
         agentShellSpy.getGuid(null);
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void setHostTestValueIsNotNullAndStartsAndEndsWithAtSignThrowConfigurationException(){
         Mockito.doReturn(true).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
+
+
+
 
         boolean error = false;
 
@@ -309,16 +299,14 @@
             throw new AssertionError("This test expects a ConfigurationException.");
         }
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.never());
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()), Mockito.never());
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void setHostTestValueIsNullPropertyStartsAndEndsWithAtSignThrowConfigurationException(){
         Mockito.doReturn(true).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn("test");
+
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn("test");
 
         boolean error = false;
 
@@ -332,37 +320,32 @@
             throw new AssertionError("This test expects a ConfigurationException.");
         }
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class);
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()));
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void setHostTestValueIsNotNullAndDoesNotStartAndEndWithAtSignSetHosts() throws ConfigurationException {
         String expected = "test";
         Mockito.doReturn(false).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
 
         agentShellSpy.setHost(expected);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.never());
-        AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()), Mockito.never());
 
         Mockito.verify(agentShellSpy).setHosts(expected);
     }
 
     @Test
-    @PrepareForTest(AgentPropertiesFileHandler.class)
     public void setHostTestValueIsNullPropertyDoesNotStartAndEndWithAtSignSetHosts() throws ConfigurationException {
         String expected = "test";
 
         Mockito.doReturn(false).when(agentShellSpy).isValueStartingAndEndingWithAtSign(Mockito.any());
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
+
+        agentPropertiesFileHandlerMocked.when(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn(expected);
 
         agentShellSpy.setHost(null);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class);
+        agentPropertiesFileHandlerMocked.verify(() -> AgentPropertiesFileHandler.getPropertyValue(Mockito.any()));
         AgentPropertiesFileHandler.getPropertyValue(Mockito.any());
 
         Mockito.verify(agentShellSpy).setHosts(expected);
diff --git a/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java b/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
index 1a73bf2..749f0f1 100644
--- a/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
+++ b/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
@@ -24,18 +24,17 @@
 import java.io.IOException;
 import java.util.Properties;
 import junit.framework.TestCase;
-import org.apache.commons.beanutils.ConvertUtils;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({PropertiesUtil.class, ConvertUtils.class})
+@RunWith(MockitoJUnitRunner.class)
 public class AgentPropertiesFileHandlerTest extends TestCase {
 
     @Mock
@@ -53,14 +52,27 @@
     @Mock
     Properties propertiesMock;
 
+    MockedStatic<PropertiesUtil> propertiesUtilMocked;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        propertiesUtilMocked = Mockito.mockStatic(PropertiesUtil.class);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        propertiesUtilMocked.close();
+    }
+
     @Test
     public void getPropertyValueTestFileNotFoundReturnDefaultValueNull() throws Exception{
         String expectedResult = null;
 
         AgentProperties.Property<String> agentPropertiesStringMock = new AgentProperties.Property<String>("Test-null", null, String.class);
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(null).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(null);
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
 
@@ -72,8 +84,7 @@
         String expectedResult = "default value";
         Mockito.doReturn(expectedResult).when(agentPropertiesStringMock).getDefaultValue();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(null).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(null);
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
 
@@ -85,9 +96,8 @@
         String expectedResult = "default value";
         Mockito.doReturn(expectedResult).when(agentPropertiesStringMock).getDefaultValue();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doThrow(new IOException()).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenThrow(new IOException());
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
 
@@ -100,10 +110,9 @@
         Mockito.doReturn(expectedResult).when(agentPropertiesStringMock).getDefaultValue();
         Mockito.doReturn("name").when(agentPropertiesStringMock).getName();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doReturn(propertiesMock).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
-        PowerMockito.doReturn("").when(propertiesMock).getProperty(Mockito.anyString());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
+        propertiesUtilMocked.when(() -> propertiesMock.getProperty(Mockito.anyString())).thenReturn("");
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
 
@@ -116,10 +125,9 @@
         Mockito.doReturn(expectedResult).when(agentPropertiesStringMock).getDefaultValue();
         Mockito.doReturn("name").when(agentPropertiesStringMock).getName();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doReturn(propertiesMock).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
-        PowerMockito.doReturn(null).when(propertiesMock).getProperty(Mockito.anyString());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
+        propertiesUtilMocked.when(() -> propertiesMock.getProperty(Mockito.anyString())).thenReturn(null);
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
 
@@ -132,9 +140,8 @@
         Mockito.doReturn("default value").when(agentPropertiesStringMock).getDefaultValue();
         Mockito.doReturn("name").when(agentPropertiesStringMock).getName();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doReturn(propertiesMock).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
         Mockito.doReturn(expectedResult).when(propertiesMock).getProperty(Mockito.anyString());
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
@@ -148,9 +155,8 @@
 
         AgentProperties.Property<String> agentPropertiesStringMock = new AgentProperties.Property<String>("Test-null", null, String.class);
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doReturn(propertiesMock).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
         Mockito.doReturn(expectedResult).when(propertiesMock).getProperty(Mockito.anyString());
 
         String result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesStringMock);
@@ -165,9 +171,8 @@
         Mockito.doReturn("name").when(agentPropertiesIntegerMock).getName();
         Mockito.doReturn(Integer.class).when(agentPropertiesIntegerMock).getTypeClass();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doReturn(propertiesMock).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
         Mockito.doReturn(String.valueOf(expectedResult)).when(propertiesMock).getProperty(Mockito.anyString());
 
         Integer result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesIntegerMock);
@@ -182,9 +187,8 @@
         Mockito.doReturn("name").when(agentPropertiesLongMock).getName();
         Mockito.doReturn(Long.class).when(agentPropertiesLongMock).getTypeClass();
 
-        PowerMockito.mockStatic(PropertiesUtil.class);
-        PowerMockito.doReturn(fileMock).when(PropertiesUtil.class, "findConfigFile", Mockito.anyString());
-        PowerMockito.doReturn(propertiesMock).when(PropertiesUtil.class, "loadFromFile", Mockito.any());
+        propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
         Mockito.doReturn(String.valueOf(expectedResult)).when(propertiesMock).getProperty(Mockito.anyString());
 
         Long result = AgentPropertiesFileHandler.getPropertyValue(agentPropertiesLongMock);
diff --git a/agent/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/agent/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/agent/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/api/pom.xml b/api/pom.xml
index fabc263..d7f4f54 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
@@ -37,6 +37,11 @@
             <artifactId>gson</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+            <version>${cs.commons-math3.version}</version>
+        </dependency>
+        <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
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 b3c6120..c873139 100644
--- a/api/src/main/java/com/cloud/agent/api/Command.java
+++ b/api/src/main/java/com/cloud/agent/api/Command.java
@@ -47,6 +47,13 @@
         return wait;
     }
 
+    /**
+     * This is the time in seconds that the agent will wait before waiting for an answer from the endpoint.
+     * The actual wait time is twice the value of this variable.
+     * See {@link com.cloud.agent.manager.AgentAttache#send(com.cloud.agent.transport.Request, int) AgentAttache#send}  implementation for more details.
+     *
+     * @param wait
+     **/
     public void setWait(int wait) {
         this.wait = wait;
     }
diff --git a/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java b/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java
index d923694..eb7e881 100644
--- a/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java
+++ b/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java
@@ -52,6 +52,13 @@
         this.details = details;
     }
 
+    public StoragePoolInfo(String uuid, String host, String hostPath, String localPath, StoragePoolType poolType, long capacityBytes, long availableBytes,
+            Map<String, String> details, String name) {
+        this(uuid, host, hostPath, localPath, poolType, capacityBytes, availableBytes);
+        this.details = details;
+        this.name = name;
+    }
+
     public long getCapacityBytes() {
         return capacityBytes;
     }
diff --git a/api/src/main/java/com/cloud/agent/api/to/HostTO.java b/api/src/main/java/com/cloud/agent/api/to/HostTO.java
index 967a63d..ee14e65 100644
--- a/api/src/main/java/com/cloud/agent/api/to/HostTO.java
+++ b/api/src/main/java/com/cloud/agent/api/to/HostTO.java
@@ -24,6 +24,7 @@
     private NetworkTO publicNetwork;
     private NetworkTO storageNetwork1;
     private NetworkTO storageNetwork2;
+    private String parent;
 
     protected HostTO() {
     }
@@ -40,6 +41,9 @@
         if (vo.getStorageIpAddressDeux() != null) {
             storageNetwork2 = new NetworkTO(vo.getStorageIpAddressDeux(), vo.getStorageNetmaskDeux(), vo.getStorageMacAddressDeux());
         }
+        if (vo.getParent() != null) {
+            parent = vo.getParent();
+        }
     }
 
     public String getGuid() {
@@ -81,4 +85,12 @@
     public void setStorageNetwork2(NetworkTO storageNetwork2) {
         this.storageNetwork2 = storageNetwork2;
     }
+
+    public String getParent() {
+        return parent;
+    }
+
+    public void setParent(String parent) {
+        this.parent = parent;
+    }
 }
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
new file mode 100644
index 0000000..6e7aa8b
--- /dev/null
+++ b/api/src/main/java/com/cloud/agent/api/to/RemoteInstanceTO.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 com.cloud.agent.api.to;
+
+import com.cloud.agent.api.LogLevel;
+import com.cloud.hypervisor.Hypervisor;
+
+import java.io.Serializable;
+
+public class RemoteInstanceTO implements Serializable {
+
+    private Hypervisor.HypervisorType hypervisorType;
+    private String hostName;
+    private String instanceName;
+
+    // Vmware Remote Instances parameters
+    // TODO: cloud.agent.transport.Request#getCommands() cannot handle gsoc decode for polymorphic classes
+    private String vcenterUsername;
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private String vcenterPassword;
+    private String vcenterHost;
+    private String datacenterName;
+    private String clusterName;
+
+    public RemoteInstanceTO() {
+    }
+
+    public RemoteInstanceTO(String hostName, String instanceName, String vcenterHost,
+                            String datacenterName, String clusterName,
+                            String vcenterUsername, String vcenterPassword) {
+        this.hypervisorType = Hypervisor.HypervisorType.VMware;
+        this.hostName = hostName;
+        this.instanceName = instanceName;
+        this.vcenterHost = vcenterHost;
+        this.datacenterName = datacenterName;
+        this.clusterName = clusterName;
+        this.vcenterUsername = vcenterUsername;
+        this.vcenterPassword = vcenterPassword;
+    }
+
+    public Hypervisor.HypervisorType getHypervisorType() {
+        return this.hypervisorType;
+    }
+
+    public String getInstanceName() {
+        return this.instanceName;
+    }
+
+    public String getHostName() {
+        return this.hostName;
+    }
+
+    public String getVcenterUsername() {
+        return vcenterUsername;
+    }
+
+    public String getVcenterPassword() {
+        return vcenterPassword;
+    }
+
+    public String getVcenterHost() {
+        return vcenterHost;
+    }
+
+    public String getDatacenterName() {
+        return datacenterName;
+    }
+
+    public String getClusterName() {
+        return clusterName;
+    }
+}
diff --git a/api/src/main/java/com/cloud/configuration/Resource.java b/api/src/main/java/com/cloud/configuration/Resource.java
index fefeeb1..32db2fc 100644
--- a/api/src/main/java/com/cloud/configuration/Resource.java
+++ b/api/src/main/java/com/cloud/configuration/Resource.java
@@ -22,29 +22,27 @@
     String UNLIMITED = "Unlimited";
 
     enum ResourceType { // Primary and Secondary storage are allocated_storage and not the physical storage.
-        user_vm("user_vm", 0, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        public_ip("public_ip", 1, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        volume("volume", 2, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        snapshot("snapshot", 3, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        template("template", 4, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        project("project", 5, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        network("network", 6, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        vpc("vpc", 7, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        cpu("cpu", 8, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        memory("memory", 9, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        primary_storage("primary_storage", 10, ResourceOwnerType.Account, ResourceOwnerType.Domain),
-        secondary_storage("secondary_storage", 11, ResourceOwnerType.Account, ResourceOwnerType.Domain);
+        user_vm("user_vm", 0),
+        public_ip("public_ip", 1),
+        volume("volume", 2),
+        snapshot("snapshot", 3),
+        template("template", 4),
+        project("project", 5),
+        network("network", 6),
+        vpc("vpc", 7),
+        cpu("cpu", 8),
+        memory("memory", 9),
+        primary_storage("primary_storage", 10),
+        secondary_storage("secondary_storage", 11);
 
         private String name;
-        private ResourceOwnerType[] supportedOwners;
         private int ordinal;
         public static final long bytesToKiB = 1024;
         public static final long bytesToMiB = bytesToKiB * 1024;
         public static final long bytesToGiB = bytesToMiB * 1024;
 
-        ResourceType(String name, int ordinal, ResourceOwnerType... supportedOwners) {
+        ResourceType(String name, int ordinal) {
             this.name = name;
-            this.supportedOwners = supportedOwners;
             this.ordinal = ordinal;
         }
 
@@ -52,25 +50,6 @@
             return name;
         }
 
-        public ResourceOwnerType[] getSupportedOwners() {
-            return supportedOwners;
-        }
-
-        public boolean supportsOwner(ResourceOwnerType ownerType) {
-            boolean success = false;
-            if (supportedOwners != null) {
-                int length = supportedOwners.length;
-                for (int i = 0; i < length; i++) {
-                    if (supportedOwners[i].getName().equalsIgnoreCase(ownerType.getName())) {
-                        success = true;
-                        break;
-                    }
-                }
-            }
-
-            return success;
-        }
-
         public int getOrdinal() {
             return ordinal;
         }
diff --git a/api/src/main/java/com/cloud/dc/VmwareDatacenter.java b/api/src/main/java/com/cloud/dc/VmwareDatacenter.java
new file mode 100644
index 0000000..859ff19
--- /dev/null
+++ b/api/src/main/java/com/cloud/dc/VmwareDatacenter.java
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.dc;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+public interface VmwareDatacenter extends Identity, InternalIdentity {
+
+    String getVmwareDatacenterName();
+
+    String getGuid();
+
+    String getVcenterHost();
+
+    @Override
+    long getId();
+
+    String getPassword();
+
+    String getUser();
+}
diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java
index f579fdb..d385fa9 100644
--- a/api/src/main/java/com/cloud/event/EventTypes.java
+++ b/api/src/main/java/com/cloud/event/EventTypes.java
@@ -29,8 +29,11 @@
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.cloudstack.config.Configuration;
 import org.apache.cloudstack.ha.HAConfig;
+import org.apache.cloudstack.storage.object.Bucket;
+import org.apache.cloudstack.storage.object.ObjectStore;
 import org.apache.cloudstack.quota.QuotaTariff;
 import org.apache.cloudstack.usage.Usage;
+import org.apache.cloudstack.vm.schedule.VMSchedule;
 
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterGuestIpv6Prefix;
@@ -112,6 +115,17 @@
     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";
+
+    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";
+
     // Domain Router
     public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
     public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY";
@@ -290,6 +304,7 @@
     public static final String EVENT_VOLUME_CREATE = "VOLUME.CREATE";
     public static final String EVENT_VOLUME_DELETE = "VOLUME.DELETE";
     public static final String EVENT_VOLUME_ATTACH = "VOLUME.ATTACH";
+    public static final String EVENT_VOLUME_CHECK = "VOLUME.CHECK";
     public static final String EVENT_VOLUME_DETACH = "VOLUME.DETACH";
     public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT";
     public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";
@@ -307,8 +322,10 @@
     public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
     public static final String EVENT_DOMAIN_DELETE = "DOMAIN.DELETE";
     public static final String EVENT_DOMAIN_UPDATE = "DOMAIN.UPDATE";
+    public static final String EVENT_DOMAIN_MOVE = "DOMAIN.MOVE";
 
     // Snapshots
+    public static final String EVENT_SNAPSHOT_COPY = "SNAPSHOT.COPY";
     public static final String EVENT_SNAPSHOT_CREATE = "SNAPSHOT.CREATE";
     public static final String EVENT_SNAPSHOT_ON_PRIMARY = "SNAPSHOT.ON_PRIMARY";
     public static final String EVENT_SNAPSHOT_OFF_PRIMARY = "SNAPSHOT.OFF_PRIMARY";
@@ -383,6 +400,9 @@
     public static final String EVENT_STORAGE_IP_RANGE_UPDATE = "STORAGE.IP.RANGE.UPDATE";
 
     public static final String EVENT_IMAGE_STORE_DATA_MIGRATE = "IMAGE.STORE.MIGRATE.DATA";
+    public static final String EVENT_IMAGE_STORE_RESOURCES_MIGRATE = "IMAGE.STORE.MIGRATE.RESOURCES";
+    public static final String EVENT_IMAGE_STORE_OBJECT_DOWNLOAD = "IMAGE.STORE.OBJECT.DOWNLOAD";
+    public static final String EVENT_UPDATE_IMAGE_STORE_ACCESS_STATE = "IMAGE.STORE.ACCESS.UPDATED";
 
     // Configuration Table
     public static final String EVENT_CONFIGURATION_VALUE_EDIT = "CONFIGURATION.VALUE.EDIT";
@@ -656,6 +676,7 @@
     public static final String EVENT_GUEST_OS_MAPPING_ADD = "GUEST.OS.MAPPING.ADD";
     public static final String EVENT_GUEST_OS_MAPPING_REMOVE = "GUEST.OS.MAPPING.REMOVE";
     public static final String EVENT_GUEST_OS_MAPPING_UPDATE = "GUEST.OS.MAPPING.UPDATE";
+    public static final String EVENT_GUEST_OS_HYPERVISOR_NAME_FETCH = "GUEST.OS.HYPERVISOR.NAME.FETCH";
 
     public static final String EVENT_NIC_SECONDARY_IP_ASSIGN = "NIC.SECONDARY.IP.ASSIGN";
     public static final String EVENT_NIC_SECONDARY_IP_UNASSIGN = "NIC.SECONDARY.IP.UNASSIGN";
@@ -665,6 +686,11 @@
     //Usage related events
     public static final String EVENT_USAGE_REMOVE_USAGE_RECORDS = "USAGE.REMOVE.USAGE.RECORDS";
 
+    // DRS Events
+    public static final String EVENT_CLUSTER_DRS = "CLUSTER.DRS";
+    public static final String EVENT_CLUSTER_DRS_GENERATE = "CLUSTER.DRS.GENERATE";
+
+
     // Netscaler Service Package events
     public static final String EVENT_NETSCALER_SERVICEPACKAGE_ADD = "NETSCALER.SERVICEPACKAGE.ADD";
     public static final String EVENT_NETSCALER_SERVICEPACKAGE_DELETE = "NETSCALER.SERVICEPACKAGE.DELETE";
@@ -694,6 +720,16 @@
     // SystemVM
     public static final String EVENT_LIVE_PATCH_SYSTEMVM = "LIVE.PATCH.SYSTEM.VM";
 
+    // OBJECT STORE
+    public static final String EVENT_OBJECT_STORE_CREATE = "OBJECT.STORE.CREATE";
+    public static final String EVENT_OBJECT_STORE_DELETE = "OBJECT.STORE.DELETE";
+    public static final String EVENT_OBJECT_STORE_UPDATE = "OBJECT.STORE.UPDATE";
+
+    // BUCKETS
+    public static final String EVENT_BUCKET_CREATE = "BUCKET.CREATE";
+    public static final String EVENT_BUCKET_DELETE = "BUCKET.DELETE";
+    public static final String EVENT_BUCKET_UPDATE = "BUCKET.UPDATE";
+
     // Quota
     public static final String EVENT_QUOTA_TARIFF_CREATE = "QUOTA.TARIFF.CREATE";
     public static final String EVENT_QUOTA_TARIFF_DELETE = "QUOTA.TARIFF.DELETE";
@@ -722,6 +758,16 @@
         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);
+
         entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
         entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);
         entityEventDetails.put(EVENT_ROUTER_START, VirtualRouter.class);
@@ -841,6 +887,7 @@
         entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class);
         entityEventDetails.put(EVENT_DOMAIN_DELETE, Domain.class);
         entityEventDetails.put(EVENT_DOMAIN_UPDATE, Domain.class);
+        entityEventDetails.put(EVENT_DOMAIN_MOVE, Domain.class);
 
         // Snapshots
         entityEventDetails.put(EVENT_SNAPSHOT_CREATE, Snapshot.class);
@@ -1098,6 +1145,7 @@
         entityEventDetails.put(EVENT_GUEST_OS_MAPPING_ADD, GuestOSHypervisor.class);
         entityEventDetails.put(EVENT_GUEST_OS_MAPPING_REMOVE, GuestOSHypervisor.class);
         entityEventDetails.put(EVENT_GUEST_OS_MAPPING_UPDATE, GuestOSHypervisor.class);
+        entityEventDetails.put(EVENT_GUEST_OS_HYPERVISOR_NAME_FETCH, GuestOSHypervisor.class);
         entityEventDetails.put(EVENT_NIC_SECONDARY_IP_ASSIGN, NicSecondaryIp.class);
         entityEventDetails.put(EVENT_NIC_SECONDARY_IP_UNASSIGN, NicSecondaryIp.class);
         entityEventDetails.put(EVENT_NIC_SECONDARY_IP_CONFIGURE, NicSecondaryIp.class);
@@ -1123,8 +1171,20 @@
         entityEventDetails.put(EVENT_IMPORT_VCENTER_STORAGE_POLICIES, "StoragePolicies");
 
         entityEventDetails.put(EVENT_IMAGE_STORE_DATA_MIGRATE, ImageStore.class);
+        entityEventDetails.put(EVENT_IMAGE_STORE_OBJECT_DOWNLOAD, ImageStore.class);
+        entityEventDetails.put(EVENT_UPDATE_IMAGE_STORE_ACCESS_STATE, ImageStore.class);
         entityEventDetails.put(EVENT_LIVE_PATCH_SYSTEMVM, "SystemVMs");
 
+        //Object Store
+        entityEventDetails.put(EVENT_OBJECT_STORE_CREATE, ObjectStore.class);
+        entityEventDetails.put(EVENT_OBJECT_STORE_UPDATE, ObjectStore.class);
+        entityEventDetails.put(EVENT_OBJECT_STORE_DELETE, ObjectStore.class);
+
+        //Buckets
+        entityEventDetails.put(EVENT_BUCKET_CREATE, Bucket.class);
+        entityEventDetails.put(EVENT_BUCKET_UPDATE, Bucket.class);
+        entityEventDetails.put(EVENT_BUCKET_DELETE, Bucket.class);
+
         // Quota
         entityEventDetails.put(EVENT_QUOTA_TARIFF_CREATE, QuotaTariff.class);
         entityEventDetails.put(EVENT_QUOTA_TARIFF_DELETE, QuotaTariff.class);
diff --git a/api/src/main/java/com/cloud/gpu/GPU.java b/api/src/main/java/com/cloud/gpu/GPU.java
index 8aa54c0..64ff24a 100644
--- a/api/src/main/java/com/cloud/gpu/GPU.java
+++ b/api/src/main/java/com/cloud/gpu/GPU.java
@@ -25,13 +25,104 @@
     }
 
     public enum GPUType {
-        GRID_K100("GRID K100"),
-        GRID_K120Q("GRID K120Q"),
-        GRID_K140Q("GRID K140Q"),
-        GRID_K200("GRID K200"),
-        GRID_K220Q("GRID K220Q"),
-        GRID_K240Q("GRID K240Q"),
-        GRID_K260("GRID K260Q"),
+        GRID_V100D_32A("GRID V100D-32A"),
+        GRID_V100D_8Q("GRID V100D-8Q"),
+        GRID_V100D_4A("GRID V100D-4A"),
+        GRID_V100D_1B("GRID V100D-1B"),
+        GRID_V100D_2Q("GRID V100D-2Q"),
+        GRID_V100D_4Q("GRID V100D-4Q"),
+        GRID_V100D_2A("GRID V100D-2A"),
+        GRID_V100D_2B("GRID V100D-2B"),
+        GRID_V100D_32Q("GRID V100D-32Q"),
+        GRID_V100D_16A("GRID V100D-16A"),
+        GRID_V100D_1Q("GRID V100D-1Q"),
+        GRID_V100D_2B4("GRID V100D-2B4"),
+        GRID_V100D_16Q("GRID V100D-16Q"),
+        GRID_V100D_8A("GRID V100D-8A"),
+        GRID_V100D_1A("GRID V100D-1A"),
+        GRID_T4_16A("GRID T4-16A"),
+        GRID_T4_2B4("GRID T4-2B4"),
+        GRID_T4_4Q("GRID T4-4Q"),
+        GRID_T4_16Q("GRID T4-16Q"),
+        GRID_T4_4A("GRID T4-4A"),
+        GRID_T4_1A("GRID T4-1A"),
+        GRID_T4_2Q("GRID T4-2Q"),
+        GRID_T4_2B("GRID T4-2B"),
+        GRID_T4_8Q("GRID T4-8Q"),
+        GRID_T4_2A("GRID T4-2A"),
+        GRID_T4_1B("GRID T4-1B"),
+        GRID_T4_1Q("GRID T4-1Q"),
+        GRID_T4_8A("GRID T4-8A"),
+        NVIDIA_RTX5500_1A("NVIDIA RTXA5500-1A"),
+        NVIDIA_RTX5500_1B("NVIDIA RTXA5500-1B"),
+        NVIDIA_RTX5500_1Q("NVIDIA RTXA5500-1Q"),
+        NVIDIA_RTX5500_2A("NVIDIA RTXA5500-2A"),
+        NVIDIA_RTX5500_2B("NVIDIA RTXA5500-2B"),
+        NVIDIA_RTX5500_2Q("NVIDIA RTXA5500-2Q"),
+        NVIDIA_RTX5500_3A("NVIDIA RTXA5500-3A"),
+        NVIDIA_RTX5500_3Q("NVIDIA RTXA5500-3Q"),
+        NVIDIA_RTX5500_4A("NVIDIA RTXA5500-4A"),
+        NVIDIA_RTX5500_4Q("NVIDIA RTXA5500-4Q"),
+        NVIDIA_RTX5500_6A("NVIDIA RTXA5500-6A"),
+        NVIDIA_RTX5500_6Q("NVIDIA RTXA5500-6Q"),
+        NVIDIA_RTX5500_8A("NVIDIA RTXA5500-8A"),
+        NVIDIA_RTX5500_8Q("NVIDIA RTXA5500-8Q"),
+        NVIDIA_RTX5500_12A("NVIDIA RTXA5500-12A"),
+        NVIDIA_RTX5500_12Q("NVIDIA RTXA5500-12Q"),
+        NVIDIA_RTX5500_24A("NVIDIA RTXA5500-24A"),
+        NVIDIA_RTX5500_24Q("NVIDIA RTXA5500-24Q"),
+        NVIDIA_A40_1A("NVIDIA A40-1A"),
+        NVIDIA_A40_1B("NVIDIA A40-1B"),
+        NVIDIA_A40_1Q("NVIDIA A40-1Q"),
+        NVIDIA_A40_2A("NVIDIA A40-2A"),
+        NVIDIA_A40_2B("NVIDIA A40-2B"),
+        NVIDIA_A40_2Q("NVIDIA A40-2Q"),
+        NVIDIA_A40_3A("NVIDIA A40-3A"),
+        NVIDIA_A40_3Q("NVIDIA A40-3Q"),
+        NVIDIA_A40_4A("NVIDIA A40-4A"),
+        NVIDIA_A40_4Q("NVIDIA A40-4Q"),
+        NVIDIA_A40_6A("NVIDIA A40-6A"),
+        NVIDIA_A40_6Q("NVIDIA A40-6Q"),
+        NVIDIA_A40_8A("NVIDIA A40-8A"),
+        NVIDIA_A40_8Q("NVIDIA A40-8Q"),
+        NVIDIA_A40_12A("NVIDIA A40-12A"),
+        NVIDIA_A40_12Q("NVIDIA A40-12Q"),
+        NVIDIA_A40_16A("NVIDIA A40-16A"),
+        NVIDIA_A40_16Q("NVIDIA A40-16Q"),
+        NVIDIA_A40_24A("NVIDIA A40-24A"),
+        NVIDIA_A40_24Q("NVIDIA A40-24Q"),
+        NVIDIA_A40_48A("NVIDIA A40-48A"),
+        NVIDIA_A40_48Q("NVIDIA A40-48Q"),
+        NVIDIA_A2_1A("NVIDIA A2-1A"),
+        NVIDIA_A2_1B("NVIDIA A2-1B"),
+        NVIDIA_A2_1Q("NVIDIA A2-1Q"),
+        NVIDIA_A2_2A("NVIDIA A2-2A"),
+        NVIDIA_A2_2B("NVIDIA A2-2B"),
+        NVIDIA_A2_2Q("NVIDIA A2-2Q"),
+        NVIDIA_A2_4A("NVIDIA A2-4A"),
+        NVIDIA_A2_4Q("NVIDIA A2-4Q"),
+        NVIDIA_A2_8A("NVIDIA A2-8A"),
+        NVIDIA_A2_8Q("NVIDIA A2-8Q"),
+        NVIDIA_A2_16A("NVIDIA A2-16A"),
+        NVIDIA_A2_16Q("NVIDIA A2-16Q"),
+        NVIDIA_A10_1A("NVIDIA A10-1A"),
+        NVIDIA_A10_1B("NVIDIA A10-1B"),
+        NVIDIA_A10_1Q("NVIDIA A10-1Q"),
+        NVIDIA_A10_2A("NVIDIA A10-2A"),
+        NVIDIA_A10_2B("NVIDIA A10-2B"),
+        NVIDIA_A10_2Q("NVIDIA A10-2Q"),
+        NVIDIA_A10_3A("NVIDIA A10-3A"),
+        NVIDIA_A10_3Q("NVIDIA A10-3Q"),
+        NVIDIA_A10_4A("NVIDIA A10-4A"),
+        NVIDIA_A10_4Q("NVIDIA A10-4Q"),
+        NVIDIA_A10_6A("NVIDIA A10-6A"),
+        NVIDIA_A10_6Q("NVIDIA A10-6Q"),
+        NVIDIA_A10_8A("NVIDIA A10-8A"),
+        NVIDIA_A10_8Q("NVIDIA A10-8Q"),
+        NVIDIA_A10_12A("NVIDIA A10-12A"),
+        NVIDIA_A10_12Q("NVIDIA A10-12Q"),
+        NVIDIA_A10_24A("NVIDIA A10-24A"),
+        NVIDIA_A10_24Q("NVIDIA A10-24Q"),
         passthrough("passthrough");
 
         private String type;
diff --git a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java
index 429de57..2f0cc73 100644
--- a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java
+++ b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java
@@ -27,7 +27,7 @@
     static Map<String, HypervisorType> hypervisorTypeMap;
     static Map<HypervisorType, ImageFormat> supportedImageFormatMap;
 
-    public static enum HypervisorType {
+    public enum HypervisorType {
         None, //for storage hosts
         XenServer,
         KVM,
@@ -40,6 +40,7 @@
         Ovm,
         Ovm3,
         LXC,
+        Custom,
 
         Any; /*If you don't care about the hypervisor type*/
 
@@ -57,6 +58,7 @@
             hypervisorTypeMap.put("lxc", HypervisorType.LXC);
             hypervisorTypeMap.put("any", HypervisorType.Any);
             hypervisorTypeMap.put("ovm3", HypervisorType.Ovm3);
+            hypervisorTypeMap.put("custom", HypervisorType.Custom);
 
             supportedImageFormatMap = new HashMap<>();
             supportedImageFormatMap.put(HypervisorType.XenServer, ImageFormat.VHD);
@@ -68,7 +70,19 @@
 
         public static HypervisorType getType(String hypervisor) {
             return hypervisor == null ? HypervisorType.None :
-                    hypervisorTypeMap.getOrDefault(hypervisor.toLowerCase(Locale.ROOT), HypervisorType.None);
+                    (hypervisor.toLowerCase(Locale.ROOT).equalsIgnoreCase(
+                            HypervisorGuru.HypervisorCustomDisplayName.value()) ? Custom :
+                            hypervisorTypeMap.getOrDefault(hypervisor.toLowerCase(Locale.ROOT), HypervisorType.None));
+        }
+
+        /**
+         * Returns the display name of a hypervisor type in case the custom hypervisor is used,
+         * using the 'hypervisor.custom.display.name' setting. Otherwise, returns hypervisor name
+         */
+        public String getHypervisorDisplayName() {
+            return !Hypervisor.HypervisorType.Custom.equals(this) ?
+                    this.toString() :
+                    HypervisorGuru.HypervisorCustomDisplayName.value();
         }
 
         /**
diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java
index c7dc0bb..3c7dbac 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.framework.config.ConfigKey;
 
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.to.NicTO;
@@ -32,9 +33,14 @@
 import com.cloud.vm.NicProfile;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 
 public interface HypervisorGuru extends Adapter {
 
+    ConfigKey<String> HypervisorCustomDisplayName = new ConfigKey<>(String.class,
+            "hypervisor.custom.display.name", ConfigKey.CATEGORY_ADVANCED, "Custom",
+            "Display name for custom hypervisor", true, ConfigKey.Scope.Global, null);
+
     HypervisorType getHypervisorType();
 
     /**
@@ -99,4 +105,25 @@
      * @return a list of commands to perform for a successful migration
      */
     List<Command> finalizeMigrate(VirtualMachine vm, Map<Volume, StoragePool> volumeToPool);
+
+
+    /**
+     * Will perform a clone of a VM on an external host (if the guru can handle)
+     * @param hostIp VM's source host IP
+     * @param vmName name of the source VM to clone from
+     * @param params hypervisor specific additional parameters
+     * @return a reference to the cloned VM
+     */
+    UnmanagedInstanceTO cloneHypervisorVMOutOfBand(String hostIp, String vmName,
+                                                   Map<String, String> params);
+
+    /**
+     * Removes a VM created as a clone of a VM on an external host
+     * @param hostIp VM's source host IP
+     * @param vmName name of the VM to remove
+     * @param params hypervisor specific additional parameters
+     * @return true if the operation succeeds, false if not
+     */
+    boolean removeClonedHypervisorVMOutOfBand(String hostIp, String vmName,
+                                              Map<String, String> params);
 }
diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java
index a2633ec7..458169c 100644
--- a/api/src/main/java/com/cloud/network/Network.java
+++ b/api/src/main/java/com/cloud/network/Network.java
@@ -256,7 +256,7 @@
 
     public static class Capability {
 
-        private static List<Capability> supportedCapabilities = new ArrayList<Capability>();
+        private static List<Capability> supportedCapabilities = new ArrayList<>();
 
         public static final Capability SupportedProtocols = new Capability("SupportedProtocols");
         public static final Capability SupportedLBAlgorithms = new Capability("SupportedLbAlgorithms");
diff --git a/api/src/main/java/com/cloud/network/NetworkModel.java b/api/src/main/java/com/cloud/network/NetworkModel.java
index 96f38b6..53ac735 100644
--- a/api/src/main/java/com/cloud/network/NetworkModel.java
+++ b/api/src/main/java/com/cloud/network/NetworkModel.java
@@ -73,6 +73,7 @@
     String HYPERVISOR_HOST_NAME_FILE = "hypervisor-host-name";
     String CLOUD_DOMAIN_FILE = "cloud-domain";
     String CLOUD_DOMAIN_ID_FILE = "cloud-domain-id";
+    String CLOUD_NAME_FILE = "cloud-name";
     int CONFIGDATA_DIR = 0;
     int CONFIGDATA_FILE = 1;
     int CONFIGDATA_CONTENT = 2;
@@ -83,11 +84,12 @@
             .put(PUBLIC_HOSTNAME_FILE, "name")
             .put(CLOUD_DOMAIN_FILE, CLOUD_DOMAIN_FILE)
             .put(CLOUD_DOMAIN_ID_FILE, CLOUD_DOMAIN_ID_FILE)
+            .put(CLOUD_NAME_FILE, CLOUD_NAME_FILE)
             .put(HYPERVISOR_HOST_NAME_FILE, HYPERVISOR_HOST_NAME_FILE)
             .build();
 
     List<String> metadataFileNames = new ArrayList<>(Arrays.asList(SERVICE_OFFERING_FILE, AVAILABILITY_ZONE_FILE, LOCAL_HOSTNAME_FILE, LOCAL_IPV4_FILE, PUBLIC_HOSTNAME_FILE, PUBLIC_IPV4_FILE,
-            INSTANCE_ID_FILE, VM_ID_FILE, PUBLIC_KEYS_FILE, CLOUD_IDENTIFIER_FILE, HYPERVISOR_HOST_NAME_FILE));
+            INSTANCE_ID_FILE, VM_ID_FILE, PUBLIC_KEYS_FILE, CLOUD_IDENTIFIER_FILE, CLOUD_NAME_FILE, HYPERVISOR_HOST_NAME_FILE));
 
     static final ConfigKey<Integer> MACIdentifier = new ConfigKey<>("Advanced",Integer.class, "mac.identifier", "0",
             "This value will be used while generating the mac addresses for isolated and shared networks. The hexadecimal equivalent value will be present at the 2nd octet of the mac address. Default value is zero (0) which means that the DB id of the zone will be used.", true, ConfigKey.Scope.Zone);
diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java
index 099d73d..82d229d 100644
--- a/api/src/main/java/com/cloud/network/NetworkService.java
+++ b/api/src/main/java/com/cloud/network/NetworkService.java
@@ -24,6 +24,8 @@
 import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd;
 import org.apache.cloudstack.api.command.admin.network.ListGuestVlansCmd;
 import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd;
+import org.apache.cloudstack.api.command.user.address.RemoveQuarantinedIpCmd;
+import org.apache.cloudstack.api.command.user.address.UpdateQuarantinedIpCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkPermissionsCmd;
 import org.apache.cloudstack.api.command.user.network.ListNetworkPermissionsCmd;
@@ -112,6 +114,8 @@
 
     IpAddress getIp(long id);
 
+    IpAddress getIp(String ipAddress);
+
     Network updateGuestNetwork(final UpdateNetworkCmd cmd);
 
     /**
@@ -246,4 +250,8 @@
     boolean resetNetworkPermissions(ResetNetworkPermissionsCmd resetNetworkPermissionsCmd);
 
     void validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(final Long serviceOfferingId) throws InvalidParameterValueException;
+
+    PublicIpQuarantine updatePublicIpAddressInQuarantine(UpdateQuarantinedIpCmd cmd);
+
+    void removePublicIpAddressFromQuarantine(RemoveQuarantinedIpCmd cmd);
 }
diff --git a/api/src/main/java/com/cloud/network/PublicIpQuarantine.java b/api/src/main/java/com/cloud/network/PublicIpQuarantine.java
new file mode 100644
index 0000000..625a1bb
--- /dev/null
+++ b/api/src/main/java/com/cloud/network/PublicIpQuarantine.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.network;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface PublicIpQuarantine extends InternalIdentity, Identity {
+    Long getPublicIpAddressId();
+
+    Long getPreviousOwnerId();
+
+    Date getEndDate();
+
+    String getRemovalReason();
+
+    Long getRemoverAccountId();
+
+    Date getRemoved();
+
+    Date getCreated();
+}
diff --git a/api/src/main/java/com/cloud/network/VNF.java b/api/src/main/java/com/cloud/network/VNF.java
new file mode 100644
index 0000000..e7a7fb0
--- /dev/null
+++ b/api/src/main/java/com/cloud/network/VNF.java
@@ -0,0 +1,111 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES 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.apache.commons.lang3.StringUtils;
+
+public class VNF {
+
+    public enum AccessMethod {
+        SSH_WITH_PASSWORD("ssh-password"),
+        SSH_WITH_KEY("ssh-key"),
+        HTTP("http"),
+        HTTPS("https"),
+        CONSOLE("console");
+
+        String _method;
+
+        AccessMethod(String method) {
+            _method = method;
+        }
+
+        @Override
+        public String toString() {
+            return _method;
+        }
+
+        public static AccessMethod fromValue(String method) {
+            if (StringUtils.isBlank(method)) {
+                return null;
+            } else {
+                for (AccessMethod accessMethod : AccessMethod.values()) {
+                    if (accessMethod.toString().equalsIgnoreCase(method)) {
+                        return accessMethod;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    public enum AccessDetail {
+        ACCESS_METHODS,
+        USERNAME,
+        PASSWORD,
+        SSH_USER,
+        SSH_PASSWORD,
+        SSH_PORT,
+        WEB_USER,
+        WEB_PASSWORD,
+        HTTP_PATH,
+        HTTP_PORT,
+        HTTPS_PATH,
+        HTTPS_PORT
+    }
+
+    public enum VnfDetail {
+        VERSION,
+        VENDOR,
+        MAINTAINER
+    }
+
+    public static class VnfNic {
+        long deviceId;
+        String name;
+        boolean required;
+        boolean management;
+        String description;
+
+        public VnfNic(long deviceId, String nicName, boolean required, boolean management, String nicDescription) {
+            this.deviceId = deviceId;
+            this.name = nicName;
+            this.required = required;
+            this.management = management;
+            this.description = nicDescription;
+        }
+
+        public long getDeviceId() {
+            return deviceId;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public boolean isRequired() {
+            return required;
+        }
+
+        public boolean isManagement() {
+            return management;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+    }
+}
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 362a110..2cdc034 100644
--- a/api/src/main/java/com/cloud/network/vpc/VpcService.java
+++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java
@@ -23,7 +23,9 @@
 import org.apache.cloudstack.api.command.user.vpc.CreateVPCCmd;
 import org.apache.cloudstack.api.command.user.vpc.ListPrivateGatewaysCmd;
 import org.apache.cloudstack.api.command.user.vpc.ListStaticRoutesCmd;
+import org.apache.cloudstack.api.command.user.vpc.ListVPCsCmd;
 import org.apache.cloudstack.api.command.user.vpc.RestartVPCCmd;
+import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd;
 
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientAddressCapacityException;
@@ -37,7 +39,6 @@
 
 public interface VpcService {
 
-    public Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException;
     /**
      * Persists VPC record in the database
      *
@@ -48,15 +49,26 @@
      * @param displayText
      * @param cidr
      * @param networkDomain TODO
+     * @param ip4Dns1
+     * @param ip4Dns2
      * @param displayVpc TODO
      * @return
      * @throws ResourceAllocationException TODO
      */
-    public Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, String displayText, String cidr, String networkDomain,
-                         String dns1, String dns2, String ip6Dns1, String ip6Dns2, Boolean displayVpc, Integer publicMtu)
+    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)
             throws ResourceAllocationException;
 
     /**
+     * Persists VPC record in the database
+     *
+     * @param cmd the command with specification data for the new vpc
+     * @return a data object describing the new vpc
+     * @throws ResourceAllocationException the resources for this VPC cannot be allocated
+     */
+    Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException;
+
+    /**
      * Deletes a VPC
      *
      * @param vpcId
@@ -65,48 +77,48 @@
      * @throws ResourceUnavailableException
      * @throws ConcurrentOperationException
      */
-    public boolean deleteVpc(long vpcId) throws ConcurrentOperationException, ResourceUnavailableException;
+    boolean deleteVpc(long vpcId) throws ConcurrentOperationException, ResourceUnavailableException;
+
+    /**
+     * Persists VPC record in the database
+     *
+     * @param cmd the command with specification data for updating the vpc
+     * @return a data object describing the new vpc state
+     * @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(UpdateVPCCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException;
 
     /**
      * Updates VPC with new name/displayText
      *
-     * @param vpcId
-     * @param vpcName
-     * @param displayText
-     * @param customId    TODO
-     * @param displayVpc  TODO
-     * @param mtu
-     * @return
+     * @param vpcId the ID of the Vpc to update
+     * @param vpcName The new name to give the vpc
+     * @param displayText the new display text to use for describing the VPC
+     * @param customId A new custom (external) ID to associate this VPC with
+     * @param displayVpc should this VPC be displayed on public lists
+     * @param mtu what maximal transfer unit to us in this VPCs networks
+     * @param sourceNatIp the source NAT address to use for this VPC (must already be associated with the VPC)
+     * @return an object describing the current state of the VPC
+     * @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
      */
-    public Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu);
+    Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException;
+
+    /**
+     * Lists VPC(s) based on the parameters passed to the API call
+     *
+     * @param cmd object containing the search specs
+     * @return the List of VPCs
+     */
+    Pair<List<? extends Vpc>, Integer> listVpcs(ListVPCsCmd cmd);
 
     /**
      * Lists VPC(s) based on the parameters passed to the method call
-     *
-     * @param id
-     * @param vpcName
-     * @param displayText
-     * @param supportedServicesStr
-     * @param cidr
-     * @param state TODO
-     * @param accountName
-     * @param domainId
-     * @param keyword
-     * @param startIndex
-     * @param pageSizeVal
-     * @param zoneId TODO
-     * @param isRecursive TODO
-     * @param listAll TODO
-     * @param restartRequired TODO
-     * @param tags TODO
-     * @param projectId TODO
-     * @param display TODO
-     * @param vpc
-     * @return
      */
-    public Pair<List<? extends Vpc>, Integer> listVpcs(Long id, String vpcName, String displayText, List<String> supportedServicesStr, String cidr, Long vpcOffId, String state,
-            String accountName, Long domainId, String keyword, Long startIndex, Long pageSizeVal, Long zoneId, Boolean isRecursive, Boolean listAll, Boolean restartRequired,
-            Map<String, String> tags, Long projectId, Boolean display);
+    Pair<List<? extends Vpc>, Integer> listVpcs(Long id, String vpcName, String displayText, List<String> supportedServicesStr, String cidr, Long vpcOffId, String state,
+                                                String accountName, Long domainId, String keyword, Long startIndex, Long pageSizeVal, Long zoneId, Boolean isRecursive, Boolean listAll, Boolean restartRequired,
+                                                Map<String, String> tags, Long projectId, Boolean display);
 
     /**
      * Starts VPC which includes starting VPC provider and applying all the networking rules on the backend
@@ -130,17 +142,17 @@
      */
     boolean shutdownVpc(long vpcId) throws ConcurrentOperationException, ResourceUnavailableException;
 
+    boolean restartVpc(RestartVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
+
     /**
      * Restarts the VPC. VPC gets shutdown and started as a part of it
      *
-     * @param id
-     * @param cleanUp
-     * @param makeredundant
-     * @return
-     * @throws InsufficientCapacityException
+     * @param networkId the network to restart
+     * @param cleanup throw away the existing VR and rebuild a new one?
+     * @param makeRedundant create two VRs for this network
+     * @return success or not
+     * @throws InsufficientCapacityException when there is no suitable deployment plan possible
      */
-    boolean restartVpc(RestartVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
-
     boolean restartVpc(Long networkId, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
 
     /**
@@ -154,23 +166,12 @@
     /**
      * Persists VPC private gateway in the Database.
      *
-     *
-     * @param vpcId TODO
-     * @param physicalNetworkId
-     * @param vlan
-     * @param ipAddress
-     * @param gateway
-     * @param netmask
-     * @param gatewayOwnerId
-     * @param networkOfferingId
-     * @param isSourceNat
-     * @param aclId
-     * @return
+     * @return data object describing the private gateway
      * @throws InsufficientCapacityException
      * @throws ConcurrentOperationException
      * @throws ResourceAllocationException
      */
-    public PrivateGateway createVpcPrivateGateway(CreatePrivateGatewayCmd command) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException;
+    PrivateGateway createVpcPrivateGateway(CreatePrivateGatewayCmd command) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException;
 
     /**
      * Applies VPC private gateway on the backend, so it becomes functional
@@ -181,12 +182,12 @@
      * @throws ResourceUnavailableException
      * @throws ConcurrentOperationException
      */
-    public PrivateGateway applyVpcPrivateGateway(long gatewayId, boolean destroyOnFailure) throws ConcurrentOperationException, ResourceUnavailableException;
+    PrivateGateway applyVpcPrivateGateway(long gatewayId, boolean destroyOnFailure) throws ConcurrentOperationException, ResourceUnavailableException;
 
     /**
      * Deletes VPC private gateway
      *
-     * @param id
+     * @param gatewayId
      * @return
      * @throws ResourceUnavailableException
      * @throws ConcurrentOperationException
@@ -199,7 +200,7 @@
      * @param listPrivateGatewaysCmd
      * @return
      */
-    public Pair<List<PrivateGateway>, Integer> listPrivateGateway(ListPrivateGatewaysCmd listPrivateGatewaysCmd);
+    Pair<List<PrivateGateway>, Integer> listPrivateGateway(ListPrivateGatewaysCmd listPrivateGatewaysCmd);
 
     /**
      * Returns Static Route found by Id
@@ -216,7 +217,7 @@
      * @return
      * @throws ResourceUnavailableException
      */
-    public boolean applyStaticRoutesForVpc(long vpcId) throws ResourceUnavailableException;
+    boolean applyStaticRoutesForVpc(long vpcId) throws ResourceUnavailableException;
 
     /**
      * Deletes static route from the backend and the database
@@ -225,7 +226,7 @@
      * @return TODO
      * @throws ResourceUnavailableException
      */
-    public boolean revokeStaticRoute(long routeId) throws ResourceUnavailableException;
+    boolean revokeStaticRoute(long routeId) throws ResourceUnavailableException;
 
     /**
      * Persists static route entry in the Database
@@ -234,15 +235,15 @@
      * @param cidr
      * @return
      */
-    public StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException;
+    StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException;
 
     /**
      * Lists static routes based on parameters passed to the call
      *
-     * @param listStaticRoutesCmd
+     * @param cmd Command object with parameters for { @see ListStaticRoutesCmd }
      * @return
      */
-    public Pair<List<? extends StaticRoute>, Integer> listStaticRoutes(ListStaticRoutesCmd cmd);
+    Pair<List<? extends StaticRoute>, Integer> listStaticRoutes(ListStaticRoutesCmd cmd);
 
     /**
      * Associates IP address from the Public network, to the VPC
@@ -262,6 +263,5 @@
      * @param routeId
      * @return
      */
-    public boolean applyStaticRoute(long routeId) throws ResourceUnavailableException;
-
+    boolean applyStaticRoute(long routeId) throws ResourceUnavailableException;
 }
diff --git a/api/src/main/java/com/cloud/offering/ServiceOffering.java b/api/src/main/java/com/cloud/offering/ServiceOffering.java
index 278acbe..58c7b0d 100644
--- a/api/src/main/java/com/cloud/offering/ServiceOffering.java
+++ b/api/src/main/java/com/cloud/offering/ServiceOffering.java
@@ -102,7 +102,7 @@
 
     boolean getDefaultUse();
 
-    String getSystemVmType();
+    String getVmType();
 
     String getDeploymentPlanner();
 
diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java
index f93b3c5..5080cb5 100644
--- a/api/src/main/java/com/cloud/projects/ProjectService.java
+++ b/api/src/main/java/com/cloud/projects/ProjectService.java
@@ -78,9 +78,9 @@
 
     Project findByNameAndDomainId(String name, long domainId);
 
-    Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException;
+    Project updateProject(long id, String name, String displayText, String newOwnerName) throws ResourceAllocationException;
 
-    Project updateProject(long id, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException;
+    Project updateProject(long id, String name, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException;
 
     boolean addAccountToProject(long projectId, String accountName, String email, Long projectRoleId, Role projectRoleType);
 
diff --git a/api/src/main/java/com/cloud/resource/ResourceService.java b/api/src/main/java/com/cloud/resource/ResourceService.java
index e2b84ba..2757c91 100644
--- a/api/src/main/java/com/cloud/resource/ResourceService.java
+++ b/api/src/main/java/com/cloud/resource/ResourceService.java
@@ -49,6 +49,8 @@
      */
     Host updateHost(UpdateHostCmd cmd) throws NoTransitionException;
 
+    Host autoUpdateHostAllocationState(Long hostId, ResourceState.Event resourceEvent) throws NoTransitionException;
+
     Host cancelMaintenance(CancelMaintenanceCmd cmd);
 
     Host reconnectHost(ReconnectHostCmd cmd) throws AgentUnavailableException;
diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java
index df44ec6..18f3e90 100644
--- a/api/src/main/java/com/cloud/server/ManagementService.java
+++ b/api/src/main/java/com/cloud/server/ManagementService.java
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd;
 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.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;
@@ -105,7 +106,7 @@
     Pair<List<? extends Configuration>, Integer> searchForConfigurations(ListCfgsByCmd c);
 
     /**
-     * returns the the configuration groups
+     * returns the configuration groups
      *
      * @return list of configuration groups
      */
@@ -189,6 +190,12 @@
     GuestOSHypervisor getAddedGuestOsMapping(Long guestOsHypervisorId);
 
     /**
+     * Get hypervisor guest OS names
+     *
+     * @return the hypervisor guest OS name that can be used for mapping, with guest OS name, and the name of guest OS specific to hypervisor
+     */
+    List<Pair<String, String>> getHypervisorGuestOsNames(GetHypervisorGuestOsNamesCmd getHypervisorGuestOsNamesCmd);
+    /**
      * Adds a new guest OS
      *
      * @return A VO containing the new guest OS, with its category ID, name and display name
@@ -434,16 +441,19 @@
      */
     Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(Long vmId, Long startIndex, Long pageSize, String keyword);
 
+    Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(VirtualMachine vm, Long startIndex, Long pageSize, String keyword, List<VirtualMachine> vmList);
+
     /**
      * List storage pools for live migrating of a volume. The API returns list of all pools in the cluster to which the
      * volume can be migrated. Current pool is not included in the list. In case of vSphere datastore cluster storage pools,
      * this method removes the child storage pools and adds the corresponding parent datastore cluster for API response listing
      *
      * @param Long volumeId
+     * @param String keyword if passed, will only return storage pools that contain this keyword in the name
      * @return Pair<List<? extends StoragePool>, List<? extends StoragePool>> List of storage pools in cluster and list
      *         of pools with enough capacity.
      */
-    Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(Long volumeId);
+    Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(Long volumeId, String keyword);
 
     Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForSystemMigrationOfVolume(Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck);
 
diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java
index 6288446..9bbb5d4 100644
--- a/api/src/main/java/com/cloud/server/ResourceTag.java
+++ b/api/src/main/java/com/cloud/server/ResourceTag.java
@@ -30,6 +30,7 @@
     public enum ResourceObjectType {
         UserVm(true, true, true),
         Template(true, true, true),
+        VnfTemplate(false, false, true),
         ISO(true, false, true),
         Volume(true, true),
         Snapshot(true, false),
@@ -68,7 +69,8 @@
         GuestOs(false, true),
         NetworkOffering(false, true),
         VpcOffering(true, false),
-        Domain(false, false, true);
+        Domain(false, false, true),
+        ObjectStore(false, false, true);
 
 
         ResourceObjectType(boolean resourceTagsSupport, boolean resourceMetadataSupport) {
diff --git a/api/src/main/java/com/cloud/storage/DataStoreRole.java b/api/src/main/java/com/cloud/storage/DataStoreRole.java
index cc20cc0..185e370 100644
--- a/api/src/main/java/com/cloud/storage/DataStoreRole.java
+++ b/api/src/main/java/com/cloud/storage/DataStoreRole.java
@@ -21,7 +21,7 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 
 public enum DataStoreRole {
-    Primary("primary"), Image("image"), ImageCache("imagecache"), Backup("backup");
+    Primary("primary"), Image("image"), ImageCache("imagecache"), Backup("backup"), Object("object");
 
     public boolean isImageStore() {
         return (role.equalsIgnoreCase("image") || role.equalsIgnoreCase("imagecache")) ? true : false;
@@ -45,6 +45,8 @@
             return ImageCache;
         } else if (role.equalsIgnoreCase("backup")) {
             return Backup;
+        } else if (role.equalsIgnoreCase("object")) {
+            return Object;
         } else {
             throw new CloudRuntimeException("can't identify the role");
         }
diff --git a/api/src/main/java/com/cloud/storage/Storage.java b/api/src/main/java/com/cloud/storage/Storage.java
index ad04a7a..1163fcc 100644
--- a/api/src/main/java/com/cloud/storage/Storage.java
+++ b/api/src/main/java/com/cloud/storage/Storage.java
@@ -77,13 +77,18 @@
     }
 
     public static enum Capability {
-        HARDWARE_ACCELERATION("HARDWARE_ACCELERATION");
+        HARDWARE_ACCELERATION("HARDWARE_ACCELERATION"),
+        ALLOW_MIGRATE_OTHER_POOLS("ALLOW_MIGRATE_OTHER_POOLS");
 
         private final String capability;
 
         private Capability(String capability) {
             this.capability = capability;
         }
+
+        public String toString() {
+            return this.capability;
+        }
     }
 
     public static enum ProvisioningType {
@@ -125,6 +130,7 @@
         BUILTIN, /* buildin 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 */
         DATADISK, /* Template corresponding to a datadisk(non root disk) present in an OVA */
         ISODISK /* Template corresponding to a iso (non root disk) present in an OVA */
     }
@@ -149,7 +155,8 @@
         ManagedNFS(true, false, false),
         Linstor(true, true, false),
         DatastoreCluster(true, true, false), // for VMware, to abstract pool of clusters
-        StorPool(true, true, true);
+        StorPool(true, true, true),
+        FiberChannel(true, true, false); // Fiber Channel Pool for KVM hypervisors is used to find the volume by WWN value (/dev/disk/by-id/wwn-<wwnvalue>)
 
         private final boolean shared;
         private final boolean overProvisioning;
diff --git a/api/src/main/java/com/cloud/storage/StorageService.java b/api/src/main/java/com/cloud/storage/StorageService.java
index bb086ad..c3609cf 100644
--- a/api/src/main/java/com/cloud/storage/StorageService.java
+++ b/api/src/main/java/com/cloud/storage/StorageService.java
@@ -24,9 +24,11 @@
 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;
+import org.apache.cloudstack.api.command.admin.storage.DeleteObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.DeletePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.DeleteSecondaryStagingStoreCmd;
 import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
+import org.apache.cloudstack.api.command.admin.storage.UpdateObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
 
 import com.cloud.exception.DiscoveryException;
@@ -34,6 +36,11 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceInUseException;
 import com.cloud.exception.ResourceUnavailableException;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.CreateSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.RemoveSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.UpdateSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+import org.apache.cloudstack.storage.object.ObjectStore;
 
 public interface StorageService {
     /**
@@ -109,4 +116,15 @@
 
     StoragePool syncStoragePool(SyncStoragePoolCmd cmd);
 
+    Heuristic createSecondaryStorageHeuristic(CreateSecondaryStorageSelectorCmd cmd);
+
+    Heuristic updateSecondaryStorageHeuristic(UpdateSecondaryStorageSelectorCmd cmd);
+
+    void removeSecondaryStorageHeuristic(RemoveSecondaryStorageSelectorCmd cmd);
+
+    ObjectStore discoverObjectStore(String name, String url, String providerName, Map details) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException;
+
+    boolean deleteObjectStore(DeleteObjectStoragePoolCmd cmd);
+
+    ObjectStore updateObjectStore(Long id, UpdateObjectStoragePoolCmd cmd);
 }
diff --git a/api/src/main/java/com/cloud/storage/Volume.java b/api/src/main/java/com/cloud/storage/Volume.java
index 57db35f..308ed25 100644
--- a/api/src/main/java/com/cloud/storage/Volume.java
+++ b/api/src/main/java/com/cloud/storage/Volume.java
@@ -39,29 +39,38 @@
     };
 
     enum State {
-        Allocated("The volume is allocated but has not been created yet."),
-        Creating("The volume is being created.  getPoolId() should reflect the pool where it is being created."),
-        Ready("The volume is ready to be used."),
-        Migrating("The volume is migrating to other storage pool"),
-        Snapshotting("There is a snapshot created on this volume, not backed up to secondary storage yet"),
-        RevertSnapshotting("There is a snapshot created on this volume, the volume is being reverting from snapshot"),
-        Resizing("The volume is being resized"),
-        Expunging("The volume is being expunging"),
-        Expunged("The volume has been expunged, and can no longer be recovered"),
-        Destroy("The volume is destroyed, and can be recovered."),
-        Destroying("The volume is destroying, and can't be recovered."),
-        UploadOp("The volume upload operation is in progress or in short the volume is on secondary storage"),
-        Copying("Volume is copying from image store to primary, in case it's an uploaded volume"),
-        Uploaded("Volume is uploaded"),
-        NotUploaded("The volume entry is just created in DB, not yet uploaded"),
-        UploadInProgress("Volume upload is in progress"),
-        UploadError("Volume upload encountered some error"),
-        UploadAbandoned("Volume upload is abandoned since the upload was never initiated within a specified time"),
-        Attaching("The volume is attaching to a VM from Ready state.");
+        Allocated(false, "The volume is allocated but has not been created yet."),
+        Creating(true, "The volume is being created.  getPoolId() should reflect the pool where it is being created."),
+        Ready(false, "The volume is ready to be used."),
+        Migrating(true, "The volume is migrating to other storage pool"),
+        Snapshotting(true, "There is a snapshot created on this volume, not backed up to secondary storage yet"),
+        RevertSnapshotting(true, "There is a snapshot created on this volume, the volume is being reverting from snapshot"),
+        Resizing(true, "The volume is being resized"),
+        Expunging(true, "The volume is being expunging"),
+        Expunged(false, "The volume has been expunged, and can no longer be recovered"),
+        Destroy(false, "The volume is destroyed, and can be recovered."),
+        Destroying(false, "The volume is destroying, and can't be recovered."),
+        UploadOp(true, "The volume upload operation is in progress or in short the volume is on secondary storage"),
+        Copying(true, "Volume is copying from image store to primary, in case it's an uploaded volume"),
+        Uploaded(false, "Volume is uploaded"),
+        NotUploaded(true, "The volume entry is just created in DB, not yet uploaded"),
+        UploadInProgress(true, "Volume upload is in progress"),
+        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.");
+
+        boolean _transitional;
 
         String _description;
 
-        private State(String description) {
+        /**
+         * Volume State
+         * @param transitional true for transition/non-final state, otherwise false
+         * @param description description of the state
+         */
+        private State(boolean transitional, String description) {
+            _transitional = transitional;
             _description = description;
         }
 
@@ -69,6 +78,10 @@
             return s_fsm;
         }
 
+        public boolean isTransitional() {
+            return _transitional;
+        }
+
         public String getDescription() {
             return _description;
         }
@@ -133,6 +146,11 @@
             s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Attaching, Event.OperationSucceeded, Ready, null));
             s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Attaching, Event.OperationFailed, Ready, null));
             s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Destroy, Event.RecoverRequested, Ready, null));
+            s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Ready, Event.RestoreRequested, Restoring, null));
+            s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Expunged, Event.RestoreRequested, Restoring, null));
+            s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Destroy, Event.RestoreRequested, Restoring, null));
+            s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Restoring, Event.RestoreSucceeded, Ready, null));
+            s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Restoring, Event.RestoreFailed, Ready, null));
         }
     }
 
@@ -156,7 +174,10 @@
         ExpungingRequested,
         ResizeRequested,
         AttachRequested,
-        OperationTimeout;
+        OperationTimeout,
+        RestoreRequested,
+        RestoreSucceeded,
+        RestoreFailed;
     }
 
     /**
diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java
index e5c938b..4f09702 100644
--- a/api/src/main/java/com/cloud/storage/VolumeApiService.java
+++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java
@@ -19,11 +19,14 @@
 package com.cloud.storage;
 
 import java.net.MalformedURLException;
+import java.util.List;
 import java.util.Map;
 
+import com.cloud.utils.Pair;
 import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
+import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
@@ -36,6 +39,7 @@
 
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.user.Account;
+import com.cloud.utils.fsm.NoTransitionException;
 
 public interface VolumeApiService {
 
@@ -104,10 +108,10 @@
 
     Volume detachVolumeFromVM(DetachVolumeCmd cmd);
 
-    Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags)
+    Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags, List<Long> zoneIds)
             throws ResourceAllocationException;
 
-    Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;
+    Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List<Long> zoneIds) throws ResourceAllocationException;
 
     Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo, String name);
 
@@ -171,7 +175,13 @@
 
     boolean validateVolumeSizeInBytes(long size);
 
+    void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge);
+
     Volume changeDiskOfferingForVolume(ChangeOfferingForVolumeCmd cmd) throws ResourceAllocationException;
 
     void publishVolumeCreationUsageEvent(Volume volume);
+
+    boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException;
+
+    Pair<String, String> checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException;
 }
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 38e5e10..0893f33 100644
--- a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java
+++ b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java
@@ -18,18 +18,20 @@
 
 import java.util.List;
 
+import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
 import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
 import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
 import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd;
 import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
+import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd;
 
 import com.cloud.api.commands.ListRecurringSnapshotScheduleCmd;
 import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.StorageUnavailableException;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.Volume;
 import com.cloud.user.Account;
 import com.cloud.utils.Pair;
-import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd;
 
 public interface SnapshotApiService {
 
@@ -50,7 +52,7 @@
      * @param snapshotId
      *            TODO
      */
-    boolean deleteSnapshot(long snapshotId);
+    boolean deleteSnapshot(long snapshotId, Long zoneId);
 
     /**
      * Creates a policy with specified schedule. maxSnaps specifies the number of most recent snapshots that are to be
@@ -88,7 +90,7 @@
 
     Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;
 
-    Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, Boolean isFromVmSnapshot)
+    Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, Boolean isFromVmSnapshot, List<Long> zoneIds)
             throws ResourceAllocationException;
 
 
@@ -124,4 +126,6 @@
     SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd);
 
     void markVolumeSnapshotsAsDestroyed(Volume volume);
+
+    Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableException, ResourceAllocationException;
 }
diff --git a/api/src/main/java/com/cloud/user/Account.java b/api/src/main/java/com/cloud/user/Account.java
index b4cdb88..bb9838f 100644
--- a/api/src/main/java/com/cloud/user/Account.java
+++ b/api/src/main/java/com/cloud/user/Account.java
@@ -30,7 +30,7 @@
      *  Account states.
      * */
     enum State {
-        DISABLED, ENABLED, LOCKED;
+        DISABLED, ENABLED, LOCKED, REMOVED;
 
         /**
          * The toString method was overridden to maintain consistency in the DB, as the GenericDaoBase uses toString in the enum value to make the sql statements
diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java
index 77a5b44..63f5455 100644
--- a/api/src/main/java/com/cloud/user/AccountService.java
+++ b/api/src/main/java/com/cloud/user/AccountService.java
@@ -70,6 +70,8 @@
 
     UserAccount getActiveUserAccount(String username, Long domainId);
 
+    List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId);
+
     UserAccount updateUser(UpdateUserCmd updateUserCmd);
 
     Account getActiveAccountById(long accountId);
diff --git a/api/src/main/java/com/cloud/user/DomainService.java b/api/src/main/java/com/cloud/user/DomainService.java
index 3ccfcbc..06109cf 100644
--- a/api/src/main/java/com/cloud/user/DomainService.java
+++ b/api/src/main/java/com/cloud/user/DomainService.java
@@ -20,9 +20,11 @@
 
 import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
 import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
+import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
 
 import com.cloud.domain.Domain;
 import com.cloud.exception.PermissionDeniedException;
+import com.cloud.exception.ResourceAllocationException;
 import com.cloud.utils.Pair;
 
 public interface DomainService {
@@ -66,4 +68,5 @@
      */
     Domain findDomainByIdOrPath(Long id, String domainPath);
 
+    Domain moveDomainAndChildrenToNewParentDomain(MoveDomainCmd cmd) throws ResourceAllocationException;
 }
diff --git a/api/src/main/java/com/cloud/user/User.java b/api/src/main/java/com/cloud/user/User.java
index c50ed3f..422e264 100644
--- a/api/src/main/java/com/cloud/user/User.java
+++ b/api/src/main/java/com/cloud/user/User.java
@@ -24,7 +24,7 @@
 
     // UNKNOWN and NATIVE can be used interchangeably
     public enum Source {
-        LDAP, SAML2, SAML2DISABLED, UNKNOWN, NATIVE
+        OAUTH2, LDAP, SAML2, SAML2DISABLED, UNKNOWN, NATIVE
     }
 
     public static final long UID_SYSTEM = 1;
diff --git a/api/src/main/java/com/cloud/vm/NicProfile.java b/api/src/main/java/com/cloud/vm/NicProfile.java
index 3f37331..d3c1daa 100644
--- a/api/src/main/java/com/cloud/vm/NicProfile.java
+++ b/api/src/main/java/com/cloud/vm/NicProfile.java
@@ -442,6 +442,6 @@
 
     @Override
     public String toString() {
-        return String.format("NicProfile %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "vmId", "reservationId", "iPv4Address", "broadcastUri"));
+        return String.format("NicProfile %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "vmId", "deviceId", "broadcastUri", "reservationId", "iPv4Address"));
     }
 }
diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java
index b9fa2ee..787ed7b 100644
--- a/api/src/main/java/com/cloud/vm/UserVmService.java
+++ b/api/src/main/java/com/cloud/vm/UserVmService.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.vm;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -404,10 +405,7 @@
     /**
      * Creates a vm group.
      *
-     * @param name
-     *            - name of the group
-     * @param accountId
-     *            - accountId
+     * @param cmd The command specifying domain ID, account name, group name, and project ID
      */
     InstanceGroup createVmGroup(CreateVMGroupCmd cmd);
 
@@ -446,16 +444,10 @@
 
     /**
      * Migrate the given VM to the destination host provided. The API returns the migrated VM if migration succeeds.
-     * Only Root
-     * Admin can migrate a VM.
+     * Only Root Admin can migrate a VM.
      *
-     * @param destinationStorage
-     *            TODO
-     * @param Long
-     *            vmId
-     *            vmId of The VM to migrate
-     * @param Host
-     *            destinationHost to migrate the VM
+     * @param vmId The ID of the VM to be migrated
+     * @param destinationHost The destination host to where the VM will be migrated
      *
      * @return VirtualMachine migrated VM
      * @throws ManagementServerException
@@ -474,14 +466,9 @@
      * Migrate the given VM with its volumes to the destination host. The API returns the migrated VM if it succeeds.
      * Only root admin can migrate a VM.
      *
-     * @param destinationStorage
-     *            TODO
-     * @param Long
-     *            vmId of The VM to migrate
-     * @param Host
-     *            destinationHost to migrate the VM
-     * @param Map
-     *            A map of volume to which pool it should be migrated
+     * @param vmId The ID of the VM to be migrated
+     * @param destinationHost The destination host to where the VM will be migrated
+     * @param volumeToPool A map of volume to which pool it should be migrated
      *
      * @return VirtualMachine migrated VM
      * @throws ManagementServerException
@@ -505,7 +492,7 @@
 
     UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException;
 
-    UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException;
+    UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map<String, String> details) throws InsufficientCapacityException, ResourceUnavailableException;
 
     UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException,
         VirtualMachineMigrationException;
@@ -515,7 +502,7 @@
     /**
      * Finds and returns an encrypted password for a VM.
      *
-     * @param  userVmId
+     * @param  vmId
      * @return Base64 encoded userdata
      */
     String getVmUserData(long vmId);
@@ -532,7 +519,8 @@
 
     UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String instanceName, 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 String hostName, final HypervisorType hypervisorType, final Map<String, String> customParameters, final VirtualMachine.PowerState powerState) throws InsufficientCapacityException;
+                    final String hostName, final HypervisorType hypervisorType, final Map<String, String> customParameters,
+                    final VirtualMachine.PowerState powerState, final LinkedHashMap<String, List<NicProfile>> networkNicMap) throws InsufficientCapacityException;
 
     /**
      * Unmanage a guest VM from CloudStack
diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java
index 7a0715f..e7c5efb 100644
--- a/api/src/main/java/com/cloud/vm/VirtualMachine.java
+++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java
@@ -57,7 +57,8 @@
         Migrating(true, "VM is being migrated.  host id holds to from host"),
         Error(false, "VM is in error"),
         Unknown(false, "VM state is unknown."),
-        Shutdown(false, "VM state is shutdown from inside");
+        Shutdown(false, "VM state is shutdown from inside"),
+        Restoring(true, "VM is being restored from backup");
 
         private final boolean _transitional;
         String _description;
@@ -126,6 +127,11 @@
             s_fsm.addTransition(new Transition<State, Event>(State.Expunging, VirtualMachine.Event.ExpungeOperation, State.Expunging,null));
             s_fsm.addTransition(new Transition<State, Event>(State.Error, VirtualMachine.Event.DestroyRequested, State.Expunging, null));
             s_fsm.addTransition(new Transition<State, Event>(State.Error, VirtualMachine.Event.ExpungeOperation, State.Expunging, null));
+            s_fsm.addTransition(new Transition<State, Event>(State.Stopped, Event.RestoringRequested, State.Restoring, null));
+            s_fsm.addTransition(new Transition<State, Event>(State.Expunging, Event.RestoringRequested, State.Restoring, null));
+            s_fsm.addTransition(new Transition<State, Event>(State.Destroyed, Event.RestoringRequested, State.Restoring, null));
+            s_fsm.addTransition(new Transition<State, Event>(State.Restoring, Event.RestoringSuccess, State.Stopped, null));
+            s_fsm.addTransition(new Transition<State, Event>(State.Restoring, Event.RestoringFailed, State.Stopped, null));
 
             s_fsm.addTransition(new Transition<State, Event>(State.Starting, VirtualMachine.Event.FollowAgentPowerOnReport, State.Running, Arrays.asList(new Impact[]{Impact.USAGE})));
             s_fsm.addTransition(new Transition<State, Event>(State.Stopping, VirtualMachine.Event.FollowAgentPowerOnReport, State.Running, null));
@@ -210,6 +216,9 @@
         AgentReportMigrated,
         RevertRequested,
         SnapshotRequested,
+        RestoringRequested,
+        RestoringFailed,
+        RestoringSuccess,
 
         // added for new VMSync logic
         FollowAgentPowerOnReport,
@@ -306,6 +315,9 @@
     @Override
     Long getHostId();
 
+
+    void setHostId(Long hostId);
+
     /**
      * @return should HA be enabled for this machine?
      */
diff --git a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java
index 6251941..f2ff3da 100644
--- a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java
+++ b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java
@@ -60,6 +60,8 @@
 
     void setConfigDriveLocation(NetworkElement.Location location);
 
+    void setServiceOffering(ServiceOffering offering);
+
     public static class Param {
 
         public static final Param VmPassword = new Param("VmPassword");
diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java
index dd0a5d7..9338cc1 100644
--- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java
+++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java
@@ -39,6 +39,7 @@
     // KVM specific (internal)
     String KVM_VNC_PORT = "kvm.vnc.port";
     String KVM_VNC_ADDRESS = "kvm.vnc.address";
+    String KVM_VNC_PASSWORD = "kvm.vnc.password";
 
     // KVM specific, custom virtual GPU hardware
     String VIDEO_HARDWARE = "video.hardware";
@@ -48,6 +49,10 @@
     String IOTHREADS = "iothreads";
     String IO_POLICY = "io.policy";
 
+    // KVM specific, the number of queues for multiqueue NICs
+    String NIC_MULTIQUEUE_NUMBER = "nic.multiqueue.number";
+    String NIC_PACKED_VIRTQUEUES_ENABLED = "nic.packed.virtqueues.enabled";
+
     // Mac OSX guest specific (internal)
     String SMC_PRESENT = "smc.present";
     String FIRMWARE = "firmware";
@@ -69,6 +74,8 @@
 
     String CONFIG_DRIVE_LOCATION = "configDriveLocation";
 
+    String SKIP_DRS = "skipFromDRS";
+
     // VM import with nic, disk and custom params for custom compute offering
     String NIC = "nic";
     String NETWORK = "network";
@@ -80,4 +87,16 @@
     String DEPLOY_AS_IS_CONFIGURATION = "configurationId";
     String KEY_PAIR_NAMES = "keypairnames";
     String CKS_CONTROL_NODE_LOGIN_USER = "controlNodeLoginUser";
+
+    // VMware to KVM VM migrations specific
+    String VMWARE_TO_KVM_PREFIX = "vmware-to-kvm";
+    String VMWARE_VCENTER_HOST = String.format("%s-vcenter", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_DATACENTER_NAME = String.format("%s-datacenter", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_CLUSTER_NAME = String.format("%s-cluster", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_VCENTER_USERNAME = String.format("%s-username", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_VCENTER_PASSWORD = String.format("%s-password", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_VM_NAME = String.format("%s-vmname", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_HOST_NAME = String.format("%s-host", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_DISK = String.format("%s-disk", VMWARE_TO_KVM_PREFIX);
+    String VMWARE_MAC_ADDRESSES = String.format("%s-mac-addresses", VMWARE_TO_KVM_PREFIX);
 }
diff --git a/api/src/main/java/org/apache/cloudstack/acl/Role.java b/api/src/main/java/org/apache/cloudstack/acl/Role.java
index c25d7a9..5e5ffd5 100644
--- a/api/src/main/java/org/apache/cloudstack/acl/Role.java
+++ b/api/src/main/java/org/apache/cloudstack/acl/Role.java
@@ -23,4 +23,5 @@
 public interface Role extends RoleEntity, InternalIdentity, Identity {
     RoleType getRoleType();
     boolean isDefault();
+    boolean isPublicRole();
 }
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 578c13e..07f62a7 100644
--- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java
+++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java
@@ -38,15 +38,19 @@
      *  Moreover, we will check if the requested role is of 'Admin' type; roles with 'Admin' type should only be visible to 'root admins'.
      *  Therefore, if a non-'root admin' user tries to search for an 'Admin' role, this method will return null.
      */
+    Role findRole(Long id, boolean ignorePrivateRoles);
+
+    List<Role> findRoles(List<Long> ids, boolean ignorePrivateRoles);
+
     Role findRole(Long id);
 
-    Role createRole(String name, RoleType roleType, String description);
+    Role createRole(String name, RoleType roleType, String description, boolean publicRole);
 
-    Role createRole(String name, Role role, String description);
+    Role createRole(String name, Role role, String description, boolean publicRole);
 
-    Role importRole(String name, RoleType roleType, String description, List<Map<String, Object>> rules, boolean forced);
+    Role importRole(String name, RoleType roleType, String description, List<Map<String, Object>> rules, boolean forced, boolean isPublicRole);
 
-    Role updateRole(Role role, String name, RoleType roleType, String description);
+    Role updateRole(Role role, String name, RoleType roleType, String description, Boolean publicRole);
 
     boolean deleteRole(Role role);
 
diff --git a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupProcessor.java b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupProcessor.java
index f3ff1dd..077953d 100644
--- a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupProcessor.java
+++ b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupProcessor.java
@@ -21,8 +21,11 @@
 import com.cloud.deploy.DeploymentPlanner.ExcludeList;
 import com.cloud.exception.AffinityConflictException;
 import com.cloud.utils.component.Adapter;
+import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
 
+import java.util.List;
+
 public interface AffinityGroupProcessor extends Adapter {
 
     /**
@@ -35,7 +38,12 @@
      *            deployment plan that tells you where it's being deployed to.
      * @param avoid
      *            avoid these data centers, pods, clusters, or hosts.
+     * @param vmList
+     *            list of virtual machines objects according to which the affinity group should be processed.
+     *            This can be used to process a theoretical state in some cases like generating DRS plans
      */
+    void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException;
+
     void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException;
 
     /**
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 d48b5fd..9995d80 100644
--- a/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java
+++ b/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java
@@ -21,14 +21,23 @@
 import com.cloud.deploy.DeploymentPlanner.ExcludeList;
 import com.cloud.exception.AffinityConflictException;
 import com.cloud.utils.component.AdapterBase;
+import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
 
+import java.util.Collections;
+import java.util.List;
+
 public class AffinityProcessorBase extends AdapterBase implements AffinityGroupProcessor {
 
     protected String _type;
 
     @Override
     public void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
+        process(vm, plan, avoid, Collections.emptyList());
+    }
+
+    @Override
+    public void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {
 
     }
 
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 5b29426..1250284 100644
--- a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java
+++ b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java
@@ -52,7 +52,7 @@
         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_USAGE_SERVER = new AlertType((short)13, "ALERT.USAGE", true);
-        public static final AlertType ALERT_TYPE_MANAGMENT_NODE = new AlertType((short)14, "ALERT.MANAGEMENT", true);
+        public static final AlertType ALERT_TYPE_MANAGEMENT_NODE = new AlertType((short)14, "ALERT.MANAGEMENT", 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);
@@ -91,6 +91,10 @@
             return null;
         }
 
+        public static Set<AlertType> getAlertTypes() {
+            return defaultAlertTypes;
+        }
+
         @Override
         public String toString() {
             return String.valueOf(this.getType());
diff --git a/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java b/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java
index c72fba2..64bd96d 100644
--- a/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java
+++ b/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java
@@ -45,7 +45,7 @@
         SERVICE_OFFERING(false), DISK_OFFERING(false), NETWORK_OFFERING(false),
         ZONE(false), POD(false), CLUSTER(false), HOST(false), DOMAIN(false),
         PRIMARY_STORAGE(false), SECONDARY_STORAGE(false), VR(false), SYSTEM_VM(false),
-        AUTOSCALE_VM_GROUP(true);
+        AUTOSCALE_VM_GROUP(true), MANAGEMENT_SERVER(false), OBJECT_STORAGE(false);
 
         private final boolean usersAllowed;
 
@@ -77,6 +77,8 @@
             list.add(EntityType.SECONDARY_STORAGE);
             list.add(EntityType.VR);
             list.add(EntityType.SYSTEM_VM);
+            list.add(EntityType.MANAGEMENT_SERVER);
+            list.add(EntityType.OBJECT_STORAGE);
             if (roleType != RoleType.DomainAdmin) {
                 list.add(EntityType.DOMAIN);
                 list.add(EntityType.SERVICE_OFFERING);
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 c82f478..ed3381a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java
@@ -31,18 +31,18 @@
 
     public static final Logger s_logger = Logger.getLogger(AbstractGetUploadParamsCmd.class.getName());
 
-    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the volume/template")
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the volume/template/iso")
     private String name;
 
-    @Parameter(name = ApiConstants.FORMAT, type = CommandType.STRING, required = true, description = "the format for the volume/template. Possible values include QCOW2, OVA, "
+    @Parameter(name = ApiConstants.FORMAT, type = CommandType.STRING, required = true, description = "the format for the volume/template/iso. Possible values include QCOW2, OVA, "
             + "and VHD.")
     private String format;
 
-    @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "the ID of the zone the volume/template is "
+    @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "the ID of the zone the volume/template/iso is "
             + "to be hosted on")
     private Long zoneId;
 
-    @Parameter(name = ApiConstants.CHECKSUM, type = CommandType.STRING, description = "the checksum value of this volume/template " + ApiConstants.CHECKSUM_PARAMETER_PREFIX_DESCRIPTION)
+    @Parameter(name = ApiConstants.CHECKSUM, type = CommandType.STRING, description = "the checksum value of this volume/template/iso " + ApiConstants.CHECKSUM_PARAMETER_PREFIX_DESCRIPTION)
     private String checksum;
 
     @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional accountName. Must be used with domainId.")
@@ -52,7 +52,7 @@
             + "domainId must also be used.")
     private Long domainId;
 
-    @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Upload volume/template for the project")
+    @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Upload volume/template/iso for the project")
     private Long projectId;
 
     public String getName() {
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 1549167..aafc039 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java
@@ -78,6 +78,9 @@
     VmSnapshot(com.cloud.vm.snapshot.VMSnapshot.class),
     Role(org.apache.cloudstack.acl.Role.class),
     VpnCustomerGateway(com.cloud.network.Site2SiteCustomerGateway.class),
+    ManagementServer(org.apache.cloudstack.management.ManagementServerHost.class),
+    ObjectStore(org.apache.cloudstack.storage.object.ObjectStore.class),
+    Bucket(org.apache.cloudstack.storage.object.Bucket.class),
     QuotaTariff(org.apache.cloudstack.quota.QuotaTariff.class);
 
     private final Class<?> clazz;
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 9f0418a..18d25a0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -32,6 +32,7 @@
     public static final String ALLOCATED_ONLY = "allocatedonly";
     public static final String ANNOTATION = "annotation";
     public static final String API_KEY = "apikey";
+    public static final String ARCHIVED = "archived";
     public static final String ASYNC_BACKUP = "asyncbackup";
     public static final String AUTO_SELECT = "autoselect";
     public static final String USER_API_KEY = "userapikey";
@@ -68,6 +69,8 @@
     public static final String CERTIFICATE_SERIALNUM = "serialnum";
     public static final String CERTIFICATE_SUBJECT = "subject";
     public static final String CERTIFICATE_VALIDITY = "validity";
+    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 CONTROLLER = "controller";
     public static final String CONTROLLER_UNIT = "controllerunit";
@@ -75,8 +78,10 @@
     public static final String CSR = "csr";
     public static final String PRIVATE_KEY = "privatekey";
     public static final String DATASTORE_HOST = "datastorehost";
+    public static final String DATASTORE_ID = "datastoreid";
     public static final String DATASTORE_NAME = "datastorename";
     public static final String DATASTORE_PATH = "datastorepath";
+    public static final String DATASTORE_STATE = "datastorestate";
     public static final String DATASTORE_TYPE = "datastoretype";
     public static final String DOMAIN_SUFFIX = "domainsuffix";
     public static final String DNS_SEARCH_ORDER = "dnssearchorder";
@@ -117,6 +122,7 @@
     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 DATADISK_OFFERING_LIST = "datadiskofferinglist";
     public static final String DEFAULT_VALUE = "defaultvalue";
     public static final String DESCRIPTION = "description";
@@ -204,7 +210,9 @@
     public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage";
     public static final String HOST_ID = "hostid";
     public static final String HOST_IDS = "hostids";
+    public static final String HOST_IP = "hostip";
     public static final String HOST_NAME = "hostname";
+    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 HYPERVISOR = "hypervisor";
@@ -221,6 +229,8 @@
     public static final String INSTANCES_STATS_USER_ONLY = "instancesstatsuseronly";
     public static final String PREFIX = "prefix";
     public static final String PREVIOUS_ACL_RULE_ID = "previousaclruleid";
+    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 MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash";
     public static final String IMAGE_PATH = "imagepath";
@@ -247,6 +257,7 @@
     public static final String IP_AVAILABLE = "ipavailable";
     public static final String IP_LIMIT = "iplimit";
     public static final String IP_TOTAL = "iptotal";
+    public static final String IS_CONTROL_NODE = "iscontrolnode";
     public static final String IS_CLEANUP_REQUIRED = "iscleanuprequired";
     public static final String IS_DYNAMIC = "isdynamic";
     public static final String IS_EDGE = "isedge";
@@ -288,6 +299,7 @@
     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 MIGRATIONS = "migrations";
     public static final String MEMORY = "memory";
     public static final String MODE = "mode";
     public static final String NAME = "name";
@@ -298,6 +310,8 @@
     public static final String NIC = "nic";
     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 NUM_RETRIES = "numretries";
@@ -310,10 +324,14 @@
     public static final String OPTIONS = "options";
     public static final String OS_CATEGORY_ID = "oscategoryid";
     public static final String OS_CATEGORY_NAME = "oscategoryname";
+    public static final String OS_NAME = "osname";
     public static final String OS_ID = "osid";
     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 GUEST_OS_LIST = "guestoslist";
+    public static final String GUEST_OS_COUNT = "guestoscount";
+    public static final String OS_MAPPING_CHECK_ENABLED = "osmappingcheckenabled";
     public static final String OUTOFBANDMANAGEMENT_POWERSTATE = "outofbandmanagementpowerstate";
     public static final String OUTOFBANDMANAGEMENT_ENABLED = "outofbandmanagementenabled";
     public static final String OUTPUT = "output";
@@ -360,11 +378,13 @@
     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 REQUIRES_HVM = "requireshvm";
     public static final String RESOURCE_NAME = "resourcename";
     public static final String RESOURCE_TYPE = "resourcetype";
     public static final String RESOURCE_TYPE_NAME = "resourcetypename";
     public static final String RESPONSE = "response";
+    public static final String RETRIEVE_ONLY_RESOURCE_COUNT = "retrieveonlyresourcecount";
     public static final String REVERTABLE = "revertable";
     public static final String REVOKED = "revoked";
     public static final String REGISTERED = "registered";
@@ -390,6 +410,7 @@
     public static final String SHOW_CAPACITIES = "showcapacities";
     public static final String SHOW_REMOVED = "showremoved";
     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 SIGNATURE = "signature";
     public static final String SIGNATURE_VERSION = "signatureversion";
@@ -424,6 +445,7 @@
     public static final String TEMPLATE_ID = "templateid";
     public static final String TEMPLATE_IDS = "templateids";
     public static final String TEMPLATE_NAME = "templatename";
+    public static final String TEMPLATE_TYPE = "templatetype";
     public static final String TIMEOUT = "timeout";
     public static final String TIMEZONE = "timezone";
     public static final String TIMEZONEOFFSET = "timezoneoffset";
@@ -457,6 +479,7 @@
     public static final String VIRTUAL_MACHINE_NAME = "virtualmachinename";
     public static final String VIRTUAL_MACHINE_ID_IP = "vmidipmap";
     public static final String VIRTUAL_MACHINE_COUNT = "virtualmachinecount";
+    public static final String VIRTUAL_MACHINE_TYPE = "virtualmachinetype";
     public static final String VIRTUAL_MACHINES = "virtualmachines";
     public static final String USAGE_ID = "usageid";
     public static final String USAGE_TYPE = "usagetype";
@@ -479,9 +502,13 @@
     public static final String IS_VOLATILE = "isvolatile";
     public static final String VOLUME_ID = "volumeid";
     public static final String VOLUMES = "volumes";
+    public static final String VOLUME_CHECK_RESULT = "volumecheckresult";
+    public static final String VOLUME_REPAIR_RESULT = "volumerepairresult";
+
     public static final String ZONE = "zone";
     public static final String ZONE_ID = "zoneid";
     public static final String ZONE_NAME = "zonename";
+    public static final String ZONE_WISE = "zonewise";
     public static final String NETWORK_TYPE = "networktype";
     public static final String PAGE = "page";
     public static final String PAGE_SIZE = "pagesize";
@@ -522,6 +549,7 @@
     public static final String PRIVATE_NETWORK_ID = "privatenetworkid";
     public static final String ALLOCATION_STATE = "allocationstate";
     public static final String MANAGED_STATE = "managedstate";
+    public static final String MANAGEMENT_SERVER_ID = "managementserverid";
     public static final String STORAGE_ID = "storageid";
     public static final String PING_STORAGE_SERVER_IP = "pingstorageserverip";
     public static final String PING_DIR = "pingdir";
@@ -578,6 +606,8 @@
     public static final String SERVICE_CAPABILITY_LIST = "servicecapabilitylist";
     public static final String CAN_CHOOSE_SERVICE_CAPABILITY = "canchooseservicecapability";
     public static final String PROVIDER = "provider";
+    public static final String OAUTH_PROVIDER = "oauthprovider";
+    public static final String OAUTH_SECRET_KEY = "secretkey";
     public static final String MANAGED = "managed";
     public static final String CAPACITY_BYTES = "capacitybytes";
     public static final String CAPACITY_IOPS = "capacityiops";
@@ -647,6 +677,7 @@
     public static final String SPECIFY_IP_RANGES = "specifyipranges";
     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 SORT_BY = "sortby";
     public static final String CHANGE_CIDR = "changecidr";
     public static final String PURPOSE = "purpose";
@@ -757,6 +788,7 @@
     public static final String VSM_CONFIG_STATE = "vsmconfigstate";
     public static final String VSM_DEVICE_STATE = "vsmdevicestate";
     public static final String VCENTER = "vcenter";
+    public static final String EXISTING_VCENTER_ID = "existingvcenterid";
     public static final String ADD_VSM_FLAG = "addvsmflag";
     public static final String END_POINT = "endpoint";
     public static final String REGION_ID = "regionid";
@@ -775,6 +807,8 @@
     public static final String IPSEC_PSK = "ipsecpsk";
     public static final String GUEST_IP = "guestip";
     public static final String REMOVED = "removed";
+    public static final String REMOVER_ACCOUNT_ID = "removeraccountid";
+    public static final String REMOVAL_REASON = "removalreason";
     public static final String COMPLETED = "completed";
     public static final String IKE_VERSION = "ikeversion";
     public static final String IKE_POLICY = "ikepolicy";
@@ -953,6 +987,7 @@
     public static final String TARGET_ID = "targetid";
     public static final String FILES = "files";
     public static final String SRC_POOL = "srcpool";
+    public static final String DEST_POOL = "destpool";
     public static final String DEST_POOLS = "destpools";
     public static final String VOLUME_IDS = "volumeids";
 
@@ -997,7 +1032,6 @@
     public static final String DEPLOY_AS_IS = "deployasis";
     public static final String DEPLOY_AS_IS_DETAILS = "deployasisdetails";
     public static final String CROSS_ZONES = "crossZones";
-    public static final String TEMPLATETYPE = "templatetype";
     public static final String SOURCETEMPLATEID = "sourcetemplateid";
     public static final String DYNAMIC_SCALING_ENABLED = "dynamicscalingenabled";
     public static final String IOTHREADS_ENABLED = "iothreadsenabled";
@@ -1017,10 +1051,48 @@
     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 SHUTDOWN_TRIGGERED = "shutdowntriggered";
+    public static final String PENDING_JOBS_COUNT = "pendingjobscount";
+
     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 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 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 MANAGEMENT = "management";
+    public static final String IS_VNF = "isvnf";
+    public static final String VNF_NICS = "vnfnics";
+    public static final String VNF_DETAILS = "vnfdetails";
+    public static final String CLEAN_UP_VNF_DETAILS = "cleanupvnfdetails";
+    public static final String CLEAN_UP_VNF_NICS = "cleanupvnfnics";
+    public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement";
+    public static final String VNF_CIDR_LIST = "vnfcidrlist";
+
+    public static final String CLIENT_ID = "clientid";
+    public static final String REDIRECT_URI = "redirecturi";
+
+    public static final String IS_TAG_A_RULE = "istagarule";
+
+    public static final String PARAMETER_DESCRIPTION_IS_TAG_A_RULE = "Whether the informed tag is a JS interpretable rule or not.";
 
     /**
      * This enum specifies IO Drivers, each option controls specific policies on I/O.
@@ -1067,7 +1139,7 @@
     }
 
     public enum VMDetails {
-        all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp;
+        all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp, vnfnics;
     }
 
     public enum DomainDetails {
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 79e103a..f329228 100644
--- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
@@ -42,7 +42,9 @@
 import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
 import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
 import org.apache.cloudstack.query.QueryService;
+import org.apache.cloudstack.storage.object.BucketApiService;
 import org.apache.cloudstack.storage.ImageStoreService;
+import org.apache.cloudstack.storage.template.VnfTemplateManager;
 import org.apache.cloudstack.usage.UsageService;
 import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
@@ -213,6 +215,11 @@
     public ResourceIconManager resourceIconManager;
     @Inject
     public Ipv6Service ipv6Service;
+    @Inject
+    public VnfTemplateManager vnfTemplateManager;
+    @Inject
+    public BucketApiService _bucketService;
+
 
     public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
         ResourceAllocationException, NetworkRuleConflictException;
diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseListRetrieveOnlyResourceCountCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseListRetrieveOnlyResourceCountCmd.java
new file mode 100644
index 0000000..0e8e136
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/BaseListRetrieveOnlyResourceCountCmd.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.api;
+
+import org.apache.commons.lang3.BooleanUtils;
+
+public abstract class BaseListRetrieveOnlyResourceCountCmd extends BaseListTaggedResourcesCmd {
+    @Parameter(name = ApiConstants.RETRIEVE_ONLY_RESOURCE_COUNT, type = CommandType.BOOLEAN, description = "makes the API's response contains only the resource count")
+    private Boolean retrieveOnlyResourceCount;
+
+    public Boolean getRetrieveOnlyResourceCount() {
+        return BooleanUtils.toBooleanDefaultIfNull(retrieveOnlyResourceCount, false);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/InternalIdentity.java b/api/src/main/java/org/apache/cloudstack/api/InternalIdentity.java
index 4149dd1..ce214e0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/InternalIdentity.java
+++ b/api/src/main/java/org/apache/cloudstack/api/InternalIdentity.java
@@ -25,4 +25,18 @@
 
 public interface InternalIdentity extends Serializable {
     long getId();
+
+    /*
+     Helper method to add conditions in joins where some column name is equal to a string value
+     */
+    default Object setString(String str) {
+        return null;
+    }
+
+    /*
+    Helper method to add conditions in joins where some column name is equal to a long value
+     */
+    default Object setLong(Long l) {
+        return null;
+    }
 }
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 31f06df..ef759aa 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
@@ -22,6 +22,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.cloudstack.storage.object.Bucket;
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
 import org.apache.cloudstack.api.ApiConstants.HostDetails;
@@ -37,6 +38,7 @@
 import org.apache.cloudstack.api.response.BackupOfferingResponse;
 import org.apache.cloudstack.api.response.BackupResponse;
 import org.apache.cloudstack.api.response.BackupScheduleResponse;
+import org.apache.cloudstack.api.response.BucketResponse;
 import org.apache.cloudstack.api.response.CapacityResponse;
 import org.apache.cloudstack.api.response.ClusterResponse;
 import org.apache.cloudstack.api.response.ConditionResponse;
@@ -62,7 +64,9 @@
 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;
@@ -80,6 +84,7 @@
 import org.apache.cloudstack.api.response.NetworkResponse;
 import org.apache.cloudstack.api.response.NicResponse;
 import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
 import org.apache.cloudstack.api.response.OvsProviderResponse;
 import org.apache.cloudstack.api.response.PhysicalNetworkResponse;
 import org.apache.cloudstack.api.response.PodResponse;
@@ -99,6 +104,7 @@
 import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
 import org.apache.cloudstack.api.response.SSHKeyPairResponse;
+import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
 import org.apache.cloudstack.api.response.ServiceResponse;
@@ -117,6 +123,7 @@
 import org.apache.cloudstack.api.response.TemplateResponse;
 import org.apache.cloudstack.api.response.TrafficMonitorResponse;
 import org.apache.cloudstack.api.response.TrafficTypeResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
 import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse;
 import org.apache.cloudstack.api.response.UsageRecordResponse;
 import org.apache.cloudstack.api.response.UserDataResponse;
@@ -143,6 +150,8 @@
 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.object.ObjectStore;
 import org.apache.cloudstack.usage.Usage;
 
 import com.cloud.capacity.Capacity;
@@ -168,6 +177,7 @@
 import com.cloud.network.PhysicalNetwork;
 import com.cloud.network.PhysicalNetworkServiceProvider;
 import com.cloud.network.PhysicalNetworkTrafficType;
+import com.cloud.network.PublicIpQuarantine;
 import com.cloud.network.RemoteAccessVpn;
 import com.cloud.network.RouterHealthCheckResult;
 import com.cloud.network.Site2SiteCustomerGateway;
@@ -228,6 +238,7 @@
 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);
@@ -437,7 +448,7 @@
      * @param result
      * @return
      */
-    PrivateGatewayResponse createPrivateGatewayResponse(PrivateGateway result);
+    PrivateGatewayResponse createPrivateGatewayResponse(ResponseView view, PrivateGateway result);
 
     /**
      * @param result
@@ -463,6 +474,8 @@
 
     GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor);
 
+    HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(List<Pair<String, String>> hypervisorGuestOsNames);
+
     SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule sched);
 
     UsageRecordResponse createUsageResponse(Usage usageRecord);
@@ -526,4 +539,14 @@
     DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair<Boolean, String> result);
 
     FirewallResponse createIpv6FirewallRuleResponse(FirewallRule acl);
+
+    UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host);
+
+    SecondaryStorageHeuristicsResponse createSecondaryStorageSelectorResponse(Heuristic heuristic);
+
+    IpQuarantineResponse createQuarantinedIpsResponse(PublicIpQuarantine publicIp);
+
+    ObjectStoreResponse createObjectStoreResponse(ObjectStore os);
+
+    BucketResponse createBucketResponse(Bucket bucket);
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java
index 2b88003..e67a3e2 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRoleCmd.java
@@ -48,6 +48,9 @@
             description = "ID of the role to be cloned from. Either roleid or type must be passed in")
     private Long roleId;
 
+    @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Indicates whether the role will be visible to all users (public) or only to root admins (private). Default is true.")
+    private boolean publicRole = true;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -60,6 +63,9 @@
         return roleId;
     }
 
+    public boolean isPublicRole() {
+        return publicRole;
+    }
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -81,10 +87,10 @@
             }
 
             CallContext.current().setEventDetails("Role: " + getRoleName() + ", from role: " + getRoleId() + ", description: " + getRoleDescription());
-            role = roleService.createRole(getRoleName(), existingRole, getRoleDescription());
+            role = roleService.createRole(getRoleName(), existingRole, getRoleDescription(), isPublicRole());
         } else {
             CallContext.current().setEventDetails("Role: " + getRoleName() + ", type: " + getRoleType() + ", description: " + getRoleDescription());
-            role = roleService.createRole(getRoleName(), getRoleType(), getRoleDescription());
+            role = roleService.createRole(getRoleName(), getRoleType(), getRoleDescription(), isPublicRole());
         }
 
         if (role == null) {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java
index 4f3e9d1..058650c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java
@@ -64,6 +64,10 @@
             description = "Force create a role with the same name. This overrides the role type, description and rule permissions for the existing role. Default is false.")
     private Boolean forced;
 
+    @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Indicates whether the role will be visible to all users (public) or only to root admins (private)." +
+            " If this parameter is not specified during the creation of the role its value will be defaulted to true (public).")
+    private boolean publicRole = true;
+
     @Inject
     ApiServerService _apiServer;
 
@@ -114,6 +118,10 @@
         return (forced != null) ? forced : false;
     }
 
+    public boolean isPublicRole() {
+        return publicRole;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -130,7 +138,7 @@
         }
 
         CallContext.current().setEventDetails("Role: " + getRoleName() + ", type: " + getRoleType() + ", description: " + getRoleDescription());
-        Role role = roleService.importRole(getRoleName(), getRoleType(), getRoleDescription(), getRules(), isForced());
+        Role role = roleService.importRole(getRoleName(), getRoleType(), getRoleDescription(), getRules(), isForced(), isPublicRole());
         if (role == null) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to import role");
         }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java
index b55dc80..fef2b27 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java
@@ -92,6 +92,7 @@
             roleResponse.setRoleType(role.getRoleType());
             roleResponse.setDescription(role.getDescription());
             roleResponse.setIsDefault(role.isDefault());
+            roleResponse.setPublicRole(role.isPublicRole());
             roleResponse.setObjectName("role");
             roleResponses.add(roleResponse);
         }
@@ -104,7 +105,7 @@
     public void execute() {
         Pair<List<Role>, Integer> roles;
         if (getId() != null && getId() > 0L) {
-            roles = new Pair<List<Role>, Integer>(Collections.singletonList(roleService.findRole(getId())), 1);
+            roles = new Pair<>(Collections.singletonList(roleService.findRole(getId(), true)), 1);
         } else if (StringUtils.isNotBlank(getName()) || StringUtils.isNotBlank(getKeyword())) {
             roles = roleService.findRolesByName(getName(), getKeyword(), getStartIndex(), getPageSizeVal());
         } else if (getRoleType() != null) {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java
index e652918..4c317d0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java
@@ -58,6 +58,7 @@
         response.setRoleName(role.getName());
         response.setRoleType(role.getRoleType());
         response.setDescription(role.getDescription());
+        response.setPublicRole(role.isPublicRole());
         response.setResponseName(getCommandName());
         response.setObjectName("role");
         setResponseObject(response);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java
index 227aaf5..7d002cd 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRoleCmd.java
@@ -52,6 +52,9 @@
     @Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, description = "The description of the role")
     private String roleDescription;
 
+    @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Indicates whether the role will be visible to all users (public) or only to root admins (private).")
+    private Boolean publicRole;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -64,6 +67,10 @@
         return roleName;
     }
 
+    public Boolean isPublicRole() {
+        return publicRole;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -80,7 +87,7 @@
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided");
         }
         CallContext.current().setEventDetails("Role: " + getRoleName() + ", type:" + getRoleType() + ", description: " + getRoleDescription());
-        role = roleService.updateRole(role, getRoleName(), getRoleType(), getRoleDescription());
+        role = roleService.updateRole(role, getRoleName(), getRoleType(), getRoleDescription(), isPublicRole());
         setupResponse(role);
     }
 
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java
index c03e6112..ed17a87 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRoleCmd.java
@@ -20,6 +20,7 @@
 import org.apache.cloudstack.acl.ProjectRole;
 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;
@@ -70,4 +71,13 @@
         return Account.ACCOUNT_ID_SYSTEM;
     }
 
+    @Override
+    public Long getApiResourceId() {
+        return getProjectId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Project;
+    }
 }
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 9b6c2e6..d39c231 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
@@ -22,6 +22,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.Parameter;
@@ -96,4 +97,14 @@
         response.setObjectName("projectrolepermission");
         setResponseObject(response);
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getProjectId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Project;
+    }
 }
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 4bb460c..9f8d824 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
@@ -21,6 +21,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseCmd;
@@ -79,4 +80,14 @@
     public long getEntityOwnerId() {
         return CallContext.current().getCallingAccountId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getProjectId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Project;
+    }
 }
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 8b83253..ac68278 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
@@ -21,6 +21,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseCmd;
@@ -80,4 +81,14 @@
     public long getEntityOwnerId() {
         return CallContext.current().getCallingAccountId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getProjectId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Project;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java
index 202daa3..3bc8b3d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRoleCmd.java
@@ -21,6 +21,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseCmd;
@@ -76,4 +77,14 @@
     public long getEntityOwnerId() {
         return 0;
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getProjectId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Project;
+    }
 }
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 d27235e..dd59310 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
@@ -26,6 +26,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseCmd;
@@ -154,4 +155,14 @@
     public long getEntityOwnerId() {
         return CallContext.current().getCallingAccountId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getProjectId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Project;
+    }
 }
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
new file mode 100644
index 0000000..60f2c2b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.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 org.apache.cloudstack.api.command.admin.cluster;
+
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.host.Host;
+import com.cloud.user.Account;
+import com.cloud.utils.UuidUtils;
+import com.cloud.vm.VirtualMachine;
+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.ClusterDrsPlanResponse;
+import org.apache.cloudstack.api.response.ClusterResponse;
+import org.apache.cloudstack.cluster.ClusterDrsService;
+import org.apache.commons.collections.MapUtils;
+
+import javax.inject.Inject;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+@APICommand(name = "executeClusterDrsPlan",
+            description = "Execute DRS for a cluster. If there is another plan in progress for the same cluster, " +
+                    "this command will fail.",
+            responseObject = ClusterDrsPlanResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
+            responseHasSensitiveInfo = false)
+public class ExecuteClusterDrsPlanCmd extends BaseAsyncCmd {
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ClusterResponse.class, required = true,
+               description = "ID of cluster")
+    private Long id;
+
+    @Parameter(
+            name = ApiConstants.MIGRATE_TO,
+            type = CommandType.MAP,
+            description = "Virtual Machine to destination host mapping. This parameter specifies the mapping between " +
+                    "a vm and a host to migrate that VM. clusterid is required if this parameter is set." +
+                    "Format of this parameter: migrateto[vm-index].vm=<uuid>&migrateto[vm-index].host=<uuid> " +
+                    "Where, [vm-index] indicates the index to identify the vm that you want to migrate, " +
+                    "vm=<uuid> indicates the UUID of the vm that you want to migrate, and " +
+                    "host=<uuid> indicates the UUID of the host where you want to migrate the vm. " +
+                    "Example: migrateto[0].vm=<71f43cd6-69b0-4d3b-9fbc-67f50963d60b>" +
+                    "&migrateto[0].host=<a382f181-3d2b-4413-b92d-b8931befa7e1>" +
+                    "&migrateto[1].vm=<88de0173-55c0-4c1c-a269-83d0279eeedf>" +
+                    "&migrateto[1].host=<95d6e97c-6766-4d67-9a30-c449c15011d1>" +
+                    "&migrateto[2].vm=<1b331390-59f2-4796-9993-bf11c6e76225>" +
+                    "&migrateto[2].host=<41fdb564-9d3b-447d-88ed-7628f7640cbc>")
+    private Map<String, String> migrateVmTo;
+
+    @Inject
+    private ClusterDrsService clusterDrsService;
+
+    public Map<VirtualMachine, Host> getVmToHostMap() {
+        Map<VirtualMachine, Host> vmToHostMap = new HashMap<>();
+        if (MapUtils.isNotEmpty(migrateVmTo)) {
+            Collection<?> allValues = migrateVmTo.values();
+            Iterator<?> iter = allValues.iterator();
+            while (iter.hasNext()) {
+                HashMap<String, String> vmToHost = (HashMap<String, String>) iter.next();
+
+                String vmId = vmToHost.get("vm");
+                String hostId = vmToHost.get("host");
+
+                VirtualMachine vm;
+                Host host;
+                if (UuidUtils.isUuid(vmId)) {
+                    vm = _entityMgr.findByUuid(VirtualMachine.class, vmId);
+                } else {
+                    vm = _entityMgr.findById(VirtualMachine.class, Long.parseLong(vmId));
+                }
+
+                if (UuidUtils.isUuid(hostId)) {
+                    host = _entityMgr.findByUuid(Host.class, hostId);
+                } else {
+                    host = _entityMgr.findById(Host.class, Long.parseLong(hostId));
+                }
+
+                if (vm == null || host == null) {
+                    throw new InvalidParameterValueException(
+                            String.format("Unable to find the vm/host for vmId=%s, destHostId=%s", vmId, hostId));
+                }
+
+                vmToHostMap.put(vm, host);
+            }
+        }
+        return vmToHostMap;
+    }
+
+    @Override
+    public void execute() {
+        ClusterDrsPlanResponse response = clusterDrsService.executeDrsPlan(this);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return getId();
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Cluster;
+    }
+
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_CLUSTER_DRS;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return String.format("Executing DRS plan for cluster: %d", getId());
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java
new file mode 100644
index 0000000..69a6c11
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.api.command.admin.cluster;
+
+import com.cloud.user.Account;
+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.ClusterDrsPlanResponse;
+import org.apache.cloudstack.api.response.ClusterResponse;
+import org.apache.cloudstack.cluster.ClusterDrsService;
+
+import javax.inject.Inject;
+
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMaxMigrations;
+
+@APICommand(name = "generateClusterDrsPlan", description = "Generate DRS plan for a cluster",
+            responseObject = ClusterDrsPlanResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
+            responseHasSensitiveInfo = false)
+public class GenerateClusterDrsPlanCmd extends BaseCmd {
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ClusterResponse.class, required = true,
+               description = "the ID of the Cluster")
+    private Long id;
+
+    @Parameter(name = ApiConstants.MIGRATIONS, type = CommandType.INTEGER,
+               description = "Maximum number of VMs to migrate for a DRS execution. Defaults to value of cluster's drs.vm.migrations setting")
+    private Integer migrations;
+
+    @Inject
+    private ClusterDrsService clusterDrsService;
+
+    public Integer getMaxMigrations() {
+        if (migrations == null) {
+            return ClusterDrsMaxMigrations.valueIn(getId());
+        }
+        return migrations;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public void execute() {
+        final ClusterDrsPlanResponse response = clusterDrsService.generateDrsPlan(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName(getCommandName());
+        this.setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return getId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Cluster;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.java
new file mode 100644
index 0000000..d34805a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.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.api.command.admin.cluster;
+
+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.ClusterDrsPlanResponse;
+import org.apache.cloudstack.api.response.ClusterResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.cluster.ClusterDrsService;
+
+import javax.inject.Inject;
+
+@APICommand(name = "listClusterDrsPlan", description = "List DRS plans for a clusters",
+            responseObject = ClusterDrsPlanResponse.class, since = "4.19.0", requestHasSensitiveInfo = false)
+public class ListClusterDrsPlanCmd extends BaseListCmd {
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ClusterDrsPlanResponse.class,
+               description = "ID of the drs plan")
+    private Long id;
+
+    @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class,
+               description = "ID of the cluster")
+    private Long clusterId;
+
+    @Inject
+    private ClusterDrsService clusterDrsService;
+
+    public Long getId() {
+        return id;
+    }
+
+    public Long getClusterId() {
+        return clusterId;
+    }
+
+    @Override
+    public void execute() {
+        ListResponse<ClusterDrsPlanResponse> response = clusterDrsService.listDrsPlan(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName(getCommandName());
+        setResponseObject(response);
+    }
+}
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 abe7e31..63dc514 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,6 +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.log4j.Logger;
@@ -150,25 +151,50 @@
         if (cfg != null) {
             ConfigurationResponse response = _responseGenerator.createConfigurationResponse(cfg);
             response.setResponseName(getCommandName());
-            if (getZoneId() != null) {
-                response.setScope("zone");
-            }
-            if (getClusterId() != null) {
-                response.setScope("cluster");
-            }
-            if (getStoragepoolId() != null) {
-                response.setScope("storagepool");
-            }
-            if (getAccountId() != null) {
-                response.setScope("account");
-            }
-            if (getDomainId() != null) {
-                response.setScope("domain");
-            }
-            response.setValue(value);
+            response = setResponseScopes(response);
+            response = setResponseValue(response, cfg);
             this.setResponseObject(response);
         } else {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update config");
         }
     }
+
+    /**
+     * 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) {
+        if (cfg.isEncrypted()) {
+            response.setValue(DBEncryptionUtil.encrypt(getValue()));
+        } else {
+            response.setValue(getValue());
+        }
+        return response;
+    }
+
+    /**
+     * Sets the scope for the Configuration response only if the field is not null.
+     * @param response to be updated
+     * @return the response updated with the scopes
+     */
+    public ConfigurationResponse setResponseScopes(ConfigurationResponse response) {
+        if (getZoneId() != null) {
+            response.setScope("zone");
+        }
+        if (getClusterId() != null) {
+            response.setScope("cluster");
+        }
+        if (getStoragepoolId() != null) {
+            response.setScope("storagepool");
+        }
+        if (getAccountId() != null) {
+            response.setScope("account");
+        }
+        if (getDomainId() != null) {
+            response.setScope("domain");
+        }
+        return response;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/MoveDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/MoveDomainCmd.java
new file mode 100644
index 0000000..586345b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/MoveDomainCmd.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.domain;
+
+import com.cloud.domain.Domain;
+import com.cloud.exception.ResourceAllocationException;
+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.DomainResponse;
+
+@APICommand(name = "moveDomain", description = "Moves a domain and its children to a new parent domain.", since = "4.19.0.0", responseObject = DomainResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
+public class MoveDomainCmd extends BaseCmd {
+
+    private static final String APINAME = "moveDomain";
+    @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "The ID of the domain to be moved.")
+    private Long domainId;
+
+    @Parameter(name = ApiConstants.PARENT_DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class,
+            description = "The ID of the new parent domain of the domain to be moved.")
+    private Long parentDomainId;
+
+    public Long getDomainId() {
+        return domainId;
+    }
+
+    public Long getParentDomainId() {
+        return parentDomainId;
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() throws ResourceAllocationException {
+        Domain domain = _domainService.moveDomainAndChildrenToNewParentDomain(this);
+
+        if (domain != null) {
+            DomainResponse response = _responseGenerator.createDomainResponse(domain);
+            response.setResponseName(getCommandName());
+            this.setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the domain.");
+        }
+    }
+}
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 b0d59d4..0ad5007 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
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.guest;
 
+import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -82,7 +83,7 @@
 
     public Map getDetails() {
         Map<String, String> detailsMap = new HashMap<>();
-        if (!details.isEmpty()) {
+        if (!MapUtils.isEmpty(details)) {
             Collection<?> servicesCollection = details.values();
             Iterator<?> iter = servicesCollection.iterator();
             while (iter.hasNext()) {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsMappingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsMappingCmd.java
index 6d9ac52..0ddd219 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsMappingCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsMappingCmd.java
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.guest;
 
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -51,12 +52,18 @@
     @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "Hypervisor type. One of : XenServer, KVM, VMWare")
     private String hypervisor;
 
-    @Parameter(name = ApiConstants.HYPERVISOR_VERSION, type = CommandType.STRING, required = true, description = "Hypervisor version to create the mapping for. Use 'default' for default versions")
+    @Parameter(name = ApiConstants.HYPERVISOR_VERSION, type = CommandType.STRING, required = true, description = "Hypervisor version to create the mapping. Use 'default' for default versions. Please check hypervisor capabilities for correct version")
     private String hypervisorVersion;
 
     @Parameter(name = ApiConstants.OS_NAME_FOR_HYPERVISOR, type = CommandType.STRING, required = true, description = "OS name specific to the hypervisor")
     private String osNameForHypervisor;
 
+    @Parameter(name = ApiConstants.OS_MAPPING_CHECK_ENABLED, type = CommandType.BOOLEAN, required = false, description = "When set to true, checks for the correct guest os mapping name in the provided hypervisor (supports VMware and XenServer only. At least one hypervisor host with the version specified must be available. Default version will not work.)", since = "4.19.0")
+    private Boolean osMappingCheckEnabled;
+
+    @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Forces add user defined guest os mapping, overrides any existing user defined mapping", since = "4.19.0")
+    private Boolean forced;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -81,6 +88,14 @@
         return osNameForHypervisor;
     }
 
+    public Boolean getOsMappingCheckEnabled() {
+        return BooleanUtils.toBooleanDefaultIfNull(osMappingCheckEnabled, false);
+    }
+
+    public boolean isForced() {
+        return BooleanUtils.toBooleanDefaultIfNull(forced, false);
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/GetHypervisorGuestOsNamesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/GetHypervisorGuestOsNamesCmd.java
new file mode 100644
index 0000000..7951770
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/GetHypervisorGuestOsNamesCmd.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
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.List;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.HypervisorGuestOsNamesResponse;
+import org.apache.log4j.Logger;
+
+import com.cloud.event.EventTypes;
+import com.cloud.user.Account;
+import com.cloud.utils.Pair;
+
+@APICommand(name = GetHypervisorGuestOsNamesCmd.APINAME, description = "Gets the guest OS names in the hypervisor", responseObject = HypervisorGuestOsNamesResponse.class,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin})
+public class GetHypervisorGuestOsNamesCmd extends BaseAsyncCmd {
+    public static final Logger s_logger = Logger.getLogger(GetHypervisorGuestOsNamesCmd.class.getName());
+
+    public static final String APINAME = "getHypervisorGuestOsNames";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "Hypervisor type. One of : VMware, XenServer",
+            validations = {ApiArgValidator.NotNullOrEmpty})
+    private String hypervisor;
+
+    @Parameter(name = ApiConstants.HYPERVISOR_VERSION, type = CommandType.STRING, required = true, description = "Hypervisor version to get the guest os names (atleast one hypervisor host with the version specified must be available)",
+            validations = {ApiArgValidator.NotNullOrEmpty})
+    private String hypervisorVersion;
+
+    @Parameter(name = ApiConstants.KEYWORD, type = CommandType.STRING, required = false, description = "Keyword for guest os name")
+    private String keyword;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getHypervisor() {
+        return hypervisor;
+    }
+
+    public String getHypervisorVersion() {
+        return hypervisorVersion;
+    }
+
+    public String getKeyword() {
+        return keyword;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() {
+        List<Pair<String, String>> hypervisorGuestOsNames = _mgr.getHypervisorGuestOsNames(this);
+        HypervisorGuestOsNamesResponse response = _responseGenerator.createHypervisorGuestOSNamesResponse(hypervisorGuestOsNames);
+        response.setHypervisor(getHypervisor());
+        response.setHypervisorVersion(getHypervisorVersion());
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_GUEST_OS_HYPERVISOR_NAME_FETCH;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "Getting guest OS names from hypervisor: " + getHypervisor() + ", version: " + getHypervisorVersion();
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/ListGuestOsMappingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/ListGuestOsMappingCmd.java
index 70c039f..29ae0b4 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/ListGuestOsMappingCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/ListGuestOsMappingCmd.java
@@ -48,6 +48,12 @@
     @Parameter(name = ApiConstants.OS_TYPE_ID, type = CommandType.UUID, entityType = GuestOSResponse.class, required = false, description = "list mapping by Guest OS Type UUID")
     private Long osTypeId;
 
+    @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, required = false, description = "list Guest OS mapping by OS display name")
+    private String osDisplayName;
+
+    @Parameter(name = ApiConstants.OS_NAME_FOR_HYPERVISOR, type = CommandType.STRING, required = false, description = "list Guest OS mapping by OS mapping name with hypervisor")
+    private String osNameForHypervisor;
+
     @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = false, description = "list Guest OS mapping by hypervisor")
     private String hypervisor;
 
@@ -66,6 +72,14 @@
         return osTypeId;
     }
 
+    public String getOsDisplayName() {
+        return osDisplayName;
+    }
+
+    public String getOsNameForHypervisor() {
+        return osNameForHypervisor;
+    }
+
     public String getHypervisor() {
         return hypervisor;
     }
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 502b1f8..25f022b 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,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.guest;
 
+import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -54,7 +55,7 @@
     @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, required = true, description = "Unique display name for Guest OS")
     private String osDisplayName;
 
-    @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = true, description = "Map of (key/value pairs)")
+    @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs)")
     private Map details;
 
     @Parameter(name="forDisplay", type=CommandType.BOOLEAN, description="whether this guest OS is available for end users", authorized = {RoleType.Admin})
@@ -74,7 +75,7 @@
 
     public Map getDetails() {
         Map<String, String> detailsMap = new HashMap<>();;
-        if (!details.isEmpty()) {
+        if (MapUtils.isNotEmpty(detailsMap)) {
             Collection<?> servicesCollection = details.values();
             Iterator<?> iter = servicesCollection.iterator();
             while (iter.hasNext()) {
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 679ec35..c83be13 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
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.guest;
 
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -47,6 +48,9 @@
     @Parameter(name = ApiConstants.OS_NAME_FOR_HYPERVISOR, type = CommandType.STRING, required = true, description = "Hypervisor specific name for this Guest OS")
     private String osNameForHypervisor;
 
+    @Parameter(name = ApiConstants.OS_MAPPING_CHECK_ENABLED, type = CommandType.BOOLEAN, required = false, description = "When set to true, checks for the correct guest os mapping name in the provided hypervisor (supports VMware and XenServer only. At least one hypervisor host with the version specified must be available. Default version will not work.)", since = "4.19.0")
+    private Boolean osMappingCheckEnabled;
+
 /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -59,6 +63,10 @@
         return osNameForHypervisor;
     }
 
+    public Boolean getOsMappingCheckEnabled() {
+        return BooleanUtils.toBooleanDefaultIfNull(osMappingCheckEnabled, false);
+    }
+
     @Override
     public void execute() {
         GuestOSHypervisor guestOsMapping = _mgr.updateGuestOsMapping(this);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeleteHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeleteHostCmd.java
index 609d189..934965c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeleteHostCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeleteHostCmd.java
@@ -64,7 +64,7 @@
         return (forced != null) ? forced : false;
     }
 
-    public boolean isForceDestoryLocalStorage() {
+    public boolean isForceDestroyLocalStorage() {
         return (forceDestroyLocalStorage != null) ? forceDestroyLocalStorage : true;
     }
 
@@ -79,7 +79,7 @@
 
     @Override
     public void execute() {
-        boolean result = _resourceService.deleteHost(getId(), isForced(), isForceDestoryLocalStorage());
+        boolean result = _resourceService.deleteHost(getId(), isForced(), isForceDestroyLocalStorage());
         if (result) {
             SuccessResponse response = new SuccessResponse(getCommandName());
             this.setResponseObject(response);
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 75fe339..b8668f6 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
@@ -119,6 +119,10 @@
         return id;
     }
 
+    public void setId(Long id) {
+        this.id = id;
+    }
+
     public String getHostName() {
         return hostName;
     }
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 5ca53c0..9cf47a9 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
@@ -19,7 +19,6 @@
 import com.cloud.host.Host;
 import com.cloud.user.Account;
 import org.apache.cloudstack.acl.RoleType;
-import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiErrorCode;
@@ -61,6 +60,9 @@
     @Parameter(name = ApiConstants.HOST_TAGS, type = CommandType.LIST, collectionType = CommandType.STRING, description = "list of tags to be added to the host")
     private List<String> hostTags;
 
+    @Parameter(name = ApiConstants.IS_TAG_A_RULE, type = CommandType.BOOLEAN, description = ApiConstants.PARAMETER_DESCRIPTION_IS_TAG_A_RULE)
+    private Boolean isTagARule;
+
     @Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "the new uri for the secondary storage: nfs://host/path")
     private String url;
 
@@ -91,6 +93,10 @@
         return hostTags;
     }
 
+    public Boolean getIsTagARule() {
+        return isTagARule;
+    }
+
     public String getUrl() {
         return url;
     }
@@ -117,9 +123,6 @@
         Host result;
         try {
             result = _resourceService.updateHost(this);
-            if(getAnnotation() != null) {
-                annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid(), true);
-            }
             HostResponse hostResponse = _responseGenerator.createHostResponse(result);
             hostResponse.setResponseName(getCommandName());
             this.setResponseObject(hostResponse);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdmin.java
index 4b6d4c0..0719d2a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdmin.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdmin.java
@@ -17,12 +17,33 @@
 package org.apache.cloudstack.api.command.admin.iso;
 
 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.iso.ListIsosCmd;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
 
 @APICommand(name = "listIsos", description = "Lists all available ISO files.", responseObject = TemplateResponse.class, responseView = ResponseView.Full,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class ListIsosCmdByAdmin extends ListIsosCmd implements AdminCmd {
+    @Parameter(name = ApiConstants.IMAGE_STORE_ID, type = CommandType.UUID, entityType = ImageStoreResponse.class,
+               description = "ID of the image or image cache store", since = "4.19")
+    private Long imageStoreId;
+
+    @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class,
+               description = "ID of the storage pool", since = "4.19")
+    private Long storagePoolId;
+
+    @Override
+    public Long getImageStoreId() {
+        return imageStoreId;
+    }
+
+    @Override
+    public Long getStoragePoolId() {
+        return storagePoolId;
+    }
 }
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 57b1428..2112be3 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
@@ -28,6 +28,7 @@
 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.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -58,7 +59,7 @@
     @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, required = true, description = "the display text of the network offering")
+    @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,
@@ -183,7 +184,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? networkOfferingName : displayText;
     }
 
     public String getTags() {
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 304b290..c2d8b3b 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
@@ -36,6 +36,7 @@
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.offering.DiskOffering;
@@ -56,7 +57,7 @@
     @Parameter(name = ApiConstants.DISK_SIZE, type = CommandType.LONG, required = false, description = "size of the disk offering in GB (1GB = 1,073,741,824 bytes)")
     private Long diskSize;
 
-    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, required = true, description = "alternate display text of the disk offering", length = 4096)
+    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "An alternate display text of the disk offering, defaults to 'name'.", length = 4096)
     private String displayText;
 
     @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "name of the disk offering")
@@ -179,7 +180,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? offeringName : displayText;
     }
 
     public String getOfferingName() {
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 24f5682..d947f6f 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
@@ -59,7 +59,7 @@
     @Parameter(name = ApiConstants.CPU_SPEED, type = CommandType.INTEGER, required = false, description = "the CPU speed of the service offering in MHz.")
     private Integer cpuSpeed;
 
-    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, required = true, description = "the display text of the service offering")
+    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the service offering, defaults to 'name'.")
     private String displayText;
 
     @Parameter(name = ApiConstants.PROVISIONINGTYPE, type = CommandType.STRING, description = "provisioning type used to create volumes. Valid values are thin, sparse, fat.")
@@ -258,10 +258,7 @@
     }
 
     public String getDisplayText() {
-        if (StringUtils.isEmpty(displayText)) {
-            throw new InvalidParameterValueException("Failed to create service offering because the offering display text has not been spified.");
-        }
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? serviceOfferingName : displayText;
     }
 
     public String getProvisioningType() {
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 1d5898e..424ee16 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
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import com.cloud.offering.DiskOffering.State;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
@@ -27,6 +28,7 @@
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.DiskOfferingResponse;
+import org.apache.commons.lang3.EnumUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
@@ -123,6 +125,9 @@
     @Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING, description = "the cache mode to use for this disk offering", since = "4.15")
     private String cacheMode;
 
+    @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the disk offering")
+    private String diskOfferingState;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -262,6 +267,13 @@
     public Long getIopsWriteRateMaxLength() {
         return iopsWriteRateMaxLength;
     }
+    public State getState() {
+        State state = EnumUtils.getEnumIgnoreCase(State.class, diskOfferingState);
+        if (StringUtils.isNotBlank(diskOfferingState) && state == null) {
+            throw new InvalidParameterValueException("Invalid state value: " + diskOfferingState);
+        }
+        return state;
+    }
 
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
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 d86564a..2f3dba4 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
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import com.cloud.offering.ServiceOffering.State;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
@@ -27,6 +28,7 @@
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
+import org.apache.commons.lang3.EnumUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
@@ -84,6 +86,11 @@
             since = "4.16")
     private String hostTags;
 
+    @Parameter(name = ApiConstants.STATE,
+            type = CommandType.STRING,
+            description = "state of the service offering")
+    private String serviceOfferingState;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -172,6 +179,14 @@
         return hostTags;
     }
 
+    public State getState() {
+        State state = EnumUtils.getEnumIgnoreCase(State.class, serviceOfferingState);
+        if (StringUtils.isNotBlank(serviceOfferingState) && state == null) {
+            throw new InvalidParameterValueException("Invalid state value: " + serviceOfferingState);
+        }
+        return state;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// 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 dad6506..e2c31d6 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
@@ -26,6 +26,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -102,4 +103,14 @@
     public String getEventDescription() {
         return "change out-of-band management password for host: " + getHostId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getHostId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Host;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java
index 699e285..157d3c6 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java
@@ -26,6 +26,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseCmd;
@@ -112,4 +113,14 @@
             builder.put(option, value);
         }
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getHostId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Host;
+    }
 }
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 604aae6..445e7b9 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
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -94,4 +95,14 @@
     public String getEventDescription() {
         return "disable out-of-band management password for cluster: " + getClusterId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getClusterId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Cluster;
+    }
 }
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 51c0fbc..7e4444e 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
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -95,4 +96,14 @@
     public String getEventDescription() {
         return "disable out-of-band management password for host: " + getHostId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getHostId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Host;
+    }
 }
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 4c3e92c..2028f8f 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
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -94,4 +95,14 @@
     public String getEventDescription() {
         return "disable out-of-band management password for zone: " + getZoneId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getZoneId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Zone;
+    }
 }
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 a69f27f..549743b 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
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -94,4 +95,14 @@
     public String getEventDescription() {
         return "enable out-of-band management password for cluster: " + getClusterId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getClusterId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Cluster;
+    }
 }
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 5e32c89..834181a 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
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -95,4 +96,14 @@
     public String getEventDescription() {
         return "enable out-of-band management password for host: " + getHostId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getHostId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Host;
+    }
 }
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 4eea4d5..de4c4d8 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
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.acl.RoleType;
 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;
 import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -94,4 +95,14 @@
     public String getEventDescription() {
         return "enable out-of-band management password for zone: " + getZoneId();
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getZoneId();
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Zone;
+    }
 }
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 d10664f..97a813c 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
@@ -114,4 +114,9 @@
     public ApiCommandResourceType getApiResourceType() {
         return ApiCommandResourceType.Host;
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getHostId();
+    }
 }
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
new file mode 100644
index 0000000..e7bfbdb
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListAlertTypesCmd.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.admin.resource;
+
+import com.cloud.user.Account;
+import org.apache.cloudstack.alert.AlertService;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.response.AlertResponse;
+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;
+
+@APICommand(name = "listAlertTypes", description = "Lists all alerts types", responseObject = AlertResponse.class,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class ListAlertTypesCmd extends BaseCmd {
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() {
+        Set<AlertService.AlertType> result = AlertService.AlertType.getAlertTypes();
+        ListResponse<AlertTypeResponse> response = new ListResponse<>();
+        List<AlertTypeResponse> typeResponseList = new ArrayList<>();
+        for (AlertService.AlertType alertType : result) {
+            AlertTypeResponse alertResponse = new AlertTypeResponse(alertType.getType(), alertType.getName());
+            alertResponse.setObjectName("alerttype");
+            typeResponseList.add(alertResponse);
+        }
+        response.setResponses(typeResponseList, result.size());
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/snapshot/ListSnapshotsCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/snapshot/ListSnapshotsCmdByAdmin.java
new file mode 100644
index 0000000..7070ba3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/snapshot/ListSnapshotsCmdByAdmin.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.api.command.admin.snapshot;
+
+import com.cloud.storage.Snapshot;
+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.snapshot.ListSnapshotsCmd;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.api.response.SnapshotResponse;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
+
+@APICommand(name = "listSnapshots", description = "Lists all available snapshots for the account.", responseObject = SnapshotResponse.class, entityType = {
+        Snapshot.class }, responseView = ResponseObject.ResponseView.Full, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class ListSnapshotsCmdByAdmin extends ListSnapshotsCmd implements AdminCmd {
+    @Parameter(name = ApiConstants.IMAGE_STORE_ID, type = CommandType.UUID, entityType = ImageStoreResponse.class,
+               description = "ID of the image or image cache store", since = "4.19")
+    private Long imageStoreId;
+
+    @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class,
+               description = "ID of the storage pool", since = "4.19")
+    private Long storagePoolId;
+
+    @Override
+    public Long getImageStoreId() {
+        return imageStoreId;
+    }
+
+    @Override
+    public Long getStoragePoolId() {
+        return storagePoolId;
+    }
+}
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
new file mode 100644
index 0000000..a538962
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmd.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.admin.storage;
+
+import org.apache.cloudstack.storage.object.ObjectStore;
+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.ObjectStoreResponse;
+import org.apache.log4j.Logger;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+@APICommand(name = "addObjectStoragePool", description = "Adds a object storage pool", responseObject = ObjectStoreResponse.class, since = "4.19.0",
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class AddObjectStoragePoolCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(AddObjectStoragePoolCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name for the object store")
+    private String name;
+
+    @Parameter(name = ApiConstants.URL, type = CommandType.STRING, length = 2048, required = true, description = "the URL for the object store")
+    private String url;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, required = true, description = "the object store provider name")
+    private String providerName;
+
+    @Parameter(name = ApiConstants.DETAILS,
+               type = CommandType.MAP,
+               description = "the details for the object store. Example: details[0].key=accesskey&details[0].value=s389ddssaa&details[1].key=secretkey&details[1].value=8dshfsss")
+    private Map details;
+
+    @Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "the tags for the storage pool")
+    private String tags;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getUrl() {
+        return url;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Map<String, String> getDetails() {
+        Map<String, String> detailsMap = null;
+        if (details != null && !details.isEmpty()) {
+            detailsMap = new HashMap<String, String>();
+            Collection<?> props = details.values();
+            Iterator<?> iter = props.iterator();
+            while (iter.hasNext()) {
+                HashMap<String, String> detail = (HashMap<String, String>)iter.next();
+                String key = detail.get(ApiConstants.KEY);
+                String value = detail.get(ApiConstants.VALUE);
+                detailsMap.put(key, value);
+            }
+        }
+        return detailsMap;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public void setDetails(Map<String, String> details) {
+        this.details = details;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute(){
+        try{
+            ObjectStore result = _storageService.discoverObjectStore(getName(), getUrl(), getProviderName(), getDetails());
+            ObjectStoreResponse storeResponse = null;
+            if (result != null) {
+                storeResponse = _responseGenerator.createObjectStoreResponse(result);
+                storeResponse.setResponseName(getCommandName());
+                storeResponse.setObjectName("objectstore");
+                setResponseObject(storeResponse);
+            } else {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add object storage");
+            }
+        } catch (Exception ex) {
+            s_logger.error("Exception: ", ex);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
+        }
+    }
+}
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 0eacc5c..477d757 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
@@ -90,6 +90,9 @@
                description = "hypervisor type of the hosts in zone that will be attached to this storage pool. KVM, VMware supported as of now.")
     private String hypervisor;
 
+    @Parameter(name = ApiConstants.IS_TAG_A_RULE, type = CommandType.BOOLEAN, description = ApiConstants.PARAMETER_DESCRIPTION_IS_TAG_A_RULE)
+    private Boolean isTagARule;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -146,6 +149,10 @@
         return hypervisor;
     }
 
+    public Boolean isTagARule() {
+        return this.isTagARule;
+    }
+
     @Override
     public long getEntityOwnerId() {
         return Account.ACCOUNT_ID_SYSTEM;
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DeleteObjectStoragePoolCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DeleteObjectStoragePoolCmd.java
new file mode 100644
index 0000000..ed305d9
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DeleteObjectStoragePoolCmd.java
@@ -0,0 +1,69 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command.admin.storage;
+
+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.ObjectStoreResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.log4j.Logger;
+
+@APICommand(name = "deleteObjectStoragePool", description = "Deletes an Object Storage Pool", responseObject = SuccessResponse.class, since = "4.19.0",
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class DeleteObjectStoragePoolCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(DeleteObjectStoragePoolCmd.class.getName());
+
+    // ///////////////////////////////////////////////////
+    // ////////////// API parameters /////////////////////
+    // ///////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ObjectStoreResponse.class, required = true, description = "The Object Storage ID.")
+    private Long id;
+
+    // ///////////////////////////////////////////////////
+    // ///////////////// Accessors ///////////////////////
+    // ///////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    // ///////////////////////////////////////////////////
+    // ///////////// API Implementation///////////////////
+    // ///////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() {
+        boolean result = _storageService.deleteObjectStore(this);
+        if (result) {
+            SuccessResponse response = new SuccessResponse(getCommandName());
+            this.setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete object store");
+        }
+    }
+}
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
new file mode 100644
index 0000000..92019e7
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.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.storage;
+
+import com.cloud.event.EventTypes;
+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.ExtractResponse;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.browser.StorageBrowser;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+
+@APICommand(name = "downloadImageStoreObject", description = "Download object at a specified path on an image store.",
+            responseObject = ExtractResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
+            responseHasSensitiveInfo = false)
+public class DownloadImageStoreObjectCmd extends BaseAsyncCmd {
+
+    @Inject
+    StorageBrowser storageBrowser;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ImageStoreResponse.class, required = true,
+               description = "id of the image store")
+    private Long storeId;
+
+    @Parameter(name = ApiConstants.PATH, type = CommandType.STRING, description = "path to download on image store")
+    private String path;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public String getPath() {
+        if (path == null) {
+            path = "/";
+        }
+        // We prepend "/" to path and normalize to prevent path traversal attacks
+        return Path.of(String.format("/%s", path)).normalize().toString().substring(1);
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        ExtractResponse response = storageBrowser.downloadImageStoreObject(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName(getCommandName());
+        this.setResponseObject(response);
+    }
+
+    /**
+     * For commands the API framework needs to know the owner of the object being acted upon. This method is
+     * used to determine that information.
+     *
+     * @return the id of the account that owns the object being acted upon
+     */
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_IMAGE_STORE_OBJECT_DOWNLOAD;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "Downloading object at path " + getPath() + " on image store " + getStoreId();
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/FindStoragePoolsForMigrationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/FindStoragePoolsForMigrationCmd.java
index 699f483..b19fa78 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/FindStoragePoolsForMigrationCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/FindStoragePoolsForMigrationCmd.java
@@ -67,7 +67,7 @@
 
     @Override
     public void execute() {
-        Pair<List<? extends StoragePool>, List<? extends StoragePool>> pools = _mgr.listStoragePoolsForMigrationOfVolume(getId());
+        Pair<List<? extends StoragePool>, List<? extends StoragePool>> pools = _mgr.listStoragePoolsForMigrationOfVolume(getId(), getKeyword());
         ListResponse<StoragePoolResponse> response = new ListResponse<StoragePoolResponse>();
         List<StoragePoolResponse> poolResponses = new ArrayList<StoragePoolResponse>();
 
@@ -87,7 +87,8 @@
             poolResponses.add(poolResponse);
         }
         sortPoolsBySuitabilityAndName(poolResponses);
-        response.setResponses(poolResponses);
+        List<StoragePoolResponse> pagingList = com.cloud.utils.StringUtils.applyPagination(poolResponses, this.getStartIndex(), this.getPageSizeVal());
+        response.setResponses(pagingList, poolResponses.size());
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.java
new file mode 100644
index 0000000..48fd25c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.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.admin.storage;
+
+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.ImageStoreResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.storage.browser.DataStoreObjectResponse;
+import org.apache.cloudstack.storage.browser.StorageBrowser;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+
+@APICommand(name = "listImageStoreObjects", description = "Lists objects at specified path on an image store.",
+            responseObject = DataStoreObjectResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
+            responseHasSensitiveInfo = false)
+public class ListImageStoreObjectsCmd extends BaseListCmd {
+
+    @Inject
+    StorageBrowser storageBrowser;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ImageStoreResponse.class, required = true,
+               description = "id of the image store")
+    private Long storeId;
+
+    @Parameter(name = ApiConstants.PATH, type = CommandType.STRING, description = "path to list on image store")
+    private String path;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public String getPath() {
+        if (path == null) {
+            path = "/";
+        }
+        // We prepend "/" to path and normalize to prevent path traversal attacks
+        return Path.of(String.format("/%s", path)).normalize().toString().substring(1);
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        ListResponse<DataStoreObjectResponse> response = storageBrowser.listImageStoreObjects(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName(getCommandName());
+        this.setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListObjectStoragePoolsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListObjectStoragePoolsCmd.java
new file mode 100644
index 0000000..9d8d8ec
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListObjectStoragePoolsCmd.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.storage;
+
+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.ListResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.log4j.Logger;
+
+@APICommand(name = "listObjectStoragePools", description = "Lists object storage pools.", responseObject = ObjectStoreResponse.class, since = "4.19.0",
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class ListObjectStoragePoolsCmd extends BaseListCmd {
+    public static final Logger s_logger = Logger.getLogger(ListObjectStoragePoolsCmd.class.getName());
+
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the object store")
+    private String storeName;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "the object store provider")
+    private String provider;
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ObjectStoreResponse.class, description = "the ID of the storage pool")
+    private Long id;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public void setProvider(String provider) {
+        this.provider = provider;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        ListResponse<ObjectStoreResponse> response = _queryService.searchForObjectStores(this);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.java
new file mode 100644
index 0000000..4dac92a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.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.storage;
+
+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.StoragePoolResponse;
+import org.apache.cloudstack.storage.browser.DataStoreObjectResponse;
+import org.apache.cloudstack.storage.browser.StorageBrowser;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+
+
+@APICommand(name = "listStoragePoolObjects", description = "Lists objects at specified path on a storage pool.",
+            responseObject = DataStoreObjectResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
+            responseHasSensitiveInfo = false)
+public class ListStoragePoolObjectsCmd extends BaseListCmd {
+
+    @Inject
+    StorageBrowser storageBrowser;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, required = true,
+               description = "id of the storage pool")
+    private Long storeId;
+
+    @Parameter(name = ApiConstants.PATH, type = CommandType.STRING, description = "path to list on storage pool")
+    private String path;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public String getPath() {
+        if (path == null) {
+            path = "/";
+        }
+        // We prepend "/" to path and normalize to prevent path traversal attacks
+        return Path.of(String.format("/%s", path)).normalize().toString().substring(1);
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        ListResponse<DataStoreObjectResponse> response = storageBrowser.listPrimaryStoreObjects(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName(getCommandName());
+        this.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 209aaac..6923353 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
@@ -24,6 +24,7 @@
 import org.apache.cloudstack.api.BaseListCmd;
 import org.apache.cloudstack.api.Parameter;
 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.PodResponse;
 import org.apache.cloudstack.api.response.StoragePoolResponse;
@@ -63,16 +64,25 @@
     @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "the ID of the storage pool")
     private Long id;
 
-    @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, entityType = StoragePoolResponse.class, description = "the ID of the storage pool")
+    @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, entityType = StoragePoolResponse.class, description = "the scope of the storage pool")
     private String scope;
 
+
     @Parameter(name = ApiConstants.STATUS, type = CommandType.STRING, description = "the status of the storage pool")
     private String status;
 
+    @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "host ID of the storage pools")
+    private Long hostId;
+
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
 
+    public Long getHostId() {
+        return hostId;
+    }
+
     public Long getClusterId() {
         return clusterId;
     }
@@ -81,6 +91,10 @@
         return ipAddress;
     }
 
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
     public String getStoragePoolName() {
         return storagePoolName;
     }
@@ -108,6 +122,15 @@
     public void setId(Long id) {
         this.id = id;
     }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -123,8 +146,4 @@
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
     }
-
-    public String getScope() {
-        return scope;
-    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/MigrateResourcesToAnotherSecondaryStorageCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/MigrateResourcesToAnotherSecondaryStorageCmd.java
new file mode 100644
index 0000000..3bc4fd7
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/MigrateResourcesToAnotherSecondaryStorageCmd.java
@@ -0,0 +1,140 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.admin.storage;
+
+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.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.api.response.MigrationResponse;
+import org.apache.cloudstack.api.response.SnapshotResponse;
+import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+@APICommand(name = "migrateResourceToAnotherSecondaryStorage",
+            description = "migrates resources from one secondary storage to destination image store",
+            responseObject = MigrationResponse.class,
+            requestHasSensitiveInfo = false,
+            responseHasSensitiveInfo = false,
+            since = "4.19.0",
+            authorized = {RoleType.Admin})
+public class MigrateResourcesToAnotherSecondaryStorageCmd extends BaseAsyncCmd {
+
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.SRC_POOL,
+               type = CommandType.UUID,
+               entityType = ImageStoreResponse.class,
+               description = "id of the image store from where the data is to be migrated",
+               required = true)
+    private Long id;
+
+
+    @Parameter(name = ApiConstants.DEST_POOL,
+               type = CommandType.UUID,
+               entityType = ImageStoreResponse.class,
+               description = "id of the destination secondary storage pool to which the resources are to be migrated",
+               required = true)
+    private Long destStoreId;
+
+    @Parameter(name = "templates",
+               type = CommandType.LIST,
+               collectionType = CommandType.UUID,
+               entityType = TemplateResponse.class,
+               description = "id(s) of the templates to be migrated",
+               required = false)
+    private List<Long> templateIdList;
+
+    @Parameter(name = "snapshots",
+               type = CommandType.LIST,
+               collectionType = CommandType.UUID,
+               entityType = SnapshotResponse.class,
+               description = "id(s) of the snapshots to be migrated",
+               required = false)
+    private List<Long> snapshotIdList;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getDestStoreId() {
+        return destStoreId;
+    }
+
+    public List<Long> getTemplateIdList() {
+        if (CollectionUtils.isEmpty(templateIdList)) {
+            return Collections.emptyList();
+        }
+        return templateIdList;
+    }
+
+    public List<Long> getSnapshotIdList() {
+        if (CollectionUtils.isEmpty(snapshotIdList)) {
+            return Collections.emptyList();
+        }
+        return snapshotIdList;
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_IMAGE_STORE_RESOURCES_MIGRATE;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "Attempting to migrate files/data objects to another image store";
+    }
+
+    @Override
+    public void execute() {
+        MigrationResponse response = _imageStoreService.migrateResources(this);
+        response.setObjectName("imagestore");
+        this.setResponseObject(response);
+        CallContext.current().setEventDetails(response.getMessage());
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccountId();
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return getId();
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.ImageStore;
+    }
+}
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
new file mode 100644
index 0000000..497179d
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmd.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.storage;
+
+import org.apache.cloudstack.storage.object.ObjectStore;
+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.ObjectStoreResponse;
+import org.apache.cloudstack.context.CallContext;
+
+@APICommand(name = UpdateObjectStoragePoolCmd.APINAME, description = "Updates object storage pool", responseObject = ObjectStoreResponse.class, entityType = {ObjectStore.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0")
+public class UpdateObjectStoragePoolCmd extends BaseCmd {
+    public static final String APINAME = "updateObjectStoragePool";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ObjectStoreResponse.class, required = true, description = "Object Store ID")
+    private Long id;
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name for the object store")
+    private String name;
+
+    @Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "the url for the object store")
+    private String url;
+
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        ObjectStore result = _storageService.updateObjectStore(getId(), this);
+
+        ObjectStoreResponse storeResponse = null;
+        if (result != null) {
+            storeResponse = _responseGenerator.createObjectStoreResponse(result);
+            storeResponse.setResponseName(getCommandName());
+            storeResponse.setObjectName("objectstore");
+            setResponseObject(storeResponse);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update object storage");
+        }
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME;
+    }
+
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccountId();
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateStoragePoolCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateStoragePoolCmd.java
index 9e34684..7a907e0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateStoragePoolCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateStoragePoolCmd.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.api.command.admin.storage;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.log4j.Logger;
@@ -32,6 +33,7 @@
 import com.cloud.storage.StoragePool;
 import com.cloud.user.Account;
 
+@SuppressWarnings("rawtypes")
 @APICommand(name = "updateStoragePool", description = "Updates a storage pool.", responseObject = StoragePoolResponse.class, since = "3.0.0",
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class UpdateStoragePoolCmd extends BaseCmd {
@@ -61,6 +63,23 @@
             " enable it back.")
     private Boolean enabled;
 
+    @Parameter(name = ApiConstants.DETAILS,
+                            type = CommandType.MAP,
+                            required = false,
+                            description = "the details for the storage pool",
+                            since = "4.19.0")
+    private Map details;
+
+    @Parameter(name = ApiConstants.URL,
+                            type = CommandType.STRING,
+                            required = false,
+                            description = "the URL of the storage pool",
+                            since = "4.19.0")
+    private String url;
+
+    @Parameter(name = ApiConstants.IS_TAG_A_RULE, type = CommandType.BOOLEAN, description = ApiConstants.PARAMETER_DESCRIPTION_IS_TAG_A_RULE)
+    private Boolean isTagARule;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -89,6 +108,10 @@
         return enabled;
     }
 
+    public Boolean isTagARule() {
+        return isTagARule;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -108,6 +131,22 @@
         return ApiCommandResourceType.StoragePool;
     }
 
+    public Map<String,String> getDetails() {
+        return details;
+    }
+
+    public void setDetails(Map<String,String> details) {
+        this.details = details;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
     @Override
     public void execute() {
         StoragePool result = _storageService.updateStoragePool(this);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/CreateSecondaryStorageSelectorCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/CreateSecondaryStorageSelectorCmd.java
new file mode 100644
index 0000000..a0e99b5
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/CreateSecondaryStorageSelectorCmd.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.storage.heuristics;
+
+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.response.SecondaryStorageHeuristicsResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+
+import static org.apache.cloudstack.api.ApiConstants.HEURISTIC_TYPE_VALID_OPTIONS;
+
+@APICommand(name = "createSecondaryStorageSelector", description = "Creates a secondary storage selector, described by the heuristic rule.", since = "4.19.0", responseObject =
+        SecondaryStorageHeuristicsResponse.class, entityType = {Heuristic.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
+public class CreateSecondaryStorageSelectorCmd extends BaseCmd{
+
+    @Parameter(name = ApiConstants.NAME, required = true, type = CommandType.STRING, description = "The name identifying the heuristic rule.")
+    private String name;
+
+    @Parameter(name = ApiConstants.DESCRIPTION, required = true, type = BaseCmd.CommandType.STRING, description = "The description of the heuristic rule.")
+    private String description;
+
+    @Parameter(name = ApiConstants.ZONE_ID, required = true, entityType = ZoneResponse.class, type = BaseCmd.CommandType.UUID, description = "The zone in which the heuristic " +
+            "rule will be applied.")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.TYPE, required = true, type = BaseCmd.CommandType.STRING, description =
+            "The resource type directed to a specific secondary storage by the selector. " + HEURISTIC_TYPE_VALID_OPTIONS)
+    private String type;
+
+    @Parameter(name = ApiConstants.HEURISTIC_RULE, required = true, type = BaseCmd.CommandType.STRING, description = "The heuristic rule, in JavaScript language. It is required " +
+            "that it returns the UUID of a secondary storage pool. An example of a rule is `if (snapshot.hypervisorType === 'KVM') { '7832f261-c602-4e8e-8580-2496ffbbc45d'; " +
+            "}` would allocate all snapshots with the KVM hypervisor to the specified secondary storage UUID.", length = 65535)
+    private String heuristicRule;
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getHeuristicRule() {
+        return heuristicRule;
+    }
+
+    @Override
+    public void execute()  {
+        Heuristic heuristic = _storageService.createSecondaryStorageHeuristic(this);
+
+        if (heuristic == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create a secondary storage selector.");
+        }
+
+        SecondaryStorageHeuristicsResponse response = _responseGenerator.createSecondaryStorageSelectorResponse(heuristic);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/ListSecondaryStorageSelectorsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/ListSecondaryStorageSelectorsCmd.java
new file mode 100644
index 0000000..c437cd6
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/ListSecondaryStorageSelectorsCmd.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.storage.heuristics;
+
+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.ListResponse;
+import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+
+import static org.apache.cloudstack.api.ApiConstants.HEURISTIC_TYPE_VALID_OPTIONS;
+
+@APICommand(name = "listSecondaryStorageSelectors", description = "Lists the secondary storage selectors and their rules.", since = "4.19.0", responseObject =
+        SecondaryStorageHeuristicsResponse.class, requestHasSensitiveInfo = false, entityType = {Heuristic.class}, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
+public class ListSecondaryStorageSelectorsCmd extends BaseListCmd {
+
+    @Parameter(name = ApiConstants.ZONE_ID, required = true, entityType = ZoneResponse.class, type = CommandType.UUID, description = "The zone ID to be used in the search filter.")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description =
+            "Whether to filter the selectors by type and, if so, which one. " + HEURISTIC_TYPE_VALID_OPTIONS)
+    private String type;
+
+    @Parameter(name = ApiConstants.SHOW_REMOVED, type = CommandType.BOOLEAN, description = "Show removed heuristics.")
+    private boolean showRemoved = false;
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public boolean isShowRemoved() {
+        return showRemoved;
+    }
+
+    @Override
+    public void execute()  {
+        ListResponse<SecondaryStorageHeuristicsResponse> response = _queryService.listSecondaryStorageSelectors(this);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.java
new file mode 100644
index 0000000..79554f4
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.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.admin.storage.heuristics;
+
+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.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+
+@APICommand(name = "removeSecondaryStorageSelector", description = "Removes an existing secondary storage selector.", since = "4.19.0", responseObject =
+        SecondaryStorageHeuristicsResponse.class, requestHasSensitiveInfo = false, entityType = {Heuristic.class}, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
+public class RemoveSecondaryStorageSelectorCmd extends BaseCmd {
+    @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, entityType = SecondaryStorageHeuristicsResponse.class, required = true,
+            description = "The unique identifier of the secondary storage selector to be removed.")
+    private Long id;
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public void execute()  {
+        _storageService.removeSecondaryStorageHeuristic(this);
+        final SuccessResponse response = new SuccessResponse();
+        response.setResponseName(getCommandName());
+        response.setSuccess(true);
+        setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/UpdateSecondaryStorageSelectorCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/UpdateSecondaryStorageSelectorCmd.java
new file mode 100644
index 0000000..e63d1cb
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/UpdateSecondaryStorageSelectorCmd.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.admin.storage.heuristics;
+
+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.SecondaryStorageHeuristicsResponse;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+
+@APICommand(name = "updateSecondaryStorageSelector", description = "Updates an existing secondary storage selector.", since = "4.19.0", responseObject =
+        SecondaryStorageHeuristicsResponse.class, requestHasSensitiveInfo = false, entityType = {Heuristic.class}, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
+public class UpdateSecondaryStorageSelectorCmd extends BaseCmd {
+    @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, entityType = SecondaryStorageHeuristicsResponse.class, required = true,
+            description = "The unique identifier of the secondary storage selector.")
+    private Long id;
+
+    @Parameter(name = ApiConstants.HEURISTIC_RULE, required = true, type = BaseCmd.CommandType.STRING, description = "The heuristic rule, in JavaScript language. It is required " +
+            "that it returns the UUID of a secondary storage pool. An example of a rule is `if (snapshot.hypervisorType === 'KVM') { '7832f261-c602-4e8e-8580-2496ffbbc45d'; " +
+            "}` would allocate all snapshots with the KVM hypervisor to the specified secondary storage UUID.", length = 65535)
+    private String heuristicRule;
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getHeuristicRule() {
+        return heuristicRule;
+    }
+
+    @Override
+    public void execute()  {
+        Heuristic heuristic = _storageService.updateSecondaryStorageHeuristic(this);
+
+        if (heuristic == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update the secondary storage selector.");
+        }
+
+        SecondaryStorageHeuristicsResponse response = _responseGenerator.createSecondaryStorageSelectorResponse(heuristic);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdmin.java
index 2f57783..22eb0ff 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdmin.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdmin.java
@@ -17,15 +17,36 @@
 package org.apache.cloudstack.api.command.admin.template;
 
 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.template.ListTemplatesCmd;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
 
 import com.cloud.template.VirtualMachineTemplate;
 
-@APICommand(name = "listTemplates", description = "List all public, private, and privileged templates.", responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Full,
-        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+@APICommand(name = "listTemplates", description = "List all public, private, and privileged templates.",
+            responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class},
+            responseView = ResponseView.Full, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class ListTemplatesCmdByAdmin extends ListTemplatesCmd implements AdminCmd {
+    @Parameter(name = ApiConstants.IMAGE_STORE_ID, type = CommandType.UUID, entityType = ImageStoreResponse.class,
+               description = "ID of the image or image cache store", since = "4.19")
+    private Long imageStoreId;
 
+    @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class,
+               description = "ID of the storage pool", since = "4.19")
+    private Long storagePoolId;
+
+    @Override
+    public Long getImageStoreId() {
+        return imageStoreId;
+    }
+
+    @Override
+    public Long getStoragePoolId() {
+        return storagePoolId;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListVnfTemplatesCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListVnfTemplatesCmdByAdmin.java
new file mode 100644
index 0000000..f231036
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListVnfTemplatesCmdByAdmin.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.api.command.admin.template;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.admin.AdminCmd;
+import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
+
+import com.cloud.template.VirtualMachineTemplate;
+
+@APICommand(name = "listVnfTemplates", description = "List all public, private, and privileged VNF templates.",
+        responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Full,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        since = "4.19.0")
+public class ListVnfTemplatesCmdByAdmin extends ListVnfTemplatesCmd implements AdminCmd {
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java
index 2859375..91c0dd5 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java
@@ -18,9 +18,11 @@
 
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.admin.AdminCmd;
 import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
 import org.apache.cloudstack.api.response.TemplateResponse;
 
 @APICommand(name = "registerTemplate", description = "Registers an existing template into the CloudStack cloud.", responseObject = TemplateResponse.class, responseView = ResponseView.Full,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
-public class RegisterTemplateCmdByAdmin extends RegisterTemplateCmd {}
+public class RegisterTemplateCmdByAdmin extends RegisterTemplateCmd implements AdminCmd {
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterVnfTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterVnfTemplateCmdByAdmin.java
new file mode 100644
index 0000000..45b40bf
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterVnfTemplateCmdByAdmin.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.api.command.admin.template;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.admin.AdminCmd;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
+
+@APICommand(name = "registerVnfTemplate",
+        description = "Registers an existing VNF template into the CloudStack cloud. ",
+        responseObject = TemplateResponse.class, responseView = ResponseView.Full,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        since = "4.19.0")
+public class RegisterVnfTemplateCmdByAdmin extends RegisterVnfTemplateCmd implements AdminCmd {
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java
index 09591c8..b1dfae3 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java
@@ -24,4 +24,5 @@
 
 @APICommand(name = "updateTemplate", description = "Updates attributes of a template.", responseObject = TemplateResponse.class, responseView = ResponseView.Full,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
-public class UpdateTemplateCmdByAdmin extends UpdateTemplateCmd implements AdminCmd {}
+public class UpdateTemplateCmdByAdmin extends UpdateTemplateCmd implements AdminCmd {
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateVnfTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateVnfTemplateCmdByAdmin.java
new file mode 100644
index 0000000..102a470
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateVnfTemplateCmdByAdmin.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.api.command.admin.template;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.admin.AdminCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
+
+@APICommand(name = "updateVnfTemplate",
+        description = "Updates a template to VNF template or attributes of a VNF template.",
+        responseObject = TemplateResponse.class, responseView = ResponseView.Full,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        since = "4.19.0")
+public class UpdateVnfTemplateCmdByAdmin extends UpdateVnfTemplateCmd implements AdminCmd {
+}
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 56552ba..b709097 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
@@ -39,7 +39,7 @@
 import com.google.common.base.Preconditions;
 
 @APICommand(name = "moveUser",
-        description = "Moves a user to another account",
+        description = "Moves a user to another account in the same domain.",
         responseObject = SuccessResponse.class,
         requestHasSensitiveInfo = false,
         responseHasSensitiveInfo = false,
@@ -56,18 +56,18 @@
             type = CommandType.UUID,
             entityType = UserResponse.class,
             required = true,
-            description = "id of the user to be deleted")
+            description = "id of the user to be moved.")
     private Long id;
 
     @Parameter(name = ApiConstants.ACCOUNT,
             type = CommandType.STRING,
-            description = "Creates the user under the specified account. If no account is specified, the username will be used as the account name.")
+            description = "Moves the user under the specified account. If no account name is specified, it is necessary to provide an account id.")
     private String accountName;
 
     @Parameter(name = ApiConstants.ACCOUNT_ID,
             type = CommandType.UUID,
             entityType = AccountResponse.class,
-            description = "Creates the user under the specified domain. Has to be accompanied with the account parameter")
+            description = "Moves the user under the specified account. If no account id is specified, it is necessary to provide an account name.")
     private Long accountId;
 
     @Inject
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/DeleteVlanIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/DeleteVlanIpRangeCmd.java
index 15f0bde..390759c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/DeleteVlanIpRangeCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/DeleteVlanIpRangeCmd.java
@@ -29,7 +29,7 @@
 
 import com.cloud.user.Account;
 
-@APICommand(name = "deleteVlanIpRange", description = "Creates a VLAN IP range.", responseObject = SuccessResponse.class,
+@APICommand(name = "deleteVlanIpRange", description = "Deletes a VLAN IP range.", responseObject = SuccessResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class DeleteVlanIpRangeCmd extends BaseCmd {
     public static final Logger s_logger = Logger.getLogger(DeleteVlanIpRangeCmd.class.getName());
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVnfApplianceCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVnfApplianceCmdByAdmin.java
new file mode 100644
index 0000000..5d2756c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVnfApplianceCmdByAdmin.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 org.apache.cloudstack.api.command.admin.vm;
+
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.command.admin.AdminCmd;
+import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
+import org.apache.cloudstack.api.response.UserVmResponse;
+
+@APICommand(name = "deployVnfAppliance",
+        description = "Creates and automatically starts a VNF appliance based on a service offering, disk offering, and template.",
+        responseObject = UserVmResponse.class,
+        responseView = ResponseObject.ResponseView.Full,
+        entityType = {VirtualMachine.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = true,
+        since = "4.19.0")
+public class DeployVnfApplianceCmdByAdmin extends DeployVnfApplianceCmd implements AdminCmd {
+}
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 7023331..d632c78 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
@@ -84,7 +84,7 @@
     @Parameter(name = ApiConstants.NAME,
             type = CommandType.STRING,
             required = true,
-            description = "the hypervisor name of the instance")
+            description = "the name of the instance as it is known to the hypervisor")
     private String name;
 
     @Parameter(name = ApiConstants.DISPLAY_NAME,
@@ -124,7 +124,7 @@
             type = CommandType.UUID,
             entityType = ServiceOfferingResponse.class,
             required = true,
-            description = "the ID of the service offering for the virtual machine")
+            description = "the service offering for the virtual machine")
     private Long serviceOfferingId;
 
     @Parameter(name = ApiConstants.NIC_NETWORK_LIST,
@@ -154,7 +154,7 @@
 
     @Parameter(name = ApiConstants.FORCED,
             type = CommandType.BOOLEAN,
-            description = "VM is imported despite some of its NIC's MAC addresses are already present")
+            description = "VM is imported despite some of its NIC's MAC addresses are already present, in case the MAC address exists then a new MAC address is generated")
     private Boolean forced;
 
     /////////////////////////////////////////////////////
@@ -279,7 +279,8 @@
 
     @Override
     public String getEventDescription() {
-        return "Importing unmanaged VM";
+        String vmName = this.name;
+        return String.format("Importing unmanaged VM: %s", vmName);
     }
 
     public boolean isForced() {
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
new file mode 100644
index 0000000..e8b9f3a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java
@@ -0,0 +1,258 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.event.EventTypes;
+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.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.NetworkResponse;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.api.response.VmwareDatacenterResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.vm.VmImportService;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+
+@APICommand(name = "importVm",
+        description = "Import virtual machine from a unmanaged host into CloudStack",
+        responseObject = UserVmResponse.class,
+        responseView = ResponseObject.ResponseView.Full,
+        requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = true,
+        authorized = {RoleType.Admin},
+        since = "4.19.0")
+public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
+    public static final Logger LOGGER = Logger.getLogger(ImportVmCmd.class);
+
+    @Inject
+    public VmImportService vmImportService;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+
+    @Parameter(name = ApiConstants.ZONE_ID,
+            type = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            required = true,
+            description = "the zone ID")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.USERNAME,
+            type = CommandType.STRING,
+            description = "the username for the host")
+    private String username;
+
+    @Parameter(name = ApiConstants.PASSWORD,
+            type = CommandType.STRING,
+            description = "the password for the host")
+    private String password;
+
+    @Parameter(name = ApiConstants.HOST,
+            type = CommandType.STRING,
+            description = "the host name or IP address")
+    private String host;
+
+    @Parameter(name = ApiConstants.HYPERVISOR,
+            type = CommandType.STRING,
+            required = true,
+            description = "hypervisor type of the host")
+    private String hypervisor;
+
+    @Parameter(name = ApiConstants.DISK_PATH,
+            type = CommandType.STRING,
+            description = "path of the disk image")
+    private String diskPath;
+
+    @Parameter(name = ApiConstants.IMPORT_SOURCE,
+            type = CommandType.STRING,
+            required = true,
+            description = "Source location for Import" )
+    private String importSource;
+
+    @Parameter(name = ApiConstants.NETWORK_ID,
+            type = CommandType.UUID,
+            entityType = NetworkResponse.class,
+            description = "the network ID")
+    private Long networkId;
+
+    @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "Host where local disk is located")
+    private Long hostId;
+
+    @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "Shared storage pool where disk is located")
+    private Long storagePoolId;
+
+    @Parameter(name = ApiConstants.TEMP_PATH,
+            type = CommandType.STRING,
+            description = "Temp Path on external host for disk image copy" )
+    private String tmpPath;
+
+    // Import from Vmware to KVM migration parameters
+
+    @Parameter(name = ApiConstants.EXISTING_VCENTER_ID,
+            type = CommandType.UUID,
+            entityType = VmwareDatacenterResponse.class,
+            description = "(only for importing migrated VMs from Vmware to KVM) UUID of a linked existing vCenter")
+    private Long existingVcenterId;
+
+    @Parameter(name = ApiConstants.HOST_IP,
+            type = BaseCmd.CommandType.STRING,
+            description = "(only for importing migrated VMs from Vmware to KVM) VMware ESXi host IP/Name.")
+    private String hostip;
+
+    @Parameter(name = ApiConstants.VCENTER,
+            type = CommandType.STRING,
+            description = "(only for importing migrated VMs from Vmware to KVM) 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.DATACENTER_NAME, type = CommandType.STRING,
+            description = "(only for importing migrated VMs from Vmware to KVM) Name of VMware datacenter.")
+    private String datacenterName;
+
+    @Parameter(name = ApiConstants.CLUSTER_NAME, type = CommandType.STRING,
+            description = "(only for importing migrated VMs from Vmware to KVM) Name of VMware cluster.")
+    private String clusterName;
+
+    @Parameter(name = ApiConstants.CONVERT_INSTANCE_HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
+            description = "(only for importing migrated VMs from Vmware to KVM) optional - the host to perform the virt-v2v migration from VMware to KVM.")
+    private Long convertInstanceHostId;
+
+    @Parameter(name = ApiConstants.CONVERT_INSTANCE_STORAGE_POOL_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class,
+            description = "(only for importing migrated VMs from Vmware to KVM) optional - the temporary storage pool to perform the virt-v2v migration from VMware to KVM.")
+    private Long convertStoragePoolId;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public Long getExistingVcenterId() {
+        return existingVcenterId;
+    }
+
+    public String getHostIp() {
+        return hostip;
+    }
+
+    public String getVcenter() {
+        return vcenter;
+    }
+
+    public String getDatacenterName() {
+        return datacenterName;
+    }
+
+    public String getClusterName() {
+        return clusterName;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public Long getConvertInstanceHostId() {
+        return convertInstanceHostId;
+    }
+
+    public Long getConvertStoragePoolId() {
+        return convertStoragePoolId;
+    }
+
+    public String getHypervisor() {
+        return hypervisor;
+    }
+
+    public String getDiskPath() {
+        return diskPath;
+    }
+
+    public String getImportSource() {
+        return importSource;
+    }
+
+    public Long getHostId() {
+        return hostId;
+    }
+
+    public Long getStoragePoolId() {
+        return storagePoolId;
+    }
+
+    public String getTmpPath() {
+        return tmpPath;
+    }
+
+    public Long getNetworkId() {
+        return networkId;
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_VM_IMPORT;
+    }
+
+    @Override
+    public String getEventDescription() {
+        String vmName = getName();
+        if (ObjectUtils.anyNotNull(vcenter, existingVcenterId)) {
+            String msg = StringUtils.isNotBlank(vcenter) ?
+                    String.format("external vCenter: %s - datacenter: %s", vcenter, datacenterName) :
+                    String.format("existing vCenter Datacenter with ID: %s", existingVcenterId);
+            return String.format("Importing unmanaged VM: %s from %s - VM: %s", getDisplayName(), msg, vmName);
+        }
+        return String.format("Importing unmanaged VM: %s", vmName);
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+        UserVmResponse response = vmImportService.importVm(this);
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java
new file mode 100644
index 0000000..88df04d
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.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.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.ListResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.apache.cloudstack.vm.VmImportService;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+
+@APICommand(name = "listVmsForImport",
+        description = "Lists virtual machines on a unmanaged host",
+        responseObject = UnmanagedInstanceResponse.class,
+        responseView = ResponseObject.ResponseView.Full,
+        entityType = {UnmanagedInstanceTO.class},
+        requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = true,
+        authorized = {RoleType.Admin},
+        since = "4.19.0")
+public class ListVmsForImportCmd extends BaseListCmd {
+    public static final Logger LOGGER = Logger.getLogger(ListVmsForImportCmd.class.getName());
+
+    @Inject
+    public VmImportService vmImportService;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ZONE_ID,
+            type = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            required = true,
+            description = "the zone ID")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.USERNAME,
+            type = CommandType.STRING,
+            description = "the username for the host")
+    private String username;
+
+    @Parameter(name = ApiConstants.PASSWORD,
+            type = CommandType.STRING,
+            description = "the password for the host")
+    private String password;
+
+    @Parameter(name = ApiConstants.HOST,
+            type = CommandType.STRING,
+            required = true,
+            description = "the host name or IP address")
+    private String host;
+
+    @Parameter(name = ApiConstants.HYPERVISOR,
+            type = CommandType.STRING,
+            required = true,
+            description = "hypervisor type of the host")
+    private String hypervisor;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public String getHypervisor() {
+        return hypervisor;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+        ListResponse<UnmanagedInstanceResponse> response = vmImportService.listVmsForImport(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/MigrateVirtualMachineWithVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java
index 731cb67..549d02b 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
@@ -83,6 +83,12 @@
                "<1b331390-59f2-4796-9993-bf11c6e76225>&migrateto[2].pool=<41fdb564-9d3b-447d-88ed-7628f7640cbc>")
     private Map migrateVolumeTo;
 
+    @Parameter(name = ApiConstants.AUTO_SELECT,
+            since = "4.19.0",
+            type = CommandType.BOOLEAN,
+            description = "Automatically select a destination host for a running instance, if hostId is not specified. false by default")
+    private Boolean autoSelect;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -144,31 +150,39 @@
         return ApiCommandResourceType.VirtualMachine;
     }
 
+    private Host getDestinationHost() {
+        if (getHostId() == null) {
+            return null;
+        }
+        Host destinationHost = _resourceService.getHost(getHostId());
+        // OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
+        if (destinationHost == null) {
+            s_logger.error(String.format("Unable to find the host with ID [%s].", getHostId()));
+            throw new InvalidParameterValueException("Unable to find the specified host to migrate the VM.");
+        }
+        return destinationHost;
+    }
+
     @Override
     public void execute() {
-        if (hostId == null && MapUtils.isEmpty(migrateVolumeTo)) {
-            throw new InvalidParameterValueException(String.format("Either %s or %s must be passed for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO));
+        if (hostId == null && MapUtils.isEmpty(migrateVolumeTo) && !Boolean.TRUE.equals(autoSelect)) {
+            throw new InvalidParameterValueException(String.format("Either %s or %s must be passed or %s must be true for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO, ApiConstants.AUTO_SELECT));
         }
 
         VirtualMachine virtualMachine = _userVmService.getVm(getVirtualMachineId());
-        if (!VirtualMachine.State.Running.equals(virtualMachine.getState()) && hostId != null) {
+        if (!VirtualMachine.State.Running.equals(virtualMachine.getState()) && (hostId != null || Boolean.TRUE.equals(autoSelect))) {
             throw new InvalidParameterValueException(String.format("%s is not in the Running state to migrate it to the new host.", virtualMachine));
         }
 
-        if (!VirtualMachine.State.Stopped.equals(virtualMachine.getState()) && hostId == null) {
-            throw new InvalidParameterValueException(String.format("%s is not in the Stopped state to migrate, use the %s parameter to migrate it to a new host.",
-                    virtualMachine, ApiConstants.HOST_ID));
+        if (!VirtualMachine.State.Stopped.equals(virtualMachine.getState()) && hostId == null && Boolean.FALSE.equals(autoSelect)) {
+            throw new InvalidParameterValueException(String.format("%s is not in the Stopped state to migrate, use the %s or %s parameter to migrate it to a new host.",
+                    virtualMachine, ApiConstants.HOST_ID,ApiConstants.AUTO_SELECT));
         }
 
         try {
             VirtualMachine migratedVm = null;
-            if (hostId != null) {
-                Host destinationHost = _resourceService.getHost(getHostId());
-                // OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
-                if (destinationHost == null) {
-                    s_logger.error(String.format("Unable to find the host with ID [%s].", getHostId()));
-                    throw new InvalidParameterValueException("Unable to find the specified host to migrate the VM.");
-                }
+            if (getHostId() != null || Boolean.TRUE.equals(autoSelect)) {
+                Host destinationHost = getDestinationHost();
                 migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, getVolumeToPool());
             } else if (MapUtils.isNotEmpty(migrateVolumeTo)) {
                 migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), getVolumeToPool());
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 f1c2a5b..b69e7f4 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
@@ -28,6 +28,7 @@
 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.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -56,7 +57,7 @@
     @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the vpc offering")
     private String vpcOfferingName;
 
-    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, required = true, description = "the display text of " + "the vpc offering")
+    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "the display text of the vpc offering, defaults to the 'name'")
     private String displayText;
 
     @Parameter(name = ApiConstants.SUPPORTED_SERVICES,
@@ -115,7 +116,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? vpcOfferingName : displayText;
     }
 
     public List<String> getSupportedServices() {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/ListPrivateGatewaysCmdByAdminCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/ListPrivateGatewaysCmdByAdminCmd.java
new file mode 100644
index 0000000..13a63e9
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/ListPrivateGatewaysCmdByAdminCmd.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.api.command.admin.vpc;
+
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.command.admin.AdminCmd;
+import org.apache.cloudstack.api.command.user.vpc.ListPrivateGatewaysCmd;
+import org.apache.cloudstack.api.response.PrivateGatewayResponse;
+
+import com.cloud.network.vpc.VpcGateway;
+
+@APICommand(name = "listPrivateGateways", description = "List private gateways", responseObject = PrivateGatewayResponse.class, entityType = {VpcGateway.class},
+        responseView = ResponseObject.ResponseView.Full,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class ListPrivateGatewaysCmdByAdminCmd extends ListPrivateGatewaysCmd implements AdminCmd {
+    public static final Logger s_logger = Logger.getLogger(ListPrivateGatewaysCmdByAdminCmd.class.getName());
+
+}
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 b31520e..f9bfcb2 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
@@ -46,10 +46,14 @@
     //////////////// API parameters /////////////////////
     /////////////////////////////////////////////////////
 
-    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IPAddressResponse.class, required = true, description = "the ID of the public IP address"
-        + " to disassociate")
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IPAddressResponse.class, description = "the ID of the public IP address"
+        + " to disassociate. Mutually exclusive with the ipaddress parameter")
     private Long id;
 
+    @Parameter(name=ApiConstants.IP_ADDRESS, type=CommandType.STRING,  since="4.19.0", description="IP Address to be disassociated."
+        +  " Mutually exclusive with the id parameter")
+    private String ipAddress;
+
     // unexposed parameter needed for events logging
     @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, expose = false)
     private Long ownerId;
@@ -59,7 +63,18 @@
     /////////////////////////////////////////////////////
 
     public Long getIpAddressId() {
-        return id;
+       if (id != null & ipAddress != null) {
+           throw new InvalidParameterValueException("id parameter is mutually exclusive with ipaddress parameter");
+       }
+
+       if (id != null) {
+            return id;
+        } else if (ipAddress != null) {
+            IpAddress ip = getIpAddressByIp(ipAddress);
+            return ip.getId();
+        }
+
+        throw new InvalidParameterValueException("Please specify either IP address or IP address ID");
     }
 
     /////////////////////////////////////////////////////
@@ -68,12 +83,13 @@
 
     @Override
     public void execute() throws InsufficientAddressCapacityException {
-        CallContext.current().setEventDetails("IP ID: " + getIpAddressId());
+        Long ipAddressId = getIpAddressId();
+        CallContext.current().setEventDetails("IP ID: " + ipAddressId);
         boolean result = false;
-        if (!isPortable(id)) {
-            result = _networkService.releaseIpAddress(getIpAddressId());
+        if (!isPortable()) {
+            result = _networkService.releaseIpAddress(ipAddressId);
         } else {
-            result = _networkService.releasePortableIpAddress(getIpAddressId());
+            result = _networkService.releasePortableIpAddress(ipAddressId);
         }
         if (result) {
             SuccessResponse response = new SuccessResponse(getCommandName());
@@ -85,7 +101,7 @@
 
     @Override
     public String getEventType() {
-        if (!isPortable(id)) {
+        if (!isPortable()) {
             return EventTypes.EVENT_NET_IP_RELEASE;
         } else {
             return EventTypes.EVENT_PORTABLE_IP_RELEASE;
@@ -100,10 +116,7 @@
     @Override
     public long getEntityOwnerId() {
         if (ownerId == null) {
-            IpAddress ip = getIpAddress(id);
-            if (ip == null) {
-                throw new InvalidParameterValueException("Unable to find IP address by ID=" + id);
-            }
+            IpAddress ip = getIpAddress();
             ownerId = ip.getAccountId();
         }
 
@@ -120,11 +133,11 @@
 
     @Override
     public Long getSyncObjId() {
-        IpAddress ip = getIpAddress(id);
+        IpAddress ip = getIpAddress();
         return ip.getAssociatedWithNetworkId();
     }
 
-    private IpAddress getIpAddress(long id) {
+    private IpAddress getIpAddressById(Long id) {
         IpAddress ip = _entityMgr.findById(IpAddress.class, id);
 
         if (ip == null) {
@@ -134,6 +147,29 @@
         }
     }
 
+    private IpAddress getIpAddressByIp(String ipAddress) {
+        IpAddress ip = _networkService.getIp(ipAddress);
+        if (ip == null) {
+            throw new InvalidParameterValueException("Unable to find IP address by IP address=" + ipAddress);
+        } else {
+            return ip;
+        }
+    }
+
+    private IpAddress getIpAddress() {
+        if (id != null & ipAddress != null) {
+            throw new InvalidParameterValueException("id parameter is mutually exclusive with ipaddress parameter");
+        }
+
+        if (id != null) {
+            return getIpAddressById(id);
+        } else if (ipAddress != null){
+            return getIpAddressByIp(ipAddress);
+        }
+
+        throw new InvalidParameterValueException("Please specify either IP address or IP address ID");
+    }
+
     @Override
     public ApiCommandResourceType getApiResourceType() {
         return ApiCommandResourceType.IpAddress;
@@ -144,8 +180,8 @@
         return getIpAddressId();
     }
 
-    private boolean isPortable(long id) {
-        IpAddress ip = getIpAddress(id);
+    private boolean isPortable() {
+        IpAddress ip = getIpAddress();
         return ip.isPortable();
     }
 }
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 e456074..22eb70c 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
@@ -25,7 +25,7 @@
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
+import org.apache.cloudstack.api.BaseListRetrieveOnlyResourceCountCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.command.user.UserCmd;
@@ -42,7 +42,7 @@
 
 @APICommand(name = "listPublicIpAddresses", description = "Lists all public IP addresses", responseObject = IPAddressResponse.class, responseView = ResponseView.Restricted,
  requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, entityType = { IpAddress.class })
-public class ListPublicIpAddressesCmd extends BaseListTaggedResourcesCmd implements UserCmd {
+public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountCmd implements UserCmd {
     public static final Logger s_logger = Logger.getLogger(ListPublicIpAddressesCmd.class.getName());
 
     private static final String s_name = "listpublicipaddressesresponse";
@@ -173,10 +173,6 @@
         return forVirtualNetwork;
     }
 
-    public Boolean getForLoadBalancing() {
-        return forLoadBalancing;
-    }
-
     public String getState() {
         return state;
     }
@@ -192,12 +188,15 @@
     @Override
     public void execute() {
         Pair<List<? extends IpAddress>, Integer> result = _mgr.searchForIPAddresses(this);
-        ListResponse<IPAddressResponse> response = new ListResponse<IPAddressResponse>();
-        List<IPAddressResponse> ipAddrResponses = new ArrayList<IPAddressResponse>();
-        for (IpAddress ipAddress : result.first()) {
-            IPAddressResponse ipResponse = _responseGenerator.createIPAddressResponse(getResponseView(), ipAddress);
-            ipResponse.setObjectName("publicipaddress");
-            ipAddrResponses.add(ipResponse);
+        ListResponse<IPAddressResponse> response = new ListResponse<>();
+        List<IPAddressResponse> ipAddrResponses = new ArrayList<>();
+
+        if (!getRetrieveOnlyResourceCount()) {
+            for (IpAddress ipAddress : result.first()) {
+                IPAddressResponse ipResponse = _responseGenerator.createIPAddressResponse(getResponseView(), ipAddress);
+                ipResponse.setObjectName("publicipaddress");
+                ipAddrResponses.add(ipResponse);
+            }
         }
 
         response.setResponses(ipAddrResponses, result.second());
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListQuarantinedIpsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListQuarantinedIpsCmd.java
new file mode 100644
index 0000000..cc01470
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListQuarantinedIpsCmd.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.command.user.address;
+
+import com.cloud.network.PublicIpQuarantine;
+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.IpQuarantineResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+
+@APICommand(name = "listQuarantinedIps", responseObject = IpQuarantineResponse.class, description = "List public IP addresses in quarantine.", since = "4.19",
+        entityType = {PublicIpQuarantine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.DomainAdmin})
+public class ListQuarantinedIpsCmd extends BaseListCmd {
+
+    @Parameter(name = ApiConstants.SHOW_REMOVED, type = CommandType.BOOLEAN, description = "Show IPs removed from quarantine.")
+    private boolean showRemoved = false;
+
+    @Parameter(name = ApiConstants.SHOW_INACTIVE, type = CommandType.BOOLEAN, description = "Show IPs that are no longer in quarantine.")
+    private boolean showInactive = false;
+
+    public boolean isShowRemoved() {
+        return showRemoved;
+    }
+    public boolean isShowInactive() {
+        return showInactive;
+    }
+
+    @Override
+    public void execute() {
+        ListResponse<IpQuarantineResponse> response = _queryService.listQuarantinedIps(this);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/RemoveQuarantinedIpCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/RemoveQuarantinedIpCmd.java
new file mode 100644
index 0000000..82e8373
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/RemoveQuarantinedIpCmd.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.user.address;
+
+import com.cloud.network.PublicIpQuarantine;
+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.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.IpQuarantineResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+
+@APICommand(name = "removeQuarantinedIp", responseObject = IpQuarantineResponse.class, description = "Removes a public IP address from quarantine. Only IPs in active " +
+        "quarantine can be removed.",
+        since = "4.19", entityType = {PublicIpQuarantine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.DomainAdmin})
+public class RemoveQuarantinedIpCmd extends BaseCmd {
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IpQuarantineResponse.class, description = "The ID of the public IP address in active quarantine. " +
+            "Either the IP address is informed, or the ID of the IP address in quarantine.")
+    private Long id;
+
+    @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, description = "The public IP address in active quarantine. Either the IP address is informed, or the ID" +
+            " of the IP address in quarantine.")
+    private String ipAddress;
+
+    @Parameter(name = ApiConstants.REMOVAL_REASON, type = CommandType.STRING, required = true, description = "The reason for removing the public IP address from quarantine " +
+            "prematurely.")
+    private String removalReason;
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public String getRemovalReason() {
+        return removalReason;
+    }
+
+    @Override
+    public void execute() {
+        _networkService.removePublicIpAddressFromQuarantine(this);
+        final SuccessResponse response = new SuccessResponse();
+        response.setResponseName(getCommandName());
+        response.setSuccess(true);
+        setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/UpdateQuarantinedIpCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/UpdateQuarantinedIpCmd.java
new file mode 100644
index 0000000..b3b71c3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/UpdateQuarantinedIpCmd.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.user.address;
+
+import com.cloud.network.PublicIpQuarantine;
+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.IpQuarantineResponse;
+
+import java.util.Date;
+
+@APICommand(name = "updateQuarantinedIp", responseObject = IpQuarantineResponse.class, description = "Updates the quarantine end date for the given public IP address.",
+        since = "4.19", entityType = {PublicIpQuarantine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.DomainAdmin})
+public class UpdateQuarantinedIpCmd extends BaseCmd {
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IpQuarantineResponse.class, description = "The ID of the public IP address in " +
+            "active quarantine.")
+    private Long id;
+
+    @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, description = "The public IP address in active quarantine. Either the IP address is informed, or the ID" +
+            " of the IP address in quarantine.")
+    private String ipAddress;
+
+    @Parameter(name = ApiConstants.END_DATE, type = BaseCmd.CommandType.DATE, required = true, description = "The date when the quarantine will no longer be active.")
+    private Date endDate;
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    @Override
+    public void execute() {
+        PublicIpQuarantine publicIpQuarantine = _networkService.updatePublicIpAddressInQuarantine(this);
+        if (publicIpQuarantine == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update public IP quarantine.");
+        }
+        IpQuarantineResponse response = _responseGenerator.createQuarantinedIpsResponse(publicIpQuarantine);
+        response.setResponseName(getCommandName());
+        this.setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
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 b4f152e..cdbe153 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
@@ -232,7 +232,7 @@
                 responseObject.setResponseName(getCommandName());
             }
         } catch (Exception ex) {
-            // TODO what will happen if Resource Layer fails in a step inbetween
+            // TODO what will happen if Resource Layer fails in a step in between
             s_logger.warn("Failed to create autoscale vm group", ex);
         } finally {
             if (!success || vmGroup == null) {
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
new file mode 100644
index 0000000..e9a140c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/CreateBucketCmd.java
@@ -0,0 +1,202 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.bucket;
+
+import com.cloud.event.EventTypes;
+import com.cloud.exception.ResourceAllocationException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.storage.object.Bucket;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCreateCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.BucketResponse;
+import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+@APICommand(name = "createBucket", responseObject = BucketResponse.class,
+        description = "Creates a bucket in the specified object storage pool. ", responseView = ResponseView.Restricted,
+        entityType = {Bucket.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class CreateBucketCmd extends BaseAsyncCreateCmd implements UserCmd {
+    public static final Logger s_logger = Logger.getLogger(CreateBucketCmd.class.getName());
+    private static final String s_name = "createbucketresponse";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ACCOUNT,
+               type = CommandType.STRING,
+               description = "the account associated with the bucket. Must be used with the domainId parameter.")
+    private String accountName;
+
+    @Parameter(name = ApiConstants.PROJECT_ID,
+               type = CommandType.UUID,
+               entityType = ProjectResponse.class,
+               description = "the project associated with the bucket. Mutually exclusive with account parameter")
+    private Long projectId;
+
+    @Parameter(name = ApiConstants.DOMAIN_ID,
+               type = CommandType.UUID,
+               entityType = DomainResponse.class,
+               description = "the domain ID associated with the bucket. If used with the account parameter"
+                   + " returns the bucket associated with the account for the specified domain.")
+    private Long domainId;
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true,description = "the name of the bucket")
+    private String bucketName;
+
+    @Parameter(name = ApiConstants.OBJECT_STORAGE_ID, type = CommandType.UUID,
+            entityType = ObjectStoreResponse.class, required = true,
+            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")
+    private Integer quota;
+
+    @Parameter(name = ApiConstants.ENCRYPTION, type = CommandType.BOOLEAN, description = "Enable bucket encryption")
+    private boolean encryption;
+
+    @Parameter(name = ApiConstants.VERSIONING, type = CommandType.BOOLEAN, description = "Enable bucket versioning")
+    private boolean versioning;
+
+    @Parameter(name = ApiConstants.OBJECT_LOCKING, type = CommandType.BOOLEAN, description = "Enable object locking in bucket")
+    private boolean objectLocking;
+
+    @Parameter(name = ApiConstants.POLICY, type = CommandType.STRING,description = "The Bucket access policy")
+    private String policy;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public Long getDomainId() {
+        return domainId;
+    }
+
+    public String getBucketName() {
+        return bucketName;
+    }
+
+    private Long getProjectId() {
+        return projectId;
+    }
+
+    public long getObjectStoragePoolId() {
+        return objectStoragePoolId;
+    }
+
+    public Integer getQuota() {
+        return quota;
+    }
+
+    public boolean isEncryption() {
+        return encryption;
+    }
+
+    public boolean isVersioning() {
+        return versioning;
+    }
+
+    public boolean isObjectLocking() {
+        return objectLocking;
+    }
+
+    public String getPolicy() {
+        return policy;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    public static String getResultObjectName() {
+        return "bucket";
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Bucket;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true);
+        if (accountId == null) {
+            return CallContext.current().getCallingAccount().getId();
+        }
+
+        return accountId;
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_BUCKET_CREATE;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return  "creating bucket: " + getBucketName();
+    }
+
+    @Override
+    public void create() throws ResourceAllocationException {
+        Bucket bucket = _bucketService.allocBucket(this);
+        if (bucket != null) {
+            setEntityId(bucket.getId());
+            setEntityUuid(bucket.getUuid());
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create bucket");
+        }
+    }
+
+    @Override
+    public void execute() {
+        CallContext.current().setEventDetails("Bucket Id: " + getEntityUuid());
+
+        Bucket bucket;
+        try {
+            bucket = _bucketService.createBucket(this);
+        } catch (Exception e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+        }
+        if (bucket != null) {
+            BucketResponse response = _responseGenerator.createBucketResponse(bucket);
+            response.setResponseName(getCommandName());
+            setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create bucket with name: "+getBucketName());
+        }
+    }
+}
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
new file mode 100644
index 0000000..bf9552b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/DeleteBucketCmd.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.bucket;
+
+import com.cloud.exception.ConcurrentOperationException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.storage.object.Bucket;
+import com.cloud.user.Account;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+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.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.BucketResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+@APICommand(name = "deleteBucket", description = "Deletes an empty Bucket.", responseObject = SuccessResponse.class, entityType = {Bucket.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class DeleteBucketCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(DeleteBucketCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @ACL(accessType = AccessType.OperateEntry)
+    @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=BucketResponse.class,
+            required=true, description="The ID of the Bucket")
+    private Long id;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    public static String getResultObjectName() {
+        return "bucket";
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Bucket Bucket = _entityMgr.findById(Bucket.class, getId());
+        if (Bucket != null) {
+            return Bucket.getAccountId();
+        }
+
+        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;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Bucket;
+    }
+
+    @Override
+    public void execute() throws ConcurrentOperationException {
+        CallContext.current().setEventDetails("Bucket Id: " + this._uuidMgr.getUuid(Bucket.class, getId()));
+        boolean result = _bucketService.deleteBucket(id, CallContext.current().getCallingAccount());
+        SuccessResponse response = new SuccessResponse(getCommandName());
+        response.setSuccess(result);
+        setResponseObject(response);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/ListBucketsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/ListBucketsCmd.java
new file mode 100644
index 0000000..897b9fc
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/ListBucketsCmd.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.bucket;
+
+import org.apache.cloudstack.storage.object.Bucket;
+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.BaseListTaggedResourcesCmd;
+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.BucketResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
+import org.apache.log4j.Logger;
+
+import java.util.List;
+
+@APICommand(name = "listBuckets", description = "Lists all Buckets.", responseObject = BucketResponse.class, responseView = ResponseView.Restricted, entityType = {
+        Bucket.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class ListBucketsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
+    public static final Logger s_logger = Logger.getLogger(ListBucketsCmd.class.getName());
+
+    private static final String s_name = "listbucketsresponse";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = BucketResponse.class, description = "the ID of the bucket")
+    private Long id;
+
+    @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = BucketResponse.class, description = "the IDs of the Buckets, mutually exclusive with id")
+    private List<Long> ids;
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the bucket")
+    private String bucketName;
+
+    @Parameter(name = ApiConstants.OBJECT_STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "the ID of the object storage pool, available to ROOT admin only", authorized = {
+            RoleType.Admin})
+    private Long objectStorageId;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getBucketName() {
+        return bucketName;
+    }
+
+    public Long getObjectStorageId() {
+        return objectStorageId;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Bucket;
+    }
+
+    @Override
+    public void execute() {
+        ListResponse<BucketResponse> response = _queryService.searchForBuckets(this);
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+    }
+
+    public List<Long> getIds() {
+        return ids;
+    }
+}
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
new file mode 100644
index 0000000..b3b7e00
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/UpdateBucketCmd.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.bucket;
+
+import com.cloud.exception.ConcurrentOperationException;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.storage.object.Bucket;
+import com.cloud.user.Account;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+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.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.BucketResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+@APICommand(name = "updateBucket", description = "Updates Bucket properties", responseObject = SuccessResponse.class, entityType = {Bucket.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class UpdateBucketCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(UpdateBucketCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @ACL(accessType = AccessType.OperateEntry)
+    @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=BucketResponse.class,
+            required=true, description="The ID of the Bucket")
+    private Long id;
+
+    @Parameter(name = ApiConstants.VERSIONING, type = CommandType.BOOLEAN, description = "Enable/Disable Bucket Versioning")
+    private Boolean versioning;
+
+    @Parameter(name = ApiConstants.ENCRYPTION, type = CommandType.BOOLEAN, description = "Enable/Disable Bucket encryption")
+    private Boolean encryption;
+
+    @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")
+    private Integer quota;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public Boolean getVersioning() {
+        return versioning;
+    }
+
+    public Boolean getEncryption() {
+        return encryption;
+    }
+
+    public String getPolicy() {
+        return policy;
+    }
+
+    public Integer getQuota() {
+        return quota;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    public static String getResultObjectName() {
+        return "bucket";
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Bucket Bucket = _entityMgr.findById(Bucket.class, getId());
+        if (Bucket != null) {
+            return Bucket.getAccountId();
+        }
+
+        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;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Bucket;
+    }
+
+    @Override
+    public void execute() throws ConcurrentOperationException {
+        CallContext.current().setEventDetails("Bucket Id: " + this._uuidMgr.getUuid(Bucket.class, getId()));
+        boolean result = false;
+        try {
+            result = _bucketService.updateBucket(this, CallContext.current().getCallingAccount());
+        } catch (Exception e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Error while updating bucket. "+e.getMessage());
+        }
+        if(result) {
+            SuccessResponse response = new SuccessResponse(getCommandName());
+            setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update bucket");
+        }
+    }
+}
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 4a2711e..65920a9 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
@@ -59,6 +59,7 @@
         response.setAllowUserViewAllDomainAccounts((Boolean)capabilities.get("allowUserViewAllDomainAccounts"));
         response.setKubernetesServiceEnabled((Boolean)capabilities.get("kubernetesServiceEnabled"));
         response.setKubernetesClusterExperimentalFeaturesEnabled((Boolean)capabilities.get("kubernetesClusterExperimentalFeaturesEnabled"));
+        response.setCustomHypervisorDisplayName((String) capabilities.get("customHypervisorDisplayName"));
         if (capabilities.containsKey("apiLimitInterval")) {
             response.setApiLimitInterval((Integer)capabilities.get("apiLimitInterval"));
         }
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 202bd36..89f1c70 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
@@ -72,6 +72,9 @@
     @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the event", since="4.17.0")
     private String resourceType;
 
+    @Parameter(name = ApiConstants.ARCHIVED, type = CommandType.BOOLEAN, description = "true to list archived events otherwise false", since="4.19.0")
+    private Boolean archived;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -116,6 +119,10 @@
         return resourceType;
     }
 
+    public boolean getArchived() {
+        return archived != null && archived;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
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 6f25afd..e175956 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
@@ -28,8 +28,8 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.GetUploadParamsResponse;
 import org.apache.cloudstack.api.response.GuestOSResponse;
-import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
 
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
@@ -37,15 +37,13 @@
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
 
-@APICommand(name = GetUploadParamsForIsoCmd.APINAME,
+@APICommand(name = "getUploadParamsForIso",
         description = "upload an existing ISO into the CloudStack cloud.",
         responseObject = GetUploadParamsResponse.class, since = "4.13",
         authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class GetUploadParamsForIsoCmd extends AbstractGetUploadParamsCmd {
 
-    public static final String APINAME = "getUploadParamsForIso";
-
     private static final String s_name = "postuploadisoresponse";
 
     /////////////////////////////////////////////////////
@@ -57,7 +55,6 @@
 
     @Parameter(name = ApiConstants.DISPLAY_TEXT,
             type = BaseCmd.CommandType.STRING,
-            required = true,
             description = "the display text of the ISO. This is usually used for display purposes.",
             length = 4096)
     private String displayText;
@@ -73,19 +70,12 @@
     @Parameter(name = ApiConstants.IS_EXTRACTABLE, type = BaseCmd.CommandType.BOOLEAN, description = "true if the ISO or its derivatives are extractable; default is false")
     private Boolean extractable;
 
-    @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true, description = "the name of the ISO")
-    private String isoName;
-
     @Parameter(name = ApiConstants.OS_TYPE_ID,
             type = BaseCmd.CommandType.UUID,
             entityType = GuestOSResponse.class,
             description = "the ID of the OS type that best represents the OS of this ISO. If the ISO is bootable this parameter needs to be passed")
     private Long osTypeId;
 
-    @Parameter(name=ApiConstants.ZONE_ID, type= BaseCmd.CommandType.UUID, entityType = ZoneResponse.class,
-            required=true, description="the ID of the zone you wish to register the ISO to.")
-    protected Long zoneId;
-
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -95,7 +85,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isBlank(displayText) ? getName() : displayText;
     }
 
     public Boolean isFeatured() {
@@ -110,17 +100,10 @@
         return extractable;
     }
 
-    public String getIsoName() {
-        return isoName;
-    }
-
     public Long getOsTypeId() {
         return osTypeId;
     }
 
-    public Long getZoneId() {
-        return zoneId;
-    }
 
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
@@ -134,7 +117,7 @@
             response.setResponseName(getCommandName());
             setResponseObject(response);
         } catch (ResourceAllocationException | MalformedURLException e) {
-            s_logger.error("Exception while registering template", e);
+            s_logger.error("Exception while registering ISO", e);
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Exception while registering ISO: " + e.getMessage());
         }
     }
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 90ecaa4..f723cb9 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
@@ -134,6 +134,10 @@
         return showUnique != null && showUnique;
     }
 
+    public Long getImageStoreId() {
+        return null;
+    }
+
     public Boolean getShowIcon () {
         return  showIcon != null ? showIcon : false;
     }
@@ -191,4 +195,8 @@
             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 bdb51e8..1d75003 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
@@ -34,6 +34,7 @@
 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 org.apache.log4j.Logger;
 
 import com.cloud.exception.ResourceAllocationException;
@@ -55,8 +56,7 @@
 
     @Parameter(name = ApiConstants.DISPLAY_TEXT,
                type = CommandType.STRING,
-               required = true,
-               description = "the display text of the ISO. This is usually used for display purposes.",
+               description = "the display text of the ISO, defaults to the 'name'",
                length = 4096)
     private String displayText;
 
@@ -133,7 +133,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? isoName : displayText;
     }
 
     public void setDisplayText(String displayText) {
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 d2574ff..783d78f 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
@@ -24,6 +24,7 @@
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.response.AsyncJobResponse;
 import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ManagementServerResponse;
 
 @APICommand(name = "listAsyncJobs", description = "Lists all pending asynchronous jobs for the account.", responseObject = AsyncJobResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -36,6 +37,9 @@
     @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "The start date of the async job (use format \"yyyy-MM-dd'T'HH:mm:ss'+'SSSS\")")
     private Date startDate;
 
+    @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;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -44,6 +48,10 @@
         return startDate;
     }
 
+    public Long getManagementServerId() {
+        return managementServerId;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
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 7647b01..c245ab2 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
@@ -133,7 +133,7 @@
                     throw new InvalidParameterValueException("Unable to find virtual machine ID: " + vmId);
                 }
 
-                //check wether the given ip is valid ip or not
+                //check whether the given ip is valid ip or not
                 if (vmIp == null || !NetUtils.isValidIp4(vmIp)) {
                     throw new InvalidParameterValueException("Invalid ip address "+ vmIp +" passed in vmidipmap for " +
                             "vmid " + vmId);
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 45e6f81..66a1598 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
@@ -68,7 +68,7 @@
     @Parameter(name = ApiConstants.METHOD_NAME,
                type = CommandType.STRING,
                required = true,
-               description = "name of the load balancer stickiness policy method, possible values can be obtained from listNetworks API")
+               description = "name of the load balancer stickiness policy method, possible values are LbCookie, AppCookie, SourceBased")
     private String stickinessMethodName;
 
     @Parameter(name = ApiConstants.PARAM_LIST, type = CommandType.MAP, description = "param list. Example: param[0].name=cookiename&param[0].value=LBCookie ")
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 77aaa6b..723e0ef 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
@@ -97,52 +97,44 @@
     public void execute() {
         Pair<List<? extends UserVm>, List<String>> vmServiceMap =  _lbService.listLoadBalancerInstances(this);
         List<? extends UserVm> result = vmServiceMap.first();
+        s_logger.debug(String.format("A total of [%s] user VMs were obtained when listing the load balancer instances: [%s].", result.size(), result));
+
         List<String> serviceStates  = vmServiceMap.second();
+        s_logger.debug(String.format("A total of [%s] service states were obtained when listing the load balancer instances: [%s].", serviceStates.size(), serviceStates));
 
         if (!isListLbVmip()) {
-            // list lb instances
-            ListResponse<UserVmResponse> response = new ListResponse<UserVmResponse>();
-            List<UserVmResponse> vmResponses = new ArrayList<UserVmResponse>();
-            if (result != null) {
-                vmResponses = _responseGenerator.createUserVmResponse(ResponseView.Restricted, "loadbalancerruleinstance", result.toArray(new UserVm[result.size()]));
+            ListResponse<UserVmResponse> response = new ListResponse<>();
+            List<UserVmResponse> vmResponses = _responseGenerator.createUserVmResponse(ResponseView.Restricted, "loadbalancerruleinstance", result.toArray(new UserVm[0]));
 
-
-                for (int i = 0; i < result.size(); i++) {
-                    vmResponses.get(i).setServiceState(serviceStates.get(i));
-                }
+            for (int i = 0; i < result.size(); i++) {
+                vmResponses.get(i).setServiceState(serviceStates.get(i));
             }
+
             response.setResponses(vmResponses);
             response.setResponseName(getCommandName());
             setResponseObject(response);
-
-
-        } else {
-            ListResponse<LoadBalancerRuleVmMapResponse> lbRes = new ListResponse<LoadBalancerRuleVmMapResponse>();
-
-            List<UserVmResponse> vmResponses = new ArrayList<UserVmResponse>();
-            List<LoadBalancerRuleVmMapResponse> listlbVmRes = new ArrayList<LoadBalancerRuleVmMapResponse>();
-
-            if (result != null) {
-                vmResponses = _responseGenerator.createUserVmResponse(getResponseView(), "loadbalancerruleinstance", result.toArray(new UserVm[result.size()]));
-
-
-                List<String> ipaddr = null;
-
-                for (int i=0;i<result.size(); i++) {
-                    LoadBalancerRuleVmMapResponse lbRuleVmIpResponse = new LoadBalancerRuleVmMapResponse();
-                    vmResponses.get(i).setServiceState(serviceStates.get(i));
-                    lbRuleVmIpResponse.setUserVmResponse(vmResponses.get(i));
-                    //get vm id from the uuid
-                    VirtualMachine lbvm = _entityMgr.findByUuid(VirtualMachine.class, vmResponses.get(i).getId());
-                    lbRuleVmIpResponse.setIpAddr(_lbService.listLbVmIpAddress(getId(), lbvm.getId()));
-                    lbRuleVmIpResponse.setObjectName("lbrulevmidip");
-                    listlbVmRes.add(lbRuleVmIpResponse);
-                }
-            }
-
-            lbRes.setResponseName(getCommandName());
-            lbRes.setResponses(listlbVmRes);
-            setResponseObject(lbRes);
+            return;
         }
+
+        ListResponse<LoadBalancerRuleVmMapResponse> lbRes = new ListResponse<>();
+
+        List<UserVmResponse> vmResponses = _responseGenerator.createUserVmResponse(getResponseView(), "loadbalancerruleinstance", result.toArray(new UserVm[0]));
+        List<LoadBalancerRuleVmMapResponse> lbRuleVmMapList = new ArrayList<>();
+
+        for (int i=0; i<result.size(); i++) {
+            LoadBalancerRuleVmMapResponse lbRuleVmIpResponse = new LoadBalancerRuleVmMapResponse();
+            UserVmResponse userVmResponse = vmResponses.get(i);
+            userVmResponse.setServiceState(serviceStates.get(i));
+            lbRuleVmIpResponse.setUserVmResponse(userVmResponse);
+
+            VirtualMachine lbVm = _entityMgr.findByUuid(VirtualMachine.class, userVmResponse.getId());
+            lbRuleVmIpResponse.setIpAddr(_lbService.listLbVmIpAddress(getId(), lbVm.getId()));
+            lbRuleVmIpResponse.setObjectName("lbrulevmidip");
+            lbRuleVmMapList.add(lbRuleVmIpResponse);
+        }
+
+        lbRes.setResponseName(getCommandName());
+        lbRes.setResponses(lbRuleVmMapList);
+        setResponseObject(lbRes);
     }
 }
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 14dbfca..e5dbcc7 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
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.network;
 
+import com.cloud.exception.PermissionDeniedException;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -26,6 +27,7 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.NetworkACLResponse;
 import org.apache.cloudstack.api.response.VpcResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.log4j.Logger;
 
 import com.cloud.event.EventTypes;
@@ -35,7 +37,8 @@
 import com.cloud.network.vpc.Vpc;
 import com.cloud.user.Account;
 
-@APICommand(name = "createNetworkACLList", description = "Creates a network ACL for the given VPC", responseObject = NetworkACLResponse.class,
+@APICommand(name = "createNetworkACLList", description = "Creates a network ACL. If no VPC is given, then it creates a global ACL that can be used by everyone.",
+        responseObject = NetworkACLResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class CreateNetworkACLListCmd extends BaseAsyncCreateCmd {
     public static final Logger s_logger = Logger.getLogger(CreateNetworkACLListCmd.class.getName());
@@ -53,7 +56,6 @@
 
     @Parameter(name = ApiConstants.VPC_ID,
                type = CommandType.UUID,
-               required = true,
                entityType = VpcResponse.class,
                description = "ID of the VPC associated with this network ACL list")
     private Long vpcId;
@@ -77,6 +79,10 @@
         return vpcId;
     }
 
+    public void setVpcId(Long vpcId) {
+        this.vpcId = vpcId;
+    }
+
     @Override
     public boolean isDisplay() {
         if (display != null) {
@@ -92,6 +98,9 @@
 
     @Override
     public void create() {
+        if (getVpcId() == null) {
+            setVpcId(0L);
+        }
         NetworkACL result = _networkACLService.createNetworkACL(getName(), getDescription(), getVpcId(), isDisplay());
         setEntityId(result.getId());
         setEntityUuid(result.getUuid());
@@ -111,12 +120,21 @@
 
     @Override
     public long getEntityOwnerId() {
-        Vpc vpc = _entityMgr.findById(Vpc.class, getVpcId());
-        if (vpc == null) {
-            throw new InvalidParameterValueException("Invalid vpcId is given");
-        }
+        Account account;
+        if (isAclAttachedToVpc(this.vpcId)) {
+            Vpc vpc = _entityMgr.findById(Vpc.class, this.vpcId);
+            if (vpc == null) {
+                throw new InvalidParameterValueException(String.format("Invalid VPC ID [%s] provided.", this.vpcId));
+            }
+            account = _accountService.getAccount(vpc.getAccountId());
+        } else {
+            account = CallContext.current().getCallingAccount();
+            if (!Account.Type.ADMIN.equals(account.getType())) {
+                s_logger.warn(String.format("Only Root Admin can create global ACLs. Account [%s] cannot create any global ACL.", account));
+                throw new PermissionDeniedException("Only Root Admin can create global ACLs.");
+            }
 
-        Account account = _accountService.getAccount(vpc.getAccountId());
+        }
         return account.getId();
     }
 
@@ -139,4 +157,8 @@
     public ApiCommandResourceType getApiResourceType() {
         return ApiCommandResourceType.NetworkAcl;
     }
+
+    public boolean isAclAttachedToVpc(Long aclVpcId) {
+        return aclVpcId != null && aclVpcId != 0;
+    }
 }
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 3bc90e5..ca379fb 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
@@ -16,7 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.network;
 
-import com.cloud.network.NetworkService;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.RoleType;
@@ -43,10 +43,10 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.network.Network;
+import com.cloud.network.NetworkService;
 import com.cloud.network.Network.GuestType;
 import com.cloud.offering.NetworkOffering;
 import com.cloud.utils.net.NetUtils;
-import org.apache.commons.lang3.StringUtils;
 
 @APICommand(name = "createNetwork", description = "Creates a network", responseObject = NetworkResponse.class, responseView = ResponseView.Restricted, entityType = {Network.class},
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -185,6 +185,14 @@
     @Parameter(name = ApiConstants.IP6_DNS2, type = CommandType.STRING, description = "the second IPv6 DNS for the network", since = "4.18.0")
     private String ip6Dns2;
 
+    @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 will be used as source NAT address for the network. " +
+                    "\nIf an address is given and it cannot be acquired, an error will be returned and the network won´t be implemented,",
+            since = "4.19")
+    private String sourceNatIP;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -268,6 +276,10 @@
         return tungstenVirtualRouterUuid;
     }
 
+    public String getSourceNatIP() {
+        return sourceNatIP;
+    }
+
     @Override
     public boolean isDisplay() {
         if(displayNetwork == null)
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 df82d9f..c1e85a9 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
@@ -23,12 +23,13 @@
 import com.cloud.server.ResourceTag;
 import org.apache.cloudstack.api.response.NetworkOfferingResponse;
 import org.apache.cloudstack.api.response.ResourceIconResponse;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
+import org.apache.cloudstack.api.BaseListRetrieveOnlyResourceCountCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.command.user.UserCmd;
@@ -44,7 +45,7 @@
 
 @APICommand(name = "listNetworks", description = "Lists all available networks.", responseObject = NetworkResponse.class, responseView = ResponseView.Restricted, entityType = {Network.class},
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
-public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserCmd {
+public class ListNetworksCmd extends BaseListRetrieveOnlyResourceCountCmd implements UserCmd {
     public static final Logger s_logger = Logger.getLogger(ListNetworksCmd.class.getName());
     private static final String s_name = "listnetworksresponse";
 
@@ -190,14 +191,11 @@
 
     @Override
     public Boolean getDisplay() {
-        if (display != null) {
-            return display;
-        }
-        return super.getDisplay();
+        return BooleanUtils.toBooleanDefaultIfNull(display, super.getDisplay());
     }
 
     public Boolean getShowIcon() {
-        return showIcon != null ? showIcon : false;
+        return BooleanUtils.toBooleanDefaultIfNull(showIcon, false);
     }
 
     public String getNetworkFilter() {
@@ -215,16 +213,21 @@
     @Override
     public void execute() {
         Pair<List<? extends Network>, Integer> networks = _networkService.searchForNetworks(this);
-        ListResponse<NetworkResponse> response = new ListResponse<NetworkResponse>();
-        List<NetworkResponse> networkResponses = new ArrayList<NetworkResponse>();
-        for (Network network : networks.first()) {
-            NetworkResponse networkResponse = _responseGenerator.createNetworkResponse(getResponseView(), network);
-            networkResponses.add(networkResponse);
+        ListResponse<NetworkResponse> response = new ListResponse<>();
+        List<NetworkResponse> networkResponses = new ArrayList<>();
+
+        if (!getRetrieveOnlyResourceCount()) {
+            for (Network network : networks.first()) {
+                NetworkResponse networkResponse = _responseGenerator.createNetworkResponse(getResponseView(), network);
+                networkResponses.add(networkResponse);
+            }
         }
+
         response.setResponses(networkResponses, networks.second());
         response.setResponseName(getCommandName());
         setResponseObject(response);
-        if (response != null && response.getCount() > 0 && getShowIcon()) {
+
+        if (!getRetrieveOnlyResourceCount() && response.getCount() > 0 && getShowIcon()) {
             updateNetworkResponse(response.getResponses());
         }
     }
@@ -232,11 +235,11 @@
     private void updateNetworkResponse(List<NetworkResponse> response) {
         for (NetworkResponse networkResponse : response) {
             ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Network, networkResponse.getId());
-            if (resourceIcon == null) {
+            if (resourceIcon == null && networkResponse.getVpcId() != null) {
                 resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Vpc, networkResponse.getVpcId());
-                if (resourceIcon == null) {
-                    continue;
-                }
+            }
+            if (resourceIcon == null) {
+                continue;
             }
             ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
             networkResponse.setResourceIconResponse(iconResponse);
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 3aef973..d3cc169 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
@@ -104,6 +104,9 @@
     @Parameter(name = ApiConstants.IP6_DNS2, type = CommandType.STRING, description = "the second IPv6 DNS for the network. Empty string will update the second IPv6 DNS with the value from the zone", since = "4.18.0")
     private String ip6Dns2;
 
+    @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;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -181,6 +184,10 @@
         return ip6Dns2;
     }
 
+    public String getSourceNatIP() {
+        return sourceNatIP;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java
index 5ab675a..6f32b58 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java
@@ -16,18 +16,24 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.offering;
 
+import com.cloud.offering.DiskOffering.State;
 import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
 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.lang3.EnumUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.BaseCmd.CommandType;
 import org.apache.cloudstack.api.response.DiskOfferingResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 
+import static com.cloud.offering.DiskOffering.State.Active;
+
 @APICommand(name = "listDiskOfferings", description = "Lists all available disk offerings.", responseObject = DiskOfferingResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class ListDiskOfferingsCmd extends BaseListProjectAndAccountResourcesCmd {
@@ -60,6 +66,17 @@
     @Parameter(name = ApiConstants.ENCRYPT, type = CommandType.BOOLEAN, description = "listed offerings support disk encryption", since = "4.18")
     private Boolean encrypt;
 
+    @Parameter(name = ApiConstants.STORAGE_TYPE,
+            type = CommandType.STRING,
+            description = "the storage type of the service offering. Values are local and shared.",
+            since = "4.19")
+    private String storageType;
+
+    @Parameter(name = ApiConstants.STATE, type = CommandType.STRING,
+               description = "Filter by state of the disk offering. Defaults to 'Active'. If set to 'all' shows both Active & Inactive offerings.",
+               since = "4.19")
+    private String diskOfferingState;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -84,6 +101,21 @@
 
     public Boolean getEncrypt() { return encrypt; }
 
+    public String getStorageType() {
+        return storageType;
+    }
+
+    public State getState() {
+        if (StringUtils.isBlank(diskOfferingState)) {
+            return Active;
+        }
+        State state = EnumUtils.getEnumIgnoreCase(State.class, diskOfferingState);
+        if (!diskOfferingState.equalsIgnoreCase("all") && state == null) {
+            throw new IllegalArgumentException("Invalid state value: " + diskOfferingState);
+        }
+        return state;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
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 cb155d2..246984a 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
@@ -16,8 +16,11 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.offering;
 
+import com.cloud.offering.ServiceOffering.State;
 import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.commons.lang3.EnumUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -27,6 +30,8 @@
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
 
+import static com.cloud.offering.ServiceOffering.State.Active;
+
 @APICommand(name = "listServiceOfferings", description = "Lists all available service offerings.", responseObject = ServiceOfferingResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesCmd {
@@ -88,6 +93,17 @@
         since = "4.18")
     private Boolean encryptRoot;
 
+    @Parameter(name = ApiConstants.STORAGE_TYPE,
+            type = CommandType.STRING,
+            description = "the storage type of the service offering. Values are local and shared.",
+            since = "4.19")
+    private String storageType;
+
+    @Parameter(name = ApiConstants.STATE, type = CommandType.STRING,
+               description = "Filter by state of the service offering. Defaults to 'Active'. If set to 'all' shows both Active & Inactive offerings.",
+               since = "4.19")
+    private String serviceOfferingState;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -130,6 +146,21 @@
 
     public Boolean getEncryptRoot() { return encryptRoot; }
 
+    public String getStorageType() {
+        return storageType;
+    }
+
+    public State getState() {
+        if (StringUtils.isBlank(serviceOfferingState)) {
+            return Active;
+        }
+        State state = EnumUtils.getEnumIgnoreCase(State.class, serviceOfferingState);
+        if (!serviceOfferingState.equalsIgnoreCase("all") && state == null) {
+            throw new IllegalArgumentException("Invalid state value: " + serviceOfferingState);
+        }
+        return state;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java
index a0902d6..a5742e8 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java
@@ -27,6 +27,7 @@
 import org.apache.cloudstack.api.response.ProjectResponse;
 import org.apache.cloudstack.api.response.UserResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.event.EventTypes;
@@ -61,7 +62,7 @@
     @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "name of the project")
     private String name;
 
-    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, required = true, description = "display text of the project")
+    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING,  description = "The display text of the project, defaults to the 'name´.")
     private String displayText;
 
     // ///////////////////////////////////////////////////
@@ -98,7 +99,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? name : displayText;
     }
 
     @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 8732f08..6520aa6 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
@@ -28,6 +28,7 @@
 import org.apache.cloudstack.api.response.UserResponse;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.commons.lang3.EnumUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.event.EventTypes;
@@ -35,7 +36,6 @@
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.projects.Project;
 import com.cloud.projects.ProjectAccount;
-import org.apache.commons.lang3.StringUtils;
 
 @APICommand(name = "updateProject", description = "Updates a project", responseObject = ProjectResponse.class, since = "3.0.0",
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -67,6 +67,9 @@
             "to promote or demote the user/account based on the roleType (Regular or Admin) provided. Defaults to true")
     private Boolean swapOwner;
 
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the project", since = "4.19.0")
+    private String name;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -87,6 +90,10 @@
         return userId;
     }
 
+    public String getName() {
+        return name;
+    }
+
     public ProjectAccount.Role getRoleType(String role) {
         String type = role.substring(0, 1).toUpperCase() + role.substring(1).toLowerCase();
         if (!EnumUtils.isValidEnum(ProjectAccount.Role.class, type)) {
@@ -136,9 +143,9 @@
 
         Project project = null;
         if (isSwapOwner()) {
-            project = _projectService.updateProject(getId(), getDisplayText(), getAccountName());
+            project = _projectService.updateProject(getId(), getName(), getDisplayText(), getAccountName());
         }  else {
-            project = _projectService.updateProject(getId(), getDisplayText(), getAccountName(), getUserId(), getAccountRole());
+            project = _projectService.updateProject(getId(), getName(), getDisplayText(), getAccountName(), getUserId(), getAccountRole());
         }
         if (project != null) {
             ProjectResponse response = _responseGenerator.createProjectResponse(project);
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
new file mode 100644
index 0000000..f6d16c3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java
@@ -0,0 +1,181 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command.user.snapshot;
+
+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.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.ResponseObject;
+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.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.log4j.Logger;
+
+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;
+
+@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 s_logger = Logger.getLogger(CopySnapshotCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID,
+            entityType = SnapshotResponse.class, required = true, description = "the ID of the snapshot.")
+    private Long id;
+
+    @Parameter(name = ApiConstants.SOURCE_ZONE_ID,
+            type = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            description = "The ID of the zone in which the snapshot is currently present. " +
+                    "If not specified then the zone of snapshot's volume will be used.")
+    private Long sourceZoneId;
+
+    @Parameter(name = ApiConstants.DESTINATION_ZONE_ID,
+            type = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            required = false,
+            description = "The ID of the zone the snapshot is being copied to.")
+    protected Long destZoneId;
+
+    @Parameter(name = ApiConstants.DESTINATION_ZONE_ID_LIST,
+            type=CommandType.LIST,
+            collectionType = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            required = false,
+            description = "A comma-separated list of IDs of the zones that the snapshot needs to be copied to. " +
+                    "Specify this list if the snapshot needs to copied to multiple zones in one go. " +
+                    "Do not specify destzoneid and destzoneids together, however one of them is required.")
+    protected List<Long> destZoneIds;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public Long getSourceZoneId() {
+        return sourceZoneId;
+    }
+
+    public List<Long> getDestinationZoneIds() {
+        if (destZoneIds != null && destZoneIds.size() != 0) {
+            return destZoneIds;
+        }
+        if (destZoneId != null) {
+            List < Long > destIds = new ArrayList<>();
+            destIds.add(destZoneId);
+            return destIds;
+        }
+        return null;
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_SNAPSHOT_COPY;
+    }
+
+    @Override
+    public String getEventDescription() {
+        StringBuilder descBuilder = new StringBuilder();
+        if (getDestinationZoneIds() != null) {
+            for (Long destId : getDestinationZoneIds()) {
+                descBuilder.append(", ");
+                descBuilder.append(_uuidMgr.getUuid(DataCenter.class, destId));
+            }
+            if (descBuilder.length() > 0) {
+                descBuilder.deleteCharAt(0);
+            }
+        }
+
+        return  "copying snapshot: " + _uuidMgr.getUuid(Snapshot.class, getId()) + ((descBuilder.length() > 0) ? " to zones: " + descBuilder.toString() : "");
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Snapshot;
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return getId();
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId());
+        if (snapshot != null) {
+            return snapshot.getAccountId();
+        }
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public void execute() throws ResourceUnavailableException {
+        try {
+            if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds))
+                throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                        "Either destzoneid or destzoneids parameters have to be specified.");
+
+            if (destZoneId != null && CollectionUtils.isNotEmpty(destZoneIds))
+                throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                        "Both destzoneid and destzoneids cannot be specified at the same time.");
+
+            CallContext.current().setEventDetails(getEventDescription());
+            Snapshot snapshot = _snapshotService.copySnapshot(this);
+
+            if (snapshot != null) {
+                SnapshotResponse response = _queryService.listSnapshot(this);
+                response.setResponseName(getCommandName());
+                setResponseObject(response);
+            } else {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to copy snapshot");
+            }
+        } catch (StorageUnavailableException ex) {
+            s_logger.warn("Exception: ", ex);
+            throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
+        } catch (ResourceAllocationException ex) {
+            s_logger.warn("Exception: ", ex);
+            throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage());
+        }
+
+    }
+}
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 56e1129..eed3aa4 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
@@ -18,6 +18,7 @@
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.cloudstack.api.APICommand;
@@ -32,6 +33,7 @@
 import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
 import org.apache.cloudstack.api.response.SnapshotResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
 
@@ -90,6 +92,15 @@
     @Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "Map of tags (key/value pairs)")
     private Map tags;
 
+    @Parameter(name = ApiConstants.ZONE_ID_LIST,
+            type=CommandType.LIST,
+            collectionType = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            description = "A comma-separated list of IDs of the 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.19.0")
+    protected List<Long> zoneIds;
+
     private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;
 
     // ///////////////////////////////////////////////////
@@ -148,6 +159,10 @@
         return _snapshotService.getHostIdForSnapshotOperation(volume);
     }
 
+    public List<Long> getZoneIds() {
+        return zoneIds;
+    }
+
     // ///////////////////////////////////////////////////
     // ///////////// API Implementation///////////////////
     // ///////////////////////////////////////////////////
@@ -196,7 +211,7 @@
 
     @Override
     public void create() throws ResourceAllocationException {
-        Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType());
+        Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds());
         if (snapshot != null) {
             setEntityId(snapshot.getId());
             setEntityUuid(snapshot.getUuid());
@@ -210,7 +225,7 @@
         Snapshot snapshot;
         try {
             snapshot =
-                _volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags());
+                _volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds());
 
             if (snapshot != null) {
                 SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);
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 1fd7cfd..7b89e87 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
@@ -186,7 +186,7 @@
         } finally {
             if (snapshot == null) {
                 try {
-                    _snapshotService.deleteSnapshot(getEntityId());
+                    _snapshotService.deleteSnapshot(getEntityId(), null);
                 } catch (Exception e) {
                     s_logger.debug("Failed to clean failed snapshot" + getEntityId());
                 }
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 a3b7984..00bfb9e 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
@@ -18,6 +18,7 @@
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.cloudstack.acl.RoleType;
@@ -30,6 +31,7 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
 
@@ -75,6 +77,14 @@
     @Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "Map of tags (key/value pairs)")
     private Map tags;
 
+    @Parameter(name = ApiConstants.ZONE_ID_LIST,
+            type=CommandType.LIST,
+            collectionType = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            description = "A list of IDs of the zones in which the snapshots will be made available." +
+                    "The snapshots will always be made available in the zone in which the volume is present.")
+    protected List<Long> zoneIds;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -107,6 +117,10 @@
             return display;
     }
 
+    public List<Long> getZoneIds() {
+        return zoneIds;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// 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 8530e0f..6d71b13 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
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.snapshot;
 
+import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@@ -48,6 +49,10 @@
     @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType = SnapshotResponse.class,
             required=true, description="The ID of the snapshot")
     private Long id;
+    @Parameter(name=ApiConstants.ZONE_ID, type=CommandType.UUID, entityType = ZoneResponse.class,
+            description="The ID of the zone for the snapshot", since = "4.19.0")
+    private Long zoneId;
+
 
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
@@ -57,6 +62,10 @@
         return id;
     }
 
+    public Long getZoneId() {
+        return zoneId;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -94,7 +103,7 @@
     @Override
     public void execute() {
         CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class, getId()));
-        boolean result = _snapshotService.deleteSnapshot(getId());
+        boolean result = _snapshotService.deleteSnapshot(getId(), getZoneId());
         if (result) {
             SuccessResponse response = new SuccessResponse(getCommandName());
             setResponseObject(response);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java
index 0b4a215..cf66512 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java
@@ -16,26 +16,24 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.snapshot;
 
-import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
 import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.SnapshotResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.log4j.Logger;
 
 import com.cloud.storage.Snapshot;
-import com.cloud.utils.Pair;
 
 @APICommand(name = "listSnapshots", description = "Lists all available snapshots for the account.", responseObject = SnapshotResponse.class, entityType = {
-        Snapshot.class }, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+        Snapshot.class }, responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class ListSnapshotsCmd extends BaseListTaggedResourcesCmd {
     public static final Logger s_logger = Logger.getLogger(ListSnapshotsCmd.class.getName());
 
@@ -65,6 +63,13 @@
     @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "list snapshots by zone id")
     private Long zoneId;
 
+    @Parameter(name = ApiConstants.SHOW_UNIQUE, type = CommandType.BOOLEAN, description = "If set to false, list templates across zones and their storages", since = "4.19.0")
+    private Boolean showUnique;
+
+    @Parameter(name = ApiConstants.LOCATION_TYPE, type = CommandType.STRING, description = "list snapshots by location type. Used only when showunique=false. " +
+            "Valid location types: 'primary', 'secondary'. Default is empty", since = "4.19.0")
+    private String locationType;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -93,6 +98,28 @@
         return zoneId;
     }
 
+    public boolean isShowUnique() {
+        if (Boolean.FALSE.equals(showUnique)) {
+            return false;
+        }
+        return true;
+    }
+
+    public String getLocationType() {
+        if (!isShowUnique()) {
+            return locationType;
+        }
+        return null;
+    }
+
+    public Long getImageStoreId() {
+        return null;
+    }
+
+    public Long getStoragePoolId() {
+        return null;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -104,15 +131,7 @@
 
     @Override
     public void execute() {
-        Pair<List<? extends Snapshot>, Integer> result = _snapshotService.listSnapshots(this);
-        ListResponse<SnapshotResponse> response = new ListResponse<SnapshotResponse>();
-        List<SnapshotResponse> snapshotResponses = new ArrayList<SnapshotResponse>();
-        for (Snapshot snapshot : result.first()) {
-            SnapshotResponse snapshotResponse = _responseGenerator.createSnapshotResponse(snapshot);
-            snapshotResponse.setObjectName("snapshot");
-            snapshotResponses.add(snapshotResponse);
-        }
-        response.setResponses(snapshotResponses, result.second());
+        ListResponse<SnapshotResponse> response = _queryService.listSnapshots(this);
         response.setResponseName(getCommandName());
 
         setResponseObject(response);
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 28bdd4d..521148b 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
@@ -95,5 +95,4 @@
         response.setObjectName("keypair");
         setResponseObject(response);
     }
-
-    }
+}
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 a21cbfb..6c39ab6 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
@@ -21,16 +21,6 @@
 import java.util.Map;
 
 import org.apache.cloudstack.acl.SecurityChecker;
-import org.apache.cloudstack.api.command.user.UserCmd;
-import org.apache.cloudstack.api.response.GuestOSResponse;
-import org.apache.cloudstack.api.response.SnapshotResponse;
-import org.apache.cloudstack.api.response.TemplateResponse;
-import org.apache.cloudstack.api.response.UserVmResponse;
-import org.apache.cloudstack.api.response.VolumeResponse;
-import org.apache.cloudstack.api.response.ProjectResponse;
-
-import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
@@ -39,13 +29,23 @@
 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.DomainResponse;
+import org.apache.cloudstack.api.response.GuestOSResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
+import org.apache.cloudstack.api.response.SnapshotResponse;
+import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
 
 import com.cloud.event.EventTypes;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.PermissionDeniedException;
 import com.cloud.exception.ResourceAllocationException;
-import com.cloud.projects.Project;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.Volume;
 import com.cloud.template.VirtualMachineTemplate;
@@ -67,8 +67,7 @@
 
     @Parameter(name = ApiConstants.DISPLAY_TEXT,
                type = CommandType.STRING,
-               required = true,
-               description = "the display text of the template. This is usually used for display purposes.",
+               description = "The display text of the template, defaults to the 'name'.",
                length = 4096)
     private String displayText;
 
@@ -135,6 +134,22 @@
     @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "create template for the project")
     private Long projectId;
 
+    @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the zone for the template. Can be specified with snapshot only", since = "4.19.0")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.DOMAIN_ID,
+          type = CommandType.UUID,
+          entityType = DomainResponse.class,
+          description = "an optional domainId. If the account parameter is used, domainId must also be used.",
+          since = "4.19.0")
+    private Long domainId;
+
+    @Parameter(name = ApiConstants.ACCOUNT,
+          type = CommandType.STRING,
+          description = "an optional accountName. Must be used with domainId.",
+          since = "4.19.0")
+    private String accountName;
+
     // ///////////////////////////////////////////////////
     // ///////////////// Accessors ///////////////////////
     // ///////////////////////////////////////////////////
@@ -144,7 +159,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? templateName : displayText;
     }
 
     public Boolean isFeatured() {
@@ -209,6 +224,18 @@
         return isDynamicallyScalable == null ? false : isDynamicallyScalable;
     }
 
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public Long getDomainId() {
+        return domainId;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
     // ///////////////////////////////////////////////////
     // ///////////// API Implementation///////////////////
     // ///////////////////////////////////////////////////
@@ -224,47 +251,12 @@
 
     @Override
     public long getEntityOwnerId() {
-        Long volumeId = getVolumeId();
-        Long snapshotId = getSnapshotId();
         Account callingAccount = CallContext.current().getCallingAccount();
-        if (volumeId != null) {
-            Volume volume = _entityMgr.findById(Volume.class, volumeId);
-            if (volume != null) {
-                _accountService.checkAccess(callingAccount, SecurityChecker.AccessType.UseEntry, false, volume);
-            } else {
-                throw new InvalidParameterValueException("Unable to find volume by id=" + volumeId);
-            }
-        } else {
-            Snapshot snapshot = _entityMgr.findById(Snapshot.class, snapshotId);
-            if (snapshot != null) {
-                _accountService.checkAccess(callingAccount, SecurityChecker.AccessType.UseEntry, false, snapshot);
-            } else {
-                throw new InvalidParameterValueException("Unable to find snapshot by id=" + snapshotId);
-            }
-        }
-
-        if(projectId != null){
-            final Project project = _projectService.getProject(projectId);
-            if (project != null) {
-                if (project.getState() == Project.State.Active) {
-                    Account projectAccount= _accountService.getAccount(project.getProjectAccountId());
-                    _accountService.checkAccess(callingAccount, SecurityChecker.AccessType.UseEntry, false, projectAccount);
-                    return project.getProjectAccountId();
-                } else {
-                    final PermissionDeniedException ex =
-                            new PermissionDeniedException("Can't add resources to the project with specified projectId in state=" + project.getState() +
-                                    " as it's no longer active");
-                    ex.addProxyObject(project.getUuid(), "projectId");
-                    throw ex;
-                }
-            } else {
-                throw new InvalidParameterValueException("Unable to find project by id");
-            }
-        }
-
-        return callingAccount.getId();
+        ensureAccessCheck(callingAccount);
+        return findAccountIdToUse(callingAccount);
     }
 
+
     @Override
     public String getEventType() {
         return EventTypes.EVENT_TEMPLATE_CREATE;
@@ -322,4 +314,47 @@
         }
 
     }
+
+    /***
+     * Performs access check on volume and snapshot for given account
+     * @param account
+     */
+    private void ensureAccessCheck(Account account) {
+        if (volumeId != null) {
+            Volume volume = _entityMgr.findById(Volume.class, volumeId);
+            if (volume != null) {
+                _accountService.checkAccess(account, SecurityChecker.AccessType.UseEntry, false, volume);
+            } else {
+                throw new InvalidParameterValueException("Unable to find volume by id=" + volumeId);
+            }
+        } else {
+            Snapshot snapshot = _entityMgr.findById(Snapshot.class, snapshotId);
+            if (snapshot != null) {
+                _accountService.checkAccess(account, SecurityChecker.AccessType.UseEntry, false, snapshot);
+            } else {
+                throw new InvalidParameterValueException("Unable to find snapshot by id=" + snapshotId);
+            }
+        }
+    }
+
+    /***
+     * Find accountId based on accountName and domainId or projectId
+     * if not found, return callingAccountId for further use
+     * @param callingAccount
+     * @return accountId
+     */
+    private Long findAccountIdToUse(Account callingAccount) {
+        Long accountIdToUse = null;
+        try {
+            accountIdToUse = _accountService.finalyzeAccountId(accountName, domainId, projectId, true);
+        } catch (InvalidParameterValueException | PermissionDeniedException ex) {
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug(String.format("An exception occurred while finalizing account id with accountName, domainId and projectId" +
+                      "using callingAccountId=%s", callingAccount.getUuid()), ex);
+            }
+            s_logger.warn("Unable to find accountId associated with accountName=" + accountName + " and domainId="
+                  + domainId + " or projectId=" + projectId + ", using callingAccountId=" + callingAccount.getUuid());
+        }
+        return accountIdToUse != null ? accountIdToUse : callingAccount.getAccountId();
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteVnfTemplateCmd.java
new file mode 100755
index 0000000..c2c712c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteVnfTemplateCmd.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.user.template;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.response.SuccessResponse;
+
+import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.user.Account;
+
+@APICommand(name = "deleteVnfTemplate",
+        responseObject = SuccessResponse.class,
+        description = "Deletes a VNF template from the system. All virtual machines using the deleted template will not be affected.",
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+        since = "4.19.0")
+public class DeleteVnfTemplateCmd extends DeleteTemplateCmd {
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        VirtualMachineTemplate template = _entityMgr.findById(VirtualMachineTemplate.class, getId());
+        if (template != null) {
+            return template.getAccountId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
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 3a9e1c8..ab872b8 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
@@ -33,6 +33,7 @@
 import org.apache.cloudstack.api.response.GetUploadParamsResponse;
 import org.apache.cloudstack.api.response.GuestOSResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.exception.ResourceAllocationException;
@@ -46,7 +47,7 @@
 
     private static final String s_name = "postuploadtemplateresponse";
 
-    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, required = true, description = "the display text of the template. This is usually used for display purposes.", length = 4096)
+    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "the display text of the template. This is usually used for display purposes.", length = 4096)
     private String displayText;
 
     @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "the target hypervisor for the template")
@@ -95,7 +96,7 @@
     private Boolean deployAsIs;
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isBlank(displayText) ? getName() : displayText;
     }
 
     public String getHypervisor() {
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 a64ce19..dae7cc9 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
@@ -24,6 +24,7 @@
 import org.apache.log4j.Logger;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import org.apache.cloudstack.api.APICommand;
@@ -96,6 +97,15 @@
             description = "comma separated list of template details requested, value can be a list of [ all, min]")
     private List<String> viewDetails;
 
+    @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING,
+            description = "the type of the template", since = "4.19.0")
+    private String templateType;
+
+    @Parameter(name = ApiConstants.IS_VNF, type = CommandType.BOOLEAN,
+            description = "flag to list VNF templates or not; true if need to list VNF templates, false otherwise.",
+            since = "4.19.0")
+    private Boolean isVnf;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -151,6 +161,10 @@
         return parentTemplateId;
     }
 
+    public String getTemplateType() {
+        return templateType;
+    }
+
     public boolean listInReadyState() {
 
         Account account = CallContext.current().getCallingAccount();
@@ -175,6 +189,10 @@
         return  showIcon != null ? showIcon : false;
     }
 
+    public Boolean getVnf() {
+        return isVnf;
+    }
+
     @Override
     public String getCommandName() {
         return s_name;
@@ -207,6 +225,17 @@
     }
 
     public List<Long> getIds() {
+        if (ids == null) {
+            return Collections.emptyList();
+        }
         return ids;
     }
+
+    public Long getImageStoreId() {
+        return null;
+    }
+
+    public Long getStoragePoolId() {
+        return null;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListVnfTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListVnfTemplatesCmd.java
new file mode 100644
index 0000000..0a98a15
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListVnfTemplatesCmd.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.api.command.user.template;
+
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
+
+import com.cloud.template.VirtualMachineTemplate;
+
+@APICommand(name = "listVnfTemplates", description = "List all public, private, and privileged VNF templates.",
+        responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Restricted,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+        since = "4.19.0")
+public class ListVnfTemplatesCmd extends ListTemplatesCmd implements UserCmd {
+}
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 255b11a..0a08788 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
@@ -23,6 +23,7 @@
 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;
@@ -39,6 +40,7 @@
 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 org.apache.log4j.Logger;
 
 import com.cloud.exception.ResourceAllocationException;
@@ -60,8 +62,7 @@
 
     @Parameter(name = ApiConstants.DISPLAY_TEXT,
                type = CommandType.STRING,
-               required = true,
-               description = "the display text of the template. This is usually used for display purposes.",
+               description = "The display text of the template, defaults to 'name'.",
                length = 4096)
     private String displayText;
 
@@ -142,6 +143,7 @@
                description = "true if template contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory")
     protected Boolean isDynamicallyScalable;
 
+    @Deprecated
     @Parameter(name = ApiConstants.ROUTING, type = CommandType.BOOLEAN, description = "true if the template type is routing i.e., if template is used to deploy router")
     protected Boolean isRoutingType;
 
@@ -167,6 +169,11 @@
             description = "(VMware only) true if VM deployments should preserve all the configurations defined for this template", since = "4.15.1")
     protected Boolean deployAsIs;
 
+    @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;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -176,7 +183,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? templateName : displayText;
     }
 
     public String getFormat() {
@@ -284,6 +291,10 @@
                 Boolean.TRUE.equals(deployAsIs);
     }
 
+    public String getTemplateType() {
+        return templateType;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -314,7 +325,7 @@
 
             VirtualMachineTemplate template = _templateService.registerTemplate(this);
             if (template != null) {
-                ListResponse<TemplateResponse> response = new ListResponse<TemplateResponse>();
+                ListResponse<TemplateResponse> response = new ListResponse<>();
                 List<TemplateResponse> templateResponses = _responseGenerator.createTemplateResponses(getResponseView(),
                         template, getZoneIds(), false);
                 response.setResponses(templateResponses);
@@ -342,9 +353,11 @@
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
                     "Parameter zoneids cannot combine all zones (-1) option with other zones");
 
-        if (isDirectDownload() && !getHypervisor().equalsIgnoreCase(Hypervisor.HypervisorType.KVM.toString())) {
-            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
-                    "Parameter directdownload is only allowed for KVM templates");
+        String customHypervisor = HypervisorGuru.HypervisorCustomDisplayName.value();
+        if (isDirectDownload() && !(getHypervisor().equalsIgnoreCase(Hypervisor.HypervisorType.KVM.toString())
+                || getHypervisor().equalsIgnoreCase(customHypervisor))) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Parameter directdownload " +
+                    "is only allowed for KVM or %s templates", customHypervisor));
         }
 
         if (!isDeployAsIs() && osTypeId == null) {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterVnfTemplateCmd.java
new file mode 100644
index 0000000..c3e99d5
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterVnfTemplateCmd.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.template;
+
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.network.VNF;
+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.ResponseView;
+import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.storage.template.VnfTemplateUtils;
+
+@APICommand(name = "registerVnfTemplate",
+        description = "Registers an existing VNF template into the CloudStack cloud. ",
+        responseObject = TemplateResponse.class, responseView = ResponseView.Restricted,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+        since = "4.19.0")
+public class RegisterVnfTemplateCmd extends RegisterTemplateCmd implements UserCmd {
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.VNF_NICS,
+            type = CommandType.MAP,
+            description = "VNF nics in key/value pairs using format vnfnics[i].keyname=keyvalue. "
+                    + " Example: vnfnics[0].deviceid=0&&vnfnics[0].name=FirstNIC&&vnfnics[0].required=true"
+                    + "&&vnfnics[1].deviceid=1&&vnfnics[1].name=SecondNIC")
+    protected Map vnfNics;
+
+    @Parameter(name = ApiConstants.VNF_DETAILS,
+            type = CommandType.MAP,
+            description = "VNF details in key/value pairs using format vnfdetails[i].keyname=keyvalue. "
+                    + "Example: vnfdetails[0].vendor=xxx&&vnfdetails[0].version=2.0")
+    protected Map vnfDetails;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public List<VNF.VnfNic> getVnfNics() {
+        return VnfTemplateUtils.getVnfNicsList(this.vnfNics);
+    }
+
+    public Map<String, String> getVnfDetails() {
+        return convertDetailsToMap(vnfDetails);
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    protected void validateParameters() {
+        super.validateParameters();
+
+        VnfTemplateUtils.validateApiCommandParams(this, null);
+    }
+}
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 28ecd45..2afa6a9 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
@@ -16,10 +16,11 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.template;
 
-import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.log4j.Logger;
 
 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.BaseUpdateTemplateOrIsoCmd;
 import org.apache.cloudstack.api.Parameter;
@@ -41,7 +42,8 @@
     //////////////// API parameters /////////////////////
     /////////////////////////////////////////////////////
 
-    @Parameter(name = "templatetype", type = CommandType.STRING, description = "the type of the template")
+    @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).")
     private String templateType;
 
     /////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateVnfTemplateCmd.java
new file mode 100644
index 0000000..7479dd3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateVnfTemplateCmd.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.template;
+
+import com.cloud.network.VNF;
+
+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.ResponseView;
+import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.storage.template.VnfTemplateUtils;
+
+import java.util.List;
+import java.util.Map;
+
+@APICommand(name = "updateVnfTemplate", description = "Updates a template to VNF template or attributes of a VNF template.",
+        responseObject = TemplateResponse.class, responseView = ResponseView.Restricted,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+        since = "4.19.0")
+public class UpdateVnfTemplateCmd extends UpdateTemplateCmd implements UserCmd {
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.VNF_NICS,
+            type = CommandType.MAP,
+            description = "VNF nics in key/value pairs using format vnfnics[i].keyname=keyvalue. "
+                    + " Example: vnfnics[0].deviceid=0&&vnfnics[0].name=FirstNIC&&vnfnics[0].required=true"
+                    + "&&vnfnics[1].deviceid=1&&vnfnics[1].name=SecondNIC")
+    protected Map vnfNics;
+
+    @Parameter(name = ApiConstants.VNF_DETAILS,
+            type = CommandType.MAP,
+            description = "VNF details in key/value pairs using format vnfdetails[i].keyname=keyvalue. "
+                    + "Example: vnfdetails[0].vendor=xxx&&vnfdetails[0].version=2.0")
+    protected Map vnfDetails;
+
+    @Parameter(name = ApiConstants.CLEAN_UP_VNF_DETAILS,
+            type = CommandType.BOOLEAN,
+            description = "optional boolean field, which indicates if VNF details will be cleaned up or not")
+    private Boolean cleanupVnfDetails = null;
+
+    @Parameter(name = ApiConstants.CLEAN_UP_VNF_NICS,
+            type = CommandType.BOOLEAN,
+            description = "optional boolean field, which indicates if VNF nics will be cleaned up or not")
+    private Boolean cleanupVnfNics = null;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+
+    public List<VNF.VnfNic> getVnfNics() {
+        return VnfTemplateUtils.getVnfNicsList(this.vnfNics);
+    }
+
+    public Map<String, String> getVnfDetails() {
+        return convertDetailsToMap(vnfDetails);
+    }
+
+    public boolean isCleanupVnfDetails(){
+        return cleanupVnfDetails == null ? false : cleanupVnfDetails.booleanValue();
+    }
+
+    public boolean isCleanupVnfNics(){
+        return cleanupVnfNics == null ? false : cleanupVnfNics.booleanValue();
+    }
+}
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 aa30066..87d8883 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
@@ -76,5 +76,4 @@
         response.setResponseName(getCommandName());
         setResponseObject(response);
     }
-
-    }
+}
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 a8a87c4..f294f7d 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
@@ -142,5 +142,4 @@
         response.setObjectName(ApiConstants.USER_DATA);
         setResponseObject(response);
     }
-
-    }
+}
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
new file mode 100644
index 0000000..935f39b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 com.cloud.exception.InvalidParameterValueException;
+import com.cloud.vm.VirtualMachine;
+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.UserVmResponse;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+
+import javax.inject.Inject;
+import java.util.Date;
+
+@APICommand(name = "createVMSchedule", description = "Create VM 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;
+
+    @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
+            type = CommandType.UUID,
+            entityType = UserVmResponse.class,
+            required = true,
+            description = "ID of the VM for which schedule is to be defined")
+    private Long vmId;
+
+    @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 VM 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 VM (start/stop/restart/force_stop/force_reboot).")
+    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."
+                    + "Use 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\")")
+    private Date endDate;
+
+    @Parameter(name = ApiConstants.ENABLED,
+            type = CommandType.BOOLEAN,
+            required = false,
+            description = "Enable VM schedule. Defaults to true")
+    private Boolean enabled;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getVmId() {
+        return vmId;
+    }
+
+    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;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        VMScheduleResponse response = vmScheduleManager.createSchedule(this);
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, getVmId());
+        if (vm == null) {
+            throw new InvalidParameterValueException(String.format("Unable to find VM by id=%d", getVmId()));
+        }
+        return vm.getAccountId();
+    }
+}
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
new file mode 100644
index 0000000..775a902
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.api.command.user.vm;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.vm.VirtualMachine;
+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.UserVmResponse;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.cloudstack.vm.schedule.VMSchedule;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.List;
+
+@APICommand(name = "deleteVMSchedule", description = "Delete VM 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;
+
+    @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
+            type = CommandType.UUID,
+            entityType = UserVmResponse.class,
+            required = true,
+            description = "ID of VM")
+    private Long vmId;
+    @Parameter(name = ApiConstants.ID,
+            type = CommandType.UUID,
+            entityType = VMScheduleResponse.class,
+            required = false,
+            description = "ID of VM schedule")
+    private Long id;
+    @Parameter(name = ApiConstants.IDS,
+            type = CommandType.LIST,
+            collectionType = CommandType.UUID,
+            entityType = VMScheduleResponse.class,
+            required = false,
+            description = "IDs of VM schedule")
+    private List<Long> ids;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public List<Long> getIds() {
+        if (ids == null) {
+            return Collections.emptyList();
+        }
+        return ids;
+    }
+
+    public Long getVmId() {
+        return vmId;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        long rowsRemoved = vmScheduleManager.removeSchedule(this);
+
+        if (rowsRemoved > 0) {
+            final SuccessResponse response = new SuccessResponse();
+            response.setResponseName(getCommandName());
+            response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+            setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete VM Schedules");
+        }
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, getVmId());
+        if (vm == null) {
+            throw new InvalidParameterValueException(String.format("Unable to find VM by id=%d", getVmId()));
+        }
+        return vm.getAccountId();
+    }
+}
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 107bd81..1cbe28f 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
@@ -105,6 +105,10 @@
     @Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "an optional user generated name for the virtual machine")
     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 virtual machine. Must be used with domainId.")
     private String accountName;
@@ -263,6 +267,14 @@
     @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;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -328,6 +340,14 @@
             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;
     }
 
@@ -448,6 +468,10 @@
         return zoneId;
     }
 
+    public String getPassword() {
+        return password;
+    }
+
     public List<Long> getNetworkIds() {
         if (MapUtils.isNotEmpty(vAppNetworks)) {
             if (CollectionUtils.isNotEmpty(networkIds) || ipAddress != null || getIp6Address() != null || MapUtils.isNotEmpty(ipToNetworkList)) {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.java
new file mode 100644
index 0000000..4d50dd9
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.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.vm;
+
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.utils.net.NetUtils;
+import com.cloud.vm.VirtualMachine;
+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.user.UserCmd;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.storage.template.VnfTemplateUtils;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@APICommand(name = "deployVnfAppliance",
+        description = "Creates and automatically starts a VNF appliance based on a service offering, disk offering, and template.",
+        responseObject = UserVmResponse.class,
+        responseView = ResponseObject.ResponseView.Restricted,
+        entityType = {VirtualMachine.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = true,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
+        since = "4.19.0")
+public class DeployVnfApplianceCmd extends DeployVMCmd implements UserCmd {
+
+    @Parameter(name = ApiConstants.VNF_CONFIGURE_MANAGEMENT, type = CommandType.BOOLEAN, required = false,
+            description = "True by default, security group or network rules (source nat and firewall rules) will be configured for VNF management interfaces. False otherwise. " +
+                    "Network rules are configured if management network is an isolated network or shared network with security groups.")
+    private Boolean vnfConfigureManagement;
+
+    @Parameter(name = ApiConstants.VNF_CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING,
+            description = "the CIDR list to forward traffic from to the VNF management interface. Multiple entries must be separated by a single comma character (,). The default value is 0.0.0.0/0.")
+    private List<String> vnfCidrlist;
+
+    public Boolean getVnfConfigureManagement() {
+        return vnfConfigureManagement != null && vnfConfigureManagement;
+    }
+
+    public List<String> getVnfCidrlist() {
+        if (CollectionUtils.isNotEmpty(vnfCidrlist)) {
+            return vnfCidrlist;
+        } else {
+            List<String> defaultCidrList = new ArrayList<String>();
+            defaultCidrList.add(NetUtils.ALL_IP4_CIDRS);
+            return defaultCidrList;
+        }
+    }
+
+    @Override
+    public void create() throws ResourceAllocationException {
+        VnfTemplateUtils.validateVnfCidrList(this.getVnfCidrlist());
+
+        super.create();
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListNicsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListNicsCmd.java
index 08235c5..44710d0 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListNicsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListNicsCmd.java
@@ -51,7 +51,7 @@
     //////////////// API parameters /////////////////////
     /////////////////////////////////////////////////////
 
-    @Parameter(name = ApiConstants.NIC_ID, type = CommandType.UUID, entityType = NicResponse.class, required = false, description = "the ID of the nic to to list IPs")
+    @Parameter(name = ApiConstants.NIC_ID, type = CommandType.UUID, entityType = NicResponse.class, required = false, description = "the ID of the nic to list IPs")
     private Long nicId;
 
     @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, required = true, description = "the ID of the vm")
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
new file mode 100644
index 0000000..3474f1d
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.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.vm;
+
+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.ListResponse;
+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 javax.inject.Inject;
+
+@APICommand(name = "listVMSchedule", description = "List VM 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;
+
+    @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
+            type = CommandType.UUID,
+            entityType = UserVmResponse.class,
+            required = true,
+            description = "ID of the VM for which schedule is to be defined")
+    private Long vmId;
+
+    @Parameter(name = ApiConstants.ID,
+            type = CommandType.UUID,
+            entityType = VMScheduleResponse.class,
+            required = false,
+            description = "ID of VM schedule")
+    private Long id;
+
+    @Parameter(name = ApiConstants.ACTION,
+            type = CommandType.STRING,
+            required = false,
+            description = "Action taken by schedule")
+    private String action;
+
+    @Parameter(name = ApiConstants.ENABLED,
+            type = CommandType.BOOLEAN,
+            required = false,
+            description = "ID of VM schedule")
+    private Boolean enabled;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getVmId() {
+        return vmId;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getAction() {
+        return action;
+    }
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+    @Override
+    public void execute() {
+        ListResponse<VMScheduleResponse> response = vmScheduleManager.listSchedule(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+        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 bd3b062..6a5ec28 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
@@ -26,7 +26,7 @@
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiConstants.VMDetails;
-import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
+import org.apache.cloudstack.api.BaseListRetrieveOnlyResourceCountCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.command.user.UserCmd;
@@ -44,6 +44,7 @@
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.api.response.VpcResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.exception.InvalidParameterValueException;
@@ -54,7 +55,7 @@
 
 @APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class},
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
-public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
+public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements UserCmd {
     public static final Logger s_logger = Logger.getLogger(ListVMsCmd.class.getName());
 
     private static final String s_name = "listvirtualmachinesresponse";
@@ -148,7 +149,6 @@
     @Parameter(name = ApiConstants.USER_DATA, type = CommandType.BOOLEAN, description = "Whether to return the VMs' user data or not. By default, user data will not be returned.", since = "4.18.0.0")
     private Boolean showUserData;
 
-
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -189,6 +189,10 @@
         return zoneId;
     }
 
+    @Parameter(name = ApiConstants.IS_VNF, type = CommandType.BOOLEAN,
+            description = "flag to list vms created from VNF templates (as known as VNF appliances) or not; true if need to list VNF appliances, false otherwise.",
+            since = "4.19.0")
+    private Boolean isVnf;
 
     public Long getNetworkId() {
         return networkId;
@@ -255,20 +259,21 @@
 
     @Override
     public Boolean getDisplay() {
-        if (display != null) {
-            return display;
-        }
-        return super.getDisplay();
+        return BooleanUtils.toBooleanDefaultIfNull(display, super.getDisplay());
     }
 
     public Boolean getShowIcon() {
-        return showIcon != null ? showIcon : false;
+        return BooleanUtils.toBooleanDefaultIfNull(showIcon, false);
     }
 
     public Boolean getAccumulate() {
         return accumulate;
     }
 
+    public Boolean getVnf() {
+        return isVnf;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
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 e275a98..1cf4c92 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
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.vm;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@@ -56,8 +57,7 @@
             required=true, description="The ID of the virtual machine")
     private Long id;
 
-    // unexposed parameter needed for serializing/deserializing the command
-    @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, expose=false)
+    @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, description="The new password of the virtual machine. If null, a random password will be generated for the VM.", since="4.19.0")
     protected String password;
 
 
@@ -118,7 +118,14 @@
 
     @Override
     public void execute() throws ResourceUnavailableException, InsufficientCapacityException {
-        password = _mgr.generateRandomPassword();
+        password = getPassword();
+        UserVm vm = _responseGenerator.findUserVmById(getId());
+        if (StringUtils.isBlank(password)) {
+            password = _mgr.generateRandomPassword();
+            s_logger.debug(String.format("Resetting VM [%s] password to a randomly generated password.", vm.getUuid()));
+        } else {
+            s_logger.debug(String.format("Resetting VM [%s] password to password defined by user.", vm.getUuid()));
+        }
         CallContext.current().setEventDetails("Vm Id: " + getId());
         UserVm result = _userVmService.resetVMPassword(this, password);
         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 4b59bf5..17c4e97 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
@@ -16,7 +16,9 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.vm;
 
+import com.cloud.vm.VmDetailConstants;
 import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.response.DiskOfferingResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@@ -42,6 +44,8 @@
 import com.cloud.uservm.UserVm;
 import com.cloud.vm.VirtualMachine;
 
+import java.util.Map;
+
 @APICommand(name = "restoreVirtualMachine", description = "Restore a VM to original template/ISO or new template/ISO", responseObject = UserVmResponse.class, since = "3.0.0", responseView = ResponseView.Restricted, entityType = {VirtualMachine.class},
             requestHasSensitiveInfo = false,
             responseHasSensitiveInfo = true)
@@ -60,6 +64,28 @@
                description = "an optional template Id to restore vm from the new template. This can be an ISO id in case of restore vm deployed using ISO")
     private Long templateId;
 
+    @Parameter(name = ApiConstants.DISK_OFFERING_ID,
+               type = CommandType.UUID,
+               entityType = DiskOfferingResponse.class,
+               description = "Override root volume's diskoffering.", since = "4.19.1")
+    private Long rootDiskOfferingId;
+
+    @Parameter(name = ApiConstants.ROOT_DISK_SIZE,
+               type = CommandType.LONG,
+               description = "Override root volume's size (in GB). Analogous to details[0].rootdisksize, which takes precedence over this parameter if both are provided",
+               since = "4.19.1")
+    private Long rootDiskSize;
+
+    @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.19.1",
+               description = "used to specify the custom parameters")
+    private Map details;
+
+    @Parameter(name = ApiConstants.EXPUNGE,
+               type = CommandType.BOOLEAN,
+               description = "Optional field to expunge old root volume after restore.",
+               since = "4.19.1")
+    private Boolean expungeRootDisk;
+
     @Override
     public String getEventType() {
         return EventTypes.EVENT_VM_RESTORE;
@@ -112,6 +138,22 @@
         return getVmId();
     }
 
+    public Long getRootDiskOfferingId() {
+        return rootDiskOfferingId;
+    }
+
+    public Map<String, String> getDetails() {
+        Map<String, String> customparameterMap = convertDetailsToMap(details);
+        if (rootDiskSize != null && !customparameterMap.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) {
+            customparameterMap.put(VmDetailConstants.ROOT_DISK_SIZE, rootDiskSize.toString());
+        }
+        return customparameterMap;
+    }
+
+    public Boolean getExpungeRootDisk() {
+        return expungeRootDisk != null && expungeRootDisk;
+    }
+
     @Override
     public Long getApiResourceId() {
         return getId();
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
new file mode 100644
index 0000000..72f85c1
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.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.command.user.vm;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.vm.VirtualMachine;
+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.VMScheduleResponse;
+import org.apache.cloudstack.vm.schedule.VMSchedule;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+
+import javax.inject.Inject;
+import java.util.Date;
+
+@APICommand(name = "updateVMSchedule", description = "Update VM 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;
+
+    @Parameter(name = ApiConstants.ID,
+            type = CommandType.UUID,
+            entityType = VMScheduleResponse.class,
+            required = true,
+            description = "ID of VM schedule")
+    private Long id;
+
+    @Parameter(name = ApiConstants.DESCRIPTION,
+            type = CommandType.STRING,
+            required = false,
+            description = "Name of the schedule")
+    private String description;
+
+    @Parameter(name = ApiConstants.SCHEDULE,
+            type = CommandType.STRING,
+            required = false,
+            description = "Schedule for action on VM 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 = false,
+            description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.")
+    private String timeZone;
+
+    @Parameter(name = ApiConstants.START_DATE,
+            type = CommandType.DATE,
+            required = false,
+            description = "start date from which the schedule becomes active"
+                    + "Use 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\")")
+    private Date endDate;
+
+    @Parameter(name = ApiConstants.ENABLED,
+            type = CommandType.BOOLEAN,
+            required = false,
+            description = "Enable VM schedule")
+    private Boolean enabled;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    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;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        VMScheduleResponse response = vmScheduleManager.updateSchedule(this);
+        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()));
+        }
+        VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmSchedule.getVmId());
+        return vm.getAccountId();
+    }
+}
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 2c0ea6b..e83c6b4 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
@@ -138,4 +138,10 @@
     public ApiCommandResourceType getApiResourceType() {
         return ApiCommandResourceType.VmSnapshot;
     }
+
+    @Override
+    public Long getApiResourceId() {
+        return getEntityId();
+    }
+
 }
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
new file mode 100644
index 0000000..e28efd1
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java
@@ -0,0 +1,140 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command.user.volume;
+
+import com.cloud.event.EventTypes;
+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.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.EnumUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.storage.Volume;
+import com.cloud.user.Account;
+import com.cloud.utils.Pair;
+import com.cloud.utils.StringUtils;
+
+import java.util.Arrays;
+
+@APICommand(name = "checkVolume", description = "Check the volume for any errors or leaks and also repairs when repair parameter is passed, this is currently supported for KVM only", responseObject = VolumeResponse.class, entityType = {Volume.class},
+        since = "4.19.1",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class CheckAndRepairVolumeCmd extends BaseAsyncCmd {
+    public static final Logger s_logger = Logger.getLogger(CheckAndRepairVolumeCmd.class.getName());
+
+    private static final String s_name = "checkandrepairvolumeresponse";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the volume")
+    private Long id;
+
+    @Parameter(name = ApiConstants.REPAIR, type = CommandType.STRING, required = false, description = "parameter to repair the volume, leaks or all are the possible values")
+    private String repair;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public enum RepairValues {
+        LEAKS, ALL
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getRepair() {
+        if (org.apache.commons.lang3.StringUtils.isNotEmpty(repair)) {
+            RepairValues repairType = EnumUtils.getEnumIgnoreCase(RepairValues.class, repair);
+            if (repairType == null) {
+                throw new InvalidParameterValueException(String.format("Repair parameter can only take the following values: %s", Arrays.toString(RepairValues.values())));
+            }
+            return repair.toLowerCase();
+        }
+        return null;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Volume volume = _entityMgr.findById(Volume.class, getId());
+        if (volume != null) {
+            return volume.getAccountId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
+    }
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_VOLUME_CHECK;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return String.format("check and repair operation on volume: %s", this._uuidMgr.getUuid(Volume.class, getId()));
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return id;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.Volume;
+    }
+
+    @Override
+    public void execute() throws ResourceAllocationException {
+        CallContext.current().setEventDetails("Volume Id: " + getId());
+        Pair<String, String> result = _volumeService.checkAndRepairVolume(this);
+        Volume volume = _responseGenerator.findVolumeById(getId());
+        if (result != null) {
+            VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, volume);
+            response.setVolumeCheckResult(StringUtils.parseJsonToMap(result.first()));
+            if (getRepair() != null) {
+                response.setVolumeRepairResult(StringUtils.parseJsonToMap(result.second()));
+            }
+            response.setResponseName(getCommandName());
+            setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to check volume and repair");
+        }
+    }
+}
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 fdac3ff..b62a909 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
@@ -22,7 +22,7 @@
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
+import org.apache.cloudstack.api.BaseListRetrieveOnlyResourceCountCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.command.user.UserCmd;
@@ -35,13 +35,14 @@
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.storage.Volume;
 
 @APICommand(name = "listVolumes", description = "Lists all volumes.", responseObject = VolumeResponse.class, responseView = ResponseView.Restricted, entityType = {
         Volume.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
-public class ListVolumesCmd extends BaseListTaggedResourcesCmd implements UserCmd {
+public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd implements UserCmd {
     public static final Logger s_logger = Logger.getLogger(ListVolumesCmd.class.getName());
 
     private static final String s_name = "listvolumesresponse";
@@ -145,15 +146,13 @@
 
     @Override
     public Boolean getDisplay() {
-        if (display != null) {
-            return display;
-        }
-        return super.getDisplay();
+        return BooleanUtils.toBooleanDefaultIfNull(display, super.getDisplay());
     }
 
     public String getState() {
         return state;
     }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreatePrivateGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreatePrivateGatewayCmd.java
index 8527bd0..cf1315c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreatePrivateGatewayCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreatePrivateGatewayCmd.java
@@ -169,7 +169,7 @@
     public void execute() throws InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException, ResourceUnavailableException {
         PrivateGateway result = _vpcService.applyVpcPrivateGateway(getEntityId(), true);
         if (result != null) {
-            PrivateGatewayResponse response = _responseGenerator.createPrivateGatewayResponse(result);
+            PrivateGatewayResponse response = _responseGenerator.createPrivateGatewayResponse(getResponseView(), result);
             response.setResponseName(getCommandName());
             setResponseObject(response);
         } else {
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 af70049..7ca66b2 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,7 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.vpc;
 
-import com.cloud.network.NetworkService;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.RoleType;
@@ -40,6 +40,7 @@
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.NetworkService;
 import com.cloud.network.vpc.Vpc;
 
 @APICommand(name = "createVPC", description = "Creates a VPC", responseObject = VpcResponse.class, responseView = ResponseView.Restricted, entityType = {Vpc.class},
@@ -72,8 +73,8 @@
     @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "the name of the VPC")
     private String vpcName;
 
-    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, required = true, description = "the display text of " +
-            "the VPC")
+    @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the VPC, defaults to its 'name'.")
+
     private String displayText;
 
     @Parameter(name = ApiConstants.CIDR, type = CommandType.STRING, required = true, description = "the cidr of the VPC. All VPC " +
@@ -112,6 +113,12 @@
     @Parameter(name = ApiConstants.IP6_DNS2, type = CommandType.STRING, description = "the second IPv6 DNS for the VPC", since = "4.18.0")
     private String ip6Dns2;
 
+    @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 will be used as source NAT address for the networks in ths VPC. " +
+            "\nIf an address is given and it cannot be acquired, an error will be returned and the network won´t be implemented,",
+            since = "4.19")
+    private String sourceNatIP;
+
     // ///////////////////////////////////////////////////
     // ///////////////// Accessors ///////////////////////
     // ///////////////////////////////////////////////////
@@ -137,7 +144,7 @@
     }
 
     public String getDisplayText() {
-        return displayText;
+        return StringUtils.isEmpty(displayText) ? vpcName    : displayText;
     }
 
     public Long getVpcOffering() {
@@ -179,6 +186,15 @@
         return display;
     }
 
+
+    public String getSourceNatIP() {
+        return sourceNatIP;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
     @Override
     public void create() throws ResourceAllocationException {
         Vpc vpc = _vpcService.createVpc(this);
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListPrivateGatewaysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListPrivateGatewaysCmd.java
index 47861c8..8813ccc 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListPrivateGatewaysCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListPrivateGatewaysCmd.java
@@ -25,6 +25,8 @@
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
 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.ListResponse;
 import org.apache.cloudstack.api.response.PrivateGatewayResponse;
 import org.apache.cloudstack.api.response.VpcResponse;
@@ -34,8 +36,9 @@
 import com.cloud.utils.Pair;
 
 @APICommand(name = "listPrivateGateways", description = "List private gateways", responseObject = PrivateGatewayResponse.class, entityType = {VpcGateway.class},
+        responseView = ResponseObject.ResponseView.Restricted,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
-public class ListPrivateGatewaysCmd extends BaseListProjectAndAccountResourcesCmd {
+public class ListPrivateGatewaysCmd extends BaseListProjectAndAccountResourcesCmd implements UserCmd {
     public static final Logger s_logger = Logger.getLogger(ListPrivateGatewaysCmd.class.getName());
 
 
@@ -90,7 +93,7 @@
         ListResponse<PrivateGatewayResponse> response = new ListResponse<PrivateGatewayResponse>();
         List<PrivateGatewayResponse> projectResponses = new ArrayList<PrivateGatewayResponse>();
         for (PrivateGateway gateway : gateways.first()) {
-            PrivateGatewayResponse gatewayResponse = _responseGenerator.createPrivateGatewayResponse(gateway);
+            PrivateGatewayResponse gatewayResponse = _responseGenerator.createPrivateGatewayResponse(getResponseView(), gateway);
             projectResponses.add(gatewayResponse);
         }
         response.setResponses(projectResponses, gateways.second());
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java
index b230603..76cbcca 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java
@@ -142,9 +142,7 @@
     @Override
     public void execute() {
         Pair<List<? extends Vpc>, Integer> vpcs =
-            _vpcService.listVpcs(getId(), getVpcName(), getDisplayText(), getSupportedServices(), getCidr(), getVpcOffId(), getState(), getAccountName(), getDomainId(),
-                getKeyword(), getStartIndex(), getPageSizeVal(), getZoneId(), isRecursive(), listAll(), getRestartRequired(), getTags(),
-                getProjectId(), getDisplay());
+            _vpcService.listVpcs(this);
         ListResponse<VpcResponse> response = new ListResponse<VpcResponse>();
         List<VpcResponse> vpcResponses = new ArrayList<VpcResponse>();
         for (Vpc vpc : vpcs.first()) {
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 4c3408e..d4c7d0d 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
@@ -34,6 +34,8 @@
 import org.apache.cloudstack.api.response.VpcResponse;
 
 import com.cloud.event.EventTypes;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.network.vpc.Vpc;
 import com.cloud.user.Account;
 
@@ -63,6 +65,12 @@
             description = "MTU to be configured on the network VR's public facing interfaces", since = "4.18.0")
     private Integer publicMtu;
 
+    @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 VPC",
+            since = "4.19")
+    private String sourceNatIP;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -87,6 +95,10 @@
         return publicMtu;
     }
 
+    public String getSourceNatIP() {
+        return sourceNatIP;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -107,13 +119,22 @@
 
     @Override
     public void execute() {
-        Vpc result = _vpcService.updateVpc(getId(), getVpcName(), getDisplayText(), getCustomId(), isDisplayVpc(), getPublicMtu());
-        if (result != null) {
-            VpcResponse response = _responseGenerator.createVpcResponse(getResponseView(), result);
-            response.setResponseName(getCommandName());
-            setResponseObject(response);
-        } else {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update VPC");
+        try {
+            Vpc result = _vpcService.updateVpc(this);
+            if (result != null) {
+                VpcResponse response = _responseGenerator.createVpcResponse(getResponseView(), result);
+                response.setResponseName(getCommandName());
+                setResponseObject(response);
+            } else {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update VPC");
+            }
+        } catch (final ResourceUnavailableException ex) {
+            s_logger.warn("Exception: ", ex);
+            throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
+        } catch (final InsufficientCapacityException ex) {
+            s_logger.info(ex);
+            s_logger.trace(ex);
+            throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage());
         }
     }
 
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 c79afb1..c29f3a8 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
@@ -16,10 +16,9 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.zone;
 
+import java.util.List;
 import java.util.Map;
 
-import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseListCmd;
@@ -30,6 +29,7 @@
 import org.apache.cloudstack.api.response.DomainResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.log4j.Logger;
 
 @APICommand(name = "listZones", description = "Lists zones", responseObject = ZoneResponse.class, responseView = ResponseView.Restricted,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -44,6 +44,9 @@
     @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the ID of the zone")
     private Long id;
 
+    @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ZoneResponse.class, description = "the IDs of the zones, mutually exclusive with id", since = "4.19.0")
+    private List<Long> ids;
+
     @Parameter(name = ApiConstants.AVAILABLE,
                type = CommandType.BOOLEAN,
                description = "true if you want to retrieve all available Zones. False if you only want to return the Zones"
@@ -76,6 +79,10 @@
         return id;
     }
 
+    public List<Long> getIds() {
+        return ids;
+    }
+
     public Boolean isAvailable() {
         return available;
     }
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
new file mode 100644
index 0000000..3f91cde
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/AlertTypeResponse.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.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+public class AlertTypeResponse extends BaseResponse {
+
+    @SerializedName("alerttypeid")
+    @Param(description = "alert type")
+    private short alertType;
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "description of alert type")
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public short getUsageType() {
+        return alertType;
+    }
+
+    public void setUsageType(short alertType) {
+        this.alertType = alertType;
+    }
+
+    public AlertTypeResponse(short alertType, String name) {
+        this.alertType = alertType;
+        this.name = name;
+        setObjectName("alerttype");
+    }
+}
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 eecd6be..3eeaaef 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
@@ -32,9 +32,21 @@
 public class AsyncJobResponse extends BaseResponse {
 
     @SerializedName("accountid")
-    @Param(description = "the account that executed the async command")
+    @Param(description = "the account id that executed the async command")
     private String accountId;
 
+    @SerializedName("account")
+    @Param(description = "the account that executed the async command")
+    private String account;
+
+    @SerializedName("domainid")
+    @Param(description = "the domain id that executed the async command")
+    private String domainid;
+
+    @SerializedName("domainpath")
+    @Param(description = "the domain that executed the async command")
+    private String domainPath;
+
     @SerializedName(ApiConstants.USER_ID)
     @Param(description = "the user that executed the async command")
     private String userId;
@@ -71,6 +83,10 @@
     @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.CREATED)
     @Param(description = "  the created date of the job")
     private Date created;
@@ -83,6 +99,18 @@
         this.accountId = accountId;
     }
 
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public void setDomainId(String domainid) {
+        this.domainid = domainid;
+    }
+
+    public void setDomainPath(String domainPath) {
+        this.domainPath = domainPath;
+    }
+
     public void setUserId(String userId) {
         this.userId = userId;
     }
@@ -127,4 +155,8 @@
     public void setRemoved(final Date removed) {
         this.removed = removed;
     }
+
+    public void setMsid(Long msid) {
+        this.msid = msid;
+    }
 }
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 d0c8e58..6341968 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
@@ -25,6 +25,8 @@
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
+import java.util.Date;
+
 @EntityReference(value = Backup.class)
 public class BackupResponse extends BaseResponse {
 
@@ -50,7 +52,7 @@
 
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "backup date")
-    private String date;
+    private Date date;
 
     @SerializedName(ApiConstants.SIZE)
     @Param(description = "backup size in bytes")
@@ -140,11 +142,11 @@
         this.type = type;
     }
 
-    public String getDate() {
-        return date;
+    public Date getDate() {
+        return this.date;
     }
 
-    public void setDate(String date) {
+    public void setDate(Date date) {
         this.date = date;
     }
 
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java
index afb3e9f..22bb099 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java
@@ -25,6 +25,8 @@
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
+import java.util.Date;
+
 @EntityReference(value = Backup.RestorePoint.class)
 public class BackupRestorePointResponse extends BaseResponse {
 
@@ -34,7 +36,7 @@
 
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "created time")
-    private String created;
+    private Date created;
 
     @SerializedName(ApiConstants.TYPE)
     @Param(description = "restore point type")
@@ -48,11 +50,11 @@
         this.id = id;
     }
 
-    public String getCreated() {
-        return created;
+    public Date getCreated() {
+        return this.created;
     }
 
-    public void setCreated(String created) {
+    public void setCreated(Date created) {
         this.created = created;
     }
 
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java
index 339d092..b1bba90 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/BaseRoleResponse.java
@@ -36,6 +36,11 @@
     @Param(description = "the description of the role")
     private String roleDescription;
 
+    @SerializedName(ApiConstants.IS_PUBLIC)
+    @Param(description = "Indicates whether the role will be visible to all users (public) or only to root admins (private)." +
+            " If this parameter is not specified during the creation of the role its value will be defaulted to true (public).")
+    private boolean publicRole = true;
+
     public void setId(String id) {
         this.id = id;
     }
@@ -47,4 +52,8 @@
     public void setDescription(String description) {
         this.roleDescription = description;
     }
+
+    public void setPublicRole(boolean publicRole) {
+        this.publicRole = publicRole;
+    }
 }
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
new file mode 100644
index 0000000..b75f360
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java
@@ -0,0 +1,293 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package 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.BaseResponseWithTagInformation;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.storage.object.Bucket;
+
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+@EntityReference(value = Bucket.class)
+@SuppressWarnings("unused")
+public class BucketResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse, ControlledEntityResponse {
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "ID of the Bucket")
+    private String id;
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "name of the Bucket")
+    private String name;
+    @SerializedName(ApiConstants.CREATED)
+    @Param(description = "the date the Bucket was created")
+    private Date created;
+    @SerializedName(ApiConstants.ACCOUNT)
+    @Param(description = "the account associated with the Bucket")
+    private String accountName;
+    @SerializedName(ApiConstants.PROJECT_ID)
+    @Param(description = "the project id of the bucket")
+    private String projectId;
+    @SerializedName(ApiConstants.PROJECT)
+    @Param(description = "the project name of the bucket")
+    private String projectName;
+    @SerializedName(ApiConstants.DOMAIN_ID)
+    @Param(description = "the ID of the domain associated with the bucket")
+    private String domainId;
+    @SerializedName(ApiConstants.DOMAIN)
+    @Param(description = "the domain associated with the bucket")
+    private String domainName;
+    @SerializedName(ApiConstants.OBJECT_STORAGE_ID)
+    @Param(description = "id of the object storage hosting the Bucket; returned to admin user only")
+    private String objectStoragePoolId;
+
+    @SerializedName(ApiConstants.OBJECT_STORAGE)
+    @Param(description = "Name of the object storage hosting the Bucket; returned to admin user only")
+    private String objectStoragePool;
+
+    @SerializedName(ApiConstants.SIZE)
+    @Param(description = "Total size of objects in Bucket")
+    private Long size;
+
+    @SerializedName(ApiConstants.STATE)
+    @Param(description = "State of the Bucket")
+    private String state;
+
+    @SerializedName(ApiConstants.QUOTA)
+    @Param(description = "Bucket Quota in GB")
+    private Integer quota;
+
+    @SerializedName(ApiConstants.ENCRYPTION)
+    @Param(description = "Bucket Encryption")
+    private Boolean encryption;
+
+    @SerializedName(ApiConstants.VERSIONING)
+    @Param(description = "Bucket Versioning")
+    private Boolean versioning;
+
+    @SerializedName(ApiConstants.OBJECT_LOCKING)
+    @Param(description = "Bucket Object Locking")
+    private Boolean objectLock;
+
+    @SerializedName(ApiConstants.POLICY)
+    @Param(description = "Bucket Access Policy")
+    private String policy;
+
+    @SerializedName(ApiConstants.URL)
+    @Param(description = "Bucket URL")
+    private String bucketURL;
+
+    @SerializedName(ApiConstants.ACCESS_KEY)
+    @Param(description = "Bucket Access Key")
+    private String accessKey;
+
+    @SerializedName(ApiConstants.SECRET_KEY)
+    @Param(description = "Bucket Secret Key")
+    private String secretKey;
+
+    @SerializedName(ApiConstants.PROVIDER)
+    @Param(description = "Object storage provider")
+    private String provider;
+
+    public BucketResponse() {
+        tags = new LinkedHashSet<ResourceTagResponse>();
+    }
+
+    @Override
+    public String getObjectId() {
+        return this.getId();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    @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 setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    @Override
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public void setTags(Set<ResourceTagResponse> tags) {
+        this.tags = tags;
+    }
+
+    public void setObjectStoragePoolId(String objectStoragePoolId) {
+        this.objectStoragePoolId = objectStoragePoolId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public String getProjectId() {
+        return projectId;
+    }
+
+    public String getProjectName() {
+        return projectName;
+    }
+
+    public String getDomainId() {
+        return domainId;
+    }
+
+    public String getDomainName() {
+        return domainName;
+    }
+
+    public String getObjectStoragePoolId() {
+        return objectStoragePoolId;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+    public long getQuota() {
+        return quota;
+    }
+
+    public void setQuota(Integer quota) {
+        this.quota = quota;
+    }
+
+    public boolean isVersioning() {
+        return versioning;
+    }
+
+    public void setVersioning(boolean versioning) {
+        this.versioning = versioning;
+    }
+
+    public boolean isEncryption() {
+        return encryption;
+    }
+
+    public void setEncryption(boolean encryption) {
+        this.encryption = encryption;
+    }
+
+    public boolean isObjectLock() {
+        return objectLock;
+    }
+
+    public void setObjectLock(boolean objectLock) {
+        this.objectLock = objectLock;
+    }
+
+    public String getPolicy() {
+        return policy;
+    }
+
+    public void setPolicy(String policy) {
+        this.policy = policy;
+    }
+
+    public String getBucketURL() {
+        return bucketURL;
+    }
+
+    public void setBucketURL(String bucketURL) {
+        this.bucketURL = bucketURL;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    public void setState(Bucket.State state) {
+        this.state = state.toString();
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setObjectStoragePool(String objectStoragePool) {
+        this.objectStoragePool = objectStoragePool;
+    }
+
+    public String getObjectStoragePool() {
+        return objectStoragePool;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public void setProvider(String provider) {
+        this.provider = provider;
+    }
+}
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 d2f7c6b..e4224c8 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
@@ -100,6 +100,10 @@
     @Param(description = "true if experimental features for Kubernetes cluster such as Docker private registry are enabled, false otherwise")
     private boolean kubernetesClusterExperimentalFeaturesEnabled;
 
+    @SerializedName("customhypervisordisplayname")
+    @Param(description = "Display name for custom hypervisor", since = "4.19.0")
+    private String customHypervisorDisplayName;
+
     @SerializedName("defaultuipagesize")
     @Param(description = "default page size in the UI for various views, value set in the configurations", since = "4.15.2")
     private Long defaultUiPageSize;
@@ -215,4 +219,8 @@
     public void setInstancesDisksStatsRetentionTime(Integer instancesDisksStatsRetentionTime) {
         this.instancesDisksStatsRetentionTime = instancesDisksStatsRetentionTime;
     }
+
+    public void setCustomHypervisorDisplayName(String customHypervisorDisplayName) {
+        this.customHypervisorDisplayName = customHypervisorDisplayName;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java
index b036cd4..8f5b5de 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java
@@ -39,7 +39,7 @@
     @Param(description = "the size of the template")
     private Integer size;
 
-    @SerializedName("templatetype")
+    @SerializedName(ApiConstants.TEMPLATE_TYPE)
     @Param(description = "the type of the template")
     private String templateType;
 
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
new file mode 100644
index 0000000..4114c22
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.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.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.jobs.JobInfo;
+
+public class ClusterDrsPlanMigrationResponse extends BaseResponse {
+    @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
+    @Param(description = "VM to migrate")
+    String vmId;
+
+    @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME)
+    @Param(description = "VM to migrate")
+    String vmName;
+
+    @SerializedName("sourcehostid")
+    @Param(description = "Original host for VM migration")
+    String srcHostId;
+
+    @SerializedName("sourcehostname")
+    @Param(description = "Original host for VM migration")
+    String srcHostName;
+
+    @SerializedName("destinationhostid")
+    @Param(description = "Destination host for VM migration")
+    String destHostId;
+
+    @SerializedName("destinationhostname")
+    @Param(description = "Destination host for VM migration")
+    String destHostName;
+
+    @SerializedName(ApiConstants.JOB_ID)
+    @Param(description = "id of VM migration async job")
+    private Long jobId;
+
+    @SerializedName(ApiConstants.JOB_STATUS)
+    @Param(description = "Job status of VM migration async job")
+    private JobInfo.Status jobStatus;
+
+
+    public ClusterDrsPlanMigrationResponse(String vmId, String vmName, String srcHostId, String srcHostName,
+                                           String destHostId, String destHostName, Long jobId,
+                                           JobInfo.Status jobStatus) {
+        this.vmId = vmId;
+        this.vmName = vmName;
+        this.srcHostId = srcHostId;
+        this.srcHostName = srcHostName;
+        this.destHostId = destHostId;
+        this.destHostName = destHostName;
+        this.jobId = jobId;
+        this.jobStatus = jobStatus;
+        this.setObjectName(ApiConstants.MIGRATIONS);
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanResponse.java
new file mode 100644
index 0000000..2a7fed7
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanResponse.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.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.cluster.ClusterDrsPlan;
+
+import java.util.Date;
+import java.util.List;
+
+@EntityReference(value = ClusterDrsPlan.class)
+public class ClusterDrsPlanResponse extends BaseResponse {
+    @SerializedName(ApiConstants.MIGRATIONS)
+    @Param(description = "List of migrations")
+    List<ClusterDrsPlanMigrationResponse> migrationPlans;
+
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "unique ID of the drs plan for cluster")
+    private String id;
+
+    @SerializedName(ApiConstants.CLUSTER_ID)
+    @Param(description = "Id of the cluster")
+    private String clusterId;
+
+    @SerializedName("eventid")
+    @Param(description = "Start event Id of the DRS Plan")
+    private String eventId;
+
+    @SerializedName(ApiConstants.TYPE)
+    @Param(description = "Type of DRS Plan (Automated or Manual))")
+    private ClusterDrsPlan.Type type;
+
+    @SerializedName(ApiConstants.STATUS)
+    @Param(description = "Status of DRS Plan")
+    private ClusterDrsPlan.Status status;
+
+    @SerializedName(ApiConstants.CREATED)
+    private Date created;
+
+
+    public ClusterDrsPlanResponse(String clusterId, ClusterDrsPlan plan, String eventId,
+                                  List<ClusterDrsPlanMigrationResponse> migrationPlans) {
+        this.clusterId = clusterId;
+        this.eventId = eventId;
+        if (plan != null) {
+            this.id = plan.getUuid();
+            this.type = plan.getType();
+            this.status = plan.getStatus();
+            this.created = plan.getCreated();
+        }
+        this.migrationPlans = migrationPlans;
+        this.setObjectName("drsPlan");
+    }
+
+    public List<ClusterDrsPlanMigrationResponse> getMigrationPlans() {
+        return migrationPlans;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
index b8244ae..5b4434f 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
@@ -53,6 +53,10 @@
     @Param(description = "the name of the disk offering")
     private String name;
 
+    @SerializedName(ApiConstants.STATE)
+    @Param(description = "state of the disk offering")
+    private String state;
+
     @SerializedName(ApiConstants.DISPLAY_TEXT)
     @Param(description = "an alternate display text of the disk offering.")
     private String displayText;
@@ -226,6 +230,14 @@
         this.name = name;
     }
 
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
     public String getDisplayText() {
         return displayText;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java
index a3ca777..8f65492 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java
@@ -93,6 +93,10 @@
     @Param(description = "whether the event is parented")
     private String parentId;
 
+    @SerializedName(ApiConstants.ARCHIVED)
+    @Param(description = "whether the event has been archived or not")
+    private Boolean archived;
+
     public void setId(String id) {
         this.id = id;
     }
@@ -173,4 +177,8 @@
     public void setProjectName(String projectName) {
         this.projectName = projectName;
     }
+
+    public void setArchived(Boolean archived) {
+        this.archived = archived;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuestOSResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuestOSResponse.java
index 7ec2677..f870a2f 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/GuestOSResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/GuestOSResponse.java
@@ -35,6 +35,18 @@
     @Param(description = "the ID of the OS category")
     private String osCategoryId;
 
+    @SerializedName(ApiConstants.OS_CATEGORY_NAME)
+    @Param(description = "the name of the OS category")
+    private String osCategoryName;
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "the name of the OS type")
+    private String name;
+
+    /**
+     * @deprecated description, as name is the correct interpretation and is needed for UI forms
+     */
+    @Deprecated(since = "4.19")
     @SerializedName(ApiConstants.DESCRIPTION)
     @Param(description = "the name/description of the OS type")
     private String description;
@@ -63,6 +75,22 @@
         this.osCategoryId = osCategoryId;
     }
 
+    public String getOsCategoryName() {
+        return osCategoryName;
+    }
+
+    public void setOsCategoryName(String osCategoryName) {
+        this.osCategoryName = osCategoryName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
     public String getDescription() {
         return description;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
index 4ed0cdd..41a0fdc 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java
@@ -24,7 +24,6 @@
 
 import com.cloud.host.Host;
 import com.cloud.host.Status;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
@@ -84,7 +83,7 @@
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the host hypervisor")
-    private HypervisorType hypervisor;
+    private String hypervisor;
 
     @SerializedName("cpunumber")
     @Param(description = "the CPU number of the host")
@@ -295,7 +294,7 @@
         this.version = version;
     }
 
-    public void setHypervisor(HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 
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 5d809cf..d72d23b 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
@@ -29,7 +29,6 @@
 
 import com.cloud.host.Host;
 import com.cloud.host.Status;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
@@ -89,7 +88,7 @@
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the host hypervisor")
-    private HypervisorType hypervisor;
+    private String hypervisor;
 
     @SerializedName("cpusockets")
     @Param(description = "the number of CPU sockets on the host")
@@ -222,6 +221,10 @@
     @Param(description = "comma-separated list of tags for the host")
     private String hostTags;
 
+    @SerializedName(ApiConstants.IS_TAG_A_RULE)
+    @Param(description = ApiConstants.PARAMETER_DESCRIPTION_IS_TAG_A_RULE)
+    private Boolean isTagARule;
+
     @SerializedName("hasenoughcapacity")
     @Param(description = "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise")
     private Boolean hasEnoughCapacity;
@@ -335,7 +338,7 @@
         this.version = version;
     }
 
-    public void setHypervisor(HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 
@@ -602,7 +605,7 @@
         return version;
     }
 
-    public HypervisorType getHypervisor() {
+    public String getHypervisor() {
         return hypervisor;
     }
 
@@ -733,4 +736,12 @@
     public void setEncryptionSupported(Boolean encryptionSupported) {
         this.encryptionSupported = encryptionSupported;
     }
+
+    public Boolean getIsTagARule() {
+        return isTagARule;
+    }
+
+    public void setIsTagARule(Boolean tagARule) {
+        isTagARule = tagARule;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
index b5e5624..c19397e 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java
@@ -20,7 +20,6 @@
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.EntityReference;
 
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.HypervisorCapabilities;
 import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
@@ -37,7 +36,7 @@
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the hypervisor type")
-    private HypervisorType hypervisor;
+    private String hypervisor;
 
     @SerializedName(ApiConstants.MAX_GUESTS_LIMIT)
     @Param(description = "the maximum number of guest vms recommended for this hypervisor")
@@ -83,11 +82,11 @@
         this.hypervisorVersion = hypervisorVersion;
     }
 
-    public HypervisorType getHypervisor() {
+    public String getHypervisor() {
         return hypervisor;
     }
 
-    public void setHypervisor(HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorGuestOsNamesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorGuestOsNamesResponse.java
new file mode 100644
index 0000000..52f55bf
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorGuestOsNamesResponse.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 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 HypervisorGuestOsNamesResponse extends BaseResponse {
+    @SerializedName(ApiConstants.HYPERVISOR)
+    @Param(description = "the hypervisor")
+    private String hypervisor;
+
+    @SerializedName(ApiConstants.HYPERVISOR_VERSION)
+    @Param(description = "version of the hypervisor for guest os names")
+    private String hypervisorVersion;
+
+    @SerializedName(ApiConstants.GUEST_OS_LIST)
+    @Param(description = "the guest OS list of the hypervisor", responseObject = HypervisorGuestOsResponse.class)
+    private List<HypervisorGuestOsResponse> guestOSList;
+
+    @SerializedName(ApiConstants.GUEST_OS_COUNT)
+    @Param(description = "the guest OS count of the hypervisor")
+    private Integer guestOSCount;
+
+    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 List<HypervisorGuestOsResponse> getGuestOSList() {
+        return guestOSList;
+    }
+
+    public void setGuestOSList(List<HypervisorGuestOsResponse> guestOSList) {
+        this.guestOSList = guestOSList;
+    }
+
+    public Integer getGuestOSCount() {
+        return guestOSCount;
+    }
+
+    public void setGuestOSCount(Integer guestOSCount) {
+        this.guestOSCount = guestOSCount;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HypervisorGuestOsResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorGuestOsResponse.java
new file mode 100644
index 0000000..e9ef630
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/HypervisorGuestOsResponse.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.google.gson.annotations.SerializedName;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+import com.cloud.serializer.Param;
+
+public class HypervisorGuestOsResponse extends BaseResponse {
+    @SerializedName(ApiConstants.OS_DISPLAY_NAME)
+    @Param(description = "standard display name for the Guest OS")
+    private String osStdName;
+
+    @SerializedName(ApiConstants.OS_NAME_FOR_HYPERVISOR)
+    @Param(description = "hypervisor specific name for the Guest OS")
+    private String osNameForHypervisor;
+
+    public String getOsStdName() {
+        return osStdName;
+    }
+
+    public void setOsStdName(String osStdName) {
+        this.osStdName = osStdName;
+    }
+
+    public String getOsNameForHypervisor() {
+        return osNameForHypervisor;
+    }
+
+    public void setOsNameForHypervisor(String osNameForHypervisor) {
+        this.osNameForHypervisor = osNameForHypervisor;
+    }
+}
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 4188543..e2bf6ef 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
@@ -96,15 +96,19 @@
     private Boolean isSystem;
 
     @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
-    @Param(description = "virtual machine id the ip address is assigned to (not null only for static nat Ip)")
+    @Param(description = "virtual machine id the ip address is assigned to")
     private String virtualMachineId;
 
+    @SerializedName(ApiConstants.VIRTUAL_MACHINE_TYPE)
+    @Param(description = "virtual machine type the ip address is assigned to", since = "4.19.0")
+    private String virtualMachineType;
+
     @SerializedName("vmipaddress")
     @Param(description = "virtual machine (dnat) ip address (not null only for static nat Ip)")
     private String virtualMachineIp;
 
     @SerializedName("virtualmachinename")
-    @Param(description = "virtual machine name the ip address is assigned to (not null only for static nat Ip)")
+    @Param(description = "virtual machine name the ip address is assigned to")
     private String virtualMachineName;
 
     @SerializedName("virtualmachinedisplayname")
@@ -159,10 +163,9 @@
     @Param(description="the name of the Network where ip belongs to")
     private String networkName;
 
-    /*
-        @SerializedName(ApiConstants.JOB_ID) @Param(description="shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the volume")
-        private IdentityProxy jobId = new IdentityProxy("async_job");
-    */
+    @SerializedName(ApiConstants.HAS_RULES)
+    @Param(description="whether the ip address has Firewall/PortForwarding/LoadBalancing rules defined")
+    private boolean hasRules;
 
     public void setIpAddress(String ipAddress) {
         this.ipAddress = ipAddress;
@@ -232,6 +235,10 @@
         this.virtualMachineId = virtualMachineId;
     }
 
+    public void setVirtualMachineType(String virtualMachineType) {
+        this.virtualMachineType = virtualMachineType;
+    }
+
     public void setVirtualMachineIp(String virtualMachineIp) {
         this.virtualMachineIp = virtualMachineIp;
     }
@@ -305,4 +312,8 @@
     public void setNetworkName(String networkName) {
         this.networkName = networkName;
     }
+
+    public void setHasRules(final boolean hasRules) {
+        this.hasRules = hasRules;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IpQuarantineResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IpQuarantineResponse.java
new file mode 100644
index 0000000..c93f259
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/IpQuarantineResponse.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.api.response;
+
+import com.cloud.network.PublicIpQuarantine;
+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;
+
+@EntityReference(value = {PublicIpQuarantine.class})
+public class IpQuarantineResponse extends BaseResponse {
+
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "ID of the quarantine process.")
+    private String id;
+
+    @SerializedName(ApiConstants.IP_ADDRESS)
+    @Param(description = "The public IP address in quarantine.")
+    private String publicIpAddress;
+
+    @SerializedName(ApiConstants.PREVIOUS_OWNER_ID)
+    @Param(description = "Account ID of the previous public IP address owner.")
+    private String previousOwnerId;
+
+    @SerializedName(ApiConstants.PREVIOUS_OWNER_NAME)
+    @Param(description = "Account name of the previous public IP address owner.")
+    private String previousOwnerName;
+
+    @SerializedName(ApiConstants.CREATED)
+    @Param(description = "When the quarantine was created.")
+    private Date created;
+
+    @SerializedName(ApiConstants.REMOVED)
+    @Param(description = "When the quarantine was removed.")
+    private Date removed;
+
+    @SerializedName(ApiConstants.END_DATE)
+    @Param(description = "End date for the quarantine.")
+    private Date endDate;
+
+    @SerializedName(ApiConstants.REMOVAL_REASON)
+    @Param(description = "The reason for removing the IP from quarantine prematurely.")
+    private String removalReason;
+
+    @SerializedName(ApiConstants.REMOVER_ACCOUNT_ID)
+    @Param(description = "ID of the account that removed the IP from quarantine.")
+    private String removerAccountId;
+
+    public IpQuarantineResponse() {
+        super("quarantinedips");
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getPublicIpAddress() {
+        return publicIpAddress;
+    }
+
+    public void setPublicIpAddress(String publicIpAddress) {
+        this.publicIpAddress = publicIpAddress;
+    }
+
+    public String getPreviousOwnerId() {
+        return previousOwnerId;
+    }
+
+    public void setPreviousOwnerId(String previousOwnerId) {
+        this.previousOwnerId = previousOwnerId;
+    }
+
+    public String getPreviousOwnerName() {
+        return previousOwnerName;
+    }
+
+    public void setPreviousOwnerName(String previousOwnerName) {
+        this.previousOwnerName = previousOwnerName;
+    }
+
+    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 Date getEndDate() {
+        return endDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    public String getRemovalReason() {
+        return removalReason;
+    }
+
+    public void setRemovalReason(String removalReason) {
+        this.removalReason = removalReason;
+    }
+
+    public String getRemoverAccountId() {
+        return removerAccountId;
+    }
+
+    public void setRemoverAccountId(String removerAccountId) {
+        this.removerAccountId = removerAccountId;
+    }
+}
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 243579e..d34f949 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
@@ -163,6 +163,10 @@
     @Param(description = "the domain name of the network owner")
     private String domain;
 
+    @SerializedName(ApiConstants.DOMAIN_PATH)
+    @Param(description = "path of the Domain the network belongs to", since = "4.19.0.0")
+    private String domainPath;
+
     @SerializedName("isdefault")
     @Param(description = "true if network is default, false otherwise")
     private Boolean isDefault;
@@ -420,6 +424,10 @@
         this.domain = domain;
     }
 
+    public void setDomainPath(String domainPath) {
+        this.domainPath = domainPath;
+    }
+
     public void setNetworkOfferingAvailability(String networkOfferingAvailability) {
         this.networkOfferingAvailability = networkOfferingAvailability;
     }
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 c267323..65e126d 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
@@ -138,6 +138,14 @@
     @Param(description = "MTU configured on the NIC", since="4.18.0")
     private Integer mtu;
 
+    @SerializedName(ApiConstants.PUBLIC_IP_ID)
+    @Param(description = "public IP address id associated with this nic via Static nat rule")
+    private String publicIpId;
+
+    @SerializedName(ApiConstants.PUBLIC_IP)
+    @Param(description = "public IP address associated with this nic via Static nat rule")
+    private String publicIp;
+
     public void setVmId(String vmId) {
         this.vmId = vmId;
     }
@@ -400,4 +408,12 @@
     public String getVpcId() {
         return vpcId;
     }
+
+    public void setPublicIpId(String publicIpId) {
+        this.publicIpId = publicIpId;
+    }
+
+    public void setPublicIp(String publicIp) {
+        this.publicIp = publicIp;
+    }
 }
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
new file mode 100644
index 0000000..e403079
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ObjectStoreResponse.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
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 org.apache.cloudstack.storage.object.ObjectStore;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.BaseResponseWithAnnotations;
+import org.apache.cloudstack.api.EntityReference;
+
+@EntityReference(value = ObjectStore.class)
+public class ObjectStoreResponse extends BaseResponseWithAnnotations {
+    @SerializedName("id")
+    @Param(description = "the ID of the object store")
+    private String id;
+
+    @SerializedName("name")
+    @Param(description = "the name of the object store")
+    private String name;
+
+    @SerializedName("url")
+    @Param(description = "the url of the object store")
+    private String url;
+
+    @SerializedName("providername")
+    @Param(description = "the provider name of the object store")
+    private String providerName;
+
+    @SerializedName("storagetotal")
+    @Param(description = "the total size of the object store")
+    private Long storageTotal;
+
+    @SerializedName("storageused")
+    @Param(description = "the object store currently used size")
+    private Long storageUsed;
+
+    public ObjectStoreResponse() {
+    }
+
+    @Override
+    public String getObjectId() {
+        return this.getId();
+    }
+
+    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 getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public Long getStorageTotal() {
+        return storageTotal;
+    }
+
+    public void setStorageTotal(Long storageTotal) {
+        this.storageTotal = storageTotal;
+    }
+
+    public Long getStorageUsed() {
+        return storageUsed;
+    }
+
+    public void setStorageUsed(Long storageUsed) {
+        this.storageUsed = storageUsed;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SecondaryStorageHeuristicsResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SecondaryStorageHeuristicsResponse.java
new file mode 100644
index 0000000..25a6b2e
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/SecondaryStorageHeuristicsResponse.java
@@ -0,0 +1,141 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements.  See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership.  The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License.  You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.secstorage.heuristics.Heuristic;
+
+import java.util.Date;
+
+import static org.apache.cloudstack.api.ApiConstants.HEURISTIC_TYPE_VALID_OPTIONS;
+
+@EntityReference(value = {Heuristic.class})
+public class SecondaryStorageHeuristicsResponse extends BaseResponse {
+
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "ID of the heuristic.")
+    private String id;
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "Name of the heuristic.")
+    private String name;
+
+    @SerializedName(ApiConstants.DESCRIPTION)
+    @Param(description = "Description of the heuristic.")
+    private String description;
+
+    @SerializedName(ApiConstants.ZONE_ID)
+    @Param(description = "The zone which the heuristic is valid upon.")
+    private String zoneId;
+
+    @SerializedName(ApiConstants.TYPE)
+    @Param(description = "The resource type directed to a specific secondary storage by the selector. " + HEURISTIC_TYPE_VALID_OPTIONS)
+    private String type;
+
+    @SerializedName(ApiConstants.HEURISTIC_RULE)
+    @Param(description = "The heuristic rule, in JavaScript language, used to select a secondary storage to be directed.")
+    private String heuristicRule;
+
+    @SerializedName(ApiConstants.CREATED)
+    @Param(description = "When the heuristic was created.")
+    private Date created;
+
+    @SerializedName(ApiConstants.REMOVED)
+    @Param(description = "When the heuristic was removed.")
+    private Date removed;
+
+
+    public SecondaryStorageHeuristicsResponse(String id, String name, String description, String zoneId, String type, String heuristicRule, Date created, Date removed) {
+        super("heuristics");
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.zoneId = zoneId;
+        this.type = type;
+        this.heuristicRule = heuristicRule;
+        this.created = created;
+        this.removed = removed;
+    }
+
+    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 getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(String zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getHeuristicRule() {
+        return heuristicRule;
+    }
+
+    public void setHeuristicRule(String heuristicRule) {
+        this.heuristicRule = heuristicRule;
+    }
+
+    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;
+    }
+}
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 53767ad..c7740c1 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
@@ -37,6 +37,10 @@
     @Param(description = "the name of the service offering")
     private String name;
 
+    @SerializedName("state")
+    @Param(description = "state of the service offering")
+    private String state;
+
     @SerializedName("displaytext")
     @Param(description = "an alternate display text of the service offering.")
     private String displayText;
@@ -249,6 +253,14 @@
         this.name = name;
     }
 
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
     public Boolean getIsSystem() {
         return isSystem;
     }
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 d1e535e..bfa1cca 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
@@ -58,8 +58,13 @@
     @Param(description = "is this policy for display to the regular user", since = "4.4", authorized = {RoleType.Admin})
     private Boolean forDisplay;
 
+    @SerializedName(ApiConstants.ZONE)
+    @Param(description = "The list of zones in which snapshot backup is scheduled", responseObject = ZoneResponse.class, since = "4.19.0")
+    protected Set<ZoneResponse> zones;
+
     public SnapshotPolicyResponse() {
         tags = new LinkedHashSet<ResourceTagResponse>();
+        zones = new LinkedHashSet<>();
     }
 
     public String getId() {
@@ -121,4 +126,8 @@
     public void setTags(Set<ResourceTagResponse> tags) {
         this.tags = tags;
     }
+
+    public void setZones(Set<ZoneResponse> zones) {
+        this.zones = zones;
+    }
 }
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 5490e8a..e160f64 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
@@ -18,6 +18,7 @@
 
 import java.util.Date;
 import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.cloudstack.api.ApiConstants;
@@ -29,7 +30,7 @@
 import com.google.gson.annotations.SerializedName;
 
 @EntityReference(value = Snapshot.class)
-public class SnapshotResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse {
+public class SnapshotResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "ID of the snapshot")
     private String id;
@@ -90,6 +91,10 @@
     @Param(description = "the state of the snapshot. BackedUp means that snapshot is ready to be used; Creating - the snapshot is being allocated on the primary storage; BackingUp - the snapshot is being backed up on secondary storage")
     private Snapshot.State state;
 
+    @SerializedName(ApiConstants.STATUS)
+    @Param(description = "the status of the template")
+    private String status;
+
     @SerializedName(ApiConstants.PHYSICAL_SIZE)
     @Param(description = "physical size of backedup snapshot on image store")
     private long physicalSize;
@@ -98,6 +103,10 @@
     @Param(description = "id of the availability zone")
     private String zoneId;
 
+    @SerializedName(ApiConstants.ZONE_NAME)
+    @Param(description = "name of the availability zone")
+    private String zoneName;
+
     @SerializedName(ApiConstants.REVERTABLE)
     @Param(description = "indicates whether the underlying storage supports reverting the volume to this snapshot")
     private boolean revertable;
@@ -114,6 +123,26 @@
     @Param(description = "virtual size of backedup snapshot on image store")
     private long virtualSize;
 
+    @SerializedName(ApiConstants.DATASTORE_ID)
+    @Param(description = "ID of the datastore for the snapshot entry", since = "4.19.0")
+    private String datastoreId;
+
+    @SerializedName(ApiConstants.DATASTORE_NAME)
+    @Param(description = "name of the datastore for the snapshot entry", since = "4.19.0")
+    private String datastoreName;
+
+    @SerializedName(ApiConstants.DATASTORE_STATE)
+    @Param(description = "state of the snapshot on the datastore", since = "4.19.0")
+    private String datastoreState;
+
+    @SerializedName(ApiConstants.DATASTORE_TYPE)
+    @Param(description = "type of the datastore for the snapshot entry", since = "4.19.0")
+    private String datastoreType;
+
+    @SerializedName(ApiConstants.DOWNLOAD_DETAILS)
+    @Param(description = "download progress of a snapshot", since = "4.19.0")
+    private Map<String, String> downloadDetails;
+
     public SnapshotResponse() {
         tags = new LinkedHashSet<ResourceTagResponse>();
     }
@@ -190,7 +219,11 @@
         this.state = state;
     }
 
-    public void setPhysicaSize(long physicalSize) {
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void setPhysicalSize(long physicalSize) {
         this.physicalSize = physicalSize;
     }
 
@@ -208,6 +241,10 @@
         this.zoneId = zoneId;
     }
 
+    public void setZoneName(String zoneName) {
+        this.zoneName = zoneName;
+    }
+
     public void setTags(Set<ResourceTagResponse> tags) {
         this.tags = tags;
     }
@@ -231,4 +268,24 @@
     public void setVirtualSize(long virtualSize) {
         this.virtualSize = virtualSize;
     }
+
+    public void setDatastoreId(String datastoreId) {
+        this.datastoreId = datastoreId;
+    }
+
+    public void setDatastoreName(String datastoreName) {
+        this.datastoreName = datastoreName;
+    }
+
+    public void setDatastoreState(String datastoreState) {
+        this.datastoreState = datastoreState;
+    }
+
+    public void setDatastoreType(String datastoreType) {
+        this.datastoreType = datastoreType;
+    }
+
+    public void setDownloadDetails(Map<String, String> downloadDetails) {
+        this.downloadDetails = downloadDetails;
+    }
 }
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 89256c2..183290e 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
@@ -101,6 +101,10 @@
     @Param(description = "the tags for the storage pool")
     private String tags;
 
+    @SerializedName(ApiConstants.IS_TAG_A_RULE)
+    @Param(description = ApiConstants.PARAMETER_DESCRIPTION_IS_TAG_A_RULE)
+    private Boolean isTagARule;
+
     @SerializedName(ApiConstants.STATE)
     @Param(description = "the state of the storage pool")
     private StoragePoolStatus state;
@@ -304,6 +308,14 @@
         this.tags = tags;
     }
 
+    public Boolean getIsTagARule() {
+        return isTagARule;
+    }
+
+    public void setIsTagARule(Boolean tagARule) {
+        isTagARule = tagARule;
+    }
+
     public StoragePoolStatus getState() {
         return state;
     }
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 69b9b4c..31a8b73 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
@@ -178,6 +178,14 @@
     @Param(description = "true if vm contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory.")
     private Boolean isDynamicallyScalable;
 
+    @SerializedName(ApiConstants.SERVICE_OFFERING_ID)
+    @Param(description = "the ID of the service offering of the system virtual machine.")
+    private String serviceOfferingId;
+
+    @SerializedName("serviceofferingname")
+    @Param(description = "the name of the service offering of the system virtual machine.")
+    private String serviceOfferingName;
+
     @Override
     public String getObjectId() {
         return this.getId();
@@ -466,4 +474,20 @@
     public void setDynamicallyScalable(Boolean dynamicallyScalable) {
         isDynamicallyScalable = dynamicallyScalable;
     }
+
+    public String getServiceOfferingId() {
+        return serviceOfferingId;
+    }
+
+    public void setServiceOfferingId(String serviceOfferingId) {
+        this.serviceOfferingId = serviceOfferingId;
+    }
+
+    public String getServiceOfferingName() {
+        return serviceOfferingName;
+    }
+
+    public void setServiceOfferingName(String serviceOfferingName) {
+        this.serviceOfferingName = serviceOfferingName;
+    }
 }
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 bd09d09..3abd449 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
@@ -123,7 +123,7 @@
     @Param(description = "the physical size of the template")
     private Long physicalSize;
 
-    @SerializedName(ApiConstants.TEMPLATETYPE)
+    @SerializedName(ApiConstants.TEMPLATE_TYPE)
     @Param(description = "the type of the template")
     private String templateType;
 
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 e866b19..7a26b17 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
@@ -39,6 +39,10 @@
     @Param(description = "the ID of the cluster to which virtual machine belongs")
     private String clusterId;
 
+    @SerializedName(ApiConstants.CLUSTER_NAME)
+    @Param(description = "the name of the cluster to which virtual machine belongs")
+    private String clusterName;
+
     @SerializedName(ApiConstants.HOST_ID)
     @Param(description = "the ID of the host to which virtual machine belongs")
     private String hostId;
@@ -104,6 +108,14 @@
         this.clusterId = clusterId;
     }
 
+    public String getClusterName() {
+        return clusterName;
+    }
+
+    public void setClusterName(String clusterName) {
+        this.clusterName = clusterName;
+    }
+
     public String getHostId() {
         return hostId;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java
index bbe27f8..e69094c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java
@@ -24,7 +24,7 @@
 import org.apache.cloudstack.api.EntityReference;
 
 @EntityReference(value = UserData.class)
-public class UserDataResponse extends BaseResponseWithAnnotations {
+public class UserDataResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse {
 
     @SerializedName(ApiConstants.ID)
     @Param(description = "ID of the ssh keypair")
@@ -40,6 +40,14 @@
     @SerializedName(ApiConstants.ACCOUNT) @Param(description="the owner of the userdata")
     private String accountName;
 
+    @SerializedName(ApiConstants.PROJECT_ID)
+    @Param(description = "the project id of the userdata", since = "4.19.1")
+    private String projectId;
+
+    @SerializedName(ApiConstants.PROJECT)
+    @Param(description = "the project name of the userdata", since = "4.19.1")
+    private String projectName;
+
     @SerializedName(ApiConstants.DOMAIN_ID) @Param(description="the domain id of the userdata owner")
     private String domainId;
 
@@ -118,6 +126,16 @@
         this.accountName = accountName;
     }
 
+    @Override
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
+    }
+
+    @Override
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
     public String getDomainName() {
         return domain;
     }
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 4c67d24..763265e 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
@@ -16,9 +16,12 @@
 // under the License.
 package org.apache.cloudstack.api.response;
 
+import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
@@ -130,6 +133,10 @@
     @Param(description = "the name of the template for the virtual machine")
     private String templateName;
 
+    @SerializedName(ApiConstants.TEMPLATE_TYPE)
+    @Param(description = "the type of the template for the virtual machine", since = "4.19.0")
+    private String templateType;
+
     @SerializedName("templatedisplaytext")
     @Param(description = " an alternate display text of the template for the virtual machine")
     private String templateDisplayText;
@@ -360,6 +367,14 @@
     @SerializedName(ApiConstants.USER_DATA_DETAILS) @Param(description="list of variables and values for the variables declared in userdata", since = "4.18.0")
     private String userDataDetails;
 
+    @SerializedName(ApiConstants.VNF_NICS)
+    @Param(description = "NICs of the VNF appliance", since = "4.19.0")
+    private List<VnfNicResponse> vnfNics;
+
+    @SerializedName(ApiConstants.VNF_DETAILS)
+    @Param(description = "VNF details", since = "4.19.0")
+    private Map<String, String> vnfDetails;
+
     public UserVmResponse() {
         securityGroupList = new LinkedHashSet<>();
         nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId())));
@@ -1045,4 +1060,49 @@
         this.userDataDetails = userDataDetails;
     }
 
+    public Long getBytesReceived() {
+        return bytesReceived;
+    }
+
+    public Long getBytesSent() {
+        return bytesSent;
+    }
+
+    public String getTemplateType() {
+        return templateType;
+    }
+
+    public void setTemplateType(String templateType) {
+        this.templateType = templateType;
+    }
+
+    public List<VnfNicResponse> getVnfNics() {
+        return vnfNics;
+    }
+
+    public void setVnfNics(List<VnfNicResponse> vnfNics) {
+        this.vnfNics = vnfNics;
+    }
+
+    public Map<String, String> getVnfDetails() {
+        return vnfDetails;
+    }
+
+    public void setVnfDetails(Map<String, String> vnfDetails) {
+        this.vnfDetails = vnfDetails;
+    }
+
+    public void addVnfNic(VnfNicResponse vnfNic) {
+        if (this.vnfNics == null) {
+            this.vnfNics = new ArrayList<>();
+        }
+        this.vnfNics.add(vnfNic);
+    }
+
+    public void addVnfDetail(String key, String value) {
+        if (this.vnfDetails == null) {
+            this.vnfDetails = new LinkedHashMap<>();
+        }
+        this.vnfDetails.put(key,value);
+    }
 }
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
new file mode 100644
index 0000000..813a48e
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.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.vm.schedule.VMSchedule;
+
+import java.util.Date;
+
+@EntityReference(value = VMSchedule.class)
+public class VMScheduleResponse extends BaseResponse {
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "the ID of VM schedule")
+    private String id;
+
+    @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
+    @Param(description = "ID of virtual machine")
+    private String vmId;
+
+    @SerializedName(ApiConstants.DESCRIPTION)
+    @Param(description = "Description of VM schedule")
+    private String description;
+
+    @SerializedName(ApiConstants.SCHEDULE)
+    @Param(description = "Cron formatted VM schedule")
+    private String schedule;
+
+    @SerializedName(ApiConstants.TIMEZONE)
+    @Param(description = "Timezone of the schedule")
+    private String timeZone;
+
+    @SerializedName(ApiConstants.ACTION)
+    @Param(description = "Action")
+    private VMSchedule.Action action;
+
+    @SerializedName(ApiConstants.ENABLED)
+    @Param(description = "VM 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.CREATED)
+    @Param(description = "Date when the schedule was created")
+    private Date created;
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setVmId(String vmId) {
+        this.vmId = vmId;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void setSchedule(String schedule) {
+        this.schedule = schedule;
+    }
+
+    public void setTimeZone(String timeZone) {
+        this.timeZone = timeZone;
+    }
+
+    public void setAction(VMSchedule.Action action) {
+        this.action = action;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    public void setCreated(Date created) {this.created = created;}
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java
index 37670cf..9b553ed 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java
@@ -25,7 +25,6 @@
 import org.apache.cloudstack.api.BaseResponseWithTagInformation;
 import org.apache.cloudstack.api.EntityReference;
 
-import com.cloud.hypervisor.Hypervisor;
 import com.cloud.serializer.Param;
 import com.cloud.vm.snapshot.VMSnapshot;
 import com.google.gson.annotations.SerializedName;
@@ -111,7 +110,7 @@
 
     @SerializedName(ApiConstants.HYPERVISOR)
     @Param(description = "the type of hypervisor on which snapshot is stored")
-    private Hypervisor.HypervisorType hypervisor;
+    private String hypervisor;
 
     public VMSnapshotResponse() {
         tags = new LinkedHashSet<ResourceTagResponse>();
@@ -266,11 +265,11 @@
         this.tags = tags;
     }
 
-    public Hypervisor.HypervisorType getHypervisor() {
+    public String getHypervisor() {
         return hypervisor;
     }
 
-    public void setHypervisor(Hypervisor.HypervisorType hypervisor) {
+    public void setHypervisor(String hypervisor) {
         this.hypervisor = hypervisor;
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VmwareDatacenterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VmwareDatacenterResponse.java
new file mode 100644
index 0000000..3cf06f3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VmwareDatacenterResponse.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.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 com.cloud.dc.VmwareDatacenter;
+import com.cloud.serializer.Param;
+
+@EntityReference(value = VmwareDatacenter.class)
+public class VmwareDatacenterResponse extends BaseResponse {
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "The VMware Datacenter ID")
+    private String id;
+
+    @SerializedName(ApiConstants.ZONE_ID)
+    @Param(description = "the Zone ID associated with this VMware Datacenter")
+    private Long zoneId;
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "The VMware Datacenter name")
+    private String name;
+
+    @SerializedName(ApiConstants.VCENTER)
+    @Param(description = "The VMware vCenter name/ip")
+    private String vCenter;
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVcenter() {
+        return vCenter;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(Long zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setVcenter(String vCenter) {
+        this.vCenter = vCenter;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java
new file mode 100644
index 0000000..af238c5
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.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.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+
+@SuppressWarnings("unused")
+public class VnfNicResponse {
+    @SerializedName(ApiConstants.DEVICE_ID)
+    @Param(description = "Device id of the NIC")
+    private long deviceId;
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "Name of the NIC")
+    private String name;
+
+    @SerializedName(ApiConstants.REQUIRED)
+    @Param(description = "True if the NIC is required. False if optional")
+    private Boolean required;
+
+    @SerializedName(ApiConstants.MANAGEMENT)
+    @Param(description = "True if the NIC is a management interface. False otherwise")
+    private Boolean management;
+
+    @SerializedName(ApiConstants.DESCRIPTION)
+    @Param(description = "Description of the NIC")
+    private String description;
+
+    @SerializedName(ApiConstants.NETWORK_ID)
+    @Param(description = "Network id of the NIC")
+    private String networkId;
+
+    @SerializedName(ApiConstants.NETWORK_NAME)
+    @Param(description = "Network name of the NIC")
+    private String networkName;
+
+    public void setDeviceId(long deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setRequired(Boolean required) {
+        this.required = required;
+    }
+
+    public void setManagement(Boolean management) {
+        this.management = management;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void setNetworkId(String networkId) {
+        this.networkId = networkId;
+    }
+
+    public void setNetworkName(String networkName) {
+        this.networkName = networkName;
+    }
+
+    public VnfNicResponse() {
+    }
+
+    public VnfNicResponse(long deviceId, String name, Boolean required, Boolean management, String description) {
+        this.deviceId = deviceId;
+        this.name = name;
+        this.required = required;
+        this.management = management;
+        this.description = description;
+    }
+
+    public long getDeviceId() {
+        return deviceId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Boolean isRequired() {
+        return required;
+    }
+
+    public Boolean isManagement() {
+        return management;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getNetworkId() {
+        return networkId;
+    }
+
+    public String getNetworkName() {
+        return networkName;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VnfTemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VnfTemplateResponse.java
new file mode 100644
index 0000000..5fd17ef
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VnfTemplateResponse.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.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("unused")
+public class VnfTemplateResponse extends TemplateResponse {
+
+    @SerializedName(ApiConstants.VNF_NICS)
+    @Param(description = "NICs of the VNF template")
+    private List<VnfNicResponse> vnfNics;
+
+    @SerializedName(ApiConstants.VNF_DETAILS)
+    @Param(description = "VNF details")
+    private Map<String, String> vnfDetails;
+
+    public void addVnfNic(VnfNicResponse vnfNic) {
+        if (this.vnfNics == null) {
+            this.vnfNics = new ArrayList<>();
+        }
+        this.vnfNics.add(vnfNic);
+    }
+
+    public void addVnfDetail(String key, String value) {
+        if (this.vnfDetails == null) {
+            this.vnfDetails = new LinkedHashMap<>();
+        }
+        this.vnfDetails.put(key,value);
+    }
+
+    public List<VnfNicResponse> getVnfNics() {
+        return vnfNics;
+    }
+
+    public Map<String, String> getVnfDetails() {
+        return vnfDetails;
+    }
+}
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 00a1eab..0d502a6 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
@@ -18,6 +18,7 @@
 
 import java.util.Date;
 import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.cloudstack.acl.RoleType;
@@ -288,6 +289,14 @@
     @Param(description = "volume uuid that is given by virtualisation provider (only for VMware)")
     private String externalUuid;
 
+    @SerializedName(ApiConstants.VOLUME_CHECK_RESULT)
+    @Param(description = "details for the volume check result, they may vary for different hypervisors, since = 4.19.1")
+    private Map<String, String> volumeCheckResult;
+
+    @SerializedName(ApiConstants.VOLUME_REPAIR_RESULT)
+    @Param(description = "details for the volume repair result, they may vary for different hypervisors, since = 4.19.1")
+    private Map<String, String> volumeRepairResult;
+
     public String getPath() {
         return path;
     }
@@ -817,4 +826,20 @@
     public void setExternalUuid(String externalUuid) {
         this.externalUuid = externalUuid;
     }
+
+    public Map<String, String> getVolumeCheckResult() {
+        return volumeCheckResult;
+    }
+
+    public void setVolumeCheckResult(Map<String, String> volumeCheckResult) {
+        this.volumeCheckResult = volumeCheckResult;
+    }
+
+    public Map<String, String> getVolumeRepairResult() {
+        return volumeRepairResult;
+    }
+
+    public void setVolumeRepairResult(Map<String, String> volumeRepairResult) {
+        this.volumeRepairResult = volumeRepairResult;
+    }
 }
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 b8824fd..4e8e665 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,7 @@
 
     @SerializedName("securitygroupsenabled")
     @Param(description = "true if security groups support is enabled, false otherwise")
-    private boolean securityGroupsEnabled;
+    private Boolean securityGroupsEnabled;
 
     @SerializedName("allocationstate")
     @Param(description = "the allocation state of the cluster")
@@ -115,7 +115,7 @@
 
     @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")
@@ -131,7 +131,7 @@
 
     @SerializedName(ApiConstants.ALLOW_USER_SPECIFY_VR_MTU)
     @Param(description = "Allow end users to specify VR MTU", since = "4.18.0")
-    private boolean allowUserSpecifyVRMtu;
+    private Boolean allowUserSpecifyVRMtu;
 
     @SerializedName(ApiConstants.ROUTER_PRIVATE_INTERFACE_MAX_MTU)
     @Param(description = "The maximum value the MTU can have on the VR's private interfaces", since = "4.18.0")
@@ -197,7 +197,7 @@
         this.networkType = networkType;
     }
 
-    public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
+    public void setSecurityGroupsEnabled(Boolean securityGroupsEnabled) {
         this.securityGroupsEnabled = securityGroupsEnabled;
     }
 
@@ -221,7 +221,7 @@
         this.domainName = domainName;
     }
 
-    public void setLocalStorageEnabled(boolean localStorageEnabled) {
+    public void setLocalStorageEnabled(Boolean localStorageEnabled) {
         this.localStorageEnabled = localStorageEnabled;
     }
 
@@ -241,6 +241,10 @@
         this.ip6Dns2 = ip6Dns2;
     }
 
+    public void setTags(Set<ResourceTagResponse> tags) {
+        this.tags = tags;
+    }
+
     public void addTag(ResourceTagResponse tag) {
         this.tags.add(tag);
     }
@@ -345,7 +349,7 @@
         return resourceIconResponse;
     }
 
-    public void setAllowUserSpecifyVRMtu(boolean allowUserSpecifyVRMtu) {
+    public void setAllowUserSpecifyVRMtu(Boolean allowUserSpecifyVRMtu) {
         this.allowUserSpecifyVRMtu = allowUserSpecifyVRMtu;
     }
 
diff --git a/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java b/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java
new file mode 100644
index 0000000..ee3b98b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.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.auth;
+
+import com.cloud.utils.component.Adapter;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public interface UserOAuth2Authenticator extends Adapter {
+    /**
+     * Returns the unique name of the provider
+     * @return returns provider name
+     */
+    String getName();
+
+    /**
+     * Returns description about the OAuth2 provider plugin
+     * @return returns description
+     */
+    String getDescription();
+
+    /**
+     * Verifies if the logged in user is
+     * @return returns true if its valid user
+     */
+    boolean verifyUser(String email, String secretCode);
+
+    /**
+     * Verifies the code provided by provider and fetches email
+     * @return returns email
+     */
+    String verifyCodeAndFetchEmail(String secretCode);
+
+
+    /**
+     * Fetches email using the accessToken
+     * @return returns email
+     */
+    String getUserEmailAddress() throws CloudRuntimeException;
+}
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 75c7ab4..f369367 100644
--- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java
+++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java
@@ -17,6 +17,8 @@
 
 package org.apache.cloudstack.backup;
 
+import java.util.Date;
+
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.api.Identity;
 import org.apache.cloudstack.api.InternalIdentity;
@@ -58,10 +60,10 @@
 
     class RestorePoint {
         private String id;
-        private String created;
+        private Date created;
         private String type;
 
-        public RestorePoint(String id, String created, String type) {
+        public RestorePoint(String id, Date created, String type) {
             this.id = id;
             this.created = created;
             this.type = type;
@@ -75,11 +77,11 @@
             this.id = id;
         }
 
-        public String getCreated() {
-            return created;
+        public Date getCreated() {
+            return this.created;
         }
 
-        public void setCreated(String created) {
+        public void setCreated(Date created) {
             this.created = created;
         }
 
@@ -134,7 +136,7 @@
     long getVmId();
     String getExternalId();
     String getType();
-    String getDate();
+    Date getDate();
     Backup.Status getStatus();
     Long getSize();
     Long getProtectedSize();
diff --git a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java
new file mode 100644
index 0000000..15f7fcd
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster;
+
+import com.cloud.host.Host;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.utils.Pair;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.component.Adapter;
+import com.cloud.vm.VirtualMachine;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
+
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetricType;
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetricUseRatio;
+
+public interface ClusterDrsAlgorithm extends Adapter {
+
+    /**
+     * Determines whether a DRS operation is needed for a given cluster and host-VM
+     * mapping.
+     *
+     * @param clusterId
+     *         the ID of the cluster to check
+     * @param cpuList
+     *         a list of Ternary of used, reserved & total CPU for each host in the cluster
+     * @param memoryList
+     *         a list of Ternary of used, reserved & total memory values for each host in the cluster
+     *
+     * @return true if a DRS operation is needed, false otherwise
+     *
+     * @throws ConfigurationException
+     *         if there is an error in the configuration
+     */
+    boolean needsDrs(long clusterId, List<Ternary<Long, Long, Long>> cpuList,
+            List<Ternary<Long, Long, Long>> memoryList) throws ConfigurationException;
+
+
+    /**
+     * Determines the metrics for a given virtual machine and destination host in a DRS cluster.
+     *
+     * @param clusterId
+     *         the ID of the cluster to check
+     * @param vm
+     *         the virtual machine to check
+     * @param serviceOffering
+     *         the service offering for the virtual machine
+     * @param destHost
+     *         the destination host for the virtual machine
+     * @param hostCpuMap
+     *         a map of host IDs to the Ternary of used, reserved and total CPU on each host
+     * @param hostMemoryMap
+     *         a map of host IDs to the Ternary of used, reserved and total memory on each host
+     * @param requiresStorageMotion
+     *         whether storage motion is required for the virtual machine
+     *
+     * @return a ternary containing improvement, cost, benefit
+     */
+    Ternary<Double, Double, Double> getMetrics(long clusterId, VirtualMachine vm, ServiceOffering serviceOffering,
+            Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
+            Boolean requiresStorageMotion) throws ConfigurationException;
+
+    /**
+     * Calculates the imbalance of the cluster after a virtual machine migration.
+     *
+     * @param serviceOffering
+     *         the service offering for the virtual machine
+     * @param vm
+     *         the virtual machine being migrated
+     * @param destHost
+     *         the destination host for the virtual machine
+     * @param hostCpuMap
+     *         a map of host IDs to the Ternary of used, reserved and total CPU on each host
+     * @param hostMemoryMap
+     *         a map of host IDs to the Ternary of used, reserved and total memory on each host
+     *
+     * @return a pair containing the CPU and memory imbalance of the cluster after the migration
+     */
+    default Double getImbalancePostMigration(ServiceOffering serviceOffering, VirtualMachine vm,
+            Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws ConfigurationException {
+        Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair = getHostMetricsMapAndType(destHost.getClusterId(), serviceOffering, hostCpuMap, hostMemoryMap);
+        long vmMetric = pair.first();
+        Map<Long, Ternary<Long, Long, Long>> hostMetricsMap = pair.second();
+
+        List<Double> list = new ArrayList<>();
+        for (Long hostId : hostMetricsMap.keySet()) {
+            list.add(getMetricValuePostMigration(destHost.getClusterId(), hostMetricsMap.get(hostId), vmMetric, hostId, destHost.getId(), vm.getHostId()));
+        }
+        return getImbalance(list);
+    }
+
+    private Pair<Long, Map<Long, Ternary<Long, Long, Long>>> getHostMetricsMapAndType(Long clusterId,
+            ServiceOffering serviceOffering, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws ConfigurationException {
+        String metric = getClusterDrsMetric(clusterId);
+        Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair;
+        switch (metric) {
+            case "cpu":
+                pair = new Pair<>((long) serviceOffering.getCpu() * serviceOffering.getSpeed(), hostCpuMap);
+                break;
+            case "memory":
+                pair = new Pair<>(serviceOffering.getRamSize() * 1024L * 1024L, hostMemoryMap);
+                break;
+            default:
+                throw new ConfigurationException(
+                        String.format("Invalid metric: %s for cluster: %d", metric, clusterId));
+        }
+        return pair;
+    }
+
+    private Double getMetricValuePostMigration(Long clusterId, Ternary<Long, Long, Long> metrics, long vmMetric,
+            long hostId, long destHostId, long vmHostId) {
+        long used = metrics.first();
+        long actualTotal = metrics.third() - metrics.second();
+        long free = actualTotal - metrics.first();
+
+        if (hostId == destHostId) {
+            used += vmMetric;
+            free -= vmMetric;
+        } else if (hostId == vmHostId) {
+            used -= vmMetric;
+            free += vmMetric;
+        }
+        return getMetricValue(clusterId, used, free, actualTotal, null);
+    }
+
+    private static Double getImbalance(List<Double> metricList) {
+        Double clusterMeanMetric = getClusterMeanMetric(metricList);
+        Double clusterStandardDeviation = getClusterStandardDeviation(metricList, clusterMeanMetric);
+        return clusterStandardDeviation / clusterMeanMetric;
+    }
+
+    static String getClusterDrsMetric(long clusterId) {
+        return ClusterDrsMetric.valueIn(clusterId);
+    }
+
+    static Double getMetricValue(long clusterId, long used, long free, long total, Float skipThreshold) {
+        boolean useRatio = getDrsMetricUseRatio(clusterId);
+        switch (getDrsMetricType(clusterId)) {
+            case "free":
+                if (skipThreshold != null && free < skipThreshold * total) return null;
+                if (useRatio) {
+                    return (double) free / total;
+                } else {
+                    return (double) free;
+                }
+            case "used":
+                if (skipThreshold != null && used > skipThreshold * total) return null;
+                if (useRatio) {
+                    return (double) used / total;
+                } else {
+                    return (double) used;
+                }
+        }
+        return null;
+    }
+
+    /**
+     * Mean is the average of a collection or set of metrics. In context of a DRS
+     * cluster, the cluster metrics defined as the average metrics value for some
+     * metric (such as CPU, memory etc.) for every resource such as host.
+     * Cluster Mean Metric, mavg = (∑mi) / N, where mi is a measurable metric for a
+     * resource ‘i’ in a cluster with total N number of resources.
+     */
+    static Double getClusterMeanMetric(List<Double> metricList) {
+        return new Mean().evaluate(metricList.stream().mapToDouble(i -> i).toArray());
+    }
+
+    /**
+     * Standard deviation is defined as the square root of the absolute squared sum
+     * of difference of a metric from its mean for every resource divided by the
+     * total number of resources. In context of the DRS, the cluster standard
+     * deviation is the standard deviation based on a metric of resources in a
+     * cluster such as for the allocation or utilisation CPU/memory metric of hosts
+     * in a cluster.
+     * Cluster Standard Deviation, σc = sqrt((∑∣mi−mavg∣^2) / N), where mavg is the
+     * mean metric value and mi is a measurable metric for some resource ‘i’ in the
+     * cluster with total N number of resources.
+     */
+    static Double getClusterStandardDeviation(List<Double> metricList, Double mean) {
+        if (mean != null) {
+            return new StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> i).toArray(), mean);
+        } else {
+            return new StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> i).toArray());
+        }
+    }
+
+    static boolean getDrsMetricUseRatio(long clusterId) {
+        return ClusterDrsMetricUseRatio.valueIn(clusterId);
+    }
+
+    static String getDrsMetricType(long clusterId) {
+        return ClusterDrsMetricType.valueIn(clusterId);
+    }
+
+    /**
+     * The cluster imbalance is defined as the percentage deviation from the mean
+     * for a configured metric of the cluster. The standard deviation is used as a
+     * mathematical tool to normalize the metric data for all the resource and the
+     * percentage deviation provides an easy tool to compare a cluster’s current
+     * state against the defined imbalance threshold. Because this is essentially a
+     * percentage, the value is a number between 0.0 and 1.0.
+     * Cluster Imbalance, Ic = σc / mavg , where σc is the standard deviation and
+     * mavg is the mean metric value for the cluster.
+     */
+    static Double getClusterImbalance(Long clusterId, List<Ternary<Long, Long, Long>> cpuList,
+            List<Ternary<Long, Long, Long>> memoryList, Float skipThreshold) throws ConfigurationException {
+        String metric = getClusterDrsMetric(clusterId);
+        List<Double> list;
+        switch (metric) {
+            case "cpu":
+                list = getMetricList(clusterId, cpuList, skipThreshold);
+                break;
+            case "memory":
+                list = getMetricList(clusterId, memoryList, skipThreshold);
+                break;
+            default:
+                throw new ConfigurationException(
+                        String.format("Invalid metric: %s for cluster: %d", metric, clusterId));
+        }
+        return getImbalance(list);
+    }
+
+    static List<Double> getMetricList(Long clusterId, List<Ternary<Long, Long, Long>> hostMetricsList,
+            Float skipThreshold) {
+        List<Double> list = new ArrayList<>();
+        for (Ternary<Long, Long, Long> ternary : hostMetricsList) {
+            long used = ternary.first();
+            long actualTotal = ternary.third() - ternary.second();
+            long free = actualTotal - ternary.first();
+            Double metricValue = getMetricValue(clusterId, used, free, actualTotal, skipThreshold);
+            if (metricValue != null) {
+                list.add(metricValue);
+            }
+        }
+        return list;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlan.java b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlan.java
new file mode 100644
index 0000000..bc1bea1
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlan.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.cluster;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface ClusterDrsPlan extends Identity, InternalIdentity {
+
+    long getClusterId();
+
+    Type getType();
+
+    Date getCreated();
+
+    Status getStatus();
+
+    String getUuid();
+
+    long getEventId();
+
+    enum Type {
+        AUTOMATED, MANUAL
+    }
+
+    enum Status {
+        UNDER_REVIEW, READY, IN_PROGRESS, COMPLETED
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigration.java b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigration.java
new file mode 100644
index 0000000..7554069
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigration.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.cluster;
+
+import org.apache.cloudstack.api.InternalIdentity;
+
+public interface ClusterDrsPlanMigration extends InternalIdentity {
+}
diff --git a/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.java b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.java
new file mode 100644
index 0000000..ba6a646
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsService.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.cluster;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.component.Manager;
+import com.cloud.utils.concurrency.Scheduler;
+import org.apache.cloudstack.api.command.admin.cluster.ExecuteClusterDrsPlanCmd;
+import org.apache.cloudstack.api.command.admin.cluster.GenerateClusterDrsPlanCmd;
+import org.apache.cloudstack.api.command.admin.cluster.ListClusterDrsPlanCmd;
+import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+
+public interface ClusterDrsService extends Manager, Configurable, Scheduler {
+
+    ConfigKey<Integer> ClusterDrsPlanExpireInterval = new ConfigKey<>(Integer.class, "drs.plan.expire.interval",
+            ConfigKey.CATEGORY_ADVANCED, "30", "The interval in days after which the DRS events will be cleaned up.",
+            false, ConfigKey.Scope.Global, null, "Expire interval for old DRS plans", null, null, null);
+
+    ConfigKey<Boolean> ClusterDrsEnabled = new ConfigKey<>(Boolean.class, "drs.automatic.enable",
+            ConfigKey.CATEGORY_ADVANCED, "false", "Enable/disable automatic DRS on a cluster.", true,
+            ConfigKey.Scope.Cluster, null, "Enable automatic DRS", null, null, null);
+
+    ConfigKey<Integer> ClusterDrsInterval = new ConfigKey<>(Integer.class, "drs.automatic.interval",
+            ConfigKey.CATEGORY_ADVANCED, "60",
+            "The interval in minutes after which a periodic background thread will schedule DRS for a cluster.", true,
+            ConfigKey.Scope.Cluster, null, "Interval for Automatic DRS ", null, null, null);
+
+    ConfigKey<Integer> ClusterDrsMaxMigrations = new ConfigKey<>(Integer.class, "drs.max.migrations",
+            ConfigKey.CATEGORY_ADVANCED, "50",
+            "Maximum number of live migrations in a DRS execution.",
+            true, ConfigKey.Scope.Cluster, null, "Maximum number of migrations for DRS", null, null, null);
+
+    ConfigKey<String> ClusterDrsAlgorithm = new ConfigKey<>(String.class, "drs.algorithm",
+            ConfigKey.CATEGORY_ADVANCED, "balanced", "The DRS algorithm to be executed on the cluster. Possible values are condensed, balanced.",
+            true, ConfigKey.Scope.Cluster, null, "DRS algorithm", null, null,
+            null, ConfigKey.Kind.Select, "condensed,balanced");
+
+    ConfigKey<Float> ClusterDrsImbalanceThreshold = new ConfigKey<>(Float.class, "drs.imbalance",
+            ConfigKey.CATEGORY_ADVANCED, "0.4",
+            "Value of imbalance allowed in the cluster. 1.0 means no imbalance is allowed and 0.0 means full imbalance is allowed",
+            true, ConfigKey.Scope.Cluster, null, "DRS imbalance", null, null, null);
+
+    ConfigKey<String> ClusterDrsMetric = new ConfigKey<>(String.class, "drs.metric", ConfigKey.CATEGORY_ADVANCED,
+            "memory",
+            "The allocated resource metric used to measure imbalance in a cluster. Possible values are memory, cpu.",
+            true, ConfigKey.Scope.Cluster, null, "DRS metric", null, null, null, ConfigKey.Kind.Select,
+            "memory,cpu");
+
+    ConfigKey<String> ClusterDrsMetricType = new ConfigKey<>(String.class, "drs.metric.type", ConfigKey.CATEGORY_ADVANCED,
+            "used",
+            "The metric type used to measure imbalance in a cluster. This can completely change the imbalance value. Possible values are free, used.",
+            true, ConfigKey.Scope.Cluster, null, "DRS metric type", null, null, null, ConfigKey.Kind.Select,
+            "free,used");
+
+    ConfigKey<Boolean> ClusterDrsMetricUseRatio = new ConfigKey<>(Boolean.class, "drs.metric.use.ratio", ConfigKey.CATEGORY_ADVANCED,
+            "true",
+            "Whether to use ratio of selected metric & total. Useful when the cluster has hosts with different capacities",
+            true, ConfigKey.Scope.Cluster, null, "DRS metric use ratio", null, null, null, ConfigKey.Kind.Select,
+            "true,false");
+
+    ConfigKey<Float> ClusterDrsImbalanceSkipThreshold = new ConfigKey<>(Float.class,
+            "drs.imbalance.condensed.skip.threshold", ConfigKey.CATEGORY_ADVANCED, "0.95",
+            "Threshold to ignore the metric for a host while calculating the imbalance to decide " +
+                    "whether DRS is required for a cluster.This is to avoid cases when the calculated imbalance" +
+                    " gets skewed due to a single host having a very high/low metric  value resulting in imbalance" +
+                    " being higher than 1. If " + ClusterDrsMetricType.key() + " is 'free', set a lower value and if it is 'used' " +
+                    "set a higher value. The value should be between 0.0 and 1.0",
+            true, ConfigKey.Scope.Cluster, null, "DRS imbalance skip threshold for Condensed algorithm",
+            null, null, null);
+
+
+    /**
+     * Generate a DRS plan for a cluster and save it as per the parameters
+     *
+     * @param cmd
+     *         the GenerateClusterDrsPlanCmd object containing the command parameters
+     *
+     * @return a ClusterDrsPlanResponse object containing information regarding the migrations
+     */
+    ClusterDrsPlanResponse generateDrsPlan(GenerateClusterDrsPlanCmd cmd);
+
+    /**
+     * Executes a DRS plan for a cluster.
+     *
+     * @param cmd
+     *         the ExecuteClusterDrsPlanCmd object containing the ID of the cluster and the map of virtual
+     *         machines to hosts
+     *
+     * @return ClusterDrsPlanResponse response object
+     *
+     * @throws InvalidParameterValueException
+     *         if there is already a plan in READY or IN_PROGRESS state for the
+     *         cluster or if the
+     *         cluster cannot be found by ID
+     */
+    ClusterDrsPlanResponse executeDrsPlan(ExecuteClusterDrsPlanCmd cmd);
+
+    /**
+     * Lists DRS plans for a cluster or a specific plan.
+     *
+     * @param cmd
+     *         the ListClusterDrsPlanCmd object containing the ID of the cluster or the ID of the plan
+     *
+     * @return a ListResponse object containing a list of ClusterDrsPlanResponse objects and the total number of plans
+     *
+     * @throws InvalidParameterValueException
+     *         if both clusterId and planId are specified or if the cluster cannot be
+     *         found by ID
+     */
+    ListResponse<ClusterDrsPlanResponse> listDrsPlan(ListClusterDrsPlanCmd cmd);
+}
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 0159fb2..54a53f3 100644
--- a/api/src/main/java/org/apache/cloudstack/management/ManagementServerHost.java
+++ b/api/src/main/java/org/apache/cloudstack/management/ManagementServerHost.java
@@ -16,12 +16,13 @@
 // under the License.
 package org.apache.cloudstack.management;
 
+import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.api.Identity;
 import org.apache.cloudstack.api.InternalIdentity;
 
-public interface ManagementServerHost extends InternalIdentity, Identity {
+public interface ManagementServerHost extends InternalIdentity, Identity, ControlledEntity {
     enum State {
-        Up, Down
+        Up, Down, PreparingToShutDown, ReadyToShutDown, ShuttingDown
     }
 
     long getMsid();
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 0587294..3299e75 100644
--- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
+++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
@@ -28,13 +28,17 @@
 import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd;
 import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd;
 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.ListStoragePoolsCmd;
 import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd;
 import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
 import org.apache.cloudstack.api.command.user.account.ListAccountsCmd;
 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.bucket.ListBucketsCmd;
 import org.apache.cloudstack.api.command.user.event.ListEventsCmd;
 import org.apache.cloudstack.api.command.user.iso.ListIsosCmd;
 import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd;
@@ -44,6 +48,8 @@
 import org.apache.cloudstack.api.command.user.project.ListProjectsCmd;
 import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
 import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
+import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
+import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
 import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
 import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
 import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
@@ -53,6 +59,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.BucketResponse;
 import org.apache.cloudstack.api.response.DetailOptionsResponse;
 import org.apache.cloudstack.api.response.DiskOfferingResponse;
 import org.apache.cloudstack.api.response.DomainResponse;
@@ -62,8 +69,10 @@
 import org.apache.cloudstack.api.response.HostTagResponse;
 import org.apache.cloudstack.api.response.ImageStoreResponse;
 import org.apache.cloudstack.api.response.InstanceGroupResponse;
+import org.apache.cloudstack.api.response.IpQuarantineResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.ManagementServerResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
 import org.apache.cloudstack.api.response.ProjectAccountResponse;
 import org.apache.cloudstack.api.response.ProjectInvitationResponse;
 import org.apache.cloudstack.api.response.ProjectResponse;
@@ -71,8 +80,10 @@
 import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
+import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
 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.StoragePoolResponse;
 import org.apache.cloudstack.api.response.StorageTagResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
@@ -179,4 +190,16 @@
     ListResponse<ManagementServerResponse> listManagementServers(ListMgmtsCmd cmd);
 
     List<RouterHealthCheckResultResponse> listRouterHealthChecks(GetRouterHealthCheckResultsCmd cmd);
+
+    ListResponse<SecondaryStorageHeuristicsResponse> listSecondaryStorageSelectors(ListSecondaryStorageSelectorsCmd cmd);
+
+    ListResponse<IpQuarantineResponse> listQuarantinedIps(ListQuarantinedIpsCmd cmd);
+
+    ListResponse<SnapshotResponse> listSnapshots(ListSnapshotsCmd cmd);
+
+    SnapshotResponse listSnapshot(CopySnapshotCmd cmd);
+
+    ListResponse<ObjectStoreResponse> searchForObjectStores(ListObjectStoragePoolsCmd listObjectStoragePoolsCmd);
+
+    ListResponse<BucketResponse> searchForBuckets(ListBucketsCmd listBucketsCmd);
 }
diff --git a/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/Heuristic.java b/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/Heuristic.java
new file mode 100644
index 0000000..2a0b8d6
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/Heuristic.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.secstorage.heuristics;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface Heuristic extends InternalIdentity, Identity {
+
+    String getName();
+
+    String getDescription();
+
+    Long getZoneId();
+
+    String getType();
+
+    String getHeuristicRule();
+
+    Date getCreated();
+
+    Date getRemoved();
+
+}
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
new file mode 100644
index 0000000..f23e4b0
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/HeuristicType.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.secstorage.heuristics;
+
+/**
+ * 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}
+ */
+public enum HeuristicType {
+    ISO, SNAPSHOT, TEMPLATE, VOLUME
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/ImageStoreObjectDownload.java b/api/src/main/java/org/apache/cloudstack/storage/ImageStoreObjectDownload.java
new file mode 100644
index 0000000..02bce11
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/ImageStoreObjectDownload.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 org.apache.cloudstack.storage;
+
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface ImageStoreObjectDownload extends InternalIdentity {
+
+    long getId();
+
+    Long getStoreId();
+
+    String getPath();
+
+    String getDownloadUrl();
+
+    Date getCreated();
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/ImageStoreService.java b/api/src/main/java/org/apache/cloudstack/storage/ImageStoreService.java
index b8f14ad..9dd54dc 100644
--- a/api/src/main/java/org/apache/cloudstack/storage/ImageStoreService.java
+++ b/api/src/main/java/org/apache/cloudstack/storage/ImageStoreService.java
@@ -17,6 +17,7 @@
 
 package org.apache.cloudstack.storage;
 
+import org.apache.cloudstack.api.command.admin.storage.MigrateResourcesToAnotherSecondaryStorageCmd;
 import org.apache.cloudstack.api.command.admin.storage.MigrateSecondaryStorageDataCmd;
 import org.apache.cloudstack.api.response.MigrationResponse;
 
@@ -26,4 +27,5 @@
         BALANCE, COMPLETE
     }
     MigrationResponse migrateData(MigrateSecondaryStorageDataCmd cmd);
+    MigrationResponse migrateResources(MigrateResourcesToAnotherSecondaryStorageCmd migrateResourcesToAnotherSecondaryStorageCmd);
 }
diff --git a/api/src/main/java/org/apache/cloudstack/storage/browser/DataStoreObjectResponse.java b/api/src/main/java/org/apache/cloudstack/storage/browser/DataStoreObjectResponse.java
new file mode 100644
index 0000000..cac5cc9
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/browser/DataStoreObjectResponse.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
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.browser;
+
+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 DataStoreObjectResponse extends BaseResponse {
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "Name of the data store object.")
+    private String name;
+
+    @SerializedName("isdirectory")
+    @Param(description = "Is it a directory.")
+    private boolean isDirectory;
+
+    @SerializedName(ApiConstants.SIZE)
+    @Param(description = "Size is in Bytes.")
+    private long size;
+
+    @SerializedName(ApiConstants.TEMPLATE_ID)
+    @Param(description = "Template ID associated with the data store object.")
+    private String templateId;
+
+    @SerializedName(ApiConstants.FORMAT)
+    @Param(description = "Format of template associated with the data store object.")
+    private String format;
+
+    @SerializedName(ApiConstants.SNAPSHOT_ID)
+    @Param(description = "Snapshot ID associated with the data store object.")
+    private String snapshotId;
+
+    @SerializedName(ApiConstants.VOLUME_ID)
+    @Param(description = "Volume ID associated with the data store object.")
+    private String volumeId;
+
+    @SerializedName(ApiConstants.LAST_UPDATED)
+    @Param(description = "Last modified date of the file/directory.")
+    private Date lastUpdated;
+
+    public DataStoreObjectResponse(String name, boolean isDirectory, long size, Date lastUpdated) {
+        this.name = name;
+        this.isDirectory = isDirectory;
+        this.size = size;
+        this.lastUpdated = lastUpdated;
+        this.setObjectName("datastoreobject");
+    }
+
+    public DataStoreObjectResponse() {
+        super();
+        this.setObjectName("datastoreobject");
+    }
+
+    public void setTemplateId(String templateId) {
+        this.templateId = templateId;
+    }
+
+    public void setFormat(String format) {
+        this.format = format;
+    }
+
+    public void setSnapshotId(String snapshotId) {
+        this.snapshotId = snapshotId;
+    }
+
+    public void setVolumeId(String volumeId) {
+        this.volumeId = volumeId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isDirectory() {
+        return isDirectory;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public String getTemplateId() {
+        return templateId;
+    }
+
+    public String getFormat() {
+        return format;
+    }
+
+    public String getSnapshotId() {
+        return snapshotId;
+    }
+
+    public String getVolumeId() {
+        return volumeId;
+    }
+
+    public Date getLastUpdated() {
+        return lastUpdated;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/browser/StorageBrowser.java b/api/src/main/java/org/apache/cloudstack/storage/browser/StorageBrowser.java
new file mode 100644
index 0000000..b9fe5b0
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/browser/StorageBrowser.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.storage.browser;
+
+import com.cloud.utils.component.PluggableService;
+import org.apache.cloudstack.api.command.admin.storage.DownloadImageStoreObjectCmd;
+import org.apache.cloudstack.api.command.admin.storage.ListImageStoreObjectsCmd;
+import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolObjectsCmd;
+import org.apache.cloudstack.api.response.ExtractResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+
+public interface StorageBrowser extends PluggableService {
+    ListResponse<DataStoreObjectResponse> listImageStoreObjects(ListImageStoreObjectsCmd cmd);
+
+    ListResponse<DataStoreObjectResponse> listPrimaryStoreObjects(ListStoragePoolObjectsCmd cmd);
+
+    ExtractResponse downloadImageStoreObject(DownloadImageStoreObjectCmd cmd);
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/object/Bucket.java b/api/src/main/java/org/apache/cloudstack/storage/object/Bucket.java
new file mode 100644
index 0000000..c821dba
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/object/Bucket.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.storage.object;
+
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface Bucket extends ControlledEntity, Identity, InternalIdentity {
+
+    long getObjectStoreId();
+
+    Date getCreated();
+
+    State getState();
+
+    void setName(String name);
+
+    Long getSize();
+
+    Integer getQuota();
+
+    boolean isVersioning();
+
+    boolean isEncryption();
+
+    boolean isObjectLock();
+
+    String getPolicy();
+
+    String getBucketURL();
+
+    String getAccessKey();
+
+    String getSecretKey();
+
+    public enum State {
+        Allocated, Created, Destroyed;
+        @Override
+        public String toString() {
+            return this.name();
+        }
+
+        public boolean equals(String status) {
+            return this.toString().equalsIgnoreCase(status);
+        }
+    }
+}
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
new file mode 100644
index 0000000..7e1361d
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.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.storage.object;
+
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.user.Account;
+import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd;
+import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd;
+
+public interface BucketApiService {
+
+
+    /**
+     * Creates the database object for a Bucket based on the given criteria
+     *
+     * @param cmd
+     *            the API command wrapping the criteria (account/domainId [admin only], zone, diskOffering, snapshot,
+     *            name)
+     * @return the Bucket object
+     */
+    Bucket allocBucket(CreateBucketCmd cmd) throws ResourceAllocationException;
+
+    /**
+     * Creates the Bucket based on the given criteria
+     *
+     * @param cmd
+     *            the API command wrapping the criteria (account/domainId [admin only], zone, diskOffering, snapshot,
+     *            name)
+     * @return the Bucket object
+     */
+    Bucket createBucket(CreateBucketCmd cmd);
+
+    boolean deleteBucket(long bucketId, Account caller);
+
+    boolean updateBucket(UpdateBucketCmd cmd, Account caller);
+
+    void getBucketUsage();
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/object/ObjectStore.java b/api/src/main/java/org/apache/cloudstack/storage/object/ObjectStore.java
new file mode 100644
index 0000000..47741fc
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/object/ObjectStore.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.storage.object;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+public interface ObjectStore extends Identity, InternalIdentity {
+
+    /**
+     * @return name of the object store.
+     */
+    String getName();
+
+    /**
+     * @return object store provider name
+     */
+    String getProviderName();
+
+    /**
+     *
+     * @return uri
+     */
+    String getUrl();
+
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java
new file mode 100644
index 0000000..6571346
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.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.storage.template;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.exception.InsufficientAddressCapacityException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.security.SecurityGroup;
+import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.user.Account;
+import com.cloud.uservm.UserVm;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import java.util.List;
+
+public interface VnfTemplateManager {
+
+    ConfigKey<Boolean> VnfTemplateAndApplianceEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class,
+            "vnf.template.appliance.enabled",
+            "true",
+            "Indicates whether the creation of VNF templates and VNF appliances is enabled or not.",
+            false);
+
+    void persistVnfTemplate(long templateId, RegisterVnfTemplateCmd cmd);
+
+    void updateVnfTemplate(long templateId, UpdateVnfTemplateCmd cmd);
+
+    void validateVnfApplianceNics(VirtualMachineTemplate template, List<Long> networkIds);
+
+    SecurityGroup createSecurityGroupForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner, DeployVnfApplianceCmd cmd);
+
+    void createIsolatedNetworkRulesForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner,
+                                                   UserVm vm, DeployVnfApplianceCmd cmd)
+            throws InsufficientAddressCapacityException, ResourceAllocationException, ResourceUnavailableException;
+}
diff --git a/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java
new file mode 100644
index 0000000..e997a50
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java
@@ -0,0 +1,152 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.template;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.network.VNF;
+import com.cloud.storage.Storage;
+import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.utils.net.NetUtils;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.command.user.template.DeleteVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
+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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class VnfTemplateUtils {
+    private VnfTemplateUtils() {
+    }
+
+    public static List<VNF.VnfNic> getVnfNicsList(Map vnfNics) {
+        List<VNF.VnfNic> nicsList = new ArrayList<>();
+        if (MapUtils.isNotEmpty(vnfNics)) {
+            Collection nicsCollection = vnfNics.values();
+            Iterator iter = nicsCollection.iterator();
+            while (iter.hasNext()) {
+                HashMap<String, String> nicDetails = (HashMap<String, String>)iter.next();
+                String deviceIdString = nicDetails.get("deviceid");
+                String name = nicDetails.get("name");
+                String requiredString = nicDetails.get("required");
+                String managementString = nicDetails.get("management");
+                String description = nicDetails.get("description");
+                Integer deviceId = null;
+                if (StringUtils.isAnyBlank(name, deviceIdString)) {
+                    throw new InvalidParameterValueException("VNF nic name and deviceid cannot be null");
+                }
+                try {
+                    deviceId = Integer.parseInt(deviceIdString);
+                } catch (NumberFormatException e) {
+                    throw new InvalidParameterValueException("Unable to parse VNF nic deviceId to Integer: " + deviceId);
+                }
+                boolean required = StringUtils.isBlank(requiredString) || Boolean.parseBoolean(requiredString);
+                boolean management = StringUtils.isBlank(managementString) || Boolean.parseBoolean(managementString);
+                nicsList.add(new VNF.VnfNic(deviceId, name, required, management, description));
+            }
+            Collections.sort(nicsList, Comparator.comparing(VNF.VnfNic::getDeviceId));
+        }
+        return nicsList;
+    }
+
+    public static void validateApiCommandParams(Map<String, String> vnfDetails, List<VNF.VnfNic> vnfNics, String templateType) {
+        if (templateType != null && !Storage.TemplateType.VNF.name().equals(templateType)) {
+            throw new InvalidParameterValueException("The template type must be VNF for VNF templates.");
+        }
+
+        if (vnfDetails != null) {
+            for (String vnfDetail : vnfDetails.keySet()) {
+                if (!EnumUtils.isValidEnumIgnoreCase(VNF.VnfDetail.class, vnfDetail) &&
+                        !EnumUtils.isValidEnumIgnoreCase(VNF.AccessDetail.class, vnfDetail)) {
+                    throw new InvalidParameterValueException(String.format("Invalid VNF detail found: %s. Valid values are %s and %s", vnfDetail,
+                            Arrays.stream(VNF.AccessDetail.values()).map(method -> method.toString()).collect(Collectors.joining(", ")),
+                            Arrays.stream(VNF.VnfDetail.values()).map(method -> method.toString()).collect(Collectors.joining(", "))));
+                }
+                if (vnfDetails.get(vnfDetail) == null) {
+                    throw new InvalidParameterValueException("Empty value found for VNF detail: " + vnfDetail);
+                }
+                if (VNF.AccessDetail.ACCESS_METHODS.name().equalsIgnoreCase(vnfDetail)) {
+                    String[] accessMethods = vnfDetails.get(vnfDetail).split(",");
+                    for (String accessMethod : accessMethods) {
+                        if (VNF.AccessMethod.fromValue(accessMethod.trim()) == null) {
+                            throw new InvalidParameterValueException(String.format("Invalid VNF access method found: %s. Valid values are %s", accessMethod,
+                                    Arrays.stream(VNF.AccessMethod.values()).map(method -> method.toString()).sorted().collect(Collectors.joining(", "))));
+                        }
+                    }
+                }
+            }
+        }
+
+        validateVnfNics(vnfNics);
+    }
+
+    public static void validateVnfNics(List<VNF.VnfNic> nicsList) {
+        long deviceId = 0L;
+        boolean required = true;
+        for (VNF.VnfNic nic : nicsList) {
+            if (nic.getDeviceId() != deviceId) {
+                throw new InvalidParameterValueException(String.format("deviceid must be consecutive and start from 0. Nic deviceid should be %s but actual is %s.", deviceId, nic.getDeviceId()));
+            }
+            if (!required && nic.isRequired()) {
+                throw new InvalidParameterValueException(String.format("required cannot be true if a preceding nic is optional. Nic with deviceid %s should be required but actual is optional.", deviceId));
+            }
+            deviceId ++;
+            required = nic.isRequired();
+        }
+    }
+
+    public static void validateApiCommandParams(BaseCmd cmd, VirtualMachineTemplate template) {
+        if (cmd instanceof RegisterVnfTemplateCmd) {
+            RegisterVnfTemplateCmd registerCmd = (RegisterVnfTemplateCmd) cmd;
+            validateApiCommandParams(registerCmd.getVnfDetails(), registerCmd.getVnfNics(), registerCmd.getTemplateType());
+        } else if (cmd instanceof UpdateVnfTemplateCmd) {
+            UpdateVnfTemplateCmd updateCmd = (UpdateVnfTemplateCmd) cmd;
+            if (!Storage.TemplateType.VNF.equals(template.getTemplateType())) {
+                throw new InvalidParameterValueException(String.format("Cannot update as template %s is not a VNF template. The template type is %s.", updateCmd.getId(), template.getTemplateType()));
+            }
+            validateApiCommandParams(updateCmd.getVnfDetails(), updateCmd.getVnfNics(), updateCmd.getTemplateType());
+        } else if (cmd instanceof DeleteVnfTemplateCmd) {
+            if (!Storage.TemplateType.VNF.equals(template.getTemplateType())) {
+                DeleteVnfTemplateCmd deleteCmd = (DeleteVnfTemplateCmd) cmd;
+                throw new InvalidParameterValueException(String.format("Cannot delete as Template %s is not a VNF template. The template type is %s.", deleteCmd.getId(), template.getTemplateType()));
+            }
+        }
+    }
+
+    public static void validateVnfCidrList(List<String> cidrList) {
+        if (CollectionUtils.isEmpty(cidrList)) {
+            return;
+        }
+        for (String cidr : cidrList) {
+            if (!NetUtils.isValidIp4Cidr(cidr)) {
+                throw new InvalidParameterValueException(String.format("Invalid cidr for VNF appliance: %s", cidr));
+            }
+        }
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java b/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java
index 48cff30..5e0f03f 100644
--- a/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java
+++ b/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java
@@ -45,6 +45,7 @@
     public static final int VOLUME_SECONDARY = 26;
     public static final int VM_SNAPSHOT_ON_PRIMARY = 27;
     public static final int BACKUP = 28;
+    public static final int BUCKET = 29;
 
     public static List<UsageTypeResponse> listUsageTypes() {
         List<UsageTypeResponse> responseList = new ArrayList<UsageTypeResponse>();
@@ -70,6 +71,7 @@
         responseList.add(new UsageTypeResponse(VOLUME_SECONDARY, "Volume on secondary storage usage"));
         responseList.add(new UsageTypeResponse(VM_SNAPSHOT_ON_PRIMARY, "VM Snapshot on primary storage usage"));
         responseList.add(new UsageTypeResponse(BACKUP, "Backup storage usage"));
+        responseList.add(new UsageTypeResponse(BUCKET, "Bucket storage usage"));
         return responseList;
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java b/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java
new file mode 100644
index 0000000..4dfcd0a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.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.userdata;
+
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.framework.config.Configurable;
+
+import com.cloud.utils.component.Manager;
+
+public interface UserDataManager extends Manager, Configurable {
+    String concatenateUserData(String userdata1, String userdata2, String userdataProvider);
+    String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod);
+}
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 95675f2..23e0e37 100644
--- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java
+++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java
@@ -33,6 +33,8 @@
 
     private PowerState powerState;
 
+    private PowerState cloneSourcePowerState;
+
     private Integer cpuCores;
 
     private Integer cpuCoresPerSocket;
@@ -45,10 +47,16 @@
 
     private String operatingSystem;
 
+    private String clusterName;
+
+    private String hostName;
+
     private List<Disk> disks;
 
     private List<Nic> nics;
 
+    private String vncPassword;
+
     public String getName() {
         return name;
     }
@@ -73,6 +81,14 @@
         this.powerState = powerState;
     }
 
+    public PowerState getCloneSourcePowerState() {
+        return cloneSourcePowerState;
+    }
+
+    public void setCloneSourcePowerState(PowerState cloneSourcePowerState) {
+        this.cloneSourcePowerState = cloneSourcePowerState;
+    }
+
     public Integer getCpuCores() {
         return cpuCores;
     }
@@ -121,6 +137,22 @@
         this.operatingSystem = operatingSystem;
     }
 
+    public String getClusterName() {
+        return clusterName;
+    }
+
+    public void setClusterName(String clusterName) {
+        this.clusterName = clusterName;
+    }
+
+    public String getHostName() {
+        return hostName;
+    }
+
+    public void setHostName(String hostName) {
+        this.hostName = hostName;
+    }
+
     public List<Disk> getDisks() {
         return disks;
     }
@@ -137,6 +169,14 @@
         this.nics = nics;
     }
 
+    public String getVncPassword() {
+        return vncPassword;
+    }
+
+    public void setVncPassword(String vncPassword) {
+        this.vncPassword = vncPassword;
+    }
+
     public static class Disk {
         private String diskId;
 
@@ -162,6 +202,8 @@
 
         private String datastorePath;
 
+        private int datastorePort;
+
         private String datastoreType;
 
         public String getDiskId() {
@@ -267,6 +309,14 @@
         public void setDatastoreType(String datastoreType) {
             this.datastoreType = datastoreType;
         }
+
+        public void setDatastorePort(int datastorePort) {
+            this.datastorePort = datastorePort;
+        }
+
+        public int getDatastorePort() {
+            return datastorePort;
+        }
     }
 
     public static class Nic {
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 2876a01..53aece9 100644
--- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java
+++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java
@@ -17,13 +17,20 @@
 
 package org.apache.cloudstack.vm;
 
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.utils.component.PluggableService;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
+import static com.cloud.hypervisor.Hypervisor.HypervisorType.KVM;
+import static com.cloud.hypervisor.Hypervisor.HypervisorType.VMware;
 
 public interface UnmanagedVMsManager extends VmImportService, UnmanageVMService, PluggableService, Configurable {
 
     ConfigKey<Boolean> 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);
+
+    static boolean isSupported(Hypervisor.HypervisorType hypervisorType) {
+        return hypervisorType == VMware || hypervisorType == KVM;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java b/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java
index cce2847..04ef248 100644
--- a/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java
+++ b/api/src/main/java/org/apache/cloudstack/vm/VmImportService.java
@@ -18,12 +18,28 @@
 package org.apache.cloudstack.vm;
 
 import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
+import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd;
 import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
+import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
 
 public interface VmImportService {
+
+    enum ImportSource {
+        UNMANAGED, VMWARE, EXTERNAL, SHARED, LOCAL;
+
+        @Override
+        public String toString() {
+            return name().toLowerCase();
+        }
+    }
+
     ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd);
     UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd);
+
+    UserVmResponse importVm(ImportVmCmd cmd);
+
+    ListResponse<UnmanagedInstanceResponse> listVmsForImport(ListVmsForImportCmd cmd);
 }
diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java
new file mode 100644
index 0000000..b1e7df3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.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.vm.schedule;
+
+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
+    }
+
+    long getVmId();
+
+    String getDescription();
+
+    String getSchedule();
+
+    String getTimeZone();
+
+    Action getAction();
+
+    boolean getEnabled();
+
+    Date getStartDate();
+
+    Date getEndDate();
+
+    ZoneId getTimeZoneId();
+
+    Date getCreated();
+}
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
new file mode 100644
index 0000000..6aca05b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.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.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<VMScheduleResponse> listSchedule(ListVMScheduleCmd listVMScheduleCmd);
+
+    VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd);
+
+    long removeScheduleByVmId(long vmId, boolean expunge);
+
+    Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd);
+}
diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java
new file mode 100644
index 0000000..d7a18b7
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.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.schedule;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import java.util.Date;
+
+public interface VMScheduledJob extends Identity, InternalIdentity {
+    long getVmId();
+
+    long getVmScheduleId();
+
+    Long getAsyncJobId();
+
+    void setAsyncJobId(long asyncJobId);
+
+    VMSchedule.Action getAction();
+
+    Date getScheduledTime();
+}
diff --git a/api/src/main/resources/META-INF/cloudstack/api-planner/module.properties b/api/src/main/resources/META-INF/cloudstack/api-planner/module.properties
index 8eed879..a207387 100644
--- a/api/src/main/resources/META-INF/cloudstack/api-planner/module.properties
+++ b/api/src/main/resources/META-INF/cloudstack/api-planner/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=api-planner
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java b/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java
index 4554e71..c8f7ef3 100644
--- a/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java
+++ b/api/src/test/java/com/cloud/agent/api/storage/OVFHelperTest.java
@@ -613,7 +613,7 @@
             "      </Property>\n" +
             "      <Property ovf:key=\"guestinfo.cis.db.servername\" ovf:type=\"string\" ovf:userConfigurable=\"false\" ovf:value=\"\">\n" +
             "        <Label>Database Server</Label>\n" +
-            "        <Description>String naming the the hostname of the server on which the external database is running (ignored when db.type is &apos;embedded&apos;).</Description>\n" +
+            "        <Description>String naming the hostname of the server on which the external database is running (ignored when db.type is &apos;embedded&apos;).</Description>\n" +
             "      </Property>\n" +
             "      <Property ovf:key=\"guestinfo.cis.db.serverport\" ovf:type=\"string\" ovf:userConfigurable=\"false\" ovf:value=\"\">\n" +
             "        <Label>Database Port</Label>\n" +
diff --git a/api/src/test/java/com/cloud/network/VNFTest.java b/api/src/test/java/com/cloud/network/VNFTest.java
new file mode 100644
index 0000000..d38e9cc
--- /dev/null
+++ b/api/src/test/java/com/cloud/network/VNFTest.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 com.cloud.network;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class VNFTest {
+
+    static long deviceId = 0L;
+    static String deviceName = "eth0";
+    static boolean required = true;
+    static boolean management = false;
+    static String description = "description of vnf nic";
+
+    @Before
+    public void setUp() {
+    }
+
+    @Test
+    public void testAccessMethods() {
+        Assert.assertEquals(VNF.AccessMethod.CONSOLE, VNF.AccessMethod.fromValue("console"));
+        Assert.assertEquals(VNF.AccessMethod.HTTP, VNF.AccessMethod.fromValue("http"));
+        Assert.assertEquals(VNF.AccessMethod.HTTPS, VNF.AccessMethod.fromValue("https"));
+        Assert.assertEquals(VNF.AccessMethod.SSH_WITH_KEY, VNF.AccessMethod.fromValue("ssh-key"));
+        Assert.assertEquals(VNF.AccessMethod.SSH_WITH_PASSWORD, VNF.AccessMethod.fromValue("ssh-password"));
+    }
+
+    @Test
+    public void testVnfNic() {
+        VNF.VnfNic vnfNic = new VNF.VnfNic(deviceId, deviceName, required, management, description);
+
+        Assert.assertEquals(deviceId, vnfNic.getDeviceId());
+        Assert.assertEquals(deviceName, vnfNic.getName());
+        Assert.assertEquals(required, vnfNic.isRequired());
+        Assert.assertEquals(management, vnfNic.isManagement());
+        Assert.assertEquals(description, vnfNic.getDescription());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
index ee942a2..d26065d 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java
@@ -52,9 +52,11 @@
     private Integer accountType = 1;
     private Long domainId = 1L;
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         ReflectionTestUtils.setField(createAccountCmd, "domainId", domainId);
         ReflectionTestUtils.setField(createAccountCmd, "accountType", accountType);
         CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
@@ -62,6 +64,7 @@
 
     @After
     public void tearDown() throws Exception {
+        closeable.close();
         CallContext.unregister();
     }
 
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmdTest.java
index 0665686..443460f 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmdTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.cloudstack.api.command.admin.annotation;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockitoAnnotations;
@@ -26,9 +27,16 @@
 
     private AddAnnotationCmd addAnnotationCmd = new AddAnnotationCmd();
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test (expected = IllegalStateException.class)
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdminTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdminTest.java
new file mode 100644
index 0000000..943db0c
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdminTest.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.admin.iso;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.assertEquals;
+
+public class ListIsosCmdByAdminTest {
+
+    @InjectMocks
+    ListIsosCmdByAdmin cmd;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetImageStoreId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "imageStoreId", id);
+        assertEquals(id, cmd.getImageStoreId());
+    }
+
+    @Test
+    public void testGetZoneId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "zoneId", id);
+        assertEquals(id, cmd.getZoneId());
+    }
+
+    @Test
+    public void testGetShowRemoved() {
+        Boolean showRemoved = true;
+        ReflectionTestUtils.setField(cmd, "showRemoved", showRemoved);
+        assertEquals(showRemoved, cmd.getShowRemoved());
+    }
+
+    @Test
+    public void testGetIsoName() {
+        String isoName = "test";
+        ReflectionTestUtils.setField(cmd, "isoName", isoName);
+        assertEquals(isoName, cmd.getIsoName());
+    }
+
+    @Test
+    public void testGetIsoFilter() {
+        String isoFilter = "test";
+        ReflectionTestUtils.setField(cmd, "isoFilter", isoFilter);
+        assertEquals(isoFilter, cmd.getIsoFilter());
+    }
+
+    @Test
+    public void testGetShowUnique() {
+        Boolean showUnique = true;
+        ReflectionTestUtils.setField(cmd, "showUnique", showUnique);
+        assertEquals(showUnique, cmd.getShowUnique());
+    }
+
+    @Test
+    public void testGetShowIcon() {
+        Boolean showIcon = true;
+        ReflectionTestUtils.setField(cmd, "showIcon", showIcon);
+        assertEquals(showIcon, cmd.getShowIcon());
+    }
+
+    @Test
+    public void testGetBootable() {
+        Boolean bootable = true;
+        ReflectionTestUtils.setField(cmd, "bootable", bootable);
+        assertEquals(bootable, cmd.isBootable());
+    }
+
+    @Test
+    public void testGetHypervisor() {
+        String hypervisor = "test";
+        ReflectionTestUtils.setField(cmd, "hypervisor", hypervisor);
+        assertEquals(hypervisor, cmd.getHypervisor());
+    }
+
+    @Test
+    public void testGetId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "id", id);
+        assertEquals(id, cmd.getId());
+    }
+
+    @Test
+    public void testGetPublic() {
+        Boolean publicIso = true;
+        ReflectionTestUtils.setField(cmd, "publicIso", publicIso);
+        assertEquals(publicIso, cmd.isPublic());
+    }
+
+    @Test
+    public void testGetReady() {
+        Boolean ready = true;
+        ReflectionTestUtils.setField(cmd, "ready", ready);
+        assertEquals(ready, cmd.isReady());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmdTest.java
new file mode 100644
index 0000000..e28720f
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmdTest.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.offering;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class CreateDiskOfferingCmdTest {
+
+    @InjectMocks
+    private CreateDiskOfferingCmd createDiskOfferingCmd = new CreateDiskOfferingCmd();
+
+    @Test
+    public void testGetDisplayTextWhenEmpty() {
+        String netName = "net-offering";
+        ReflectionTestUtils.setField(createDiskOfferingCmd , "offeringName", netName);
+        Assert.assertEquals(createDiskOfferingCmd.getDisplayText(), netName);
+    }
+
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateNetworkOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateNetworkOfferingCmdTest.java
new file mode 100644
index 0000000..8b95456
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateNetworkOfferingCmdTest.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.offering;
+
+import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class CreateNetworkOfferingCmdTest {
+
+    @InjectMocks
+    private CreateNetworkOfferingCmd createNetworkOfferingCmd = new CreateNetworkOfferingCmd();
+
+    @Test
+    public void createVpcNtwkOffWithEmptyDisplayText() {
+        String netName = "network";
+        ReflectionTestUtils.setField(createNetworkOfferingCmd, "networkOfferingName", netName);
+        Assert.assertEquals(createNetworkOfferingCmd.getDisplayText(), netName);
+    }
+}
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
new file mode 100644
index 0000000..717b5c326
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.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.api.command.admin.offering;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CreateServiceOfferingCmdTest {
+
+    @InjectMocks
+    private CreateServiceOfferingCmd createServiceOfferingCmd;
+
+    @Test
+    public void testGetDisplayTextWhenEmpty() {
+        String netName = "net-offering";
+        ReflectionTestUtils.setField(createServiceOfferingCmd, "serviceOfferingName", netName);
+        Assert.assertEquals(createServiceOfferingCmd.getDisplayText(), netName);
+    }
+
+}
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
new file mode 100644
index 0000000..f64df16
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmdTest.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.admin.storage;
+
+import com.cloud.exception.DiscoveryException;
+import com.cloud.storage.StorageService;
+import org.apache.cloudstack.api.ResponseGenerator;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.object.ObjectStore;
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.anyObject;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AddObjectStoragePoolCmdTest {
+    public static final Logger s_logger = Logger.getLogger(AddObjectStoragePoolCmdTest.class.getName());
+
+    @Mock
+    StorageService storageService;
+
+    @Mock
+    ObjectStore objectStore;
+
+    @Mock
+    ResponseGenerator responseGenerator;
+
+    @Spy
+    AddObjectStoragePoolCmd addObjectStoragePoolCmdSpy;
+
+    String name = "testObjStore";
+
+    String url = "testURL";
+
+    String provider = "Simulator";
+
+    Map<String, String> details;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        details = new HashMap<>();
+        addObjectStoragePoolCmdSpy = Mockito.spy(new AddObjectStoragePoolCmd());
+        ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "name", name);
+        ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "url", url);
+        ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "providerName", provider);
+        ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "details", details);
+        addObjectStoragePoolCmdSpy._storageService = storageService;
+        addObjectStoragePoolCmdSpy._responseGenerator = responseGenerator;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        CallContext.unregister();
+    }
+
+    @Test
+    public void testAddObjectStore() throws DiscoveryException {
+        Mockito.doReturn(objectStore).when(storageService).discoverObjectStore(Mockito.anyString(),
+                Mockito.anyString(), Mockito.anyString(), anyObject());
+        ObjectStoreResponse objectStoreResponse = new ObjectStoreResponse();
+        Mockito.doReturn(objectStoreResponse).when(responseGenerator).createObjectStoreResponse(anyObject());
+        addObjectStoragePoolCmdSpy.execute();
+
+        Mockito.verify(storageService, Mockito.times(1))
+                .discoverObjectStore(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DeleteObjectStoragePoolCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DeleteObjectStoragePoolCmdTest.java
new file mode 100644
index 0000000..35be56d
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DeleteObjectStoragePoolCmdTest.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.admin.storage;
+
+import com.cloud.storage.StorageService;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+public class DeleteObjectStoragePoolCmdTest {
+    public static final Logger s_logger = Logger.getLogger(DeleteObjectStoragePoolCmdTest.class.getName());
+    @Mock
+    private StorageService storageService;
+
+    @Spy
+    DeleteObjectStoragePoolCmd deleteObjectStoragePoolCmd;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        deleteObjectStoragePoolCmd = Mockito.spy(new DeleteObjectStoragePoolCmd());
+        deleteObjectStoragePoolCmd._storageService = storageService;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        CallContext.unregister();
+    }
+
+    @Test
+    public void testDeleteObjectStore()  {
+        Mockito.doReturn(true).when(storageService).deleteObjectStore(deleteObjectStoragePoolCmd);
+        deleteObjectStoragePoolCmd.execute();
+        Mockito.verify(storageService, Mockito.times(1))
+                .deleteObjectStore(deleteObjectStoragePoolCmd);
+    }
+}
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
new file mode 100644
index 0000000..98435bf
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmdTest.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.admin.storage;
+
+import com.cloud.utils.Pair;
+import org.apache.cloudstack.api.response.ExtractResponse;
+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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DownloadImageStoreObjectCmdTest {
+
+    @Mock
+    private StorageBrowser storageBrowser;
+
+    @InjectMocks
+    @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);
+        ReflectionTestUtils.setField(cmd, "path", "path/to/object");
+        ExtractResponse response = mock(ExtractResponse.class);
+        when(storageBrowser.downloadImageStoreObject(cmd)).thenReturn(response);
+
+        cmd.execute();
+
+        verify(storageBrowser).downloadImageStoreObject(cmd);
+        verify(response).setResponseName("downloadImageStoreObjectResponse".toLowerCase());
+        verify(response).setObjectName("downloadImageStoreObjectResponse".toLowerCase());
+        verify(cmd).setResponseObject(response);
+    }
+
+    @Test
+    public void testGetPath() {
+        List<Pair<String, String>> pair = List.of(
+                new Pair<>("", null),
+                new Pair<>("", ""),
+                new Pair<>("", "/"),
+                new Pair<>("etc", "etc"),
+                new Pair<>("etc", "/etc"),
+                new Pair<>("etc/passwd", "etc/passwd"),
+                new Pair<>("etc/passwd", "//etc/passwd"),
+                new Pair<>("", "/etc/passwd/../../.."),
+                new Pair<>("etc/passwd", "../../etc/passwd"),
+                new Pair<>("etc/passwd", "/../../etc/passwd"),
+                new Pair<>("etc/passwd", ";../../../etc/passwd"),
+                new Pair<>("etc/passwd", "///etc/passwd"),
+                new Pair<>("etc/passwd", "/abc/xyz/../../../etc/passwd")
+        );
+
+        for (Pair<String, String> p : pair) {
+            String expectedPath = p.first();
+            String path = p.second();
+            ReflectionTestUtils.setField(cmd, "path", path);
+            Assert.assertEquals(expectedPath, cmd.getPath());
+        }
+    }
+
+    @Test
+    public void testGetEventType() {
+        String eventType = cmd.getEventType();
+
+        Assert.assertEquals("IMAGE.STORE.OBJECT.DOWNLOAD", eventType);
+    }
+
+    @Test
+    public void testGetEventDescription() {
+        ReflectionTestUtils.setField(cmd, "storeId", 1L);
+        ReflectionTestUtils.setField(cmd, "path", "path/to/object");
+        String eventDescription = cmd.getEventDescription();
+
+        Assert.assertEquals("Downloading object at path path/to/object on image store 1", eventDescription);
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmdTest.java
new file mode 100644
index 0000000..88dcde1
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmdTest.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.admin.storage;
+
+import com.cloud.utils.Pair;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.storage.browser.DataStoreObjectResponse;
+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.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+
+public class ListImageStoreObjectsCmdTest {
+
+    @Mock
+    StorageBrowser storageBrowser;
+
+    @InjectMocks
+    private ListImageStoreObjectsCmd cmd = new ListImageStoreObjectsCmd();
+
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetStoreId() {
+        Long id = 123L;
+        ReflectionTestUtils.setField(cmd, "storeId", id);
+        Assert.assertEquals(id, cmd.getStoreId());
+    }
+
+    @Test
+    public void testGetPath() {
+        List<Pair<String, String>> pair = List.of(
+            new Pair<>("", null),
+            new Pair<>("", ""),
+            new Pair<>("", "/"),
+            new Pair<>("etc", "etc"),
+            new Pair<>("etc", "/etc"),
+            new Pair<>("etc/passwd", "etc/passwd"),
+            new Pair<>("etc/passwd", "//etc/passwd"),
+            new Pair<>("", "/etc/passwd/../../.."),
+            new Pair<>("etc/passwd", "../../etc/passwd"),
+            new Pair<>("etc/passwd", "/../../etc/passwd"),
+            new Pair<>("etc/passwd", ";../../../etc/passwd"),
+            new Pair<>("etc/passwd", "///etc/passwd"),
+            new Pair<>("etc/passwd", "/abc/xyz/../../../etc/passwd")
+        );
+
+        for (Pair<String, String> p : pair) {
+            String expectedPath = p.first();
+            String path = p.second();
+            ReflectionTestUtils.setField(cmd, "path", path);
+            Assert.assertEquals(expectedPath, cmd.getPath());
+        }
+    }
+
+    @Test
+    public void testSuccessfulExecution() {
+        ListResponse<DataStoreObjectResponse> response = Mockito.mock(ListResponse.class);
+        Mockito.when(storageBrowser.listImageStoreObjects(cmd)).thenReturn(response);
+        cmd.execute();
+        Assert.assertEquals(response, cmd.getResponseObject());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmdTest.java
new file mode 100644
index 0000000..a15740d
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmdTest.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.admin.storage;
+
+import com.cloud.utils.Pair;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.storage.browser.DataStoreObjectResponse;
+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.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+
+public class ListStoragePoolObjectsCmdTest {
+    @Mock
+    StorageBrowser storageBrowser;
+
+    @InjectMocks
+    private ListStoragePoolObjectsCmd cmd = new ListStoragePoolObjectsCmd();
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetStoreId() {
+        Long id = 123L;
+        ReflectionTestUtils.setField(cmd, "storeId", id);
+        Assert.assertEquals(id, cmd.getStoreId());
+    }
+
+    @Test
+    public void testGetPath() {
+        List<Pair<String, String>> pair = List.of(
+                new Pair<>("", null),
+                new Pair<>("", ""),
+                new Pair<>("", "/"),
+                new Pair<>("etc", "etc"),
+                new Pair<>("etc", "/etc"),
+                new Pair<>("etc/passwd", "etc/passwd"),
+                new Pair<>("etc/passwd", "//etc/passwd"),
+                new Pair<>("", "/etc/passwd/../../.."),
+                new Pair<>("etc/passwd", "../../etc/passwd"),
+                new Pair<>("etc/passwd", "/../../etc/passwd"),
+                new Pair<>("etc/passwd", ";../../../etc/passwd"),
+                new Pair<>("etc/passwd", "///etc/passwd"),
+                new Pair<>("etc/passwd", "/abc/xyz/../../../etc/passwd")
+        );
+
+        for (Pair<String, String> p : pair) {
+            String expectedPath = p.first();
+            String path = p.second();
+            ReflectionTestUtils.setField(cmd, "path", path);
+            Assert.assertEquals(expectedPath, cmd.getPath());
+        }
+    }
+
+    @Test
+    public void testSuccessfulExecution() {
+        ListResponse<DataStoreObjectResponse> response = Mockito.mock(ListResponse.class);
+        Mockito.when(storageBrowser.listPrimaryStoreObjects(cmd)).thenReturn(response);
+        cmd.execute();
+        Assert.assertEquals(response, cmd.getResponseObject());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/MigrateResourcesToAnotherSecondaryStorageCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/MigrateResourcesToAnotherSecondaryStorageCmdTest.java
new file mode 100644
index 0000000..2f000ee
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/MigrateResourcesToAnotherSecondaryStorageCmdTest.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.storage;
+
+import org.apache.cloudstack.api.response.MigrationResponse;
+import org.apache.cloudstack.storage.ImageStoreService;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+
+public class MigrateResourcesToAnotherSecondaryStorageCmdTest {
+
+
+    @Mock
+    ImageStoreService _imageStoreService;
+
+    @InjectMocks
+    MigrateResourcesToAnotherSecondaryStorageCmd cmd;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetDestStoreId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "destStoreId", id);
+        Assert.assertEquals(id, cmd.getDestStoreId());
+    }
+
+    @Test
+    public void testGetId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "id", id);
+        Assert.assertEquals(id, cmd.getId());
+    }
+
+    @Test
+    public void testGetTemplateIdList() {
+        List<Long> ids = List.of(1234L, 5678L);
+        ReflectionTestUtils.setField(cmd, "templateIdList", ids);
+        Assert.assertEquals(ids, cmd.getTemplateIdList());
+    }
+
+    @Test
+    public void testGetSnapshotIdList() {
+        List<Long> ids = List.of(1234L, 5678L);
+        ReflectionTestUtils.setField(cmd, "snapshotIdList", ids);
+        Assert.assertEquals(ids, cmd.getSnapshotIdList());
+    }
+
+    @Test
+    public void testExecute() {
+        MigrationResponse response = Mockito.mock(MigrationResponse.class);
+        Mockito.when(_imageStoreService.migrateResources(Mockito.any())).thenReturn(response);
+        cmd.execute();
+        Assert.assertEquals(response, cmd.getResponseObject());
+    }
+
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmdTest.java
new file mode 100644
index 0000000..ef66c2a
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmdTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.api.command.admin.storage;
+
+import com.cloud.storage.StorageService;
+import org.apache.cloudstack.api.ResponseGenerator;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.object.ObjectStore;
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.ArgumentMatchers.anyObject;
+
+public class UpdateObjectStoragePoolCmdTest {
+    public static final Logger s_logger = Logger.getLogger(UpdateObjectStoragePoolCmdTest.class.getName());
+
+    @Mock
+    private StorageService storageService;
+
+    @Spy
+    UpdateObjectStoragePoolCmd updateObjectStoragePoolCmd;
+
+    @Mock
+    ObjectStore objectStore;
+
+    @Mock
+    ResponseGenerator responseGenerator;
+
+    private String name = "testObjStore";
+
+    private String url = "testURL";
+
+    private String provider = "Simulator";
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        updateObjectStoragePoolCmd = Mockito.spy(new UpdateObjectStoragePoolCmd());
+        updateObjectStoragePoolCmd._storageService = storageService;
+        updateObjectStoragePoolCmd._responseGenerator = responseGenerator;
+        ReflectionTestUtils.setField(updateObjectStoragePoolCmd, "name", name);
+        ReflectionTestUtils.setField(updateObjectStoragePoolCmd, "url", url);
+        ReflectionTestUtils.setField(updateObjectStoragePoolCmd, "id", 1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        CallContext.unregister();
+    }
+
+    @Test
+    public void testUpdateObjectStore() {
+        Mockito.doReturn(objectStore).when(storageService).updateObjectStore(1L, updateObjectStoragePoolCmd);
+        ObjectStoreResponse objectStoreResponse = new ObjectStoreResponse();
+        Mockito.doReturn(objectStoreResponse).when(responseGenerator).createObjectStoreResponse(anyObject());
+        updateObjectStoragePoolCmd.execute();
+        Mockito.verify(storageService, Mockito.times(1))
+                .updateObjectStore(1L, updateObjectStoragePoolCmd);
+    }
+
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java
index 125c8b5..4f58bd6 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java
@@ -20,23 +20,20 @@
 import com.cloud.user.Account;
 import com.cloud.utils.Pair;
 import org.apache.cloudstack.context.CallContext;
+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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class PatchSystemVMCmdTest {
 
     @Mock
@@ -45,9 +42,16 @@
     @InjectMocks
     PatchSystemVMCmd cmd = new PatchSystemVMCmd();
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -76,17 +80,16 @@
 
     @Test
     public void validateArgsForPatchSystemVMApi() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Account accountMock = PowerMockito.mock(Account.class);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
-        Mockito.when(accountMock.getId()).thenReturn(2L);
-        ReflectionTestUtils.setField(cmd, "id", 1L);
-        Assert.assertEquals((long)cmd.getId(), 1L);
-        Assert.assertFalse(cmd.isForced());
-        Assert.assertEquals(cmd.getEntityOwnerId(), 2L);
-
-
+        try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+            Account accountMock = Mockito.mock(Account.class);
+            Mockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
+            Mockito.when(accountMock.getId()).thenReturn(2L);
+            ReflectionTestUtils.setField(cmd, "id", 1L);
+            Assert.assertEquals((long) cmd.getId(), 1L);
+            Assert.assertFalse(cmd.isForced());
+            Assert.assertEquals(cmd.getEntityOwnerId(), 2L);
+        }
     }
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdminTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdminTest.java
new file mode 100644
index 0000000..bdb1496
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdminTest.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.admin.template;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.assertEquals;
+
+public class ListTemplatesCmdByAdminTest {
+
+    @InjectMocks
+    ListTemplatesCmdByAdmin cmd;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetImageStoreId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "imageStoreId", id);
+        assertEquals(id, cmd.getImageStoreId());
+    }
+
+    @Test
+    public void testGetZoneId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "zoneId", id);
+        assertEquals(id, cmd.getZoneId());
+    }
+
+    @Test
+    public void testGetShowRemoved() {
+        Boolean showRemoved = true;
+        ReflectionTestUtils.setField(cmd, "showRemoved", showRemoved);
+        assertEquals(showRemoved, cmd.getShowRemoved());
+    }
+
+    @Test
+    public void testGetShowUnique() {
+        Boolean showUnique = true;
+        ReflectionTestUtils.setField(cmd, "showUnique", showUnique);
+        assertEquals(showUnique, cmd.getShowUnique());
+    }
+
+    @Test
+    public void testGetShowIcon() {
+        Boolean showIcon = true;
+        ReflectionTestUtils.setField(cmd, "showIcon", showIcon);
+        assertEquals(showIcon, cmd.getShowIcon());
+    }
+
+    @Test
+    public void testGetHypervisor() {
+        String hypervisor = "test";
+        ReflectionTestUtils.setField(cmd, "hypervisor", hypervisor);
+        assertEquals(hypervisor, cmd.getHypervisor());
+    }
+
+    @Test
+    public void testGetId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "id", id);
+        assertEquals(id, cmd.getId());
+    }
+
+}
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 cde3b2d..bc1e185 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
@@ -46,14 +46,17 @@
     @InjectMocks
     private CreateUserCmd createUserCmd = new CreateUserCmd();
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
     }
 
     @After
     public void tearDown() throws Exception {
+        closeable.close();
         CallContext.unregister();
     }
 
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 7a98626..61a3c8f 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
@@ -16,16 +16,10 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.vm;
 
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.ManagementServerException;
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.exception.VirtualMachineMigrationException;
-import com.cloud.host.Host;
-import com.cloud.resource.ResourceService;
-import com.cloud.uservm.UserVm;
-import com.cloud.utils.db.UUIDManager;
-import com.cloud.vm.UserVmService;
-import com.cloud.vm.VirtualMachine;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ResponseGenerator;
 import org.apache.cloudstack.api.ResponseObject;
@@ -40,13 +34,21 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.util.List;
-import java.util.Map;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ManagementServerException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.exception.VirtualMachineMigrationException;
+import com.cloud.host.Host;
+import com.cloud.resource.ResourceService;
+import com.cloud.uservm.UserVm;
+import com.cloud.utils.db.UUIDManager;
+import com.cloud.vm.UserVmService;
+import com.cloud.vm.VirtualMachine;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class MigrateVirtualMachineWithVolumeCmdTest {
     @Mock
     UserVmService userVmServiceMock;
@@ -68,44 +70,46 @@
 
     @Spy
     @InjectMocks
-    MigrateVirtualMachineWithVolumeCmd cmdSpy = new MigrateVirtualMachineWithVolumeCmd();
+    MigrateVirtualMachineWithVolumeCmd cmdSpy;
 
     private Long hostId = 1L;
-    private Long virtualMachineUuid = 1L;
+    private Long virtualMachineId = 1L;
     private String virtualMachineName = "VM-name";
-    private Map<String, String> migrateVolumeTo = Map.of("key","value");
+    private  Map<String, String> migrateVolumeTo = null;
     private SystemVmResponse systemVmResponse = new SystemVmResponse();
     private UserVmResponse userVmResponse = new UserVmResponse();
 
     @Before
-    public void setup() {
-        Mockito.when(cmdSpy.getVirtualMachineId()).thenReturn(virtualMachineUuid);
-        Mockito.when(cmdSpy.getHostId()).thenReturn(hostId);
-        Mockito.when(cmdSpy.getVolumeToPool()).thenReturn(migrateVolumeTo);
+    public void setUp() throws Exception {
+        ReflectionTestUtils.setField(cmdSpy, "virtualMachineId", virtualMachineId);
+        migrateVolumeTo = new HashMap<>();
+        migrateVolumeTo.put("volume", "abc");
+        migrateVolumeTo.put("pool", "xyz");
     }
 
     @Test
-    public void executeTestHostIdIsNullAndMigrateVolumeToIsNullThrowsInvalidParameterValueException(){
+    public void executeTestRequiredArgsNullThrowsInvalidParameterValueException() {
         ReflectionTestUtils.setField(cmdSpy, "hostId", null);
         ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", null);
+        ReflectionTestUtils.setField(cmdSpy, "autoSelect", null);
 
         try {
             cmdSpy.execute();
         } catch (Exception e) {
             Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
-            String expected = String.format("Either %s or %s must be passed for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO);
+            String expected = String.format("Either %s or %s must be passed or %s must be true for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO, ApiConstants.AUTO_SELECT);
             Assert.assertEquals(expected , e.getMessage());
         }
     }
 
     @Test
-    public void executeTestVMIsStoppedAndHostIdIsNotNullThrowsInvalidParameterValueException(){
+    public void executeTestVMIsStoppedAndHostIdIsNotNullThrowsInvalidParameterValueException() {
         ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
         ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
-        Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineUuid, virtualMachineName));
+        Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineId, virtualMachineName));
 
         try {
             cmdSpy.execute();
@@ -117,33 +121,35 @@
     }
 
     @Test
-    public void executeTestVMIsRunningAndHostIdIsNullThrowsInvalidParameterValueException(){
+    public void executeTestVMIsRunningHostIdIsNullAndAutoSelectIsFalseThrowsInvalidParameterValueException() {
         ReflectionTestUtils.setField(cmdSpy, "hostId", null);
+        ReflectionTestUtils.setField(cmdSpy, "autoSelect", false);
         ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
-        Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineUuid, virtualMachineName));
+        Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineId, virtualMachineName));
 
         try {
             cmdSpy.execute();
         } catch (Exception e) {
             Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
-            String expected = String.format("%s is not in the Stopped state to migrate, use the %s parameter to migrate it to a new host.", virtualMachineMock,
-                    ApiConstants.HOST_ID);
+            String expected = String.format("%s is not in the Stopped state to migrate, use the %s or %s parameter to migrate it to a new host.", virtualMachineMock,
+                    ApiConstants.HOST_ID, ApiConstants.AUTO_SELECT);
             Assert.assertEquals(expected , e.getMessage());
         }
     }
 
     @Test
-    public void executeTestHostIdIsNullThrowsInvalidParameterValueException(){
+    public void executeTestHostIdIsNullThrowsInvalidParameterValueException() {
+        ReflectionTestUtils.setField(cmdSpy, "virtualMachineId", virtualMachineId);
         ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
         ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
+        ReflectionTestUtils.setField(cmdSpy, "autoSelect", false);
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
         Mockito.when(resourceServiceMock.getHost(Mockito.anyLong())).thenReturn(null);
-        Mockito.when(uuidManagerMock.getUuid(Host.class, virtualMachineUuid)).thenReturn(virtualMachineUuid.toString());
 
         try {
             cmdSpy.execute();
@@ -154,15 +160,22 @@
         }
     }
 
+    private Map getMockedMigrateVolumeToApiCmdParam() {
+        Map<String, String> migrateVolumeTo = new HashMap<>();
+        migrateVolumeTo.put("volume", "abc");
+        migrateVolumeTo.put("pool", "xyz");
+        return Map.of("", migrateVolumeTo);
+    }
+
     @Test
     public void executeTestHostIsNotNullMigratedVMIsNullThrowsServerApiException() throws ManagementServerException, ResourceUnavailableException, VirtualMachineMigrationException {
         ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
-        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
+        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
-        Mockito.when(resourceServiceMock.getHost(Mockito.anyLong())).thenReturn(hostMock);
-        Mockito.when(userVmServiceMock.migrateVirtualMachineWithVolume(virtualMachineUuid, hostMock, migrateVolumeTo)).thenReturn(null);
+        Mockito.when(resourceServiceMock.getHost(hostId)).thenReturn(hostMock);
+        Mockito.when(userVmServiceMock.migrateVirtualMachineWithVolume(Mockito.anyLong(), Mockito.any(), Mockito.anyMap())).thenReturn(null);
 
         try {
             cmdSpy.execute();
@@ -176,11 +189,11 @@
     @Test
     public void executeTestHostIsNullMigratedVMIsNullThrowsServerApiException() {
         ReflectionTestUtils.setField(cmdSpy, "hostId", null);
-        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
+        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
-        Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(null);
+        Mockito.when(userVmServiceMock.vmStorageMigration(Mockito.anyLong(), Mockito.anyMap())).thenReturn(null);
 
         try {
             cmdSpy.execute();
@@ -194,11 +207,11 @@
     @Test
     public void executeTestSystemVMMigratedWithSuccess() {
         ReflectionTestUtils.setField(cmdSpy, "hostId", null);
-        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
+        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
-        Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(virtualMachineMock);
+        Mockito.when(userVmServiceMock.vmStorageMigration(Mockito.anyLong(), Mockito.anyMap())).thenReturn(virtualMachineMock);
         Mockito.when(virtualMachineMock.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
         Mockito.when(responseGeneratorMock.createSystemVmResponse(virtualMachineMock)).thenReturn(systemVmResponse);
 
@@ -211,11 +224,11 @@
     public void executeTestUserVMMigratedWithSuccess() {
         UserVm userVmMock = Mockito.mock(UserVm.class);
         ReflectionTestUtils.setField(cmdSpy, "hostId", null);
-        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
+        ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());
 
         Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(userVmMock);
         Mockito.when(userVmMock.getState()).thenReturn(VirtualMachine.State.Stopped);
-        Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(userVmMock);
+        Mockito.when(userVmServiceMock.vmStorageMigration(Mockito.anyLong(), Mockito.anyMap())).thenReturn(userVmMock);
         Mockito.when(userVmMock.getType()).thenReturn(VirtualMachine.Type.User);
         Mockito.when(responseGeneratorMock.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock)).thenReturn(List.of(userVmResponse));
 
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java
index 18a2882..16b716d 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java
@@ -25,6 +25,8 @@
 
 import org.apache.cloudstack.api.ApiCmdTestUtil;
 import org.apache.cloudstack.api.ApiConstants;
+import org.springframework.test.util.ReflectionTestUtils;
+
 
 public class CreateVPCOfferingCmdTest {
 
@@ -61,4 +63,12 @@
         Assert.assertNull(cmd.getServiceProviders());
     }
 
+    @Test
+    public void testCreateVPCOfferingWithEmptyDisplayText() {
+        CreateVPCOfferingCmd cmd = new CreateVPCOfferingCmd();
+        String netName = "net-vpc";
+        ReflectionTestUtils.setField(cmd,"vpcOfferingName", netName);
+        Assert.assertEquals(cmd.getDisplayText(), netName);
+    }
+
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java
index a910de7..4b9d4fd 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java
@@ -54,7 +54,7 @@
         when(role.getDescription()).thenReturn("User test");
         when(role.getName()).thenReturn("testuser");
         when(role.getRoleType()).thenReturn(RoleType.User);
-        when(roleService.createRole(createRoleCmd.getRoleName(), createRoleCmd.getRoleType(), createRoleCmd.getRoleDescription())).thenReturn(role);
+        when(roleService.createRole(createRoleCmd.getRoleName(), createRoleCmd.getRoleType(), createRoleCmd.getRoleDescription(), true)).thenReturn(role);
         createRoleCmd.execute();
         RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject();
         Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), role.getName());
@@ -71,7 +71,7 @@
         when(newRole.getDescription()).thenReturn("User test");
         when(newRole.getName()).thenReturn("testuser");
         when(newRole.getRoleType()).thenReturn(RoleType.User);
-        when(roleService.createRole(createRoleCmd.getRoleName(), role, createRoleCmd.getRoleDescription())).thenReturn(newRole);
+        when(roleService.createRole(createRoleCmd.getRoleName(), role, createRoleCmd.getRoleDescription(), true)).thenReturn(newRole);
         createRoleCmd.execute();
         RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject();
         Assert.assertEquals((String) ReflectionTestUtils.getField(response, "roleName"), newRole.getName());
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 0d3251a..c528806 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
@@ -23,6 +23,7 @@
 import static org.mockito.Matchers.isNull;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.cloudstack.api.ResponseGenerator;
@@ -92,7 +93,7 @@
         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))).thenReturn(snapshot);
+                    nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class))).thenReturn(snapshot);
 
         } catch (Exception e) {
             Assert.fail("Received exception when success expected " + e.getMessage());
@@ -125,7 +126,7 @@
 
         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), anyObject())).thenReturn(null);
+                        nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), anyObject(), Mockito.anyList())).thenReturn(null);
         } catch (Exception e) {
             Assert.fail("Received exception when success expected " + e.getMessage());
         }
@@ -159,4 +160,14 @@
         ReflectionTestUtils.setField(createSnapshotCmd, "tags", tagsParams);
         Assert.assertEquals(createSnapshotCmd.getTags(), expectedTags);
     }
+
+    @Test
+    public void testGetZoneIds() {
+        final CreateSnapshotCmd cmd = new CreateSnapshotCmd();
+        List<Long> ids = List.of(400L, 500L);
+        ReflectionTestUtils.setField(cmd, "zoneIds", ids);
+        Assert.assertEquals(ids.size(), cmd.getZoneIds().size());
+        Assert.assertEquals(ids.get(0), cmd.getZoneIds().get(0));
+        Assert.assertEquals(ids.get(1), cmd.getZoneIds().get(1));
+    }
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java
index 8de0148..6299c1e 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java
@@ -93,7 +93,7 @@
         when(role.getDescription()).thenReturn("test user imported");
         when(role.getName()).thenReturn("Test User");
         when(role.getRoleType()).thenReturn(RoleType.User);
-        when(roleService.importRole(anyString(),any(), anyString(), any(), anyBoolean())).thenReturn(role);
+        when(roleService.importRole(anyString(), any(), anyString(), any(), anyBoolean(), anyBoolean())).thenReturn(role);
 
         importRoleCmd.execute();
         RoleResponse response = (RoleResponse) importRoleCmd.getResponseObject();
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/ResetVMUserDataCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/ResetVMUserDataCmdTest.java
index afd5bd7..50fe08d 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/test/ResetVMUserDataCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/test/ResetVMUserDataCmdTest.java
@@ -24,18 +24,17 @@
 import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd;
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.context.CallContext;
+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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
@@ -45,9 +44,7 @@
 
 import static org.mockito.Mockito.when;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class ResetVMUserDataCmdTest {
 
     @InjectMocks
@@ -66,14 +63,22 @@
     private static final long PROJECT_ID = 10L;
     private static final String ACCOUNT_NAME = "user";
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         ReflectionTestUtils.setField(cmd, "accountName", ACCOUNT_NAME);
         ReflectionTestUtils.setField(cmd, "domainId", DOMAIN_ID);
         ReflectionTestUtils.setField(cmd, "projectId", PROJECT_ID);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+
     @Test
     public void testValidResetVMUserDataExecute() {
         UserVm result = Mockito.mock(UserVm.class);
@@ -95,22 +100,23 @@
 
     @Test
     public void validateArgsCmd() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
+        try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            callContextMocked.when(CallContext::current).thenReturn(callContextMock);
 
-        ReflectionTestUtils.setField(cmd, "id", 1L);
-        ReflectionTestUtils.setField(cmd, "userdataId", 2L);
-        ReflectionTestUtils.setField(cmd, "userData", "testUserdata");
+            ReflectionTestUtils.setField(cmd, "id", 1L);
+            ReflectionTestUtils.setField(cmd, "userdataId", 2L);
+            ReflectionTestUtils.setField(cmd, "userData", "testUserdata");
 
-        UserVm vm = Mockito.mock(UserVm.class);
-        when(_responseGenerator.findUserVmById(1L)).thenReturn(vm);
-        when(vm.getAccountId()).thenReturn(200L);
+            UserVm vm = Mockito.mock(UserVm.class);
+            when(_responseGenerator.findUserVmById(1L)).thenReturn(vm);
+            when(vm.getAccountId()).thenReturn(200L);
 
-        Assert.assertEquals(1L, (long)cmd.getId());
-        Assert.assertEquals(2L, (long)cmd.getUserdataId());
-        Assert.assertEquals("testUserdata", cmd.getUserData());
-        Assert.assertEquals(200L, cmd.getEntityOwnerId());
+            Assert.assertEquals(1L, (long) cmd.getId());
+            Assert.assertEquals(2L, (long) cmd.getUserdataId());
+            Assert.assertEquals("testUserdata", cmd.getUserData());
+            Assert.assertEquals(200L, cmd.getEntityOwnerId());
+        }
     }
 
     @Test
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java
index d298def..84b9152 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java
@@ -58,7 +58,7 @@
         when(role.getDescription()).thenReturn("Default user");
         when(role.getName()).thenReturn("User");
         when(role.getRoleType()).thenReturn(RoleType.User);
-        when(roleService.updateRole(role,updateRoleCmd.getRoleName(),updateRoleCmd.getRoleType(),updateRoleCmd.getRoleDescription())).thenReturn(role);
+        when(roleService.updateRole(role, updateRoleCmd.getRoleName(), updateRoleCmd.getRoleType(), updateRoleCmd.getRoleDescription(), updateRoleCmd.isPublicRole())).thenReturn(role);
         when(role.getId()).thenReturn(1L);
         when(role.getDescription()).thenReturn("Description Initial");
         when(role.getName()).thenReturn("User");
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmdTest.java
index 6dcecc3..beba0c1 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmdTest.java
@@ -20,29 +20,41 @@
 import com.cloud.network.NetworkService;
 import com.cloud.utils.net.NetUtils;
 import org.apache.cloudstack.api.ServerApiException;
+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.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
 
-@PrepareForTest(NetUtils.class)
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CreateEgressFirewallRuleCmdTest {
 
     @Mock
     NetworkService networkServiceMock;
 
     @Spy
-    @InjectMocks
     CreateEgressFirewallRuleCmd cmdMock = new CreateEgressFirewallRuleCmd();
 
+    MockedStatic<NetUtils> netUtilsMocked;
+
+    @Before
+    public void setUp() throws Exception {
+        ReflectionTestUtils.setField(cmdMock, "_networkService", networkServiceMock);;
+        netUtilsMocked = Mockito.mockStatic(NetUtils.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        netUtilsMocked.close();
+    }
+
     @Test
     public void validateCidrsTestValidCidrs(){
         ArrayList<String> listMock = new ArrayList<>();
@@ -59,10 +71,9 @@
 
         Mockito.doReturn(cidrMock).when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(cidrMock)).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(cidrMock)).thenReturn(false);
-        PowerMockito.when(NetUtils.isNetworkAWithinNetworkB(cidrMock,cidrMock)).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(cidrMock)).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(cidrMock)).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isNetworkAWithinNetworkB(cidrMock,cidrMock)).thenReturn(true);
 
         cmdMock.validateCidrs();
 
@@ -71,12 +82,9 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.times(2));
-        NetUtils.isValidIp4Cidr(cidrMock);
-        NetUtils.isValidIp6Cidr(cidrMock);
-
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isNetworkAWithinNetworkB(cidrMock,cidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(cidrMock), Mockito.times(2));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(cidrMock), Mockito.never());
+        netUtilsMocked.verify(() -> NetUtils.isNetworkAWithinNetworkB(cidrMock,cidrMock), Mockito.times(1));
     }
 
     @Test
@@ -95,10 +103,9 @@
 
         Mockito.doReturn("10.1.1.0/24").when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(Mockito.eq("10.1.1.0/24"))).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(Mockito.eq("10.1.1.0/24"))).thenReturn(false);
-        PowerMockito.when(NetUtils.isNetworkAWithinNetworkB(Mockito.eq("10.1.1.0/24"), Mockito.eq("10.1.1.0/24"))).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(Mockito.eq("10.1.1.0/24"))).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(Mockito.eq("10.1.1.0/24"))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isNetworkAWithinNetworkB(Mockito.eq("10.1.1.0/24"), Mockito.eq("10.1.1.0/24"))).thenReturn(true);
 
         cmdMock.validateCidrs();
 
@@ -107,12 +114,10 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.times(2));
-        NetUtils.isValidIp4Cidr(Mockito.eq("10.1.1.0/24"));
-        NetUtils.isValidIp6Cidr(Mockito.eq("10.1.1.0/24"));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(Mockito.eq("10.1.1.0/24")), Mockito.times(2));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(Mockito.eq("10.1.1.0/24")), Mockito.never());
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isNetworkAWithinNetworkB(Mockito.eq("10.1.1.0/24"), Mockito.eq("10.1.1.0/24"));
+        netUtilsMocked.verify(() -> NetUtils.isNetworkAWithinNetworkB(Mockito.eq("10.1.1.0/24"), Mockito.eq("10.1.1.0/24")));
     }
 
     @Test (expected = ServerApiException.class)
@@ -131,9 +136,8 @@
 
         Mockito.doReturn("10.1.1.0/24").when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(sourceCidrMock)).thenReturn(false);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(sourceCidrMock)).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(sourceCidrMock)).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(sourceCidrMock)).thenReturn(false);
 
         cmdMock.validateCidrs();
 
@@ -142,9 +146,8 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.times(1));
-        NetUtils.isValidIp4Cidr(sourceCidrMock);
-        NetUtils.isValidIp6Cidr(sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(sourceCidrMock), Mockito.times(1));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(sourceCidrMock), Mockito.times(1));
     }
 
     @Test (expected = ServerApiException.class)
@@ -167,13 +170,12 @@
 
         Mockito.doReturn(sourceCidrMock).when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(Mockito.eq(sourceCidrMock))).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(Mockito.eq(sourceCidrMock))).thenReturn(false);
-        PowerMockito.when(NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock)).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(Mockito.eq(sourceCidrMock))).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(Mockito.eq(sourceCidrMock))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock)).thenReturn(true);
 
-        PowerMockito.when(NetUtils.isValidIp4Cidr(Mockito.eq(destCidrMock))).thenReturn(false);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(Mockito.eq(destCidrMock))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(Mockito.eq(destCidrMock))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(Mockito.eq(destCidrMock))).thenReturn(false);
 
         cmdMock.validateCidrs();
 
@@ -182,16 +184,14 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isValidIp4Cidr(sourceCidrMock);
-        NetUtils.isValidIp6Cidr(sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(sourceCidrMock));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(sourceCidrMock));
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isNetworkAWithinNetworkB(sourceCidrMock,sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock));
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isValidIp4Cidr(destCidrMock);
-        NetUtils.isValidIp6Cidr(destCidrMock);
+
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(destCidrMock));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(destCidrMock));
     }
 
     @Test
@@ -214,12 +214,11 @@
 
         Mockito.doReturn(sourceCidrMock).when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(Mockito.eq(sourceCidrMock))).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(Mockito.eq(sourceCidrMock))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(Mockito.eq(sourceCidrMock))).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(Mockito.eq(sourceCidrMock))).thenReturn(false);
 
-        PowerMockito.when(NetUtils.isValidIp4Cidr(Mockito.eq(destCidrMock))).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(Mockito.eq(destCidrMock))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(Mockito.eq(destCidrMock))).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(Mockito.eq(destCidrMock))).thenReturn(false);
 
         cmdMock.validateCidrs();
 
@@ -228,16 +227,13 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isValidIp4Cidr(sourceCidrMock);
-        NetUtils.isValidIp6Cidr(sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(sourceCidrMock), Mockito.times(1));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(sourceCidrMock), Mockito.never());
 
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.never());
-        NetUtils.isNetworkAWithinNetworkB(sourceCidrMock,sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock), Mockito.never());
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isValidIp4Cidr(destCidrMock);
-        NetUtils.isValidIp6Cidr(destCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(destCidrMock));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(destCidrMock), Mockito.never());
     }
 
     @Test(expected = ServerApiException.class)
@@ -256,10 +252,9 @@
 
         Mockito.doReturn(cidrMock).when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(cidrMock)).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(cidrMock)).thenReturn(false);
-        PowerMockito.when(NetUtils.isNetworkAWithinNetworkB(cidrMock, cidrMock)).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(cidrMock)).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(cidrMock)).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isNetworkAWithinNetworkB(cidrMock, cidrMock)).thenReturn(false);
 
         cmdMock.validateCidrs();
 
@@ -268,12 +263,10 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.times(1));
-        NetUtils.isValidIp4Cidr(cidrMock);
-        NetUtils.isValidIp6Cidr(cidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(cidrMock));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(cidrMock));
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isNetworkAWithinNetworkB(cidrMock, cidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isNetworkAWithinNetworkB(cidrMock, cidrMock));
     }
 
     @Test
@@ -291,10 +284,9 @@
 
         Mockito.doReturn(sourceCidrMock).when(networkMock).getCidr();
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isValidIp4Cidr(Mockito.eq(sourceCidrMock))).thenReturn(true);
-        PowerMockito.when(NetUtils.isValidIp6Cidr(Mockito.eq(sourceCidrMock))).thenReturn(false);
-        PowerMockito.when(NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock)).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp4Cidr(Mockito.eq(sourceCidrMock))).thenReturn(true);
+        netUtilsMocked.when(() -> NetUtils.isValidIp6Cidr(Mockito.eq(sourceCidrMock))).thenReturn(false);
+        netUtilsMocked.when(() -> NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock)).thenReturn(true);
 
         cmdMock.validateCidrs();
 
@@ -303,15 +295,12 @@
         Mockito.verify(networkServiceMock).getNetwork(networkIdMock);
         Mockito.verify(networkMock).getCidr();
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isValidIp4Cidr(sourceCidrMock);
-        NetUtils.isValidIp6Cidr(sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(sourceCidrMock));
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(sourceCidrMock), Mockito.never());
 
-        PowerMockito.verifyStatic(NetUtils.class);
-        NetUtils.isNetworkAWithinNetworkB(sourceCidrMock,sourceCidrMock);
+        netUtilsMocked.verify(() -> NetUtils.isNetworkAWithinNetworkB(sourceCidrMock, sourceCidrMock));
 
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.never());
-        NetUtils.isValidIp4Cidr(null);
-        NetUtils.isValidIp6Cidr(null);
+        netUtilsMocked.verify(() -> NetUtils.isValidIp4Cidr(null), Mockito.never());
+        netUtilsMocked.verify(() -> NetUtils.isValidIp6Cidr(null), Mockito.never());
     }
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmdTest.java
new file mode 100644
index 0000000..cc8a31f
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmdTest.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.iso;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ListIsosCmdTest {
+
+    @InjectMocks
+    ListIsosCmd cmd;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetImageStoreId() {
+        ListIsosCmd cmd = new ListIsosCmd();
+        assertNull(cmd.getImageStoreId());
+    }
+
+    @Test
+    public void testGetZoneId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "zoneId", id);
+        assertEquals(id, cmd.getZoneId());
+    }
+
+    @Test
+    public void testGetShowRemoved() {
+        Boolean showRemoved = true;
+        ReflectionTestUtils.setField(cmd, "showRemoved", showRemoved);
+        assertEquals(showRemoved, cmd.getShowRemoved());
+    }
+
+    @Test
+    public void testGetIsoName() {
+        String isoName = "test";
+        ReflectionTestUtils.setField(cmd, "isoName", isoName);
+        assertEquals(isoName, cmd.getIsoName());
+    }
+
+    @Test
+    public void testGetIsoFilter() {
+        String isoFilter = "test";
+        ReflectionTestUtils.setField(cmd, "isoFilter", isoFilter);
+        assertEquals(isoFilter, cmd.getIsoFilter());
+    }
+
+    @Test
+    public void testGetShowUnique() {
+        Boolean showUnique = true;
+        ReflectionTestUtils.setField(cmd, "showUnique", showUnique);
+        assertEquals(showUnique, cmd.getShowUnique());
+    }
+
+    @Test
+    public void testGetShowIcon() {
+        Boolean showIcon = true;
+        ReflectionTestUtils.setField(cmd, "showIcon", showIcon);
+        assertEquals(showIcon, cmd.getShowIcon());
+    }
+
+    @Test
+    public void testGetBootable() {
+        Boolean bootable = true;
+        ReflectionTestUtils.setField(cmd, "bootable", bootable);
+        assertEquals(bootable, cmd.isBootable());
+    }
+
+    @Test
+    public void testGetHypervisor() {
+        String hypervisor = "test";
+        ReflectionTestUtils.setField(cmd, "hypervisor", hypervisor);
+        assertEquals(hypervisor, cmd.getHypervisor());
+    }
+
+    @Test
+    public void testGetId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "id", id);
+        assertEquals(id, cmd.getId());
+    }
+
+    @Test
+    public void testGetPublic() {
+        Boolean publicIso = true;
+        ReflectionTestUtils.setField(cmd, "publicIso", publicIso);
+        assertEquals(publicIso, cmd.isPublic());
+    }
+
+    @Test
+    public void testGetReady() {
+        Boolean ready = true;
+        ReflectionTestUtils.setField(cmd, "ready", ready);
+        assertEquals(ready, cmd.isReady());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmdTest.java
new file mode 100644
index 0000000..55a41c6
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmdTest.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.api.command.user.iso;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RegisterIsoCmdTest  {
+
+    @InjectMocks
+    private RegisterIsoCmd registerIsoCmd = new RegisterIsoCmd();
+
+    @Test
+    public void testGetDisplayTextWhenEmpty() {
+        String netName = "net-iso";
+        ReflectionTestUtils.setField(registerIsoCmd, "isoName", netName);
+        Assert.assertEquals(registerIsoCmd.getDisplayText(), netName);
+    }
+
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmdTest.java
new file mode 100644
index 0000000..ee31931
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmdTest.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.api.command.user.project;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CreateProjectCmdTest  {
+
+    @InjectMocks
+    private CreateProjectCmd createProjectCmd = new CreateProjectCmd();
+
+    @Test
+    public void testGetDisplayTextWhenEmpty() {
+        String netName = "net-project";
+        ReflectionTestUtils.setField(createProjectCmd , "name", netName);
+        Assert.assertEquals(createProjectCmd.getDisplayText(), netName);
+    }
+
+}
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
new file mode 100644
index 0000000..632496a
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmdTest.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.command.user.snapshot;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.SnapshotResponse;
+import org.apache.cloudstack.query.QueryService;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.snapshot.SnapshotApiService;
+import com.cloud.utils.db.UUIDManager;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CopySnapshotCmdTest {
+
+    @Test
+    public void testGetId() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        Long id = 100L;
+        ReflectionTestUtils.setField(cmd, "id", id);
+        Assert.assertEquals(id, cmd.getId());
+    }
+
+    @Test
+    public void testGetSourceZoneId() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        Long id = 200L;
+        ReflectionTestUtils.setField(cmd, "sourceZoneId", id);
+        Assert.assertEquals(id, cmd.getSourceZoneId());
+    }
+
+    @Test
+    public void testGetDestZoneIdWithSingleId() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        Long id = 300L;
+        ReflectionTestUtils.setField(cmd, "destZoneId", id);
+        Assert.assertEquals(1, cmd.getDestinationZoneIds().size());
+        Assert.assertEquals(id, cmd.getDestinationZoneIds().get(0));
+    }
+
+    @Test
+    public void testGetDestZoneIdWithMultipleId() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        List<Long> ids = List.of(400L, 500L);
+        ReflectionTestUtils.setField(cmd, "destZoneIds", ids);
+        Assert.assertEquals(ids.size(), cmd.getDestinationZoneIds().size());
+        Assert.assertEquals(ids.get(0), cmd.getDestinationZoneIds().get(0));
+        Assert.assertEquals(ids.get(1), cmd.getDestinationZoneIds().get(1));
+    }
+
+    @Test
+    public void testGetDestZoneIdWithBothParams() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        List<Long> ids = List.of(400L, 500L);
+        ReflectionTestUtils.setField(cmd, "destZoneIds", ids);
+        ReflectionTestUtils.setField(cmd, "destZoneId", 100L);
+        Assert.assertEquals(ids.size(), cmd.getDestinationZoneIds().size());
+        Assert.assertEquals(ids.get(0), cmd.getDestinationZoneIds().get(0));
+        Assert.assertEquals(ids.get(1), cmd.getDestinationZoneIds().get(1));
+    }
+
+    @Test (expected = ServerApiException.class)
+    public void testExecuteWrongNoParams() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        try {
+            cmd.execute();
+        } catch (ResourceUnavailableException e) {
+            Assert.fail(String.format("Exception: %s", e.getMessage()));
+        }
+    }
+
+    @Test (expected = ServerApiException.class)
+    public void testExecuteWrongBothParams() {
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        List<Long> ids = List.of(400L, 500L);
+        ReflectionTestUtils.setField(cmd, "destZoneIds", ids);
+        ReflectionTestUtils.setField(cmd, "destZoneId", 100L);
+        try {
+            cmd.execute();
+        } catch (ResourceUnavailableException e) {
+            Assert.fail(String.format("Exception: %s", e.getMessage()));
+        }
+    }
+
+    @Test
+    public void testExecuteSuccess() {
+        SnapshotApiService snapshotApiService = Mockito.mock(SnapshotApiService.class);
+        QueryService queryService = Mockito.mock(QueryService.class);
+        UUIDManager uuidManager = Mockito.mock(UUIDManager.class);
+        final CopySnapshotCmd cmd = new CopySnapshotCmd();
+        cmd._snapshotService = snapshotApiService;
+        cmd._queryService = queryService;
+        cmd._uuidMgr = uuidManager;
+        Snapshot snapshot = Mockito.mock(Snapshot.class);
+        final Long id = 100L;
+        ReflectionTestUtils.setField(cmd, "destZoneId", id);
+        SnapshotResponse snapshotResponse = Mockito.mock(SnapshotResponse.class);
+        try {
+            Mockito.when(snapshotApiService.copySnapshot(cmd)).thenReturn(snapshot);
+            Mockito.when(queryService.listSnapshot(cmd)).thenReturn(snapshotResponse);
+            Mockito.when(uuidManager.getUuid(DataCenter.class, id)).thenReturn(UUID.randomUUID().toString());
+            cmd.execute();
+        } catch (ResourceAllocationException | ResourceUnavailableException e) {
+            Assert.fail(String.format("Exception: %s", e.getMessage()));
+        }
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmdTest.java
index 111ac70..258f29e 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmdTest.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.api.command.user.snapshot;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.junit.Assert;
@@ -43,4 +44,14 @@
         ReflectionTestUtils.setField(createSnapshotPolicyCmd, "tags", tagsParams);
         Assert.assertEquals(createSnapshotPolicyCmd.getTags(), expectedTags);
     }
+
+    @Test
+    public void testGetZoneIds() {
+        final CreateSnapshotPolicyCmd cmd = new CreateSnapshotPolicyCmd();
+        List<Long> ids = List.of(400L, 500L);
+        ReflectionTestUtils.setField(cmd, "zoneIds", ids);
+        Assert.assertEquals(ids.size(), cmd.getZoneIds().size());
+        Assert.assertEquals(ids.get(0), cmd.getZoneIds().get(0));
+        Assert.assertEquals(ids.get(1), cmd.getZoneIds().get(1));
+    }
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmdTest.java
new file mode 100644
index 0000000..48c4279
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmdTest.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.api.command.user.snapshot;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class DeleteSnapshotCmdTest {
+
+    @Test
+    public void testGetZoneId() {
+        final DeleteSnapshotCmd cmd = new DeleteSnapshotCmd();
+        Long id = 400L;
+        ReflectionTestUtils.setField(cmd, "zoneId", id);
+        Assert.assertEquals(id, cmd.getZoneId());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmdTest.java
new file mode 100644
index 0000000..c882691
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmdTest.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.snapshot;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class ListSnapshotsCmdTest {
+
+    @Test
+    public void testIsShowUniqueNoValue() {
+        final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
+        Assert.assertTrue(cmd.isShowUnique());
+    }
+
+    @Test
+    public void testIsShowUniqueFalse() {
+        final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
+        ReflectionTestUtils.setField(cmd, "showUnique", false);
+        Assert.assertFalse(cmd.isShowUnique());
+    }
+
+    @Test
+    public void testIsShowUniqueTrue() {
+        final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
+        ReflectionTestUtils.setField(cmd, "showUnique", true);
+        Assert.assertTrue(cmd.isShowUnique());
+    }
+
+    @Test
+    public void testGetLocationTypeNoUnique() {
+        final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
+        ReflectionTestUtils.setField(cmd, "locationType", "primary");
+        Assert.assertNull(cmd.getLocationType());
+    }
+
+    @Test
+    public void testGetLocationTypeUnique() {
+        final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
+        ReflectionTestUtils.setField(cmd, "showUnique", false);
+        String value = "secondary";
+        ReflectionTestUtils.setField(cmd, "locationType", value);
+        Assert.assertEquals(value, cmd.getLocationType());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmdTest.java
new file mode 100644
index 0000000..55e9c8d
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmdTest.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.api.command.user.template;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class CreateTemplateCmdTest {
+
+    @Test
+    public void testGetZoneId() {
+        final CreateTemplateCmd cmd = new CreateTemplateCmd();
+        Long id = 400L;
+        ReflectionTestUtils.setField(cmd, "zoneId", id);
+        Assert.assertEquals(id, cmd.getZoneId());
+    }
+
+    @Test
+    public void testDomainId() {
+        final CreateTemplateCmd cmd = new CreateTemplateCmd();
+        Long id = 2L;
+        ReflectionTestUtils.setField(cmd, "domainId", id);
+        Assert.assertEquals(id, cmd.getDomainId());
+    }
+
+    @Test
+    public void testGetAccountName() {
+        final CreateTemplateCmd cmd = new CreateTemplateCmd();
+        String accountName = "user1";
+        ReflectionTestUtils.setField(cmd, "accountName", accountName);
+        Assert.assertEquals(accountName, cmd.getAccountName());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmdTest.java
new file mode 100644
index 0000000..c48ca47
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmdTest.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.template;
+
+import org.apache.cloudstack.api.command.user.iso.ListIsosCmd;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ListTemplatesCmdTest {
+
+    @InjectMocks
+    ListTemplatesCmd cmd;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetImageStoreId() {
+        ListIsosCmd cmd = new ListIsosCmd();
+        assertNull(cmd.getImageStoreId());
+    }
+
+    @Test
+    public void testGetZoneId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "zoneId", id);
+        assertEquals(id, cmd.getZoneId());
+    }
+
+    @Test
+    public void testGetShowRemoved() {
+        Boolean showRemoved = true;
+        ReflectionTestUtils.setField(cmd, "showRemoved", showRemoved);
+        assertEquals(showRemoved, cmd.getShowRemoved());
+    }
+
+    @Test
+    public void testGetShowUnique() {
+        Boolean showUnique = true;
+        ReflectionTestUtils.setField(cmd, "showUnique", showUnique);
+        assertEquals(showUnique, cmd.getShowUnique());
+    }
+
+    @Test
+    public void testGetShowIcon() {
+        Boolean showIcon = true;
+        ReflectionTestUtils.setField(cmd, "showIcon", showIcon);
+        assertEquals(showIcon, cmd.getShowIcon());
+    }
+
+    @Test
+    public void testGetHypervisor() {
+        String hypervisor = "test";
+        ReflectionTestUtils.setField(cmd, "hypervisor", hypervisor);
+        assertEquals(hypervisor, cmd.getHypervisor());
+    }
+
+    @Test
+    public void testGetId() {
+        Long id = 1234L;
+        ReflectionTestUtils.setField(cmd, "id", id);
+        assertEquals(id, cmd.getId());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmdTest.java
index dfd3b6e..0c31e50 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmdTest.java
@@ -30,6 +30,8 @@
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
 import java.util.ArrayList;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -139,4 +141,12 @@
         testIsDeployAsIsBase(Hypervisor.HypervisorType.XenServer, true, false);
         testIsDeployAsIsBase(Hypervisor.HypervisorType.Any, true, false);
     }
+
+    @Test
+    public void testGetDisplayTextWhenEmpty() {
+        registerTemplateCmd = new RegisterTemplateCmd();
+        String netName = "net-template";
+        ReflectionTestUtils.setField(registerTemplateCmd , "templateName", netName);
+        Assert.assertEquals(registerTemplateCmd.getDisplayText(), netName);
+    }
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/DeleteUserDataCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/DeleteUserDataCmdTest.java
index fbaf46f..255c9f4 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/DeleteUserDataCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/DeleteUserDataCmdTest.java
@@ -22,23 +22,20 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.context.CallContext;
+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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteUserDataCmdTest {
 
     @InjectMocks
@@ -53,14 +50,21 @@
     private static final long PROJECT_ID = 10L;
     private static final String ACCOUNT_NAME = "user";
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         ReflectionTestUtils.setField(cmd, "accountName", ACCOUNT_NAME);
         ReflectionTestUtils.setField(cmd, "domainId", DOMAIN_ID);
         ReflectionTestUtils.setField(cmd, "projectId", PROJECT_ID);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void testValidUserDataExecute() {
         Mockito.doReturn(true).when(_mgr).deleteUserData(cmd);
@@ -81,18 +85,18 @@
 
     @Test
     public void validateArgsCmd() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Account accountMock = PowerMockito.mock(Account.class);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
-        Mockito.when(accountMock.getId()).thenReturn(2L);
-        Mockito.doReturn(false).when(_accountService).isAdmin(2L);
+        try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+            Account accountMock = Mockito.mock(Account.class);
+            Mockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
+            Mockito.when(accountMock.getId()).thenReturn(2L);
+            Mockito.doReturn(false).when(_accountService).isAdmin(2L);
 
-        ReflectionTestUtils.setField(cmd, "id", 1L);
+            ReflectionTestUtils.setField(cmd, "id", 1L);
 
-        Assert.assertEquals(1L, (long)cmd.getId());
-        Assert.assertEquals(2L, cmd.getEntityOwnerId());
+            Assert.assertEquals(1L, (long) cmd.getId());
+            Assert.assertEquals(2L, cmd.getEntityOwnerId());
+        }
     }
-
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmdTest.java
index c5581ed..b431c7c 100644
--- a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmdTest.java
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmdTest.java
@@ -20,29 +20,25 @@
 import com.cloud.storage.Storage;
 import com.cloud.template.TemplateApiService;
 import com.cloud.template.VirtualMachineTemplate;
-import com.cloud.user.Account;
 import com.cloud.user.UserData;
 import com.cloud.utils.db.EntityManager;
 import org.apache.cloudstack.api.ResponseGenerator;
 import org.apache.cloudstack.api.response.TemplateResponse;
 import org.apache.cloudstack.context.CallContext;
+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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class LinkUserDataToTemplateCmdTest {
 
     @Mock
@@ -60,9 +56,16 @@
     @Mock
     VirtualMachineTemplate virtualMachineTemplate;
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -84,21 +87,19 @@
 
     @Test
     public void validateArgsCmd() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Account accountMock = PowerMockito.mock(Account.class);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
-        Mockito.when(accountMock.getId()).thenReturn(2L);
-        ReflectionTestUtils.setField(cmd, "templateId", 1L);
-        ReflectionTestUtils.setField(cmd, "userdataId", 3L);
+        try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+            ReflectionTestUtils.setField(cmd, "templateId", 1L);
+            ReflectionTestUtils.setField(cmd, "userdataId", 3L);
 
-        Mockito.doReturn(virtualMachineTemplate).when(_entityMgr).findById(VirtualMachineTemplate.class, cmd.getTemplateId());
-        PowerMockito.when(virtualMachineTemplate.getAccountId()).thenReturn(1L);
+            Mockito.doReturn(virtualMachineTemplate).when(_entityMgr).findById(VirtualMachineTemplate.class, cmd.getTemplateId());
+            Mockito.when(virtualMachineTemplate.getAccountId()).thenReturn(1L);
 
-        Assert.assertEquals(1L, (long)cmd.getTemplateId());
-        Assert.assertEquals(3L, (long)cmd.getUserdataId());
-        Assert.assertEquals(1L, cmd.getEntityOwnerId());
+            Assert.assertEquals(1L, (long) cmd.getTemplateId());
+            Assert.assertEquals(3L, (long) cmd.getUserdataId());
+            Assert.assertEquals(1L, cmd.getEntityOwnerId());
+        }
     }
 
     @Test
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 fd3b1a7..3f47a07 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
@@ -20,10 +20,10 @@
 import com.cloud.user.UserData;
 import com.cloud.utils.Pair;
 import org.apache.cloudstack.api.response.ListResponse;
+import org.junit.After;
 import org.junit.Assert;
 import org.apache.cloudstack.api.ResponseGenerator;
 import org.apache.cloudstack.api.response.UserDataResponse;
-import org.apache.cloudstack.context.CallContext;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,16 +31,12 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class ListUserDataCmdTest {
 
     @InjectMocks
@@ -52,9 +48,16 @@
     @Mock
     ResponseGenerator _responseGenerator;
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
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 901ec84..e960552 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
@@ -18,31 +18,27 @@
 
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.server.ManagementService;
-import com.cloud.user.Account;
 import com.cloud.user.AccountService;
 import com.cloud.user.UserData;
 import org.apache.cloudstack.api.ResponseGenerator;
 import org.apache.cloudstack.api.response.UserDataResponse;
 import org.apache.cloudstack.context.CallContext;
+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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import static org.mockito.Mockito.when;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class RegisterUserDataCmdTest {
 
     @InjectMocks
@@ -61,14 +57,22 @@
     private static final long PROJECT_ID = 10L;
     private static final String ACCOUNT_NAME = "user";
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         ReflectionTestUtils.setField(cmd, "accountName", ACCOUNT_NAME);
         ReflectionTestUtils.setField(cmd, "domainId", DOMAIN_ID);
         ReflectionTestUtils.setField(cmd, "projectId", PROJECT_ID);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+
     @Test
     public void testValidUserDataExecute() {
         UserData result = Mockito.mock(UserData.class);
@@ -87,20 +91,18 @@
 
     @Test
     public void validateArgsCmd() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Account accountMock = PowerMockito.mock(Account.class);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
-        Mockito.when(accountMock.getId()).thenReturn(2L);
-        ReflectionTestUtils.setField(cmd, "name", "testUserdataName");
-        ReflectionTestUtils.setField(cmd, "userData", "testUserdata");
+        try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+            ReflectionTestUtils.setField(cmd, "name", "testUserdataName");
+            ReflectionTestUtils.setField(cmd, "userData", "testUserdata");
 
-        when(_accountService.finalyzeAccountId(ACCOUNT_NAME, DOMAIN_ID, PROJECT_ID, true)).thenReturn(200L);
+            when(_accountService.finalyzeAccountId(ACCOUNT_NAME, DOMAIN_ID, PROJECT_ID, true)).thenReturn(200L);
 
-        Assert.assertEquals("testUserdataName", cmd.getName());
-        Assert.assertEquals("testUserdata", cmd.getUserData());
-        Assert.assertEquals(200L, cmd.getEntityOwnerId());
+            Assert.assertEquals("testUserdataName", cmd.getName());
+            Assert.assertEquals("testUserdata", cmd.getUserData());
+            Assert.assertEquals(200L, cmd.getEntityOwnerId());
+        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
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
new file mode 100644
index 0000000..2fdef2a
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.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.vm;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.security.InvalidParameterException;
+
+public class CreateVMScheduleCmdTest {
+    @Mock
+    public VMScheduleManager vmScheduleManager;
+    @Mock
+    public EntityManager entityManager;
+    @InjectMocks
+    private CreateVMScheduleCmd createVMScheduleCmd = new CreateVMScheduleCmd();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and CreateVMScheduleCmd"
+     * when: "CreateVMScheduleCmd is executed successfully"
+     * then: "a VMSchedule response is created"
+     */
+    @Test
+    public void testSuccessfulExecution() {
+        VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
+        Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenReturn(vmScheduleResponse);
+        createVMScheduleCmd.execute();
+        Assert.assertEquals(vmScheduleResponse, createVMScheduleCmd.getResponseObject());
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and CreateVMScheduleCmd"
+     * when: "CreateVMScheduleCmd is executed with an invalid parameter"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @Test(expected = InvalidParameterException.class)
+    public void testInvalidParameterException() {
+        Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterException.class);
+        createVMScheduleCmd.execute();
+    }
+
+    /**
+     * given: "We have an EntityManager and CreateVMScheduleCmd"
+     * when: "CreateVMScheduleCmd.getEntityOwnerId is executed for a VM which does exist"
+     * then: "owner of that VM is returned"
+     */
+    @Test
+    public void testSuccessfulGetEntityOwnerId() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(entityManager.findById(VirtualMachine.class, createVMScheduleCmd.getVmId())).thenReturn(vm);
+        long ownerId = createVMScheduleCmd.getEntityOwnerId();
+        Assert.assertEquals(vm.getAccountId(), ownerId);
+    }
+
+    /**
+     * given: "We have an EntityManager and CreateVMScheduleCmd"
+     * when: "CreateVMScheduleCmd.getEntityOwnerId is executed for a VM which doesn't exist"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @Test(expected = InvalidParameterValueException.class)
+    public void testFailureGetEntityOwnerId() {
+        Mockito.when(entityManager.findById(VirtualMachine.class, createVMScheduleCmd.getVmId())).thenReturn(null);
+        long ownerId = createVMScheduleCmd.getEntityOwnerId();
+    }
+}
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
new file mode 100644
index 0000000..6adfc2b
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.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.vm;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.db.EntityManager;
+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.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.security.InvalidParameterException;
+
+public class DeleteVMScheduleCmdTest {
+    @Mock
+    public VMScheduleManager vmScheduleManager;
+    @Mock
+    public EntityManager entityManager;
+
+    @InjectMocks
+    private DeleteVMScheduleCmd deleteVMScheduleCmd = new DeleteVMScheduleCmd();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and DeleteVMScheduleCmd"
+     * when: "VMScheduleManager.removeSchedule() is executed returning 1 row"
+     * then: "a Success response is created"
+     */
+    @Test
+    public void testSuccessfulExecution() {
+        final SuccessResponse response = new SuccessResponse();
+        response.setResponseName(deleteVMScheduleCmd.getCommandName());
+        response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+
+        Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(1L);
+        deleteVMScheduleCmd.execute();
+        SuccessResponse actualResponse = (SuccessResponse) deleteVMScheduleCmd.getResponseObject();
+        Assert.assertEquals(response.getResponseName(), actualResponse.getResponseName());
+        Assert.assertEquals(response.getObjectName(), actualResponse.getObjectName());
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and DeleteVMScheduleCmd"
+     * when: "VMScheduleManager.removeSchedule() is executed returning 0 row"
+     * then: "ServerApiException is thrown"
+     */
+    @Test(expected = ServerApiException.class)
+    public void testServerApiException() {
+        Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(0L);
+        deleteVMScheduleCmd.execute();
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and DeleteVMScheduleCmd"
+     * when: "DeleteVMScheduleCmd is executed with an invalid parameter"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @Test(expected = InvalidParameterException.class)
+    public void testInvalidParameterException() {
+        Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterException.class);
+        deleteVMScheduleCmd.execute();
+    }
+
+    /**
+     * given: "We have an EntityManager and DeleteVMScheduleCmd"
+     * when: "DeleteVMScheduleCmd.getEntityOwnerId is executed for a VM which does exist"
+     * then: "owner of that VM is returned"
+     */
+    @Test
+    public void testSuccessfulGetEntityOwnerId() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(entityManager.findById(VirtualMachine.class, deleteVMScheduleCmd.getVmId())).thenReturn(vm);
+        long ownerId = deleteVMScheduleCmd.getEntityOwnerId();
+        Assert.assertEquals(vm.getAccountId(), ownerId);
+    }
+
+    /**
+     * given: "We have an EntityManager and DeleteVMScheduleCmd"
+     * when: "DeleteVMScheduleCmd.getEntityOwnerId is executed for a VM which doesn't exist"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @Test(expected = InvalidParameterValueException.class)
+    public void testFailureGetEntityOwnerId() {
+        Mockito.when(entityManager.findById(VirtualMachine.class, deleteVMScheduleCmd.getVmId())).thenReturn(null);
+        long ownerId = deleteVMScheduleCmd.getEntityOwnerId();
+    }
+}
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
new file mode 100644
index 0000000..18657b4
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.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.vm;
+
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class ListVMScheduleCmdTest {
+    @Mock
+    public VMScheduleManager vmScheduleManager;
+    @InjectMocks
+    private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * given: "We have a VMScheduleManager with 0 schedules and ListVMScheduleCmd"
+     * when: "ListVMScheduleCmd is executed"
+     * then: "a list of size 0 is returned"
+     */
+    @Test
+    public void testEmptyResponse() {
+        ListResponse<VMScheduleResponse> response = new ListResponse<VMScheduleResponse>();
+        response.setResponses(new ArrayList<VMScheduleResponse>());
+        Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response);
+        listVMScheduleCmd.execute();
+        ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
+        Assert.assertEquals(response, actualResponseObject);
+        Assert.assertEquals(0L, actualResponseObject.getResponses().size());
+    }
+
+    /**
+     * given: "We have a VMScheduleManager with 1 schedule and ListVMScheduleCmd"
+     * when: "ListVMScheduleCmd is executed"
+     * then: "a list of size 1 is returned"
+     */
+    @Test
+    public void testNonEmptyResponse() {
+        ListResponse<VMScheduleResponse> listResponse = new ListResponse<VMScheduleResponse>();
+        VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class);
+        listResponse.setResponses(Collections.singletonList(response));
+        Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(listResponse);
+        listVMScheduleCmd.execute();
+        ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
+        Assert.assertEquals(listResponse, actualResponseObject);
+        Assert.assertEquals(1L, actualResponseObject.getResponses().size());
+        Assert.assertEquals(response, actualResponseObject.getResponses().get(0));
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and ListVMScheduleCmd"
+     * when: "ListVMScheduleCmd is executed with an invalid parameter"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @Test(expected = InvalidParameterException.class)
+    public void testInvalidParameterException() {
+        Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterException.class);
+        listVMScheduleCmd.execute();
+        ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
+    }
+}
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
new file mode 100644
index 0000000..044685b
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.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.vm;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.cloudstack.vm.schedule.VMSchedule;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.security.InvalidParameterException;
+
+public class UpdateVMScheduleCmdTest {
+    @Mock
+    public VMScheduleManager vmScheduleManager;
+    @Mock
+    public EntityManager entityManager;
+    @InjectMocks
+    private UpdateVMScheduleCmd updateVMScheduleCmd = new UpdateVMScheduleCmd();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and UpdateVMScheduleCmd"
+     * when: "UpdateVMScheduleCmd is executed successfully"
+     * then: "a VMSchedule response is created"
+     */
+    @Test
+    public void testSuccessfulExecution() {
+        VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
+        Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenReturn(vmScheduleResponse);
+        updateVMScheduleCmd.execute();
+        Assert.assertEquals(vmScheduleResponse, updateVMScheduleCmd.getResponseObject());
+    }
+
+    /**
+     * given: "We have a VMScheduleManager and UpdateVMScheduleCmd"
+     * when: "UpdateVMScheduleCmd is executed with an invalid parameter"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @Test(expected = InvalidParameterException.class)
+    public void testInvalidParameterException() {
+        Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterException.class);
+        updateVMScheduleCmd.execute();
+    }
+
+    /**
+     * given: "We have an EntityManager and UpdateVMScheduleCmd"
+     * when: "UpdateVMScheduleCmd.getEntityOwnerId is executed for a VM which does exist"
+     * then: "owner of that VM is returned"
+     */
+    @Test
+    public void testSuccessfulGetEntityOwnerId() {
+        VMSchedule vmSchedule = Mockito.mock(VMSchedule.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);
+
+        long ownerId = updateVMScheduleCmd.getEntityOwnerId();
+        Assert.assertEquals(vm.getAccountId(), ownerId);
+    }
+
+    /**
+     * given: "We have an EntityManager and UpdateVMScheduleCmd"
+     * when: "UpdateVMScheduleCmd.getEntityOwnerId is executed for a VM Schedule which doesn't exist"
+     * then: "an InvalidParameterException is thrown"
+     */
+    @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();
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmdTest.java
new file mode 100644
index 0000000..226ff88
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmdTest.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.volume;
+
+import com.cloud.exception.InvalidParameterValueException;
+import junit.framework.TestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CheckAndRepairVolumeCmdTest extends TestCase {
+    private CheckAndRepairVolumeCmd checkAndRepairVolumeCmd;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setup() {
+        closeable = MockitoAnnotations.openMocks(this);
+        checkAndRepairVolumeCmd = new CheckAndRepairVolumeCmd();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetRepair() {
+        ReflectionTestUtils.setField(checkAndRepairVolumeCmd, "repair", "all");
+        assertEquals("all", checkAndRepairVolumeCmd.getRepair());
+
+        ReflectionTestUtils.setField(checkAndRepairVolumeCmd, "repair", "LEAKS");
+        assertEquals("leaks", checkAndRepairVolumeCmd.getRepair());
+
+        ReflectionTestUtils.setField(checkAndRepairVolumeCmd, "repair", null);
+        assertNull(checkAndRepairVolumeCmd.getRepair());
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetRepairInvalid() {
+        ReflectionTestUtils.setField(checkAndRepairVolumeCmd, "repair", "RANDOM STRING");
+        checkAndRepairVolumeCmd.getRepair();
+    }
+}
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 92ac005..79f27fd 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
@@ -30,14 +30,15 @@
 import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.response.VpcResponse;
 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.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CreateVPCCmdTest extends TestCase {
 
     @Mock
@@ -48,91 +49,107 @@
     public AccountService _accountService;
     private ResponseGenerator responseGenerator;
     @InjectMocks
-    CreateVPCCmd cmd = new CreateVPCCmd() {
-        @Override
-        public Long getEntityId() {
-            return 2L;
-        }
-    };
+    CreateVPCCmd cmd = new CreateVPCCmd();
 
+    @Test
     public void testGetAccountName() {
         String accountName = "admin";
         ReflectionTestUtils.setField(cmd, "accountName", accountName);
         Assert.assertEquals(cmd.getAccountName(), accountName);
     }
 
+    @Test
     public void testGetDomainId() {
         Long domainId = 1L;
         ReflectionTestUtils.setField(cmd, "domainId", domainId);
         Assert.assertEquals(cmd.getDomainId(), domainId);
     }
 
+    @Test
     public void testGetZoneId() {
         Long zoneId = 1L;
         ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
         Assert.assertEquals(cmd.getZoneId(), zoneId);
     }
 
+    @Test
     public void testGetVpcName() {
         String vpcName = "vpcNet";
         ReflectionTestUtils.setField(cmd, "vpcName", vpcName);
         Assert.assertEquals(cmd.getVpcName(), vpcName);
     }
 
+    @Test
     public void testGetCidr() {
         String cidr = "10.0.0.0/8";
         ReflectionTestUtils.setField(cmd, "cidr", cidr);
         Assert.assertEquals(cmd.getCidr(), cidr);
     }
 
+    @Test
     public void testGetDisplayText() {
         String displayText = "VPC Network";
         ReflectionTestUtils.setField(cmd, "displayText", displayText);
         Assert.assertEquals(cmd.getDisplayText(), displayText);
     }
 
+    @Test
     public void testGetVpcOffering() {
         Long vpcOffering = 1L;
         ReflectionTestUtils.setField(cmd, "vpcOffering", vpcOffering);
         Assert.assertEquals(cmd.getVpcOffering(), vpcOffering);
     }
 
+    @Test
     public void testGetNetworkDomain() {
         String netDomain = "cs1cloud.internal";
         ReflectionTestUtils.setField(cmd, "networkDomain", netDomain);
         Assert.assertEquals(cmd.getNetworkDomain(), netDomain);
     }
 
+    @Test
     public void testGetPublicMtuWhenNotSet() {
         Integer publicMtu = null;
         ReflectionTestUtils.setField(cmd, "publicMtu", publicMtu);
         Assert.assertEquals(NetworkService.DEFAULT_MTU, cmd.getPublicMtu());
     }
 
+    @Test
     public void testGetPublicMtuWhenSet() {
         Integer publicMtu = 1450;
         ReflectionTestUtils.setField(cmd, "publicMtu", publicMtu);
         Assert.assertEquals(cmd.getPublicMtu(), publicMtu);
     }
 
+    @Test
     public void testIsStartWhenNull() {
         Boolean start = null;
         ReflectionTestUtils.setField(cmd, "start", start);
         Assert.assertTrue(cmd.isStart());
     }
 
+    @Test
     public void testIsStartWhenValidValuePassed() {
         Boolean start = true;
         ReflectionTestUtils.setField(cmd, "start", start);
         Assert.assertTrue(cmd.isStart());
     }
 
+    @Test
     public void testGetDisplayVpc() {
         Boolean display = true;
         ReflectionTestUtils.setField(cmd, "display", display);
         Assert.assertTrue(cmd.getDisplayVpc());
     }
 
+    @Test
+    public void testGetDisplayTextWhenEmpty() {
+        String netName = "net-vpc";
+        ReflectionTestUtils.setField(cmd, "vpcName", netName);
+        Assert.assertEquals(cmd.getDisplayText(), netName);
+    }
+
+    @Test
     public void testCreate() throws ResourceAllocationException {
         Vpc vpc = Mockito.mock(Vpc.class);
         ReflectionTestUtils.setField(cmd, "zoneId", 1L);
@@ -143,17 +160,18 @@
         ReflectionTestUtils.setField(cmd, "networkDomain", "cs1cloud.internal");
         ReflectionTestUtils.setField(cmd, "display", true);
         ReflectionTestUtils.setField(cmd, "publicMtu", 1450);
-        Mockito.when(_accountService.finalyzeAccountId(Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(1L);
-        Mockito.when(cmd.getEntityOwnerId()).thenReturn(1L);
         Mockito.when(_vpcService.createVpc(Mockito.any(CreateVPCCmd.class))).thenReturn(vpc);
         cmd.create();
         Mockito.verify(_vpcService, Mockito.times(1)).createVpc(Mockito.any(CreateVPCCmd.class));
     }
 
+    @Test
     public void testExecute() throws ResourceUnavailableException, InsufficientCapacityException {
         ReflectionTestUtils.setField(cmd, "start", true);
         Vpc vpc = Mockito.mock(Vpc.class);
         VpcResponse response = Mockito.mock(VpcResponse.class);
+
+        ReflectionTestUtils.setField(cmd, "id", 1L);
         responseGenerator = Mockito.mock(ResponseGenerator.class);
         Mockito.when(_vpcService.startVpc(1L, true)).thenReturn(true);
         Mockito.when(_entityMgr.findById(Mockito.eq(Vpc.class), Mockito.any(Long.class))).thenReturn(vpc);
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 3f27f4c..acb2dc6 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
@@ -17,21 +17,23 @@
 
 package org.apache.cloudstack.api.command.user.vpc;
 
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.network.vpc.Vpc;
 import com.cloud.network.vpc.VpcService;
 import junit.framework.TestCase;
 import org.apache.cloudstack.api.ResponseGenerator;
-import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.response.VpcResponse;
 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.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class UpdateVPCCmdTest extends TestCase {
 
     @Mock
@@ -42,37 +44,43 @@
     @InjectMocks
     UpdateVPCCmd cmd = new UpdateVPCCmd();
 
+    @Test
     public void testGetVpcName() {
         String vpcName = "updatedVpcName";
         ReflectionTestUtils.setField(cmd, "vpcName", vpcName);
         Assert.assertEquals(cmd.getVpcName(), vpcName);
     }
 
+    @Test
     public void testGetDisplayText() {
         String displayText = "Updated VPC Name";
         ReflectionTestUtils.setField(cmd, "displayText", displayText);
         Assert.assertEquals(cmd.getDisplayText(), displayText);
     }
 
+    @Test
     public void testGetId() {
         Long id = 1L;
         ReflectionTestUtils.setField(cmd, "id", id);
         Assert.assertEquals(cmd.getId(), id);
     }
 
+    @Test
     public void testIsDisplayVpc() {
         Boolean display = true;
         ReflectionTestUtils.setField(cmd, "display", display);
         Assert.assertEquals(cmd.isDisplayVpc(), display);
     }
 
+    @Test
     public void testGetPublicMtu() {
         Integer publicMtu = 1450;
         ReflectionTestUtils.setField(cmd, "publicMtu", publicMtu);
         Assert.assertEquals(cmd.getPublicMtu(), publicMtu);
     }
 
-    public void testExecute() {
+    @Test
+    public void testExecute() throws ResourceUnavailableException, InsufficientCapacityException {
         ReflectionTestUtils.setField(cmd, "id", 1L);
         ReflectionTestUtils.setField(cmd, "vpcName", "updatedVpcName");
         ReflectionTestUtils.setField(cmd, "displayText", "Updated VPC Name");
@@ -84,11 +92,8 @@
         VpcResponse response = Mockito.mock(VpcResponse.class);
         responseGenerator = Mockito.mock(ResponseGenerator.class);
         cmd._responseGenerator = responseGenerator;
-        Mockito.when(_vpcService.updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(),
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt())).thenReturn(vpc);
-        Mockito.when(responseGenerator.createVpcResponse(ResponseObject.ResponseView.Full, vpc)).thenReturn(response);
         Mockito.verify(_vpcService, Mockito.times(0)).updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(),
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
+                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString());
 
     }
 }
diff --git a/api/src/test/java/org/apache/cloudstack/api/response/VnfNicResponseTest.java b/api/src/test/java/org/apache/cloudstack/api/response/VnfNicResponseTest.java
new file mode 100644
index 0000000..cdfbf45
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/response/VnfNicResponseTest.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.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 VnfNicResponseTest {
+
+    static long deviceId = 0L;
+    static String deviceName = "eth0";
+    static boolean required = true;
+    static boolean management = false;
+    static String description = "description of vnf nic";
+
+    static String networkUuid = "networkuuid";
+    static String networkName = "networkname";
+
+    @Test
+    public void testNewVnfNicResponse() {
+        final VnfNicResponse response = new VnfNicResponse(deviceId, deviceName, required, management, description);
+        Assert.assertEquals(deviceId, response.getDeviceId());
+        Assert.assertEquals(deviceName, response.getName());
+        Assert.assertEquals(required, response.isRequired());
+        Assert.assertEquals(management, response.isManagement());
+        Assert.assertEquals(description, response.getDescription());
+    }
+
+    @Test
+    public void testSetVnfNicResponse() {
+        final VnfNicResponse response = new VnfNicResponse();
+        response.setNetworkId(networkUuid);
+        response.setNetworkName(networkName);
+        Assert.assertEquals(networkUuid, response.getNetworkId());
+        Assert.assertEquals(networkName, response.getNetworkName());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/api/response/VnfTemplateResponseTest.java b/api/src/test/java/org/apache/cloudstack/api/response/VnfTemplateResponseTest.java
new file mode 100644
index 0000000..38875c0
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/api/response/VnfTemplateResponseTest.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.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 VnfTemplateResponseTest {
+
+    @Test
+    public void testAddVnfNicToResponse() {
+        final VnfTemplateResponse response = new VnfTemplateResponse();
+
+        response.addVnfNic(new VnfNicResponse());
+        response.addVnfNic(new VnfNicResponse());
+
+        Assert.assertEquals(2, response.getVnfNics().size());
+    }
+
+    @Test
+    public void testAddVnfDetailToResponse() {
+        final VnfTemplateResponse response = new VnfTemplateResponse();
+
+        response.addVnfDetail("key1", "value1");
+        response.addVnfDetail("key2", "value2");
+        response.addVnfDetail("key3", "value3");
+
+        Assert.assertEquals(3, response.getVnfDetails().size());
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithmTest.java b/api/src/test/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithmTest.java
new file mode 100644
index 0000000..8837981
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithmTest.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.cluster;
+
+import com.cloud.utils.Ternary;
+import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getMetricValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterDrsAlgorithmTest extends TestCase {
+
+    @Test
+    public void testGetMetricValue() {
+        List<Ternary<Boolean, String, Double>> testData = List.of(
+                new Ternary<>(true, "free", 0.4),
+                new Ternary<>(false, "free", 40.0),
+                new Ternary<>(true, "used", 0.3),
+                new Ternary<>(false, "used", 30.0)
+        );
+
+        long used = 30;
+        long free = 40;
+        long total = 100;
+
+        for (Ternary<Boolean, String, Double> data : testData) {
+            boolean useRatio = data.first();
+            String metricType = data.second();
+            double expectedValue = data.third();
+
+            try (MockedStatic<ClusterDrsAlgorithm> ignored = Mockito.mockStatic(ClusterDrsAlgorithm.class)) {
+                when(ClusterDrsAlgorithm.getDrsMetricUseRatio(1L)).thenReturn(useRatio);
+                when(ClusterDrsAlgorithm.getDrsMetricType(1L)).thenReturn(metricType);
+                when(ClusterDrsAlgorithm.getMetricValue(anyLong(), anyLong(), anyLong(), anyLong(), any())).thenCallRealMethod();
+
+                assertEquals(expectedValue, getMetricValue(1, used, free, total, null));
+            }
+        }
+    }
+
+    @Test
+    public void testGetMetricValueWithSkipThreshold() {
+        List<Ternary<Boolean, String, Double>> testData = List.of(
+                new Ternary<>(true, "free", 0.15),
+                new Ternary<>(false, "free", 15.0),
+                new Ternary<>(true, "used", null),
+                new Ternary<>(false, "used", null)
+        );
+
+        long used = 80;
+        long free = 15;
+        long total = 100;
+
+        for (Ternary<Boolean, String, Double> data : testData) {
+            boolean useRatio = data.first();
+            String metricType = data.second();
+            Double expectedValue = data.third();
+            float skipThreshold = metricType.equals("free") ? 0.1f : 0.7f;
+
+            try (MockedStatic<ClusterDrsAlgorithm> ignored = Mockito.mockStatic(ClusterDrsAlgorithm.class)) {
+                when(ClusterDrsAlgorithm.getDrsMetricUseRatio(1L)).thenReturn(useRatio);
+                when(ClusterDrsAlgorithm.getDrsMetricType(1L)).thenReturn(metricType);
+                when(ClusterDrsAlgorithm.getMetricValue(anyLong(), anyLong(), anyLong(), anyLong(), anyFloat())).thenCallRealMethod();
+
+                assertEquals(expectedValue, ClusterDrsAlgorithm.getMetricValue(1L, used, free, total, skipThreshold));
+            }
+        }
+    }
+}
diff --git a/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateUtilsTest.java b/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateUtilsTest.java
new file mode 100644
index 0000000..7ba8564
--- /dev/null
+++ b/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateUtilsTest.java
@@ -0,0 +1,261 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.template;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.network.VNF.VnfNic;
+import com.cloud.storage.Storage;
+import com.cloud.template.VirtualMachineTemplate;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VnfTemplateUtilsTest {
+
+    @Test
+    public void testGetVnfNicsListAllGood() {
+        final Map<String, Object> vnfNics = new HashMap<>();
+        vnfNics.put("0", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "1"),
+                Map.entry("name", "eth1"),
+                Map.entry("required", "true"),
+                Map.entry("description", "The second NIC of VNF appliance")
+        )));
+        vnfNics.put("1", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "2"),
+                Map.entry("name", "eth2"),
+                Map.entry("required", "false"),
+                Map.entry("description", "The third NIC of VNF appliance")
+        )));
+        vnfNics.put("2", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "0"),
+                Map.entry("name", "eth0"),
+                Map.entry("description", "The first NIC of VNF appliance")
+        )));
+
+        Map<String, Object> vnfNicsMock = Mockito.mock(Map.class);
+        Mockito.when(vnfNicsMock.values()).thenReturn(vnfNics.values());
+
+        List<VnfNic> nicsList = VnfTemplateUtils.getVnfNicsList(vnfNicsMock);
+        Mockito.verify(vnfNicsMock).values();
+
+        Assert.assertEquals(3, nicsList.size());
+        Assert.assertEquals(0, nicsList.get(0).getDeviceId());
+        Assert.assertEquals("eth0", nicsList.get(0).getName());
+        Assert.assertTrue(nicsList.get(0).isRequired());
+        Assert.assertEquals(1, nicsList.get(1).getDeviceId());
+        Assert.assertEquals("eth1", nicsList.get(1).getName());
+        Assert.assertTrue(nicsList.get(1).isRequired());
+        Assert.assertEquals(2, nicsList.get(2).getDeviceId());
+        Assert.assertEquals("eth2", nicsList.get(2).getName());
+        Assert.assertFalse(nicsList.get(2).isRequired());
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetVnfNicsListWithEmptyName() {
+        final Map<String, Object> vnfNics = new HashMap<>();
+        vnfNics.put("0", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "1"),
+                Map.entry("required", "true"),
+                Map.entry("description", "The second NIC of VNF appliance")
+        )));
+
+        Map<String, Object> vnfNicsMock = Mockito.mock(Map.class);
+        Mockito.when(vnfNicsMock.values()).thenReturn(vnfNics.values());
+
+        List<VnfNic> nicsList = VnfTemplateUtils.getVnfNicsList(vnfNicsMock);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetVnfNicsListWithEmptyDeviceId() {
+        final Map<String, Object> vnfNics = new HashMap<>();
+        vnfNics.put("0", new HashMap<>(Map.ofEntries(
+                Map.entry("name", "eth1"),
+                Map.entry("required", "true"),
+                Map.entry("description", "The second NIC of VNF appliance")
+        )));
+
+        Map<String, Object> vnfNicsMock = Mockito.mock(Map.class);
+        Mockito.when(vnfNicsMock.values()).thenReturn(vnfNics.values());
+
+        List<VnfNic> nicsList = VnfTemplateUtils.getVnfNicsList(vnfNicsMock);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetVnfNicsListWithInvalidDeviceId() {
+        final Map<String, Object> vnfNics = new HashMap<>();
+        vnfNics.put("0", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "invalid"),
+                Map.entry("name", "eth1"),
+                Map.entry("required", "true"),
+                Map.entry("description", "The second NIC of VNF appliance")
+        )));
+
+        Map<String, Object> vnfNicsMock = Mockito.mock(Map.class);
+        Mockito.when(vnfNicsMock.values()).thenReturn(vnfNics.values());
+
+        List<VnfNic> nicsList = VnfTemplateUtils.getVnfNicsList(vnfNicsMock);
+    }
+
+    @Test
+    public void testValidateVnfNicsAllGood() {
+        VnfNic nic1 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic1.getDeviceId()).thenReturn(0L);
+        Mockito.when(nic1.isRequired()).thenReturn(true);
+
+        VnfNic nic2 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic2.getDeviceId()).thenReturn(1L);
+        Mockito.when(nic2.isRequired()).thenReturn(true);
+
+        VnfNic nic3 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic3.getDeviceId()).thenReturn(2L);
+        Mockito.when(nic3.isRequired()).thenReturn(false);
+
+        List<VnfNic> nicsList = Arrays.asList(nic1, nic2, nic3);
+
+        VnfTemplateUtils.validateVnfNics(nicsList);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateVnfNicsStartWithNonzero() {
+        VnfNic nic1 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic1.getDeviceId()).thenReturn(1L);
+
+        VnfNic nic2 = Mockito.mock(VnfNic.class);
+
+        VnfNic nic3 = Mockito.mock(VnfNic.class);
+
+        List<VnfNic> nicsList = Arrays.asList(nic1, nic2, nic3);
+
+        VnfTemplateUtils.validateVnfNics(nicsList);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateVnfNicsWithNonConstantDeviceIds() {
+        VnfNic nic1 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic1.getDeviceId()).thenReturn(0L);
+        Mockito.when(nic1.isRequired()).thenReturn(true);
+
+        VnfNic nic2 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic2.getDeviceId()).thenReturn(2L);
+
+        VnfNic nic3 = Mockito.mock(VnfNic.class);
+
+        List<VnfNic> nicsList = Arrays.asList(nic1, nic2, nic3);
+
+        VnfTemplateUtils.validateVnfNics(nicsList);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateVnfNicsWithInvalidRequired() {
+        VnfNic nic1 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic1.getDeviceId()).thenReturn(0L);
+        Mockito.when(nic1.isRequired()).thenReturn(true);
+
+        VnfNic nic2 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic2.getDeviceId()).thenReturn(1L);
+        Mockito.when(nic2.isRequired()).thenReturn(false);
+
+        VnfNic nic3 = Mockito.mock(VnfNic.class);
+        Mockito.when(nic3.getDeviceId()).thenReturn(2L);
+        Mockito.when(nic3.isRequired()).thenReturn(true);
+
+        List<VnfNic> nicsList = Arrays.asList(nic1, nic2, nic3);
+
+        VnfTemplateUtils.validateVnfNics(nicsList);
+    }
+
+    @Test
+    public void testValidateApiCommandParamsAllGood() {
+        VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class);
+        RegisterVnfTemplateCmd cmd = Mockito.mock(RegisterVnfTemplateCmd.class);
+        Map<String, String> vnfDetails = Mockito.spy(new HashMap<>());
+        vnfDetails.put("username", "admin");
+        vnfDetails.put("password", "password");
+        vnfDetails.put("version", "4.19.0");
+        vnfDetails.put("vendor", "cloudstack");
+        Mockito.when(cmd.getVnfDetails()).thenReturn(vnfDetails);
+
+        VnfTemplateUtils.validateApiCommandParams(cmd, template);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateApiCommandParamsInvalidAccessMethods() {
+        VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class);
+        Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
+        UpdateVnfTemplateCmd cmd = Mockito.mock(UpdateVnfTemplateCmd.class);
+        Map<String, String> vnfDetails = Mockito.spy(new HashMap<>());
+        vnfDetails.put("access_methods", "invalid");
+        Mockito.when(cmd.getVnfDetails()).thenReturn(vnfDetails);
+
+        VnfTemplateUtils.validateApiCommandParams(cmd, template);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateApiCommandParamsInvalidAccessDetails() {
+        VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class);
+        Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
+        UpdateVnfTemplateCmd cmd = Mockito.mock(UpdateVnfTemplateCmd.class);
+        Map<String, String> vnfDetails = Mockito.spy(new HashMap<>());
+        vnfDetails.put("invalid", "value");
+        Mockito.when(cmd.getVnfDetails()).thenReturn(vnfDetails);
+
+        VnfTemplateUtils.validateApiCommandParams(cmd, template);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateApiCommandParamsInvalidTemplateType() {
+        VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class);
+        Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.USER);
+        UpdateVnfTemplateCmd cmd = Mockito.mock(UpdateVnfTemplateCmd.class);
+
+        VnfTemplateUtils.validateApiCommandParams(cmd, template);
+    }
+
+    @Test
+    public void testValidateVnfCidrList() {
+        List<String> cidrList = new ArrayList<>();
+        cidrList.add("10.10.10.0/24");
+        VnfTemplateUtils.validateVnfCidrList(cidrList);
+    }
+
+    @Test
+    public void testValidateVnfCidrListWithEmptyList() {
+        List<String> cidrList = new ArrayList<>();
+        VnfTemplateUtils.validateVnfCidrList(cidrList);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateVnfCidrListwithInvalidList() {
+        List<String> cidrList = new ArrayList<>();
+        cidrList.add("10.10.10.0/24");
+        cidrList.add("10.10.10.0/33");
+        VnfTemplateUtils.validateVnfCidrList(cidrList);
+    }
+}
diff --git a/api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/client/conf/db.properties.in b/client/conf/db.properties.in
index 572cfbc..8f31aff 100644
--- a/client/conf/db.properties.in
+++ b/client/conf/db.properties.in
@@ -29,6 +29,10 @@
 db.cloud.port=3306
 db.cloud.name=cloud
 
+# Connection URI to the database "cloud". When this property is set, only the following properties will be used along with it: db.cloud.maxActive, db.cloud.maxIdle, db.cloud.maxWait, db.cloud.username, db.cloud.password, db.cloud.driver, db.cloud.validationQuery, db.cloud.isolation.level. Other properties will be ignored.
+db.cloud.uri=
+
+
 # CloudStack database tuning parameters
 db.cloud.maxActive=250
 db.cloud.maxIdle=30
@@ -61,6 +65,10 @@
 db.usage.port=3306
 db.usage.name=cloud_usage
 
+# Connection URI to the database "usage". When this property is set, only the following properties will be used along with it: db.usage.maxActive, db.cloud.maxIdle, db.cloud.maxWait, db.usage.username, db.usage.password, db.usage.driver, db.usage.validationQuery, db.usage.isolation.level. Other properties will be ignored.
+db.usage.uri=
+
+
 # usage database tuning parameters
 db.usage.maxActive=100
 db.usage.maxIdle=30
@@ -79,6 +87,9 @@
 db.simulator.maxWait=10000
 db.simulator.autoReconnect=true
 
+# Connection URI to the database "simulator". When this property is set, only the following properties will be used along with it: db.simulator.host, db.simulator.port, db.simulator.name, db.simulator.autoReconnect. Other properties will be ignored.
+db.simulator.uri=
+
 
 # High Availability And Cluster Properties
 db.ha.enabled=false
diff --git a/client/conf/server.properties.in b/client/conf/server.properties.in
index 42d98a6..57d81c8 100644
--- a/client/conf/server.properties.in
+++ b/client/conf/server.properties.in
@@ -29,19 +29,22 @@
 # Max inactivity time in minutes for the session
 session.timeout=30
 
-# Options to configure and enable HTTPS on management server
+# Max allowed API request payload/content size in bytes
+request.content.size=1048576
+
+# Options to configure and enable HTTPS on the management server
 #
-# For management server to pickup these configuration settings, the configured
-# keystore file should exists and be readable by the management server.
+# For the management server to pick up these configuration settings, the configured
+# keystore file should exist and be readable by the management server.
 https.enable=false
 https.port=8443
 
 # The keystore and manager passwords are assumed to be same.
 https.keystore=/etc/cloudstack/management/cloud.jks
-# If you want to encrypt the password follow the steps mentioned at:
-# http://docs.cloudstack.apache.org/projects/archived-cloudstack-administration/en/latest/management.html?highlight=jasypt#changing-the-database-password
+# If you want to encrypt the password itself, follow the steps mentioned at:
+http://docs.cloudstack.apache.org/en/latest/adminguide/management.html?highlight=jasypt#changing-the-database-password
 https.keystore.password=vmops.com
-# If an encrypted password is used, specify the encryption type - valid types: file, env (set environment variable CLOUD_SECRET_KEY), web
+# If an encrypted password is used, specify the encryption type. Valid types: file, web, env (set environment variable CLOUD_SECRET_KEY)
 # password.encryption.type=none
 
 # The path to webapp directory
diff --git a/client/pom.xml b/client/pom.xml
index 6253975..b27c702 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <repositories>
         <repository>
@@ -68,8 +68,58 @@
             <artifactId>jetty-util</artifactId>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-agent</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-cluster</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-config</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-db</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-events</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-jobs</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-managed-context</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-security</artifactId>
+            <version>${project.version}</version>
         </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
@@ -83,6 +133,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-volume-adaptive</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-plugin-storage-volume-solidfire</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -113,6 +168,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-volume-primera</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-volume-flasharray</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-server</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -163,6 +228,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-user-authenticator-oauth2</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-plugin-user-authenticator-pbkdf2</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -354,6 +424,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-userdata-cloud-init</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-userdata</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-mom-rabbitmq</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -519,6 +599,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-cluster-drs-balanced</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-cluster-drs-condensed</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-plugin-database-quota</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -547,6 +637,36 @@
             <artifactId>cloud-plugin-integrations-kubernetes-service</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+           <groupId>org.apache.cloudstack</groupId>
+           <artifactId>cloud-plugin-shutdown</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-object</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-object-minio</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-object-simulator</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+           <groupId>org.apache.cloudstack</groupId>
+           <artifactId>cloud-usage</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+        <dependency>
+           <groupId>org.apache.cloudstack</groupId>
+           <artifactId>cloud-utils</artifactId>
+           <version>${project.version}</version>
+       </dependency>
     </dependencies>
     <build>
         <plugins>
@@ -589,8 +709,8 @@
                 <dependencies>
                     <!-- specify the dependent jdbc driver here -->
                     <dependency>
-                        <groupId>mysql</groupId>
-                        <artifactId>mysql-connector-java</artifactId>
+                        <groupId>com.mysql</groupId>
+                        <artifactId>mysql-connector-j</artifactId>
                         <version>${cs.mysql.version}</version>
                     </dependency>
                     <dependency>
@@ -778,8 +898,8 @@
                                     <outputDirectory>${project.build.directory}/pythonlibs</outputDirectory>
                                 </artifactItem>
                                 <artifactItem>
-                                    <groupId>mysql</groupId>
-                                    <artifactId>mysql-connector-java</artifactId>
+                                    <groupId>com.mysql</groupId>
+                                    <artifactId>mysql-connector-j</artifactId>
                                     <overWrite>false</overWrite>
                                     <outputDirectory>${project.build.directory}/lib</outputDirectory>
                                 </artifactItem>
@@ -808,6 +928,12 @@
                                     <outputDirectory>${project.build.directory}/lib</outputDirectory>
                                 </artifactItem>
                                 <artifactItem>
+                                    <groupId>com.linbit.linstor.api</groupId>
+                                    <artifactId>java-linstor</artifactId>
+                                    <overWrite>false</overWrite>
+                                    <outputDirectory>${project.build.directory}/lib</outputDirectory>
+                                </artifactItem>
+                                <artifactItem>
                                     <groupId>org.bouncycastle</groupId>
                                     <artifactId>bctls-jdk15on</artifactId>
                                     <overWrite>false</overWrite>
@@ -840,8 +966,6 @@
                                     <exclude>com.tngtech.java:junit-dataprovider</exclude>
                                     <exclude>org.mockito:mockito-all</exclude>
                                     <exclude>org.hamcrest:hamcrest-all</exclude>
-                                    <exclude>org.powermock:powermock-module-junit4</exclude>
-                                    <exclude>org.powermock:powermock-api-mockito2</exclude>
                                     <exclude>org.springframework:spring-test</exclude>
                                     <exclude>org.apache.tomcat.embed:tomcat-embed-core</exclude>
                                     <exclude>org.apache.geronimo.specs:geronimo-servlet_3.0_spec</exclude>
@@ -849,9 +973,11 @@
                                     <exclude>org.bouncycastle:bcprov-jdk15on</exclude>
                                     <exclude>org.bouncycastle:bcpkix-jdk15on</exclude>
                                     <exclude>org.bouncycastle:bctls-jdk15on</exclude>
-                                    <exclude>mysql:mysql-connector-java</exclude>
+                                    <exclude>com.mysql:mysql-connector-j</exclude>
                                     <exclude>org.apache.cloudstack:cloud-plugin-storage-volume-storpool</exclude>
                                     <exclude>org.apache.cloudstack:cloud-plugin-storage-volume-linstor</exclude>
+                                    <exclude>org.apache.cloudstack:cloud-usage</exclude>
+                                    <exclude>com.linbit.linstor.api:java-linstor</exclude>
                                 </excludes>
                             </artifactSet>
                             <transformers>
diff --git a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java
index ce92760..fb84e12 100644
--- a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java
+++ b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java
@@ -26,10 +26,10 @@
 import java.net.URL;
 import java.util.Properties;
 
-import com.cloud.utils.Pair;
-import com.cloud.utils.server.ServerProperties;
 import org.apache.commons.daemon.Daemon;
 import org.apache.commons.daemon.DaemonContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
 import org.eclipse.jetty.jmx.MBeanContainer;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -39,19 +39,21 @@
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.server.handler.MovedContextHandler;
 import org.eclipse.jetty.server.handler.RequestLogHandler;
 import org.eclipse.jetty.server.handler.gzip.GzipHandler;
 import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.ssl.KeyStoreScanner;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
 import org.eclipse.jetty.webapp.WebAppContext;
-import org.apache.log4j.Logger;
 
+import com.cloud.utils.Pair;
 import com.cloud.utils.PropertiesUtil;
-import org.apache.commons.lang3.StringUtils;
+import com.cloud.utils.server.ServerProperties;
 
 /***
  * The ServerDaemon class implements the embedded server, it can be started either
@@ -77,6 +79,8 @@
     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";
+    private static final String REQUEST_CONTENT_SIZE_KEY = "request.content.size";
+    private static final int DEFAULT_REQUEST_CONTENT_SIZE = 1048576;
 
     ////////////////////////////////////////////////////////
     /////////////// Server Configuration ///////////////////
@@ -88,6 +92,7 @@
     private int httpPort = 8080;
     private int httpsPort = 8443;
     private int sessionTimeout = 30;
+    private int maxFormContentSize = DEFAULT_REQUEST_CONTENT_SIZE;
     private boolean httpsEnable = false;
     private String accessLogFile = "access.log";
     private String bindInterface = null;
@@ -134,6 +139,7 @@
             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))));
         } catch (final IOException e) {
             LOG.warn("Failed to read configuration from server.properties file", e);
         } finally {
@@ -184,6 +190,7 @@
 
         // Extra config options
         server.setStopAtShutdown(true);
+        server.setAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize);
 
         // HTTPS Connector
         createHttpsConnector(httpConfig);
@@ -240,6 +247,14 @@
             sslConnector.setPort(httpsPort);
             sslConnector.setHost(bindInterface);
             server.addConnector(sslConnector);
+
+            // add scanner to auto-reload certs
+            try {
+                KeyStoreScanner scanner = new KeyStoreScanner(sslContextFactory);
+                server.addBean(scanner);
+            } catch (Exception ex) {
+                LOG.error("failed to set up keystore scanner, manual refresh of certificates will be required", ex);
+            }
         }
     }
 
@@ -247,6 +262,7 @@
         final WebAppContext webApp = new WebAppContext();
         webApp.setContextPath(contextPath);
         webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
+        webApp.setMaxFormContentSize(maxFormContentSize);
 
         // GZIP handler
         final GzipHandler gzipHandler = new GzipHandler();
@@ -345,4 +361,8 @@
     public void setSessionTimeout(int sessionTimeout) {
         this.sessionTimeout = sessionTimeout;
     }
+
+    public void setMaxFormContentSize(int maxFormContentSize) {
+        this.maxFormContentSize = maxFormContentSize;
+    }
 }
diff --git a/core/pom.xml b/core/pom.xml
index 7bc3e3a..a6906e7 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
diff --git a/core/src/main/java/com/cloud/agent/api/CheckGuestOsMappingAnswer.java b/core/src/main/java/com/cloud/agent/api/CheckGuestOsMappingAnswer.java
new file mode 100644
index 0000000..53cfded
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/CheckGuestOsMappingAnswer.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 CheckGuestOsMappingAnswer extends Answer {
+
+    protected CheckGuestOsMappingAnswer() {
+    }
+
+    public CheckGuestOsMappingAnswer(CheckGuestOsMappingCommand cmd) {
+        super(cmd, true, null);
+    }
+
+    public CheckGuestOsMappingAnswer(CheckGuestOsMappingCommand cmd, String details) {
+        super(cmd, false, details);
+    }
+
+    public CheckGuestOsMappingAnswer(CheckGuestOsMappingCommand cmd, Throwable th) {
+        super(cmd, false, th.getMessage());
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/CheckGuestOsMappingCommand.java b/core/src/main/java/com/cloud/agent/api/CheckGuestOsMappingCommand.java
new file mode 100644
index 0000000..11efd80
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/CheckGuestOsMappingCommand.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.agent.api;
+
+import java.util.Objects;
+
+public class CheckGuestOsMappingCommand extends Command {
+    String guestOsName;
+    String guestOsHypervisorMappingName;
+    String hypervisorVersion;
+
+    public CheckGuestOsMappingCommand() {
+        super();
+    }
+
+    public CheckGuestOsMappingCommand(String guestOsName, String guestOsHypervisorMappingName, String hypervisorVersion) {
+        super();
+        this.guestOsName = guestOsName;
+        this.guestOsHypervisorMappingName = guestOsHypervisorMappingName;
+        this.hypervisorVersion = hypervisorVersion;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    public String getGuestOsName() {
+        return guestOsName;
+    }
+
+    public String getGuestOsHypervisorMappingName() {
+        return guestOsHypervisorMappingName;
+    }
+
+    public String getHypervisorVersion() {
+        return hypervisorVersion;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        if (!super.equals(o)) return false;
+        CheckGuestOsMappingCommand that = (CheckGuestOsMappingCommand) o;
+        return Objects.equals(guestOsName, that.guestOsName) && Objects.equals(guestOsHypervisorMappingName, that.guestOsHypervisorMappingName) && Objects.equals(hypervisorVersion, that.hypervisorVersion);
+    }
+}
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 b405517..982e10c 100644
--- a/core/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java
@@ -24,6 +24,7 @@
 
 public class CheckOnHostCommand extends Command {
     HostTO host;
+    boolean reportCheckFailureIfOneStorageIsDown;
 
     protected CheckOnHostCommand() {
     }
@@ -33,10 +34,20 @@
         setWait(20);
     }
 
+    public CheckOnHostCommand(Host host, boolean reportCheckFailureIfOneStorageIsDown) {
+        super();
+        this.host = new HostTO(host);
+        this.reportCheckFailureIfOneStorageIsDown = reportCheckFailureIfOneStorageIsDown;
+    }
+
     public HostTO getHost() {
         return host;
     }
 
+    public boolean isCheckFailedOnOneStorage() {
+        return reportCheckFailureIfOneStorageIsDown;
+    }
+
     @Override
     public boolean executeInSequence() {
         return false;
diff --git a/core/src/main/java/com/cloud/agent/api/CheckVolumeAnswer.java b/core/src/main/java/com/cloud/agent/api/CheckVolumeAnswer.java
new file mode 100644
index 0000000..dd136d8
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/CheckVolumeAnswer.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 com.cloud.agent.api;
+
+@LogLevel(LogLevel.Log4jLevel.Trace)
+public class CheckVolumeAnswer extends Answer {
+
+    private long size;
+
+    CheckVolumeAnswer() {
+    }
+
+    public CheckVolumeAnswer(CheckVolumeCommand cmd, String details, long size) {
+        super(cmd, true, details);
+        this.size = size;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public String getString() {
+        return "CheckVolumeAnswer [size=" + size + "]";
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/CheckVolumeCommand.java b/core/src/main/java/com/cloud/agent/api/CheckVolumeCommand.java
new file mode 100644
index 0000000..b4036be
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/CheckVolumeCommand.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.StorageFilerTO;
+
+@LogLevel(LogLevel.Log4jLevel.Trace)
+public class CheckVolumeCommand extends Command {
+
+    String srcFile;
+
+    StorageFilerTO storageFilerTO;
+
+
+    public String getSrcFile() {
+        return srcFile;
+    }
+
+    public void setSrcFile(String srcFile) {
+        this.srcFile = srcFile;
+    }
+
+    public CheckVolumeCommand() {
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    public String getString() {
+        return "CheckVolumeCommand [srcFile=" + srcFile + "]";
+    }
+
+    public StorageFilerTO getStorageFilerTO() {
+        return storageFilerTO;
+    }
+
+    public void setStorageFilerTO(StorageFilerTO storageFilerTO) {
+        this.storageFilerTO = storageFilerTO;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.java b/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.java
new file mode 100644
index 0000000..8298885
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/ConvertInstanceAnswer.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 com.cloud.agent.api;
+
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+
+public class ConvertInstanceAnswer extends Answer {
+
+    public ConvertInstanceAnswer() {
+        super();
+    }
+    private UnmanagedInstanceTO convertedInstance;
+
+    public ConvertInstanceAnswer(Command command, boolean success, String details) {
+        super(command, success, details);
+    }
+
+    public ConvertInstanceAnswer(Command command, UnmanagedInstanceTO convertedInstance) {
+        super(command, true, "");
+        this.convertedInstance = convertedInstance;
+    }
+
+    public UnmanagedInstanceTO getConvertedInstance() {
+        return convertedInstance;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java
new file mode 100644
index 0000000..63234b0
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.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.api;
+
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.RemoteInstanceTO;
+import com.cloud.hypervisor.Hypervisor;
+
+import java.util.List;
+
+public class ConvertInstanceCommand extends Command {
+
+    private RemoteInstanceTO sourceInstance;
+    private Hypervisor.HypervisorType destinationHypervisorType;
+    private List<String> destinationStoragePools;
+    private DataStoreTO conversionTemporaryLocation;
+
+    public ConvertInstanceCommand() {
+    }
+
+    public ConvertInstanceCommand(RemoteInstanceTO sourceInstance, Hypervisor.HypervisorType destinationHypervisorType,
+                                  List<String> destinationStoragePools, DataStoreTO conversionTemporaryLocation) {
+        this.sourceInstance = sourceInstance;
+        this.destinationHypervisorType = destinationHypervisorType;
+        this.destinationStoragePools = destinationStoragePools;
+        this.conversionTemporaryLocation = conversionTemporaryLocation;
+    }
+
+    public RemoteInstanceTO getSourceInstance() {
+        return sourceInstance;
+    }
+
+    public Hypervisor.HypervisorType getDestinationHypervisorType() {
+        return destinationHypervisorType;
+    }
+
+    public List<String> getDestinationStoragePools() {
+        return destinationStoragePools;
+    }
+
+    public DataStoreTO getConversionTemporaryLocation() {
+        return conversionTemporaryLocation;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.java
new file mode 100644
index 0000000..f6d7cab
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeAnswer.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;
+
+@LogLevel(LogLevel.Log4jLevel.Trace)
+public class CopyRemoteVolumeAnswer extends Answer {
+
+    private String remoteIp;
+    private String filename;
+
+    private long size;
+
+    CopyRemoteVolumeAnswer() {
+    }
+
+    public CopyRemoteVolumeAnswer(CopyRemoteVolumeCommand cmd, String details, String filename, long size) {
+        super(cmd, true, details);
+        this.remoteIp = cmd.getRemoteIp();
+        this.filename = filename;
+        this.size = size;
+    }
+
+    public String getRemoteIp() {
+        return remoteIp;
+    }
+
+    public void setRemoteIp(String remoteIp) {
+        this.remoteIp = remoteIp;
+    }
+
+    public void setFilename(String filename) {
+        this.filename = filename;
+    }
+
+    public String getFilename() {
+        return filename;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public String getString() {
+        return "CopyRemoteVolumeAnswer [remoteIp=" + remoteIp + "]";
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java
new file mode 100644
index 0000000..82bc4d7
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/CopyRemoteVolumeCommand.java
@@ -0,0 +1,101 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.agent.api;
+
+import com.cloud.agent.api.to.StorageFilerTO;
+
+@LogLevel(LogLevel.Log4jLevel.Trace)
+public class CopyRemoteVolumeCommand extends Command {
+
+    String remoteIp;
+    String username;
+    String password;
+    String srcFile;
+
+    String tmpPath;
+
+    StorageFilerTO storageFilerTO;
+
+    public CopyRemoteVolumeCommand(String remoteIp, String username, String password) {
+        this.remoteIp = remoteIp;
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getRemoteIp() {
+        return remoteIp;
+    }
+
+    public void setRemoteIp(String remoteIp) {
+        this.remoteIp = remoteIp;
+    }
+
+    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 getSrcFile() {
+        return srcFile;
+    }
+
+    public void setSrcFile(String srcFile) {
+        this.srcFile = srcFile;
+    }
+
+    public CopyRemoteVolumeCommand() {
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    public String getString() {
+        return "CopyRemoteVolumeCommand [remoteIp=" + remoteIp + "]";
+    }
+
+    public void setTempPath(String tmpPath) {
+        this.tmpPath = tmpPath;
+    }
+
+    public String getTmpPath() {
+        return tmpPath;
+    }
+
+    public StorageFilerTO getStorageFilerTO() {
+        return storageFilerTO;
+    }
+
+    public void setStorageFilerTO(StorageFilerTO storageFilerTO) {
+        this.storageFilerTO = storageFilerTO;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/FenceCommand.java b/core/src/main/java/com/cloud/agent/api/FenceCommand.java
index 88e508a..2158923 100644
--- a/core/src/main/java/com/cloud/agent/api/FenceCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/FenceCommand.java
@@ -19,6 +19,7 @@
 
 package com.cloud.agent.api;
 
+import com.cloud.agent.api.to.HostTO;
 import com.cloud.host.Host;
 import com.cloud.vm.VirtualMachine;
 
@@ -32,6 +33,8 @@
     String hostGuid;
     String hostIp;
     boolean inSeq;
+    HostTO host;
+    boolean reportCheckFailureIfOneStorageIsDown;
 
     public FenceCommand(VirtualMachine vm, Host host) {
         super();
@@ -39,6 +42,7 @@
         hostGuid = host.getGuid();
         hostIp = host.getPrivateIpAddress();
         inSeq = false;
+        this.host = new HostTO(host);
     }
 
     public void setSeq(boolean inseq) {
@@ -61,4 +65,16 @@
     public boolean executeInSequence() {
         return inSeq;
     }
+
+    public HostTO getHost() {
+        return host;
+    }
+
+    public boolean isReportCheckFailureIfOneStorageIsDown() {
+        return reportCheckFailureIfOneStorageIsDown;
+    }
+
+    public void setReportCheckFailureIfOneStorageIsDown(boolean reportCheckFailureIfOneStorageIsDown) {
+        this.reportCheckFailureIfOneStorageIsDown = reportCheckFailureIfOneStorageIsDown;
+    }
 }
diff --git a/core/src/main/java/com/cloud/agent/api/GetHypervisorGuestOsNamesAnswer.java b/core/src/main/java/com/cloud/agent/api/GetHypervisorGuestOsNamesAnswer.java
new file mode 100644
index 0000000..d59d66c
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/GetHypervisorGuestOsNamesAnswer.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 com.cloud.agent.api;
+
+import java.util.List;
+import java.util.Objects;
+
+import com.cloud.utils.Pair;
+
+public class GetHypervisorGuestOsNamesAnswer extends Answer {
+    private List<Pair<String, String>> hypervisorGuestOsNames;
+
+    protected GetHypervisorGuestOsNamesAnswer() {
+    }
+
+    public GetHypervisorGuestOsNamesAnswer(GetHypervisorGuestOsNamesCommand cmd, List<Pair<String, String>> hypervisorGuestOsNames) {
+        super(cmd, true, null);
+        this.hypervisorGuestOsNames = hypervisorGuestOsNames;
+    }
+
+    public GetHypervisorGuestOsNamesAnswer(GetHypervisorGuestOsNamesCommand cmd, String details) {
+        super(cmd, false, details);
+    }
+
+    public GetHypervisorGuestOsNamesAnswer(GetHypervisorGuestOsNamesCommand cmd, Throwable th) {
+        super(cmd, false, th.getMessage());
+    }
+
+    public List<Pair<String, String>> getHypervisorGuestOsNames() {
+        return hypervisorGuestOsNames;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        if (!super.equals(o)) return false;
+        GetHypervisorGuestOsNamesAnswer that = (GetHypervisorGuestOsNamesAnswer) o;
+        return Objects.equals(hypervisorGuestOsNames, that.hypervisorGuestOsNames);
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/GetHypervisorGuestOsNamesCommand.java b/core/src/main/java/com/cloud/agent/api/GetHypervisorGuestOsNamesCommand.java
new file mode 100644
index 0000000..b31ff0e
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/GetHypervisorGuestOsNamesCommand.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 java.util.Objects;
+
+public class GetHypervisorGuestOsNamesCommand extends Command {
+    String keyword;
+
+    public GetHypervisorGuestOsNamesCommand() {
+        super();
+    }
+
+    public GetHypervisorGuestOsNamesCommand(String keyword) {
+        super();
+        this.keyword = keyword;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    public String getKeyword() {
+        return keyword;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        if (!super.equals(o)) return false;
+        GetHypervisorGuestOsNamesCommand that = (GetHypervisorGuestOsNamesCommand) o;
+        return Objects.equals(keyword, that.keyword);
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java
new file mode 100644
index 0000000..8cd072f
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsAnswer.java
@@ -0,0 +1,75 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.agent.api;
+
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+
+import java.util.HashMap;
+import java.util.List;
+
+@LogLevel(LogLevel.Log4jLevel.Trace)
+public class GetRemoteVmsAnswer extends Answer {
+
+    private String remoteIp;
+    private HashMap<String, UnmanagedInstanceTO> unmanagedInstances;
+
+    List<String> vmNames;
+
+    GetRemoteVmsAnswer() {
+    }
+
+    public GetRemoteVmsAnswer(GetRemoteVmsCommand cmd, String details, HashMap<String, UnmanagedInstanceTO> unmanagedInstances) {
+        super(cmd, true, details);
+        this.remoteIp = cmd.getRemoteIp();
+        this.unmanagedInstances = unmanagedInstances;
+    }
+
+    public GetRemoteVmsAnswer(GetRemoteVmsCommand cmd, String details, List<String> vmNames) {
+        super(cmd, true, details);
+        this.remoteIp = cmd.getRemoteIp();
+        this.vmNames = vmNames;
+    }
+
+    public String getRemoteIp() {
+        return remoteIp;
+    }
+
+    public void setRemoteIp(String remoteIp) {
+        this.remoteIp = remoteIp;
+    }
+
+    public HashMap<String, UnmanagedInstanceTO> getUnmanagedInstances() {
+        return unmanagedInstances;
+    }
+
+    public void setUnmanagedInstances(HashMap<String, UnmanagedInstanceTO> unmanagedInstances) {
+        this.unmanagedInstances = unmanagedInstances;
+    }
+
+    public List<String> getVmNames() {
+        return vmNames;
+    }
+
+    public void setVmNames(List<String> vmNames) {
+        this.vmNames = vmNames;
+    }
+
+    public String getString() {
+        return "GetRemoteVmsAnswer [remoteIp=" + remoteIp + "]";
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.java b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.java
new file mode 100644
index 0000000..5c71d12
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/GetRemoteVmsCommand.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.agent.api;
+
+@LogLevel(LogLevel.Log4jLevel.Trace)
+public class GetRemoteVmsCommand extends Command {
+
+    String remoteIp;
+    String username;
+    String password;
+
+    public GetRemoteVmsCommand(String remoteIp, String username, String password) {
+        this.remoteIp = remoteIp;
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getRemoteIp() {
+        return remoteIp;
+    }
+
+    public void setRemoteIp(String remoteIp) {
+        this.remoteIp = remoteIp;
+    }
+
+    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 GetRemoteVmsCommand() {
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    public String getString() {
+        return "GetRemoteVmsCommand [remoteIp=" + remoteIp + "]";
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java b/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java
index 3c6118d..771d472 100644
--- a/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java
+++ b/core/src/main/java/com/cloud/agent/api/GetUnmanagedInstancesAnswer.java
@@ -30,6 +30,10 @@
     GetUnmanagedInstancesAnswer() {
     }
 
+    public GetUnmanagedInstancesAnswer(GetUnmanagedInstancesCommand cmd, String details) {
+        super(cmd, false, details);
+    }
+
     public GetUnmanagedInstancesAnswer(GetUnmanagedInstancesCommand cmd, String details, HashMap<String, UnmanagedInstanceTO> unmanagedInstances) {
         super(cmd, true, details);
         this.instanceName = cmd.getInstanceName();
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 1d62c5d..4192fc2 100644
--- a/core/src/main/java/com/cloud/agent/api/PingCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/PingCommand.java
@@ -24,6 +24,7 @@
 public class PingCommand extends Command {
     Host.Type hostType;
     long hostId;
+    boolean outOfBand;
 
     protected PingCommand() {
     }
@@ -33,6 +34,12 @@
         hostId = id;
     }
 
+    public PingCommand(Host.Type type, long id, boolean oob) {
+        hostType = type;
+        hostId = id;
+        outOfBand = oob;
+    }
+
     public Host.Type getHostType() {
         return hostType;
     }
@@ -41,6 +48,10 @@
         return hostId;
     }
 
+    public boolean getOutOfBand() { return outOfBand; }
+
+    public void setOutOfBand(boolean oob) { this.outOfBand = oob; }
+
     @Override
     public boolean executeInSequence() {
         return false;
diff --git a/core/src/main/java/com/cloud/agent/api/PingRoutingCommand.java b/core/src/main/java/com/cloud/agent/api/PingRoutingCommand.java
index d7733ee..ce529ad 100644
--- a/core/src/main/java/com/cloud/agent/api/PingRoutingCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/PingRoutingCommand.java
@@ -29,6 +29,7 @@
 
     boolean _gatewayAccessible = true;
     boolean _vnetAccessible = true;
+    private Boolean hostHealthCheckResult;
 
     protected PingRoutingCommand() {
     }
@@ -57,4 +58,12 @@
     public void setVnetAccessible(boolean vnetAccessible) {
         _vnetAccessible = vnetAccessible;
     }
+
+    public Boolean getHostHealthCheckResult() {
+        return hostHealthCheckResult;
+    }
+
+    public void setHostHealthCheckResult(Boolean hostHealthCheckResult) {
+        this.hostHealthCheckResult = hostHealthCheckResult;
+    }
 }
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 b459f88..b4f9d20 100644
--- a/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java
@@ -44,6 +44,7 @@
     List<String> hostTags = new ArrayList<String>();
     String hypervisorVersion;
     HashMap<String, HashMap<String, VgpuTypesInfo>> groupDetails = new HashMap<String, HashMap<String, VgpuTypesInfo>>();
+    private Boolean hostHealthCheckResult;
 
     public StartupRoutingCommand() {
         super(Host.Type.Routing);
@@ -188,4 +189,12 @@
     public void setSupportsClonedVolumes(boolean supportsClonedVolumes) {
         this.supportsClonedVolumes = supportsClonedVolumes;
     }
+
+    public Boolean getHostHealthCheckResult() {
+        return hostHealthCheckResult;
+    }
+
+    public void setHostHealthCheckResult(Boolean hostHealthCheckResult) {
+        this.hostHealthCheckResult = hostHealthCheckResult;
+    }
 }
diff --git a/core/src/main/java/com/cloud/agent/api/storage/CheckAndRepairVolumeAnswer.java b/core/src/main/java/com/cloud/agent/api/storage/CheckAndRepairVolumeAnswer.java
new file mode 100644
index 0000000..3dc7752
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/storage/CheckAndRepairVolumeAnswer.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.storage;
+
+import com.cloud.agent.api.Answer;
+
+public class CheckAndRepairVolumeAnswer extends Answer {
+    private String volumeCheckExecutionResult;
+    private String volumeRepairExecutionResult;
+
+    protected CheckAndRepairVolumeAnswer() {
+        super();
+    }
+
+    public CheckAndRepairVolumeAnswer(CheckAndRepairVolumeCommand cmd, boolean result, String details, String volumeCheckExecutionResult, String volumeRepairedExecutionResult) {
+        super(cmd, result, details);
+        this.volumeCheckExecutionResult = volumeCheckExecutionResult;
+        this.volumeRepairExecutionResult = volumeRepairedExecutionResult;
+    }
+
+    public CheckAndRepairVolumeAnswer(CheckAndRepairVolumeCommand cmd, boolean result, String details) {
+        super(cmd, result, details);
+    }
+
+    public String getVolumeCheckExecutionResult() {
+        return volumeCheckExecutionResult;
+    }
+
+    public String getVolumeRepairExecutionResult() {
+        return volumeRepairExecutionResult;
+    }
+
+    public void setVolumeCheckExecutionResult(String volumeCheckExecutionResult) {
+        this.volumeCheckExecutionResult = volumeCheckExecutionResult;
+    }
+
+    public void setVolumeRepairExecutionResult(String volumeRepairExecutionResult) {
+        this.volumeRepairExecutionResult = volumeRepairExecutionResult;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/storage/CheckAndRepairVolumeCommand.java b/core/src/main/java/com/cloud/agent/api/storage/CheckAndRepairVolumeCommand.java
new file mode 100644
index 0000000..2553fdf
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/storage/CheckAndRepairVolumeCommand.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 com.cloud.agent.api.storage;
+
+import com.cloud.agent.api.Command;
+import com.cloud.agent.api.LogLevel;
+import com.cloud.agent.api.to.StorageFilerTO;
+
+import java.util.Arrays;
+
+public class CheckAndRepairVolumeCommand extends Command {
+    private String path;
+    private StorageFilerTO pool;
+    private String repair;
+    @LogLevel(LogLevel.Log4jLevel.Off)
+    private byte[] passphrase;
+    private String encryptFormat;
+
+    public CheckAndRepairVolumeCommand(String path, StorageFilerTO pool, String repair, byte[] passphrase, String encryptFormat) {
+        this.path = path;
+        this.pool = pool;
+        this.repair = repair;
+        this.passphrase = passphrase;
+        this.encryptFormat = encryptFormat;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getPoolUuid() {
+        return pool.getUuid();
+    }
+
+    public StorageFilerTO getPool() {
+        return pool;
+    }
+
+    public String getRepair() {
+        return repair;
+    }
+
+    public String getEncryptFormat() { return encryptFormat; }
+
+    public byte[] getPassphrase() { return passphrase; }
+
+    public void clearPassphrase() {
+        if (this.passphrase != null) {
+            Arrays.fill(this.passphrase, (byte) 0);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+}
diff --git a/core/src/main/java/com/cloud/agent/api/storage/CreateEntityDownloadURLAnswer.java b/core/src/main/java/com/cloud/agent/api/storage/CreateEntityDownloadURLAnswer.java
index cdcb3c0..4fdd6d4 100644
--- a/core/src/main/java/com/cloud/agent/api/storage/CreateEntityDownloadURLAnswer.java
+++ b/core/src/main/java/com/cloud/agent/api/storage/CreateEntityDownloadURLAnswer.java
@@ -23,18 +23,15 @@
 
 public class CreateEntityDownloadURLAnswer extends Answer {
 
-    String resultString;
-    short resultCode;
     public static final short RESULT_SUCCESS = 1;
     public static final short RESULT_FAILURE = 0;
 
     public CreateEntityDownloadURLAnswer(String resultString, short resultCode) {
         super();
-        this.resultString = resultString;
-        this.resultCode = resultCode;
+        this.details = resultString;
+        this.result = resultCode == RESULT_SUCCESS;
     }
 
     public CreateEntityDownloadURLAnswer() {
     }
-
 }
diff --git a/core/src/main/java/com/cloud/agent/transport/Request.java b/core/src/main/java/com/cloud/agent/transport/Request.java
index 2880934..241ccd4 100644
--- a/core/src/main/java/com/cloud/agent/transport/Request.java
+++ b/core/src/main/java/com/cloud/agent/transport/Request.java
@@ -32,12 +32,20 @@
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
-import com.cloud.utils.HumanReadableJson;
-
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.BadCommand;
+import com.cloud.agent.api.Command;
+import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig;
+import com.cloud.exception.UnsupportedVersionException;
+import com.cloud.serializer.GsonHelper;
+import com.cloud.utils.HumanReadableJson;
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
 import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonDeserializationContext;
@@ -49,16 +57,6 @@
 import com.google.gson.JsonSerializer;
 import com.google.gson.stream.JsonReader;
 
-import com.cloud.agent.api.Answer;
-import com.cloud.agent.api.BadCommand;
-import com.cloud.agent.api.Command;
-import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig;
-import com.cloud.exception.UnsupportedVersionException;
-import com.cloud.serializer.GsonHelper;
-import com.cloud.utils.NumbersUtil;
-import com.cloud.utils.Pair;
-import com.cloud.utils.exception.CloudRuntimeException;
-
 /**
  * Request is a simple wrapper around command and answer to add sequencing,
  * versioning, and flags. Note that the version here represents the changes
@@ -253,6 +251,7 @@
                 jsonReader.setLenient(true);
                 _cmds = s_gson.fromJson(jsonReader, (Type)Command[].class);
             } catch (JsonParseException e) {
+                s_logger.error("Caught problem while parsing JSON command " + _content, e);
                 _cmds = new Command[] { new BadCommand() };
             } catch (RuntimeException e) {
                 s_logger.error("Caught problem with " + _content, e);
diff --git a/core/src/main/java/com/cloud/resource/AgentStatusUpdater.java b/core/src/main/java/com/cloud/resource/AgentStatusUpdater.java
new file mode 100644
index 0000000..63d5576
--- /dev/null
+++ b/core/src/main/java/com/cloud/resource/AgentStatusUpdater.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.resource;
+
+/**
+ * AgentStatusUpdater is an agent with triggerable update functionality
+ */
+public interface AgentStatusUpdater {
+    /**
+     * Trigger the sending of an update (Ping).
+     */
+    void triggerUpdate();
+}
diff --git a/core/src/main/java/com/cloud/resource/ResourceStatusUpdater.java b/core/src/main/java/com/cloud/resource/ResourceStatusUpdater.java
new file mode 100644
index 0000000..df59e3a
--- /dev/null
+++ b/core/src/main/java/com/cloud/resource/ResourceStatusUpdater.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 com.cloud.resource;
+
+/**
+ * ResourceStatusUpdater is a resource that can trigger out of band status updates
+ */
+public interface ResourceStatusUpdater {
+    /**
+     * Register an AgentStatusUpdater to use for triggering out of band updates.
+     *
+     * @param updater The object to call triggerUpdate() on
+     */
+    void registerStatusUpdater(AgentStatusUpdater updater);
+}
diff --git a/core/src/main/java/com/cloud/resource/ServerResourceBase.java b/core/src/main/java/com/cloud/resource/ServerResourceBase.java
index f3bad61..18121e2 100644
--- a/core/src/main/java/com/cloud/resource/ServerResourceBase.java
+++ b/core/src/main/java/com/cloud/resource/ServerResourceBase.java
@@ -19,6 +19,7 @@
 
 package com.cloud.resource;
 
+import java.io.File;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.NetworkInterface;
@@ -33,6 +34,7 @@
 
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
@@ -46,18 +48,18 @@
 
 public abstract class ServerResourceBase implements ServerResource {
     private static final Logger s_logger = Logger.getLogger(ServerResourceBase.class);
-    protected String _name;
-    private ArrayList<String> _warnings = new ArrayList<String>();
-    private ArrayList<String> _errors = new ArrayList<String>();
-    protected NetworkInterface _publicNic;
-    protected NetworkInterface _privateNic;
-    protected NetworkInterface _storageNic;
-    protected NetworkInterface _storageNic2;
-    protected IAgentControl _agentControl;
+    protected String name;
+    private ArrayList<String> warnings = new ArrayList<String>();
+    private ArrayList<String> errors = new ArrayList<String>();
+    protected NetworkInterface publicNic;
+    protected NetworkInterface privateNic;
+    protected NetworkInterface storageNic;
+    protected NetworkInterface storageNic2;
+    protected IAgentControl agentControl;
 
     @Override
     public String getName() {
-        return _name;
+        return name;
     }
 
     protected String findScript(String script) {
@@ -68,15 +70,15 @@
 
     @Override
     public boolean configure(final String name, Map<String, Object> params) throws ConfigurationException {
-        _name = name;
+        this.name = name;
 
         defineResourceNetworkInterfaces(params);
 
-        if (_privateNic == null) {
+        if (privateNic == null) {
             tryToAutoDiscoverResourcePrivateNetworkInterface();
         }
 
-        String infos[] = NetUtils.getNetworkParams(_privateNic);
+        String infos[] = NetUtils.getNetworkParams(privateNic);
         if (infos == null) {
             s_logger.warn("Incorrect details for private Nic during initialization of ServerResourceBase");
             return false;
@@ -97,10 +99,10 @@
         String storageNic = (String) params.get("storage.network.device");
         String storageNic2 = (String) params.get("storage.network.device.2");
 
-        _privateNic = NetUtils.getNetworkInterface(privateNic);
-        _publicNic = NetUtils.getNetworkInterface(publicNic);
-        _storageNic = NetUtils.getNetworkInterface(storageNic);
-        _storageNic2 = NetUtils.getNetworkInterface(storageNic2);
+        this.privateNic = NetUtils.getNetworkInterface(privateNic);
+        this.publicNic = NetUtils.getNetworkInterface(publicNic);
+        this.storageNic = NetUtils.getNetworkInterface(storageNic);
+        this.storageNic2 = NetUtils.getNetworkInterface(storageNic2);
     }
 
     protected void tryToAutoDiscoverResourcePrivateNetworkInterface() throws ConfigurationException {
@@ -121,7 +123,7 @@
         for (NetworkInterface nic : nics) {
             if (isValidNicToUseAsPrivateNic(nic))  {
                 s_logger.info(String.format("Using NIC [%s] as private NIC.", nic));
-                _privateNic = nic;
+                privateNic = nic;
                 return;
             }
         }
@@ -150,10 +152,43 @@
         return true;
     }
 
+     protected Answer listFilesAtPath(String nfsMountPoint, String relativePath, int startIndex, int pageSize) {
+        int count = 0;
+        File file = new File(nfsMountPoint, relativePath);
+        List<String> names = new ArrayList<>();
+        List<String> paths = new ArrayList<>();
+        List<String> absPaths = new ArrayList<>();
+        List<Boolean> isDirs = new ArrayList<>();
+        List<Long> sizes = new ArrayList<>();
+        List<Long> modifiedList = new ArrayList<>();
+        if (file.isFile()) {
+            count = 1;
+            names.add(file.getName());
+            paths.add(file.getPath().replace(nfsMountPoint, ""));
+            absPaths.add(file.getPath());
+            isDirs.add(file.isDirectory());
+            sizes.add(file.length());
+            modifiedList.add(file.lastModified());
+        } else if (file.isDirectory()) {
+            String[] files = file.list();
+            count = files.length;
+            for (int i = startIndex; i < startIndex + pageSize && i < count; i++) {
+                File f = new File(nfsMountPoint, relativePath + '/' + files[i]);
+                names.add(f.getName());
+                paths.add(f.getPath().replace(nfsMountPoint, ""));
+                absPaths.add(f.getPath());
+                isDirs.add(f.isDirectory());
+                sizes.add(f.length());
+                modifiedList.add(f.lastModified());
+            }
+        }
+         return new ListDataStoreObjectsAnswer(file.exists(), count, names, paths, absPaths, isDirs, sizes, modifiedList);
+    }
+
     protected void fillNetworkInformation(final StartupCommand cmd) {
         String[] info = null;
-        if (_privateNic != null) {
-            info = NetUtils.getNetworkParams(_privateNic);
+        if (privateNic != null) {
+            info = NetUtils.getNetworkParams(privateNic);
             if (info != null) {
                 if (s_logger.isDebugEnabled()) {
                     s_logger.debug("Parameters for private nic: " + info[0] + " - " + info[1] + "-" + info[2]);
@@ -164,11 +199,11 @@
             }
         }
 
-        if (_storageNic != null) {
+        if (storageNic != null) {
             if (s_logger.isDebugEnabled()) {
-                s_logger.debug("Storage has its now nic: " + _storageNic.getName());
+                s_logger.debug("Storage has its now nic: " + storageNic.getName());
             }
-            info = NetUtils.getNetworkParams(_storageNic);
+            info = NetUtils.getNetworkParams(storageNic);
         }
 
         // NOTE: In case you're wondering, this is not here by mistake.
@@ -181,8 +216,8 @@
             cmd.setStorageNetmask(info[2]);
         }
 
-        if (_publicNic != null) {
-            info = NetUtils.getNetworkParams(_publicNic);
+        if (publicNic != null) {
+            info = NetUtils.getNetworkParams(publicNic);
             if (info != null) {
                 if (s_logger.isDebugEnabled()) {
                     s_logger.debug("Parameters for public nic: " + info[0] + " - " + info[1] + "-" + info[2]);
@@ -193,8 +228,8 @@
             }
         }
 
-        if (_storageNic2 != null) {
-            info = NetUtils.getNetworkParams(_storageNic2);
+        if (storageNic2 != null) {
+            info = NetUtils.getNetworkParams(storageNic2);
             if (info != null) {
                 if (s_logger.isDebugEnabled()) {
                     s_logger.debug("Parameters for storage nic 2: " + info[0] + " - " + info[1] + "-" + info[2]);
@@ -212,18 +247,18 @@
 
     @Override
     public IAgentControl getAgentControl() {
-        return _agentControl;
+        return agentControl;
     }
 
     @Override
     public void setAgentControl(IAgentControl agentControl) {
-        _agentControl = agentControl;
+        this.agentControl = agentControl;
     }
 
     protected void recordWarning(final String msg, final Throwable th) {
         final String str = getLogStr(msg, th);
-        synchronized (_warnings) {
-            _warnings.add(str);
+        synchronized (warnings) {
+            warnings.add(str);
         }
     }
 
@@ -232,25 +267,25 @@
     }
 
     protected List<String> getWarnings() {
-        synchronized (_warnings) {
-            final List<String> results = new LinkedList<String>(_warnings);
-            _warnings.clear();
+        synchronized (warnings) {
+            final List<String> results = new LinkedList<String>(warnings);
+            warnings.clear();
             return results;
         }
     }
 
     protected List<String> getErrors() {
-        synchronized (_errors) {
-            final List<String> result = new LinkedList<String>(_errors);
-            _errors.clear();
+        synchronized (errors) {
+            final List<String> result = new LinkedList<String>(errors);
+            errors.clear();
             return result;
         }
     }
 
     protected void recordError(final String msg, final Throwable th) {
         final String str = getLogStr(msg, th);
-        synchronized (_errors) {
-            _errors.add(str);
+        synchronized (errors) {
+            errors.add(str);
         }
     }
 
diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java b/core/src/main/java/com/cloud/serializer/GsonHelper.java
index 7ac85d3..7c33ef0 100644
--- a/core/src/main/java/com/cloud/serializer/GsonHelper.java
+++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java
@@ -51,6 +51,8 @@
         GsonBuilder loggerBuilder = new GsonBuilder();
         loggerBuilder.disableHtmlEscaping();
         loggerBuilder.setExclusionStrategies(new LoggingExclusionStrategy(s_logger));
+        loggerBuilder.serializeSpecialFloatingPointValues();
+        // maybe add loggerBuilder.serializeNulls(); as well?
         s_gogger = setDefaultGsonConfig(loggerBuilder);
         s_logger.info("Default Builder inited.");
     }
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 4a9a24a..75d5f49 100644
--- a/core/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java
+++ b/core/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java
@@ -19,23 +19,23 @@
 
 package com.cloud.storage.resource;
 
-import com.cloud.serializer.GsonHelper;
 import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
-import org.apache.cloudstack.storage.to.VolumeObjectTO;
-import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
-import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.storage.command.AttachCommand;
+import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.CreateObjectAnswer;
 import org.apache.cloudstack.storage.command.CreateObjectCommand;
 import org.apache.cloudstack.storage.command.DeleteCommand;
 import org.apache.cloudstack.storage.command.DettachCommand;
 import org.apache.cloudstack.storage.command.IntroduceObjectCmd;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
 import org.apache.cloudstack.storage.command.ResignatureCommand;
 import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
 import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.Command;
@@ -43,6 +43,7 @@
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.DiskTO;
+import com.cloud.serializer.GsonHelper;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Volume;
 import com.google.gson.Gson;
@@ -81,6 +82,8 @@
             return processor.checkDataStoreStoragePolicyCompliance((CheckDataStoreStoragePolicyComplainceCommand) command);
         } else if (command instanceof SyncVolumePathCommand) {
             return processor.syncVolumePath((SyncVolumePathCommand) command);
+        } else if (command instanceof QuerySnapshotZoneCopyCommand) {
+            return execute((QuerySnapshotZoneCopyCommand)command);
         }
 
         return new Answer((Command)command, false, "not implemented yet");
@@ -175,6 +178,10 @@
         }
     }
 
+    protected Answer execute(QuerySnapshotZoneCopyCommand cmd) {
+        return new QuerySnapshotZoneCopyAnswer(cmd, "Unsupported command");
+    }
+
     private void logCommand(Command cmd) {
         try {
             s_logger.debug(String.format("Executing command %s: [%s].", cmd.getClass().getSimpleName(), s_gogger.toJson(cmd)));
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 25f177e..7ad8070 100755
--- a/core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java
+++ b/core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java
@@ -19,6 +19,8 @@
 
 package com.cloud.storage.template;
 
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -28,7 +30,8 @@
 import java.util.Date;
 import java.util.List;
 
-import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
 import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
 import org.apache.commons.httpclient.Credentials;
 import org.apache.commons.httpclient.Header;
@@ -45,16 +48,12 @@
 import org.apache.commons.httpclient.params.HttpMethodParams;
 import org.apache.log4j.Logger;
 
-import org.apache.cloudstack.managed.context.ManagedContextRunnable;
-import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
-
 import com.cloud.storage.StorageLayer;
 import com.cloud.utils.Pair;
 import com.cloud.utils.UriUtils;
+import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.Proxy;
 
-import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
-
 /**
  * Download a template file using HTTP
  *
@@ -249,7 +248,9 @@
         while (!done && status != Status.ABORTED && offset <= remoteSize) {
             if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
                 offset = writeBlock(bytes, out, block, offset);
-                if (!verifyFormat.isVerifiedFormat() && (offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file
+                if (!ResourceType.SNAPSHOT.equals(resourceType) &&
+                        !verifyFormat.isVerifiedFormat() &&
+                        (offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file
                     verifyFormat.invoke();
                 }
             } else {
diff --git a/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java b/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java
new file mode 100644
index 0000000..56cf76f
--- /dev/null
+++ b/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java
@@ -0,0 +1,497 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES 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.template;
+
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.storage.command.DownloadCommand;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpMethodRetryHandler;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.NoHttpResponseException;
+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.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.storage.StorageLayer;
+
+public class SimpleHttpMultiFileDownloader extends ManagedContextRunnable implements TemplateDownloader {
+    public static final Logger s_logger = Logger.getLogger(SimpleHttpMultiFileDownloader.class.getName());
+    private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
+
+    private static final int CHUNK_SIZE = 1024 * 1024; //1M
+    private String[] downloadUrls;
+    private String currentToFile;
+    public TemplateDownloader.Status currentStatus;
+    public TemplateDownloader.Status status;
+    private String errorString = null;
+    private long totalRemoteSize = 0;
+    private long currentRemoteSize = 0;
+    public long downloadTime = 0;
+    public long currentTotalBytes;
+    public long totalBytes = 0;
+    private final HttpClient client;
+    private GetMethod request;
+    private boolean resume = false;
+    private DownloadCompleteCallback completionCallback;
+    StorageLayer _storage;
+    boolean inited = true;
+
+    private String toDir;
+    private long maxTemplateSizeInBytes;
+    private DownloadCommand.ResourceType resourceType = DownloadCommand.ResourceType.TEMPLATE;
+    private final HttpMethodRetryHandler retryHandler;
+
+    private HashMap<String, String> urlFileMap;
+    private boolean followRedirects = false;
+
+    public SimpleHttpMultiFileDownloader(StorageLayer storageLayer, String[] downloadUrls, String toDir,
+                                         DownloadCompleteCallback callback, long maxTemplateSizeInBytes,
+                                         DownloadCommand.ResourceType resourceType) {
+        _storage = storageLayer;
+        this.downloadUrls = downloadUrls;
+        this.toDir = toDir;
+        this.resourceType = resourceType;
+        this.maxTemplateSizeInBytes = maxTemplateSizeInBytes;
+        completionCallback = callback;
+        status = TemplateDownloader.Status.NOT_STARTED;
+        currentStatus = TemplateDownloader.Status.NOT_STARTED;
+        currentTotalBytes = 0;
+        client = new HttpClient(s_httpClientManager);
+        retryHandler = createRetryTwiceHandler();
+        urlFileMap = new HashMap<>();
+    }
+
+    private GetMethod createRequest(String downloadUrl) {
+        GetMethod request = new GetMethod(downloadUrl);
+        request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler);
+        request.setFollowRedirects(followRedirects);
+        return request;
+    }
+
+    private void checkTemporaryDestination(String toDir) {
+        try {
+            File f = File.createTempFile("dnld", "tmp_", new File(toDir));
+            if (_storage != null) {
+                _storage.setWorldReadableAndWriteable(f);
+            }
+            currentToFile = f.getAbsolutePath();
+        } catch (IOException ex) {
+            errorString = "Unable to start download -- check url? ";
+            currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
+            s_logger.warn("Exception in constructor -- " + ex.toString());
+        }
+    }
+
+    private HttpMethodRetryHandler createRetryTwiceHandler() {
+        return new HttpMethodRetryHandler() {
+            @Override
+            public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) {
+                if (executionCount >= 2) {
+                    // Do not retry if over max retry count
+                    return false;
+                }
+                if (exception instanceof NoHttpResponseException) {
+                    // Retry if the server dropped connection on us
+                    return true;
+                }
+                if (!method.isRequestSent()) {
+                    // Retry if the request has not been sent fully or
+                    // if it's OK to retry methods that have been sent
+                    return true;
+                }
+                // otherwise do not retry
+                return false;
+            }
+        };
+    }
+
+    private void tryAndGetTotalRemoteSize() {
+        for (String downloadUrl : downloadUrls) {
+            if (StringUtils.isBlank(downloadUrl)) {
+                continue;
+            }
+            HeadMethod headMethod = new HeadMethod(downloadUrl);
+            try {
+                if (client.executeMethod(headMethod) != HttpStatus.SC_OK) {
+                    continue;
+                }
+                Header contentLengthHeader = headMethod.getResponseHeader("content-length");
+                if (contentLengthHeader == null) {
+                    continue;
+                }
+                totalRemoteSize += Long.parseLong(contentLengthHeader.getValue());
+            } catch (IOException e) {
+                s_logger.warn(String.format("Cannot reach URL: %s while trying to get remote sizes due to: %s", downloadUrl, e.getMessage()), e);
+            } finally {
+                headMethod.releaseConnection();
+            }
+        }
+    }
+
+    private long downloadFile(String downloadUrl) {
+        s_logger.debug("Starting download for " + downloadUrl);
+        currentTotalBytes = 0;
+        currentRemoteSize = 0;
+        File file = null;
+        request = null;
+        try {
+            request = createRequest(downloadUrl);
+            checkTemporaryDestination(toDir);
+            urlFileMap.put(downloadUrl, currentToFile);
+            file = new File(currentToFile);
+            long localFileSize = checkLocalFileSizeForResume(resume, file);
+            if (checkServerResponse(localFileSize, downloadUrl)) return 0;
+            if (!tryAndGetRemoteSize()) return 0;
+            if (!canHandleDownloadSize()) return 0;
+            checkAndSetDownloadSize();
+            try (InputStream in = request.getResponseBodyAsStream();
+                 RandomAccessFile out = new RandomAccessFile(file, "rw");
+            ) {
+                out.seek(localFileSize);
+                s_logger.info("Starting download from " + downloadUrl + " to " + currentToFile + " remoteSize=" + toHumanReadableSize(currentRemoteSize) + " , max size=" + toHumanReadableSize(maxTemplateSizeInBytes));
+                if (copyBytes(file, in, out)) return 0;
+                checkDownloadCompletion();
+            }
+            return currentTotalBytes;
+        } catch (HttpException hte) {
+            currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
+            errorString = hte.getMessage();
+        } catch (IOException ioe) {
+            currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR; //probably a file write error?
+            // Let's not overwrite the original error message.
+            if (errorString == null) {
+                errorString = ioe.getMessage();
+            }
+        } finally {
+            if (currentStatus == Status.UNRECOVERABLE_ERROR && file != null && file.exists() && !file.isDirectory()) {
+                file.delete();
+            }
+            if (request != null) {
+                request.releaseConnection();
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public long download(boolean resume, DownloadCompleteCallback callback) {
+        if (skipDownloadOnStatus()) return 0;
+        if (resume) {
+            s_logger.error("Resume not allowed for this downloader");
+            status = Status.UNRECOVERABLE_ERROR;
+            return 0;
+        }
+        s_logger.debug("Starting downloads");
+        status = Status.IN_PROGRESS;
+        Date start = new Date();
+        tryAndGetTotalRemoteSize();
+        for (String downloadUrl : downloadUrls) {
+            if (StringUtils.isBlank(downloadUrl)) {
+                continue;
+            }
+            long bytes = downloadFile(downloadUrl);
+            if (currentStatus != Status.DOWNLOAD_FINISHED) {
+                break;
+            }
+            totalBytes += bytes;
+        }
+        status = currentStatus;
+        Date finish = new Date();
+        downloadTime += finish.getTime() - start.getTime();
+        if (callback != null) {
+            callback.downloadComplete(status);
+        }
+        return 0;
+    }
+
+    private boolean copyBytes(File file, InputStream in, RandomAccessFile out) throws IOException {
+        int bytes;
+        byte[] block = new byte[CHUNK_SIZE];
+        long offset = 0;
+        boolean done = false;
+        currentStatus = Status.IN_PROGRESS;
+        while (!done && currentStatus != Status.ABORTED && offset <= currentRemoteSize) {
+            if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
+                offset = writeBlock(bytes, out, block, offset);
+            } else {
+                done = true;
+            }
+        }
+        out.getFD().sync();
+        return false;
+    }
+
+    private long writeBlock(int bytes, RandomAccessFile out, byte[] block, long offset) throws IOException {
+        out.write(block, 0, bytes);
+        offset += bytes;
+        out.seek(offset);
+        currentTotalBytes += bytes;
+        return offset;
+    }
+
+    private void checkDownloadCompletion() {
+        String downloaded = "(incomplete download)";
+        if (currentTotalBytes >= currentRemoteSize) {
+            currentStatus = Status.DOWNLOAD_FINISHED;
+            downloaded = "(download complete remote=" + toHumanReadableSize(currentRemoteSize) + " bytes)";
+        }
+        errorString = "Downloaded " + toHumanReadableSize(currentTotalBytes) + " bytes " + downloaded;
+    }
+
+    private boolean canHandleDownloadSize() {
+        if (currentRemoteSize > maxTemplateSizeInBytes) {
+            s_logger.info("Remote size is too large: " + toHumanReadableSize(currentRemoteSize) + " , max=" + toHumanReadableSize(maxTemplateSizeInBytes));
+            currentStatus = Status.UNRECOVERABLE_ERROR;
+            errorString = "Download file size is too large";
+            return false;
+        }
+        return true;
+    }
+
+    private void checkAndSetDownloadSize() {
+        if (currentRemoteSize == 0) {
+            currentRemoteSize = maxTemplateSizeInBytes;
+        }
+        if (totalRemoteSize == 0) {
+            totalRemoteSize = currentRemoteSize;
+        }
+    }
+
+    private boolean tryAndGetRemoteSize() {
+        Header contentLengthHeader = request.getResponseHeader("content-length");
+        boolean chunked = false;
+        long reportedRemoteSize = 0;
+        if (contentLengthHeader == null) {
+            Header chunkedHeader = request.getResponseHeader("Transfer-Encoding");
+            if (chunkedHeader == null || !"chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
+                currentStatus = Status.UNRECOVERABLE_ERROR;
+                errorString = " Failed to receive length of download ";
+                return false;
+            } else if ("chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
+                chunked = true;
+            }
+        } else {
+            reportedRemoteSize = Long.parseLong(contentLengthHeader.getValue());
+            if (reportedRemoteSize == 0) {
+                currentStatus = Status.DOWNLOAD_FINISHED;
+                String downloaded = "(download complete remote=" + currentRemoteSize + "bytes)";
+                errorString = "Downloaded " + currentTotalBytes + " bytes " + downloaded;
+                downloadTime = 0;
+                return false;
+            }
+        }
+
+        if (currentRemoteSize == 0) {
+            currentRemoteSize = reportedRemoteSize;
+        }
+        return true;
+    }
+
+    private boolean checkServerResponse(long localFileSize, String downloadUrl) throws IOException {
+        int responseCode = 0;
+
+        if (localFileSize > 0) {
+            // require partial content support for resume
+            request.addRequestHeader("Range", "bytes=" + localFileSize + "-");
+            if (client.executeMethod(request) != HttpStatus.SC_PARTIAL_CONTENT) {
+                errorString = "HTTP Server does not support partial get";
+                currentStatus = Status.UNRECOVERABLE_ERROR;
+                return true;
+            }
+        } else if ((responseCode = client.executeMethod(request)) != HttpStatus.SC_OK) {
+            currentStatus = Status.UNRECOVERABLE_ERROR;
+            errorString = " HTTP Server returned " + responseCode + " (expected 200 OK) ";
+            if (List.of(HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_MOVED_TEMPORARILY).contains(responseCode)
+                    && !followRedirects) {
+                errorString = String.format("Failed to download %s due to redirection, response code: %d",
+                        downloadUrl, responseCode);
+                s_logger.error(errorString);
+            }
+            return true; //FIXME: retry?
+        }
+        return false;
+    }
+
+    private long checkLocalFileSizeForResume(boolean resume, File file) {
+        // TODO check the status of this downloader as well?
+        long localFileSize = 0;
+        if (file.exists() && resume) {
+            localFileSize = file.length();
+            s_logger.info("Resuming download to file (current size)=" + toHumanReadableSize(localFileSize));
+        }
+        return localFileSize;
+    }
+
+    private boolean skipDownloadOnStatus() {
+        switch (currentStatus) {
+            case ABORTED:
+            case UNRECOVERABLE_ERROR:
+            case DOWNLOAD_FINISHED:
+                return true;
+            default:
+
+        }
+        return false;
+    }
+
+    public String[] getDownloadUrls() {
+        return downloadUrls;
+    }
+
+    public String getCurrentToFile() {
+        File file = new File(currentToFile);
+
+        return file.getAbsolutePath();
+    }
+
+    @Override
+    public TemplateDownloader.Status getStatus() {
+        return currentStatus;
+    }
+
+    @Override
+    public long getDownloadTime() {
+        return downloadTime;
+    }
+
+    @Override
+    public long getDownloadedBytes() {
+        return totalBytes;
+    }
+
+    @Override
+    @SuppressWarnings("fallthrough")
+    public boolean stopDownload() {
+        switch (getStatus()) {
+            case IN_PROGRESS:
+                if (request != null) {
+                    request.abort();
+                }
+                currentStatus = TemplateDownloader.Status.ABORTED;
+                return true;
+            case UNKNOWN:
+            case NOT_STARTED:
+            case RECOVERABLE_ERROR:
+            case UNRECOVERABLE_ERROR:
+            case ABORTED:
+                currentStatus = TemplateDownloader.Status.ABORTED;
+            case DOWNLOAD_FINISHED:
+                File f = new File(currentToFile);
+                if (f.exists()) {
+                    f.delete();
+                }
+                return true;
+
+            default:
+                return true;
+        }
+    }
+
+    @Override
+    public int getDownloadPercent() {
+        if (totalRemoteSize == 0) {
+            return 0;
+        }
+
+        return (int)(100.0 * totalBytes / totalRemoteSize);
+    }
+
+    @Override
+    protected void runInContext() {
+        try {
+            download(resume, completionCallback);
+        } catch (Throwable t) {
+            s_logger.warn("Caught exception during download " + t.getMessage(), t);
+            errorString = "Failed to install: " + t.getMessage();
+            currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
+        }
+
+    }
+
+    @Override
+    public void setStatus(TemplateDownloader.Status status) {
+        this.currentStatus = status;
+    }
+
+    public boolean isResume() {
+        return resume;
+    }
+
+    @Override
+    public String getDownloadError() {
+        return errorString == null ? " " : errorString;
+    }
+
+    @Override
+    public String getDownloadLocalPath() {
+        return toDir;
+    }
+
+    @Override
+    public void setResume(boolean resume) {
+        this.resume = resume;
+    }
+
+    @Override
+    public long getMaxTemplateSizeInBytes() {
+        return maxTemplateSizeInBytes;
+    }
+
+    @Override
+    public void setDownloadError(String error) {
+        errorString = error;
+    }
+
+    @Override
+    public boolean isInited() {
+        return inited;
+    }
+
+    public DownloadCommand.ResourceType getResourceType() {
+        return resourceType;
+    }
+
+    public Map<String, String> getDownloadedFilesMap() {
+        return urlFileMap;
+    }
+
+    @Override
+    public void setFollowRedirects(boolean followRedirects) {
+        this.followRedirects = followRedirects;
+        if (this.request != null) {
+            this.request.setFollowRedirects(followRedirects);
+        }
+    }
+}
diff --git a/core/src/main/java/com/cloud/storage/template/TemplateLocation.java b/core/src/main/java/com/cloud/storage/template/TemplateLocation.java
index 99360ee..6ff53a0 100644
--- a/core/src/main/java/com/cloud/storage/template/TemplateLocation.java
+++ b/core/src/main/java/com/cloud/storage/template/TemplateLocation.java
@@ -19,26 +19,25 @@
 
 package com.cloud.storage.template;
 
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Properties;
-import java.util.Arrays;
-
-import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
+import org.apache.log4j.Logger;
 
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.StorageLayer;
 import com.cloud.storage.template.Processor.FormatInfo;
 import com.cloud.utils.NumbersUtil;
 
-import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
-
 public class TemplateLocation {
     private static final Logger s_logger = Logger.getLogger(TemplateLocation.class);
     public final static String Filename = "template.properties";
@@ -65,6 +64,9 @@
         if (_templatePath.matches(".*" + "volumes" + ".*")) {
             _file = _storage.getFile(_templatePath + "volume.properties");
             _resourceType = ResourceType.VOLUME;
+        } else if (_templatePath.matches(".*" + "snapshots" + ".*")) {
+            _file = _storage.getFile(_templatePath + "snapshot.properties");
+            _resourceType = ResourceType.SNAPSHOT;
         } else {
             _file = _storage.getFile(_templatePath + Filename);
         }
@@ -170,6 +172,8 @@
         tmplInfo.installPath = _templatePath + _props.getProperty("filename"); // _templatePath endsWith /
         if (_resourceType == ResourceType.VOLUME) {
             tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("volumes"));
+        } else if (_resourceType == ResourceType.SNAPSHOT) {
+            tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("snapshots"));
         } else {
             tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("template"));
         }
diff --git a/core/src/main/java/org/apache/cloudstack/agent/directdownload/CheckUrlCommand.java b/core/src/main/java/org/apache/cloudstack/agent/directdownload/CheckUrlCommand.java
index fe941b3..325f614 100644
--- a/core/src/main/java/org/apache/cloudstack/agent/directdownload/CheckUrlCommand.java
+++ b/core/src/main/java/org/apache/cloudstack/agent/directdownload/CheckUrlCommand.java
@@ -55,14 +55,14 @@
         this.followRedirects = followRedirects;
     }
 
-    public CheckUrlCommand(final String format,final String url, Integer connectTimeout,
-               Integer connectionRequestTimeout, Integer socketTimeout, final boolean followRedirects) {
+    public CheckUrlCommand(final String format,final String url, Integer connectTimeout, Integer connectionRequestTimeout, Integer socketTimeout, final boolean followRedirects) {
         super();
         this.format = format;
         this.url = url;
         this.connectTimeout = connectTimeout;
         this.socketTimeout = socketTimeout;
         this.connectionRequestTimeout = connectionRequestTimeout;
+        this.followRedirects = followRedirects;
     }
 
     @Override
diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadHelper.java b/core/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadHelper.java
index 12fd5ad..a00274e 100644
--- a/core/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadHelper.java
+++ b/core/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadHelper.java
@@ -65,11 +65,9 @@
         }
     }
 
-    public static boolean checkUrlExistence(String url, Integer connectTimeout, Integer connectionRequestTimeout,
-            Integer socketTimeout, boolean followRedirects) {
+    public static boolean checkUrlExistence(String url, Integer connectTimeout, Integer connectionRequestTimeout, Integer socketTimeout, boolean followRedirects) {
         try {
-            DirectTemplateDownloader checker = getCheckerDownloader(url, connectTimeout, connectionRequestTimeout,
-                    socketTimeout, followRedirects);
+            DirectTemplateDownloader checker = getCheckerDownloader(url, connectTimeout, connectionRequestTimeout, socketTimeout, followRedirects);
             return checker.checkUrl(url);
         } catch (CloudRuntimeException e) {
             LOGGER.error(String.format("Cannot check URL %s is reachable due to: %s", url, e.getMessage()), e);
@@ -77,8 +75,7 @@
         }
     }
 
-    private static DirectTemplateDownloader getCheckerDownloader(String url, Integer connectTimeout,
-             Integer connectionRequestTimeout, Integer socketTimeout, boolean followRedirects) {
+    private static DirectTemplateDownloader getCheckerDownloader(String url, Integer connectTimeout, Integer connectionRequestTimeout, Integer socketTimeout, boolean followRedirects) {
         if (url.toLowerCase().startsWith("https:")) {
             return new HttpsDirectTemplateDownloader(url, connectTimeout, connectionRequestTimeout, socketTimeout, followRedirects);
         } else if (url.toLowerCase().startsWith("http:")) {
@@ -97,8 +94,7 @@
         return checker.getRemoteFileSize(url, format);
     }
 
-    public static Long getFileSize(String url, String format, Integer connectTimeout, Integer connectionRequestTimeout,
-           Integer socketTimeout, boolean followRedirects) {
+    public static Long getFileSize(String url, String format, Integer connectTimeout, Integer connectionRequestTimeout, Integer socketTimeout, boolean followRedirects) {
         DirectTemplateDownloader checker = getCheckerDownloader(url, connectTimeout, connectionRequestTimeout, socketTimeout, followRedirects);
         return checker.getRemoteFileSize(url, format);
     }
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 a43ce9b..068f6b0 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
@@ -50,8 +50,7 @@
     protected GetMethod request;
     protected Map<String, String> reqHeaders = new HashMap<>();
 
-    protected HttpDirectTemplateDownloader(String url, Integer connectTimeout, Integer socketTimeout,
-           boolean followRedirects) {
+    protected HttpDirectTemplateDownloader(String url, Integer connectTimeout, Integer socketTimeout, boolean followRedirects) {
         this(url, null, null, null, null, connectTimeout, socketTimeout, null, followRedirects);
     }
 
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 524c87b..3a48ade 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
@@ -68,10 +68,8 @@
     protected CloseableHttpClient httpsClient;
     private HttpUriRequest req;
 
-    protected HttpsDirectTemplateDownloader(String url, Integer connectTimeout, Integer connectionRequestTimeout,
-            Integer socketTimeout, boolean followRedirects) {
-        this(url, null, null, null, null, connectTimeout, socketTimeout,
-                connectionRequestTimeout, null, followRedirects);
+    protected HttpsDirectTemplateDownloader(String url, Integer connectTimeout, Integer connectionRequestTimeout, Integer socketTimeout, boolean followRedirects) {
+        this(url, null, null, null, null, connectTimeout, socketTimeout, connectionRequestTimeout, null, followRedirects);
     }
 
     public HttpsDirectTemplateDownloader(String url, Long templateId, String destPoolPath, String checksum,
diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java
index f6f7f7e..86b9788 100644
--- a/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java
+++ b/core/src/main/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloader.java
@@ -59,8 +59,7 @@
         }
     }
 
-    protected MetalinkDirectTemplateDownloader(String url, Integer connectTimeout, Integer socketTimeout,
-           boolean followRedirects) {
+    protected MetalinkDirectTemplateDownloader(String url, Integer connectTimeout, Integer socketTimeout, boolean followRedirects) {
         this(url, null, null, null, null, connectTimeout, socketTimeout, null, followRedirects);
     }
 
diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java
index 7b41bd9..f44220f 100644
--- a/core/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java
+++ b/core/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java
@@ -20,6 +20,7 @@
 package org.apache.cloudstack.storage.command;
 
 import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 
@@ -33,7 +34,7 @@
 public class DownloadCommand extends AbstractDownloadCommand implements InternalIdentity {
 
     public static enum ResourceType {
-        VOLUME, TEMPLATE
+        VOLUME, TEMPLATE, SNAPSHOT
     }
 
     private boolean hvm;
@@ -101,6 +102,18 @@
         this.followRedirects = volume.isFollowRedirects();
     }
 
+    public DownloadCommand(SnapshotObjectTO snapshot, Long maxDownloadSizeInBytes, String url) {
+        super(snapshot.getName(), url, null, snapshot.getAccountId());
+        _store = snapshot.getDataStore();
+        installPath = snapshot.getPath();
+        id = snapshot.getId();
+        if (_store instanceof NfsTO) {
+            setSecUrl(((NfsTO)_store).getUrl());
+        }
+        this.maxDownloadSizeInBytes = maxDownloadSizeInBytes;
+        this.resourceType = ResourceType.SNAPSHOT;
+    }
+
     @Override
     public long getId() {
         return id;
diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyAnswer.java b/core/src/main/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyAnswer.java
new file mode 100644
index 0000000..7c96225
--- /dev/null
+++ b/core/src/main/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyAnswer.java
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.storage.command;
+
+import java.util.List;
+
+import com.cloud.agent.api.Answer;
+
+public class QuerySnapshotZoneCopyAnswer extends Answer {
+    private List<String> files;
+
+    public QuerySnapshotZoneCopyAnswer(QuerySnapshotZoneCopyCommand cmd, List<String> files) {
+        super(cmd);
+        this.files = files;
+    }
+
+    public QuerySnapshotZoneCopyAnswer(QuerySnapshotZoneCopyCommand cmd, String errMsg) {
+        super(null, false, errMsg);
+    }
+
+    public List<String> getFiles() {
+        return files;
+    }
+}
diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyCommand.java
new file mode 100644
index 0000000..5bca524
--- /dev/null
+++ b/core/src/main/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyCommand.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.storage.command;
+
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+
+/*
+Command to get the list of snapshot files for copying a snapshot to a different zone
+ */
+
+public class QuerySnapshotZoneCopyCommand extends StorageSubSystemCommand {
+
+    private SnapshotObjectTO snapshot;
+
+    public QuerySnapshotZoneCopyCommand(final SnapshotObjectTO snapshot) {
+        super();
+        this.snapshot = snapshot;
+    }
+
+    public SnapshotObjectTO getSnapshot() {
+        return snapshot;
+    }
+
+    public void setSnapshot(final SnapshotObjectTO snapshot) {
+        this.snapshot = snapshot;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    @Override
+    public void setExecuteInSequence(boolean inSeq) {}
+}
diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsAnswer.java b/core/src/main/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsAnswer.java
new file mode 100644
index 0000000..eb8d099
--- /dev/null
+++ b/core/src/main/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsAnswer.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.storage.command.browser;
+
+import com.cloud.agent.api.Answer;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ListDataStoreObjectsAnswer extends Answer {
+
+    private boolean pathExists;
+
+    private int count;
+
+    private List<String> names;
+
+    private List<String> paths;
+
+    private List<String> absPaths;
+
+    private List<Boolean> isDirs;
+
+    private List<Long> sizes;
+
+    private List<Long> lastModified;
+
+    public ListDataStoreObjectsAnswer() {
+        super();
+    }
+
+    public ListDataStoreObjectsAnswer(boolean pathExists, int count, List<String> names, List<String> paths,
+            List<String> absPaths, List<Boolean> isDirs,
+            List<Long> sizes,
+            List<Long> lastModified) {
+        super();
+        this.pathExists = pathExists;
+        this.count = count;
+        this.names = names;
+        this.paths = paths;
+        this.absPaths = absPaths;
+        this.isDirs = isDirs;
+        this.sizes = sizes;
+        this.lastModified = lastModified;
+    }
+
+    public boolean isPathExists() {
+        return pathExists;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public List<String> getNames() {
+        if (names == null) {
+            return Collections.emptyList();
+        }
+        return names;
+    }
+
+    public List<String> getPaths() {
+        if (paths == null) {
+            return Collections.emptyList();
+        }
+        return paths;
+    }
+
+    public List<String> getAbsPaths() {
+        if (absPaths == null) {
+            return Collections.emptyList();
+        }
+        return absPaths;
+    }
+
+    public List<Boolean> getIsDirs() {
+        if (isDirs == null) {
+            return Collections.emptyList();
+        }
+        return isDirs;
+    }
+
+    public List<Long> getSizes() {
+        if (sizes == null) {
+            return Collections.emptyList();
+        }
+        return sizes;
+    }
+
+    public List<Long> getLastModified() {
+        if (lastModified == null) {
+            return Collections.emptyList();
+        }
+        return lastModified;
+    }
+}
diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsCommand.java
new file mode 100644
index 0000000..c5c0dc5
--- /dev/null
+++ b/core/src/main/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsCommand.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.storage.command.browser;
+
+import com.cloud.agent.api.storage.StorageCommand;
+import com.cloud.agent.api.to.DataStoreTO;
+
+public class ListDataStoreObjectsCommand extends StorageCommand {
+
+    private DataStoreTO store;
+
+    private String path;
+
+    private int startIndex;
+
+    private int pageSize;
+
+    public ListDataStoreObjectsCommand() {
+    }
+
+    public ListDataStoreObjectsCommand(DataStoreTO store, String path, int startIndex, int pageSize) {
+        super();
+        this.store = store;
+        this.path = path;
+        this.startIndex = startIndex;
+        this.pageSize = pageSize;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public DataStoreTO getStore() {
+        return store;
+    }
+
+    public int getStartIndex() {
+        return startIndex;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+}
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 72e6492..76b9390 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
@@ -42,6 +42,7 @@
     private boolean quiescevm;
     private String[] parents;
     private Long physicalSize = (long) 0;
+    private long accountId;
 
 
     public SnapshotObjectTO() {
@@ -51,6 +52,7 @@
     public SnapshotObjectTO(SnapshotInfo snapshot) {
         this.path = snapshot.getPath();
         this.setId(snapshot.getId());
+        this.accountId = snapshot.getAccountId();
         VolumeInfo vol = snapshot.getBaseVolume();
         if (vol != null) {
             this.volume = (VolumeObjectTO)vol.getTO();
@@ -168,6 +170,14 @@
         return parents;
     }
 
+    public long getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(long accountId) {
+        this.accountId = accountId;
+    }
+
     @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/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml b/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml
index 0e00fda..a0d1b4c 100644
--- a/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml
+++ b/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml
@@ -31,4 +31,4 @@
 
     <bean id="usageEventUtils" class="com.cloud.event.UsageEventUtils" />
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml
index cd98a63..ec3bb63 100644
--- a/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml
+++ b/core/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml
@@ -41,4 +41,4 @@
             value="com.cloud.consoleproxy.ConsoleProxyAllocator" />
     </bean>
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/core/src/main/resources/META-INF/cloudstack/cloudstack/direct-download/module.properties b/core/src/main/resources/META-INF/cloudstack/cloudstack/direct-download/module.properties
index 63e1a8b..cdd8e2e 100644
--- a/core/src/main/resources/META-INF/cloudstack/cloudstack/direct-download/module.properties
+++ b/core/src/main/resources/META-INF/cloudstack/cloudstack/direct-download/module.properties
@@ -18,4 +18,4 @@
 #
 
 name=direct-download
-parent=backend
\ No newline at end of file
+parent=backend
diff --git a/core/src/main/resources/META-INF/cloudstack/cluster/module.properties b/core/src/main/resources/META-INF/cloudstack/cluster/module.properties
new file mode 100644
index 0000000..e1b7a0e
--- /dev/null
+++ b/core/src/main/resources/META-INF/cloudstack/cluster/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=cluster
+parent=core
diff --git a/core/src/main/resources/META-INF/cloudstack/cluster/spring-core-lifecycle-cluster-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/cluster/spring-core-lifecycle-cluster-context-inheritable.xml
new file mode 100644
index 0000000..6278c0f
--- /dev/null
+++ b/core/src/main/resources/META-INF/cloudstack/cluster/spring-core-lifecycle-cluster-context-inheritable.xml
@@ -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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+>
+    <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
+        <property name="registry" ref="clusterDrsAlgorithmRegistry" />
+        <property name="typeClass" value="org.apache.cloudstack.cluster.ClusterDrsAlgorithm" />
+    </bean>
+
+</beans>
diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml
index b754d6b..71d1656 100644
--- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml
+++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml
@@ -39,5 +39,10 @@
         <property name="typeClass"
             value="com.cloud.utils.component.PluggableService" />
     </bean>
-    
-</beans>
\ No newline at end of file
+
+    <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
+        <property name="registry" ref="userDataProvidersRegistry" />
+        <property name="typeClass" value="org.apache.cloudstack.userdata.UserDataProvider" />
+    </bean>
+
+</beans>
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 cb55913..a36d124 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
@@ -33,7 +33,7 @@
         class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
         <property name="orderConfigKey" value="user.authenticators.order" />
         <property name="excludeKey" value="user.authenticators.exclude" />
-        <property name="orderConfigDefault" value="PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT" />
+        <property name="orderConfigDefault" value="PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT,OAUTH2" />
     </bean>
 
     <bean id="userTwoFactorAuthenticatorsRegistry"
@@ -47,7 +47,7 @@
           class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
         <property name="orderConfigKey" value="pluggableApi.authenticators.order" />
         <property name="excludeKey" value="pluggableApi.authenticators.exclude" />
-        <property name="orderConfigDefault" value="SAML2Auth" />
+        <property name="orderConfigDefault" value="SAML2Auth,OAUTH2Auth" />
     </bean>
 
     <bean id="userPasswordEncodersRegistry"
@@ -342,4 +342,12 @@
     <bean id="kubernetesClusterHelperRegistry"
           class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
     </bean>
+
+    <bean id="userDataProvidersRegistry"
+          class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
+    </bean>
+
+    <bean id="clusterDrsAlgorithmRegistry"
+          class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
+    </bean>
 </beans>
diff --git a/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties b/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties
index ea954a9..09f265b 100644
--- a/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties
+++ b/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties
@@ -18,4 +18,4 @@
 #
 
 name=kubernetes
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/core/src/main/resources/META-INF/cloudstack/planner/module.properties b/core/src/main/resources/META-INF/cloudstack/planner/module.properties
index 26c61d9..02a2606 100644
--- a/core/src/main/resources/META-INF/cloudstack/planner/module.properties
+++ b/core/src/main/resources/META-INF/cloudstack/planner/module.properties
@@ -18,4 +18,4 @@
 #
 
 name=planner
-parent=allocator
\ No newline at end of file
+parent=allocator
diff --git a/core/src/test/java/com/cloud/agent/api/GetHypervisorGuestOsNamesAnswerTest.java b/core/src/test/java/com/cloud/agent/api/GetHypervisorGuestOsNamesAnswerTest.java
new file mode 100644
index 0000000..36d2fb1
--- /dev/null
+++ b/core/src/test/java/com/cloud/agent/api/GetHypervisorGuestOsNamesAnswerTest.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 com.cloud.agent.api;
+
+import com.cloud.utils.Pair;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class GetHypervisorGuestOsNamesAnswerTest {
+
+    @Test
+    public void testGuestOsMappingAnswerSuccess() {
+        List<Pair<String, String>> hypervisorGuestOsNames = new ArrayList<>();
+        hypervisorGuestOsNames.add(new Pair<>("centos", "centos"));
+        GetHypervisorGuestOsNamesCommand cmd = new GetHypervisorGuestOsNamesCommand();
+        GetHypervisorGuestOsNamesAnswer answer = new GetHypervisorGuestOsNamesAnswer(cmd, hypervisorGuestOsNames);
+        List<Pair<String, String>> resultOsNames = answer.getHypervisorGuestOsNames();
+
+        Assert.assertEquals("centos", resultOsNames.get(0).first());
+        assertTrue(answer.getResult());
+    }
+
+    @Test
+    public void testGuestOsMappingAnswerFailure1() {
+        Throwable th = Mockito.mock(Throwable.class);
+        Mockito.when(th.getMessage()).thenReturn("failure");
+        GetHypervisorGuestOsNamesCommand cmd = new GetHypervisorGuestOsNamesCommand();
+        GetHypervisorGuestOsNamesAnswer answer = new GetHypervisorGuestOsNamesAnswer(cmd, th);
+
+        Assert.assertEquals("failure", answer.getDetails());
+        assertFalse(answer.getResult());
+    }
+
+    @Test
+    public void testGuestOsMappingAnswerFailure2() {
+        GetHypervisorGuestOsNamesCommand cmd = new GetHypervisorGuestOsNamesCommand();
+        GetHypervisorGuestOsNamesAnswer answer = new GetHypervisorGuestOsNamesAnswer(cmd, "failure");
+
+        Assert.assertEquals("failure", answer.getDetails());
+        assertFalse(answer.getResult());
+    }
+}
diff --git a/core/src/test/java/com/cloud/agent/api/GetHypervisorGuestOsNamesCommandTest.java b/core/src/test/java/com/cloud/agent/api/GetHypervisorGuestOsNamesCommandTest.java
new file mode 100644
index 0000000..5d7cb88
--- /dev/null
+++ b/core/src/test/java/com/cloud/agent/api/GetHypervisorGuestOsNamesCommandTest.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 com.cloud.agent.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+public class GetHypervisorGuestOsNamesCommandTest {
+
+    @Test
+    public void testExecuteInSequence() {
+        GetHypervisorGuestOsNamesCommand cmd =  new GetHypervisorGuestOsNamesCommand();
+        assertFalse(cmd.executeInSequence());
+    }
+
+    @Test
+    public void testKeyword() {
+        GetHypervisorGuestOsNamesCommand cmd =  new GetHypervisorGuestOsNamesCommand("centos");
+        assertEquals("centos", cmd.getKeyword());
+    }
+}
diff --git a/core/src/test/java/com/cloud/resource/ServerResourceBaseTest.java b/core/src/test/java/com/cloud/resource/ServerResourceBaseTest.java
index d9df73f..ed64e14 100644
--- a/core/src/test/java/com/cloud/resource/ServerResourceBaseTest.java
+++ b/core/src/test/java/com/cloud/resource/ServerResourceBaseTest.java
@@ -17,18 +17,21 @@
 package com.cloud.resource;
 
 import com.cloud.utils.net.NetUtils;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.commons.collections.CollectionUtils;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import javax.naming.ConfigurationException;
+import java.io.File;
+import java.io.IOException;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.ArrayList;
@@ -40,7 +43,7 @@
 import java.util.Map;
 
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ServerResourceBaseTest {
 
     private static final String[] NIC_NAME_STARTS_TO_AVOID = {"vnif", "vnbr", "peth", "vif", "virbr"};
@@ -54,165 +57,162 @@
 
     @Before
     public void setup() {
-        networkInterfaceMock1 = PowerMockito.mock(NetworkInterface.class);
-        networkInterfaceMock2 = PowerMockito.mock(NetworkInterface.class);
-        networkInterfaceMock3 = PowerMockito.mock(NetworkInterface.class);
-        networkInterfaceMock4 = PowerMockito.mock(NetworkInterface.class);
+        networkInterfaceMock1 = Mockito.mock(NetworkInterface.class);
+        networkInterfaceMock2 = Mockito.mock(NetworkInterface.class);
+        networkInterfaceMock3 = Mockito.mock(NetworkInterface.class);
+        networkInterfaceMock4 = Mockito.mock(NetworkInterface.class);
     }
 
     @Test
-    @PrepareForTest(ServerResourceBase.class)
     public void isValidNicToUseAsPrivateNicTestReturnFalseWhenNicIsVirtual() {
-        NetworkInterface networkInterfaceMock = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.when(networkInterfaceMock.isVirtual()).thenReturn(true);
+        NetworkInterface networkInterfaceMock = Mockito.mock(NetworkInterface.class);
+        Mockito.when(networkInterfaceMock.isVirtual()).thenReturn(true);
 
         Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
     }
 
     @Test
-    @PrepareForTest(ServerResourceBase.class)
     public void isValidNicToUseAsPrivateNicTestReturnFalseWhenNicNameStartsWithOneOfTheAvoidList() {
-        NetworkInterface networkInterfaceMock = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
-        PowerMockito.when(networkInterfaceMock.getName()).thenReturn("vniftest", "vnbrtest", "pethtest", "viftest", "virbrtest");
+        NetworkInterface networkInterfaceMock = Mockito.mock(NetworkInterface.class);
+        Mockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
+        Mockito.when(networkInterfaceMock.getName()).thenReturn("vniftest", "vnbrtest", "pethtest", "viftest", "virbrtest");
 
         Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock)));
     }
 
     @Test
-    @PrepareForTest(ServerResourceBase.class)
     public void isValidNicToUseAsPrivateNicTestReturnFalseWhenNicNameContainsColon() {
-        NetworkInterface networkInterfaceMock = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
-        PowerMockito.when(networkInterfaceMock.getName()).thenReturn("te:st");
+        NetworkInterface networkInterfaceMock = Mockito.mock(NetworkInterface.class);
+        Mockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
+        Mockito.when(networkInterfaceMock.getName()).thenReturn("te:st");
 
         Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
     }
 
     @Test
-    @PrepareForTest({ServerResourceBase.class, NetUtils.class})
     public void isValidNicToUseAsPrivateNicTestReturnFalseWhenNetUtilsGetNicParamsReturnsNull() {
-        NetworkInterface networkInterfaceMock = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
-        PowerMockito.when(networkInterfaceMock.getName()).thenReturn("testvnif", "testvnbr", "testpeth", "testvif", "testvirbr");
+        NetworkInterface networkInterfaceMock = Mockito.mock(NetworkInterface.class);
+        Mockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
+        Mockito.when(networkInterfaceMock.getName()).thenReturn("testvnif", "testvnbr", "testpeth", "testvif", "testvirbr");
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNicParams(Mockito.anyString())).thenReturn(null);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.getNicParams(Mockito.anyString())).thenReturn(null);
 
-        Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> {
-            Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
-        });
+            Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> {
+                Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
+            });
+        }
     }
 
     @Test
-    @PrepareForTest({ServerResourceBase.class, NetUtils.class})
     public void isValidNicToUseAsPrivateNicTestReturnFalseWhenNetUtilsGetNicParamsReturnsFirstElementNull() {
-        NetworkInterface networkInterfaceMock = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
-        PowerMockito.when(networkInterfaceMock.getName()).thenReturn("testvnif", "testvnbr", "testpeth", "testvif", "testvirbr");
+        NetworkInterface networkInterfaceMock = Mockito.mock(NetworkInterface.class);
+        Mockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
+        Mockito.when(networkInterfaceMock.getName()).thenReturn("testvnif", "testvnbr", "testpeth", "testvif", "testvirbr");
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNicParams(Mockito.anyString())).thenReturn(new String[]{null});
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.getNicParams(Mockito.anyString())).thenReturn(new String[]{null});
 
-        Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> {
-            Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
-        });
+            Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> {
+                Assert.assertFalse(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
+            });
+        }
     }
 
     @Test
-    @PrepareForTest({ServerResourceBase.class, NetUtils.class})
     public void isValidNicToUseAsPrivateNicTestReturnTrueWhenNetUtilsGetNicParamsReturnsAValidFirstElement() {
-        NetworkInterface networkInterfaceMock = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
-        PowerMockito.when(networkInterfaceMock.getName()).thenReturn("testvnif", "testvnbr", "testpeth", "testvif", "testvirbr");
+        NetworkInterface networkInterfaceMock = Mockito.mock(NetworkInterface.class);
+        Mockito.when(networkInterfaceMock.isVirtual()).thenReturn(false);
+        Mockito.when(networkInterfaceMock.getName()).thenReturn("testvnif", "testvnbr", "testpeth", "testvif", "testvirbr");
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNicParams(Mockito.anyString())).thenReturn(new String[]{"test"});
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.getNicParams(Mockito.anyString())).thenReturn(new String[]{"test"});
 
-        Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> {
-            Assert.assertTrue(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
-        });
+            Arrays.asList(NIC_NAME_STARTS_TO_AVOID).forEach(type -> {
+                Assert.assertTrue(serverResourceBaseSpy.isValidNicToUseAsPrivateNic(networkInterfaceMock));
+            });
+        }
     }
 
     @Test(expected = ConfigurationException.class)
-    @PrepareForTest(ServerResourceBase.class)
     public void tryToAutoDiscoverResourcePrivateNetworkInterfaceTestThrowConfigurationExceptionWhenNicsDoesNotHaveMoreElements() throws SocketException, ConfigurationException {
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getNetworkInterfaces()).thenReturn(Collections.enumeration(new ArrayList<>()));
+        try (MockedStatic<NetworkInterface> ignored = Mockito.mockStatic(NetworkInterface.class)) {
+            Mockito.when(NetworkInterface.getNetworkInterfaces()).thenReturn(Collections.enumeration(new ArrayList<>()));
 
-        serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
+            serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
+        }
     }
 
     @Test(expected = ConfigurationException.class)
-    @PrepareForTest(ServerResourceBase.class)
     public void tryToAutoDiscoverResourcePrivateNetworkInterfaceTestThrowConfigurationExceptionWhenNicsGetNetworkInterfacesThrowsSocketException() throws SocketException, ConfigurationException {
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getNetworkInterfaces()).thenThrow(SocketException.class);
+        try (MockedStatic<NetworkInterface> ignored = Mockito.mockStatic(NetworkInterface.class)) {
+            Mockito.when(NetworkInterface.getNetworkInterfaces()).thenThrow(SocketException.class);
 
-        serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
+            serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
+        }
     }
 
     @Test(expected = ConfigurationException.class)
-    @PrepareForTest(ServerResourceBase.class)
     public void tryToAutoDiscoverResourcePrivateNetworkInterfaceTestThrowConfigurationExceptionWhenThereIsNoValidNics() throws SocketException, ConfigurationException {
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getNetworkInterfaces()).thenReturn(Collections.enumeration(Arrays.asList(networkInterfaceMock1, networkInterfaceMock2)));
-        Mockito.doReturn(false).when(serverResourceBaseSpy).isValidNicToUseAsPrivateNic(Mockito.any());
+        try (MockedStatic<NetworkInterface> ignored = Mockito.mockStatic(NetworkInterface.class)) {
+            Mockito.when(NetworkInterface.getNetworkInterfaces()).thenReturn(Collections.enumeration(Arrays.asList(networkInterfaceMock1, networkInterfaceMock2)));
+            Mockito.doReturn(false).when(serverResourceBaseSpy).isValidNicToUseAsPrivateNic(Mockito.any());
 
-        serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
+            serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
 
-        Mockito.verify(serverResourceBaseSpy, Mockito.times(2)).isValidNicToUseAsPrivateNic(Mockito.any());
+            Mockito.verify(serverResourceBaseSpy, Mockito.times(2)).isValidNicToUseAsPrivateNic(Mockito.any());
+        }
     }
 
     @Test
-    @PrepareForTest(ServerResourceBase.class)
     public void tryToAutoDiscoverResourcePrivateNetworkInterfaceTestReturnNic() throws SocketException, ConfigurationException {
         Enumeration<NetworkInterface> interfaces = Collections.enumeration(Arrays.asList(networkInterfaceMock1, networkInterfaceMock2));
 
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getNetworkInterfaces()).thenReturn(interfaces);
-        Mockito.doReturn(false, true).when(serverResourceBaseSpy).isValidNicToUseAsPrivateNic(Mockito.any());
+        try (MockedStatic<NetworkInterface> ignored = Mockito.mockStatic(NetworkInterface.class)) {
+            Mockito.when(NetworkInterface.getNetworkInterfaces()).thenReturn(interfaces);
+            Mockito.doReturn(false, true).when(serverResourceBaseSpy).isValidNicToUseAsPrivateNic(Mockito.any());
 
-        serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
+            serverResourceBaseSpy.tryToAutoDiscoverResourcePrivateNetworkInterface();
 
-        Assert.assertEquals(networkInterfaceMock2, serverResourceBaseSpy._privateNic);
-        Mockito.verify(serverResourceBaseSpy, Mockito.times(2)).isValidNicToUseAsPrivateNic(Mockito.any());
+            Assert.assertEquals(networkInterfaceMock2, serverResourceBaseSpy.privateNic);
+            Mockito.verify(serverResourceBaseSpy, Mockito.times(2)).isValidNicToUseAsPrivateNic(Mockito.any());
+        }
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void defineResourceNetworkInterfacesTestUseXenbr0WhenPrivateNetworkInterfaceNotConfigured() {
         Map<String, Object> params = createParamsMap(null, "cloudbr1", "cloudbr2", "cloudbr3");
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2, networkInterfaceMock3, networkInterfaceMock4);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2, networkInterfaceMock3, networkInterfaceMock4);
 
-        serverResourceBaseSpy.defineResourceNetworkInterfaces(params);
+            serverResourceBaseSpy.defineResourceNetworkInterfaces(params);
 
-        verifyAndAssertNetworkInterfaces("xenbr0", "cloudbr1", "cloudbr2", "cloudbr3");
+            verifyAndAssertNetworkInterfaces("xenbr0", "cloudbr1", "cloudbr2", "cloudbr3");
+        }
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void defineResourceNetworkInterfacesTestUseXenbr1WhenPublicNetworkInterfaceNotConfigured() {
         Map<String, Object> params = createParamsMap("cloudbr0", null, "cloudbr2", "cloudbr3");
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2, networkInterfaceMock3, networkInterfaceMock4);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2, networkInterfaceMock3, networkInterfaceMock4);
 
-        serverResourceBaseSpy.defineResourceNetworkInterfaces(params);
+            serverResourceBaseSpy.defineResourceNetworkInterfaces(params);
 
-        verifyAndAssertNetworkInterfaces("cloudbr0", "xenbr1", "cloudbr2", "cloudbr3");
+            verifyAndAssertNetworkInterfaces("cloudbr0", "xenbr1", "cloudbr2", "cloudbr3");
+        }
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void defineResourceNetworkInterfacesTestUseConfiguredNetworkInterfaces() {
         Map<String, Object> params = createParamsMap("cloudbr0", "cloudbr1", "cloudbr2", "cloudbr3");
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2, networkInterfaceMock3, networkInterfaceMock4);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2, networkInterfaceMock3, networkInterfaceMock4);
 
-        serverResourceBaseSpy.defineResourceNetworkInterfaces(params);
+            serverResourceBaseSpy.defineResourceNetworkInterfaces(params);
 
-        verifyAndAssertNetworkInterfaces("cloudbr0", "cloudbr1", "cloudbr2", "cloudbr3");
+            verifyAndAssertNetworkInterfaces("cloudbr0", "cloudbr1", "cloudbr2", "cloudbr3");
+        }
     }
 
     private Map<String, Object> createParamsMap(String... params) {
@@ -225,7 +225,7 @@
     }
 
     private void verifyAndAssertNetworkInterfaces(String... expectedResults) {
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.times(4));
+        Mockito.verify(NetUtils.class, Mockito.times(4));
         NetUtils.getNetworkInterface(keyCaptor.capture());
         List<String> keys = keyCaptor.getAllValues();
 
@@ -233,9 +233,42 @@
             Assert.assertEquals(expectedResults[i], keys.get(i));
         }
 
-        Assert.assertEquals(networkInterfaceMock1, serverResourceBaseSpy._privateNic);
-        Assert.assertEquals(networkInterfaceMock2, serverResourceBaseSpy._publicNic);
-        Assert.assertEquals(networkInterfaceMock3, serverResourceBaseSpy._storageNic);
-        Assert.assertEquals(networkInterfaceMock4, serverResourceBaseSpy._storageNic2);
+        Assert.assertEquals(networkInterfaceMock1, serverResourceBaseSpy.privateNic);
+        Assert.assertEquals(networkInterfaceMock2, serverResourceBaseSpy.publicNic);
+        Assert.assertEquals(networkInterfaceMock3, serverResourceBaseSpy.storageNic);
+        Assert.assertEquals(networkInterfaceMock4, serverResourceBaseSpy.storageNic2);
+    }
+
+    @Test
+    public void testListFilesAtPath() throws IOException {
+        String nfsMountPoint = "/tmp/nfs";
+        String relativePath = "test";
+        int startIndex = 0;
+        int pageSize = 10;
+
+        // create a test directory with some files
+        File testDir = new File(nfsMountPoint, relativePath);
+        testDir.mkdirs();
+        File file1 = new File(testDir, "file1.txt");
+        File file2 = new File(testDir, "file2.txt");
+        file1.createNewFile();
+        file2.createNewFile();
+
+        ListDataStoreObjectsAnswer result = (ListDataStoreObjectsAnswer) serverResourceBaseSpy.listFilesAtPath(nfsMountPoint, relativePath, startIndex, pageSize);
+
+        Assert.assertTrue(result.getResult());
+        Assert.assertEquals(2, result.getCount());
+        List<String> expectedNames = Arrays.asList("file1.txt", "file2.txt");
+        List<String> expectedPaths = Arrays.asList("/test/file1.txt", "/test/file2.txt");
+        List<String> expectedAbsPaths = Arrays.asList(nfsMountPoint + "/test/file1.txt", nfsMountPoint + "/test/file2.txt");
+        List<Boolean> expectedIsDirs = Arrays.asList(false, false);
+        List<Long> expectedSizes = Arrays.asList(file2.length(), file1.length());
+        List<Long> expectedModifiedList = Arrays.asList(file2.lastModified(), file1.lastModified());
+        Assert.assertTrue(CollectionUtils.isEqualCollection(expectedNames, result.getNames()));
+        Assert.assertTrue(CollectionUtils.isEqualCollection(expectedPaths, result.getPaths()));
+        Assert.assertTrue(CollectionUtils.isEqualCollection(expectedAbsPaths, result.getAbsPaths()));
+        Assert.assertTrue(CollectionUtils.isEqualCollection(expectedIsDirs, result.getIsDirs()));
+        Assert.assertTrue(CollectionUtils.isEqualCollection(expectedSizes, result.getSizes()));
+        Assert.assertTrue(CollectionUtils.isEqualCollection(expectedModifiedList, result.getLastModified()));
     }
 }
diff --git a/core/src/test/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBaseTest.java b/core/src/test/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBaseTest.java
new file mode 100644
index 0000000..104c652
--- /dev/null
+++ b/core/src/test/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBaseTest.java
@@ -0,0 +1,43 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.storage.resource;
+
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.agent.api.Answer;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StorageSubsystemCommandHandlerBaseTest {
+
+    @Test
+    public void testHandleQuerySnapshotCommand() {
+        StorageSubsystemCommandHandlerBase storageSubsystemCommandHandlerBase = new StorageSubsystemCommandHandlerBase(Mockito.mock(StorageProcessor.class));
+        QuerySnapshotZoneCopyCommand querySnapshotZoneCopyCommand = new QuerySnapshotZoneCopyCommand(Mockito.mock(SnapshotObjectTO.class));
+        Answer answer = storageSubsystemCommandHandlerBase.handleStorageCommands(querySnapshotZoneCopyCommand);
+        Assert.assertTrue(answer instanceof QuerySnapshotZoneCopyAnswer);
+        QuerySnapshotZoneCopyAnswer querySnapshotZoneCopyAnswer = (QuerySnapshotZoneCopyAnswer)answer;
+        Assert.assertFalse(querySnapshotZoneCopyAnswer.getResult());
+        Assert.assertEquals("Unsupported command", querySnapshotZoneCopyAnswer.getDetails());
+    }
+}
diff --git a/core/src/test/java/com/cloud/storage/template/OVAProcessorTest.java b/core/src/test/java/com/cloud/storage/template/OVAProcessorTest.java
index 2387842..8ab5464 100644
--- a/core/src/test/java/com/cloud/storage/template/OVAProcessorTest.java
+++ b/core/src/test/java/com/cloud/storage/template/OVAProcessorTest.java
@@ -28,19 +28,15 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"javax.xml.*", "java.xml.*", "javax.management.*", "org.apache.xerces.*"})
-@PrepareForTest(OVAProcessor.class)
+@RunWith(MockitoJUnitRunner.class)
 public class OVAProcessorTest {
     OVAProcessor processor;
 
@@ -49,7 +45,7 @@
 
     @Before
     public void setUp() throws Exception {
-        processor = PowerMockito.spy(new OVAProcessor());
+        processor = Mockito.spy(new OVAProcessor());
         Map<String, Object> params = new HashMap<String, Object>();
         params.put(StorageLayer.InstanceConfigKey, mockStorageLayer);
         processor.configure("OVA Processor", params);
@@ -62,11 +58,11 @@
 
         Mockito.when(mockStorageLayer.exists(Mockito.anyString())).thenReturn(true);
 
-        Script mockScript = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(mockScript);
-        PowerMockito.when(mockScript.execute()).thenReturn("error while untaring the file");
-
-        processor.process(templatePath, null, templateName);
+        try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
+            Mockito.when(mock.execute()).thenReturn("error while untaring the file");
+        })) {
+            processor.process(templatePath, null, templateName);
+        }
     }
 
     @Test(expected = InternalErrorException.class)
@@ -78,11 +74,11 @@
         Mockito.when(mockStorageLayer.getSize(Mockito.anyString())).thenReturn(1000l);
         Mockito.doThrow(new InternalErrorException("virtual size calculation failed")).when(processor).getTemplateVirtualSize(Mockito.anyString(), Mockito.anyString());
 
-        Script mockScript = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(mockScript);
-        PowerMockito.when(mockScript.execute()).thenReturn(null);
-
-        processor.process(templatePath, null, templateName);
+        try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
+            Mockito.when(mock.execute()).thenReturn(null);
+        })) {
+            processor.process(templatePath, null, templateName);
+        }
     }
 
     @Test
@@ -96,15 +92,15 @@
         Mockito.when(mockStorageLayer.getSize(Mockito.anyString())).thenReturn(actualSize);
         Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize(Mockito.anyString(), Mockito.anyString());
 
-        Script mockScript = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(mockScript);
-        PowerMockito.when(mockScript.execute()).thenReturn(null);
-
-        Processor.FormatInfo info = processor.process(templatePath, null, templateName);
-        Assert.assertEquals(Storage.ImageFormat.OVA, info.format);
-        Assert.assertEquals("actual size:", actualSize, info.size);
-        Assert.assertEquals("virtual size:", virtualSize, info.virtualSize);
-        Assert.assertEquals("template name:", templateName + ".ova", info.filename);
+        try (MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
+            Mockito.when(mock.execute()).thenReturn(null);
+        })) {
+            Processor.FormatInfo info = processor.process(templatePath, null, templateName);
+            Assert.assertEquals(Storage.ImageFormat.OVA, info.format);
+            Assert.assertEquals("actual size:", actualSize, info.size);
+            Assert.assertEquals("virtual size:", virtualSize, info.virtualSize);
+            Assert.assertEquals("template name:", templateName + ".ova", info.filename);
+        }
     }
 
     @Test
@@ -129,7 +125,6 @@
         String templatePath = "/tmp";
         String templateName = "template";
         File mockFile = Mockito.mock(File.class);
-        Mockito.when(mockFile.length()).thenReturn(actualSize);
         Mockito.when(mockFile.getParent()).thenReturn(templatePath);
         Mockito.when(mockFile.getName()).thenReturn(templateName);
         Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize(templatePath, templateName);
diff --git a/core/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java b/core/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java
index 8fff0ad..abab008 100644
--- a/core/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java
+++ b/core/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java
@@ -29,15 +29,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.exception.InternalErrorException;
 import com.cloud.storage.Storage;
 import com.cloud.storage.StorageLayer;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(QCOW2Processor.class)
+@RunWith(MockitoJUnitRunner.class)
 public class QCOW2ProcessorTest {
     QCOW2Processor processor;
 
@@ -102,9 +100,7 @@
     @Test
     public void testGetVirtualSize() throws Exception {
         long virtualSize = 2000;
-        long actualSize = 1000;
         File mockFile = Mockito.mock(File.class);
-        Mockito.when(mockFile.length()).thenReturn(actualSize);
         Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize((File)Mockito.any());
         Assert.assertEquals(virtualSize, processor.getVirtualSize(mockFile));
         Mockito.verify(mockFile, Mockito.times(0)).length();
diff --git a/core/src/test/java/com/cloud/storage/template/VhdProcessorTest.java b/core/src/test/java/com/cloud/storage/template/VhdProcessorTest.java
index 2be4353..467d9a3 100644
--- a/core/src/test/java/com/cloud/storage/template/VhdProcessorTest.java
+++ b/core/src/test/java/com/cloud/storage/template/VhdProcessorTest.java
@@ -32,15 +32,14 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+
 
 import com.cloud.exception.InternalErrorException;
 import com.cloud.storage.Storage;
 import com.cloud.storage.StorageLayer;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(VhdProcessor.class)
+@RunWith(MockitoJUnitRunner.class)
 public class VhdProcessorTest {
     VhdProcessor processor;
 
@@ -107,7 +106,6 @@
         long virtualSize = 2000;
         long actualSize = 1000;
         File mockFile = Mockito.mock(File.class);
-        Mockito.when(mockFile.length()).thenReturn(actualSize);
         Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize((File) Mockito.any());
         Assert.assertEquals(virtualSize, processor.getVirtualSize(mockFile));
         Mockito.verify(mockFile,Mockito.times(0)).length();
diff --git a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingAnswerTest.java b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingAnswerTest.java
new file mode 100644
index 0000000..1ade5e0
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingAnswerTest.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.api.agent.test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.cloud.agent.api.CheckGuestOsMappingAnswer;
+import com.cloud.agent.api.CheckGuestOsMappingCommand;
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.mockito.Mockito;
+
+public class CheckGuestOsMappingAnswerTest {
+    CheckGuestOsMappingCommand cmd = new CheckGuestOsMappingCommand("CentOS 7.2", "centos64Guest", "6.0");
+    CheckGuestOsMappingAnswer answer = new CheckGuestOsMappingAnswer(cmd);
+
+    @Test
+    public void testGetResult() {
+        boolean b = answer.getResult();
+        assertTrue(b);
+    }
+
+    @Test
+    public void testExecuteInSequence() {
+        boolean b = answer.executeInSequence();
+        assertFalse(b);
+    }
+
+    @Test
+    public void testGuestOsMappingAnswerDetails() {
+        CheckGuestOsMappingCommand cmd = new CheckGuestOsMappingCommand("CentOS 7.2", "centos64Guest", "6.0");
+        CheckGuestOsMappingAnswer answer = new CheckGuestOsMappingAnswer(cmd, "details");
+        String details = answer.getDetails();
+        Assert.assertEquals("details", details);
+    }
+
+    @Test
+    public void testGuestOsMappingAnswerFailure() {
+        Throwable th = Mockito.mock(Throwable.class);
+        Mockito.when(th.getMessage()).thenReturn("Failure");
+        CheckGuestOsMappingCommand cmd = new CheckGuestOsMappingCommand("CentOS 7.2", "centos64Guest", "6.0");
+        CheckGuestOsMappingAnswer answer = new CheckGuestOsMappingAnswer(cmd, th);
+        assertFalse(answer.getResult());
+        Assert.assertEquals("Failure", answer.getDetails());
+
+    }
+}
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
new file mode 100644
index 0000000..3f68422
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingCommandTest.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.agent.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.cloud.agent.api.CheckGuestOsMappingCommand;
+import org.junit.Test;
+
+import com.cloud.agent.api.AgentControlCommand;
+
+public class CheckGuestOsMappingCommandTest {
+
+    @Test
+    public void testExecuteInSequence() {
+        CheckGuestOsMappingCommand cmd = new CheckGuestOsMappingCommand();
+        boolean b = cmd.executeInSequence();
+        assertFalse(b);
+    }
+
+    @Test
+    public void testCommandParams() {
+        CheckGuestOsMappingCommand cmd = new CheckGuestOsMappingCommand("CentOS 7.2", "centos64Guest", "6.0");
+        assertEquals("CentOS 7.2", cmd.getGuestOsName());
+        assertEquals("centos64Guest", cmd.getGuestOsHypervisorMappingName());
+        assertEquals("6.0", cmd.getHypervisorVersion());
+    }
+}
diff --git a/core/src/test/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloaderTest.java b/core/src/test/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloaderTest.java
index 81e504e..2fc0f7b 100644
--- a/core/src/test/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloaderTest.java
+++ b/core/src/test/java/org/apache/cloudstack/direct/download/MetalinkDirectTemplateDownloaderTest.java
@@ -26,7 +26,6 @@
 
     @InjectMocks
     protected MetalinkDirectTemplateDownloader metalinkDownloader = new MetalinkDirectTemplateDownloader(httpsUrl, 1000, 1000, false);
-
     @Test
     public void testCheckUrlMetalink() {
         metalinkDownloader.downloader = httpsDownloader;
diff --git a/core/src/test/java/org/apache/cloudstack/storage/command/DownloadCommandTest.java b/core/src/test/java/org/apache/cloudstack/storage/command/DownloadCommandTest.java
new file mode 100644
index 0000000..59263b1
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/storage/command/DownloadCommandTest.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 org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class DownloadCommandTest {
+
+    @Test
+    public void testDownloadCOmmandSnapshot() {
+        SnapshotObjectTO snapshotObjectTO = Mockito.mock(SnapshotObjectTO.class);
+        Long maxDownloadSizeInBytes = 1000L;
+        String url = "SOMEURL";
+        DownloadCommand cmd = new DownloadCommand(snapshotObjectTO, maxDownloadSizeInBytes, url);
+        Assert.assertEquals(DownloadCommand.ResourceType.SNAPSHOT, cmd.getResourceType());
+        Assert.assertEquals(maxDownloadSizeInBytes, cmd.getMaxDownloadSizeInBytes());
+        Assert.assertEquals(url, cmd.getUrl());
+    }
+}
diff --git a/core/src/test/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyAnswerTest.java b/core/src/test/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyAnswerTest.java
new file mode 100644
index 0000000..73221eb
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/storage/command/QuerySnapshotZoneCopyAnswerTest.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.storage.command;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class QuerySnapshotZoneCopyAnswerTest {
+
+    @Test
+    public void testQuerySnapshotZoneCopyAnswerSuccess() {
+        QuerySnapshotZoneCopyCommand cmd = Mockito.mock(QuerySnapshotZoneCopyCommand.class);
+        List<String> files = List.of("File1", "File2");
+        QuerySnapshotZoneCopyAnswer answer = new QuerySnapshotZoneCopyAnswer(cmd, files);
+        Assert.assertTrue(answer.getResult());
+        Assert.assertEquals(files.size(), answer.getFiles().size());
+        Assert.assertEquals(files.get(0), answer.getFiles().get(0));
+        Assert.assertEquals(files.get(1), answer.getFiles().get(1));
+    }
+
+    @Test
+    public void testQuerySnapshotZoneCopyAnswerFailure() {
+        QuerySnapshotZoneCopyCommand cmd = Mockito.mock(QuerySnapshotZoneCopyCommand.class);
+        String err = "SOMEERROR";
+        QuerySnapshotZoneCopyAnswer answer = new QuerySnapshotZoneCopyAnswer(cmd, err);
+        Assert.assertFalse(answer.getResult());
+        Assert.assertEquals(err, answer.getDetails());
+    }
+}
diff --git a/core/src/test/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsAnswerTest.java b/core/src/test/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsAnswerTest.java
new file mode 100644
index 0000000..fdf3c5b
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsAnswerTest.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.storage.command.browser;
+
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ListDataStoreObjectsAnswerTest {
+
+    @Test
+    public void testGetters() {
+        ListDataStoreObjectsAnswer answer = new ListDataStoreObjectsAnswer(true, 2,
+                Arrays.asList("file1", "file2"), Arrays.asList("path1", "path2"),
+                Arrays.asList("/mnt/datastore/path1", "/mnt/datastore/path2"), Arrays.asList(false, false),
+                Arrays.asList(1024L, 2048L), Arrays.asList(123456789L, 987654321L));
+
+        assertTrue(answer.isPathExists());
+        assertEquals(2, answer.getCount());
+        assertEquals(Arrays.asList("file1", "file2"), answer.getNames());
+        assertEquals(Arrays.asList("path1", "path2"), answer.getPaths());
+        assertEquals(Arrays.asList("/mnt/datastore/path1", "/mnt/datastore/path2"), answer.getAbsPaths());
+        assertEquals(Arrays.asList(false, false), answer.getIsDirs());
+        assertEquals(Arrays.asList(1024L, 2048L), answer.getSizes());
+        assertEquals(Arrays.asList(123456789L, 987654321L), answer.getLastModified());
+    }
+
+    @Test
+    public void testEmptyLists() {
+        ListDataStoreObjectsAnswer answer = new ListDataStoreObjectsAnswer(true, 0, null, null, null, null, null, null);
+
+        assertTrue(answer.isPathExists());
+        assertEquals(0, answer.getCount());
+        assertEquals(Collections.emptyList(), answer.getNames());
+        assertEquals(Collections.emptyList(), answer.getPaths());
+        assertEquals(Collections.emptyList(), answer.getAbsPaths());
+        assertEquals(Collections.emptyList(), answer.getIsDirs());
+        assertEquals(Collections.emptyList(), answer.getSizes());
+        assertEquals(Collections.emptyList(), answer.getLastModified());
+    }
+}
diff --git a/core/src/test/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsCommandTest.java b/core/src/test/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsCommandTest.java
new file mode 100644
index 0000000..b7c037a
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/storage/command/browser/ListDataStoreObjectsCommandTest.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.storage.command.browser;
+
+import com.cloud.agent.api.to.DataStoreTO;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class ListDataStoreObjectsCommandTest {
+
+    @Test
+    public void testStartIndex() {
+        DataStoreTO dataStore = Mockito.mock(DataStoreTO.class);
+        ListDataStoreObjectsCommand cmd = new ListDataStoreObjectsCommand(dataStore, "path", 40, 10);
+        assertEquals(40, cmd.getStartIndex());
+        assertEquals(10, cmd.getPageSize());
+        assertEquals("path", cmd.getPath());
+        assertEquals(dataStore, cmd.getStore());
+        assertFalse(cmd.executeInSequence());
+    }
+
+}
diff --git a/core/src/test/java/org/apache/cloudstack/storage/to/SnapshotObjectTOTest.java b/core/src/test/java/org/apache/cloudstack/storage/to/SnapshotObjectTOTest.java
new file mode 100644
index 0000000..e76bfb0
--- /dev/null
+++ b/core/src/test/java/org/apache/cloudstack/storage/to/SnapshotObjectTOTest.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.storage.to;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.test.util.ReflectionTestUtils;
+
+public class SnapshotObjectTOTest {
+
+    @Test
+    public void testAccountId() {
+        SnapshotObjectTO obj = new SnapshotObjectTO();
+        long accountId = 1L;
+        ReflectionTestUtils.setField(obj, "accountId", accountId);
+        Assert.assertEquals(accountId, obj.getAccountId());
+        accountId = 100L;
+        obj.setAccountId(accountId);
+        Assert.assertEquals(accountId, obj.getAccountId());
+        SnapshotInfo snapshot = Mockito.mock(SnapshotInfo.class);
+        Mockito.when(snapshot.getAccountId()).thenReturn(accountId);
+        Mockito.when(snapshot.getDataStore()).thenReturn(Mockito.mock(DataStore.class));
+        SnapshotObjectTO object = new SnapshotObjectTO(snapshot);
+        Assert.assertEquals(accountId, object.getAccountId());
+    }
+}
diff --git a/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/debian/changelog b/debian/changelog
index e9f708d..2b9b97d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,31 +1,19 @@
-cloudstack (4.18.3.0-SNAPSHOT) unstable; urgency=low
+cloudstack (4.19.1.0-SNAPSHOT) unstable; urgency=low
 
-  * Update the version to 4.18.3.0-SNAPSHOT
+  * Update the version to 4.19.1.0-SNAPSHOT
 
- -- the Apache CloudStack project <dev@cloudstack.apache.org>  Fri, 17 Apr 2024 11:08:04 -0300
+ -- the Apache CloudStack project <dev@cloudstack.apache.org>  Mon, 29 Jan 2024 10:21:52 +0530
 
-cloudstack (4.18.2.0) unstable; urgency=low
+ cloudstack (4.19.0.0) unstable; urgency=low
 
-  * Update the version to 4.18.2.0
+  * Update the version to 4.19.0.0
 
- -- the Apache CloudStack project <dev@cloudstack.apache.org>  Fri, 12 Apr 2024 08:25:04 -0300
-
-cloudstack (4.18.2.0-SNAPSHOT) unstable; urgency=low
-
-  * Update the version to 4.18.2.0-SNAPSHOT
-
- -- the Apache CloudStack project <dev@cloudstack.apache.org>  Thu, 12 Sep 2023 17:36:00 +0200
+ -- the Apache CloudStack project <dev@cloudstack.apache.org>  Mon, 29 Jan 2024 10:21:52 +0530
 
 cloudstack (4.18.1.0) unstable; urgency=low
 
   * Update the version to 4.18.1.0
 
- -- the Apache CloudStack project <dev@cloudstack.apache.org>  Thu, 07 Sep 2023 08:50:50 +0200
-
-cloudstack (4.18.1.0-SNAPSHOT) unstable; urgency=low
-
-  * Update the version to 4.18.1.0-SNAPSHOT
-
  -- the Apache CloudStack project <dev@cloudstack.apache.org>  Wed, 21 Jun 2023 12:30:00 +0200
 
 cloudstack (4.18.0.0) unstable; urgency=low
diff --git a/debian/cloudstack-cli.install b/debian/cloudstack-cli.install
index 287f9b1..978b68a 100644
--- a/debian/cloudstack-cli.install
+++ b/debian/cloudstack-cli.install
@@ -13,4 +13,4 @@
 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
-# under the License.
\ No newline at end of file
+# under the License.
diff --git a/debian/cloudstack-common.postinst b/debian/cloudstack-common.postinst
index 360f3bd..aa99eda 100644
--- a/debian/cloudstack-common.postinst
+++ b/debian/cloudstack-common.postinst
@@ -32,4 +32,4 @@
 cp $CLOUDUTILS_DIR/cloud_utils.py $DIST_DIR
 cp -R $CLOUDUTILS_DIR/cloudutils $DIST_DIR
 python3 -m py_compile $DIST_DIR/cloud_utils.py
-python3 -m compileall $DIST_DIR/cloudutils
\ No newline at end of file
+python3 -m compileall $DIST_DIR/cloudutils
diff --git a/debian/cloudstack-docs.install b/debian/cloudstack-docs.install
index 287f9b1..978b68a 100644
--- a/debian/cloudstack-docs.install
+++ b/debian/cloudstack-docs.install
@@ -13,4 +13,4 @@
 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
-# under the License.
\ No newline at end of file
+# under the License.
diff --git a/debian/cloudstack-management.install b/debian/cloudstack-management.install
index e637541..3d0d7e2 100644
--- a/debian/cloudstack-management.install
+++ b/debian/cloudstack-management.install
@@ -35,4 +35,5 @@
 /usr/bin/cloudstack-migrate-databases
 /usr/bin/cloudstack-setup-encryption
 /usr/bin/cloudstack-sysvmadm
+/usr/bin/cmk
 /usr/share/cloudstack-management/*
diff --git a/debian/control b/debian/control
index 7c1ff8e..9fec540 100644
--- a/debian/control
+++ b/debian/control
@@ -24,7 +24,7 @@
 
 Package: cloudstack-agent
 Architecture: all
-Depends: ${python:Depends}, ${python3:Depends}, openjdk-11-jre-headless | java11-runtime-headless | java11-runtime | openjdk-11-jre-headless | zulu-11, 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, lsb-release, aria2, ufw, apparmor
+Depends: ${python:Depends}, ${python3:Depends}, openjdk-11-jre-headless | java11-runtime-headless | java11-runtime | openjdk-11-jre-headless | zulu-11, 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, lsb-release, ufw, apparmor
 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 16f10ad..f8228e6 100755
--- a/debian/rules
+++ b/debian/rules
@@ -84,6 +84,10 @@
 	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
 
+	# Bundle cmk in cloudstack-management
+	wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.3.0/cmk.linux.x86-64 -O $(DESTDIR)/usr/bin/cmk
+	chmod +x $(DESTDIR)/usr/bin/cmk
+
 	# 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)
@@ -135,7 +139,7 @@
 	install -D systemvm/dist/* $(DESTDIR)/usr/share/$(PACKAGE)-common/vms/
 	# We need jasypt for cloud-install-sys-tmplt, so this is a nasty hack to get it into the right place
 	install -D agent/target/dependencies/jasypt-1.9.3.jar $(DESTDIR)/usr/share/$(PACKAGE)-common/lib
-	install -D utils/target/cloud-utils-$(VERSION).jar $(DESTDIR)/usr/share/$(PACKAGE)-common/lib/$(PACKAGE)-utils.jar
+	install -D utils/target/cloud-utils-$(VERSION)-bundled.jar $(DESTDIR)/usr/share/$(PACKAGE)-common/lib/$(PACKAGE)-utils.jar
 
 	# cloudstack-python
 	mkdir -p $(DESTDIR)/usr/share/pyshared
diff --git a/developer/pom.xml b/developer/pom.xml
index 0e6cd1d..8a875bb 100644
--- a/developer/pom.xml
+++ b/developer/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
@@ -112,8 +112,8 @@
                         <dependencies>
                             <!-- specify the dependent jdbc driver here -->
                             <dependency>
-                                <groupId>mysql</groupId>
-                                <artifactId>mysql-connector-java</artifactId>
+                                <groupId>com.mysql</groupId>
+                                <artifactId>mysql-connector-j</artifactId>
                                 <version>${cs.mysql.version}</version>
                             </dependency>
                         </dependencies>
@@ -180,8 +180,8 @@
                         <version>1.2.1</version>
                         <dependencies>
                             <dependency>
-                                <groupId>mysql</groupId>
-                                <artifactId>mysql-connector-java</artifactId>
+                                <groupId>com.mysql</groupId>
+                                <artifactId>mysql-connector-j</artifactId>
                                 <version>${cs.mysql.version}</version>
                             </dependency>
                         </dependencies>
@@ -238,8 +238,8 @@
                         <version>1.2.1</version>
                         <dependencies>
                             <dependency>
-                                <groupId>mysql</groupId>
-                                <artifactId>mysql-connector-java</artifactId>
+                                <groupId>com.mysql</groupId>
+                                <artifactId>mysql-connector-j</artifactId>
                                 <version>${cs.mysql.version}</version>
                             </dependency>
                         </dependencies>
diff --git a/engine/api/pom.xml b/engine/api/pom.xml
index f3378ca..780c6a4 100644
--- a/engine/api/pom.xml
+++ b/engine/api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
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 82396cf..3f7d6be 100644
--- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java
+++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java
@@ -83,6 +83,9 @@
     ConfigKey<Boolean> AllowExposeDomainInMetadata = new ConfigKey<>("Advanced", Boolean.class, "metadata.allow.expose.domain",
             "false", "If set to true, it allows the VM's domain to be seen in metadata.", true, ConfigKey.Scope.Domain);
 
+    ConfigKey<String> MetadataCustomCloudName = new ConfigKey<>("Advanced", String.class, "metadata.custom.cloud.name", "",
+            "If provided, a custom cloud-name in cloud-init metadata", true, ConfigKey.Scope.Zone);
+
     interface Topics {
         String VM_POWER_STATE = "vm.powerstate";
     }
@@ -251,7 +254,7 @@
      */
     boolean unmanage(String vmUuid);
 
-    UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException;
+    UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map<String, String> details) throws ResourceUnavailableException, InsufficientCapacityException;
 
     boolean checkIfVmHasClusterWideVolumes(Long vmId);
 
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 6d7c540..2005b70 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
@@ -20,6 +20,7 @@
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.dc.DataCenter;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.ConfigKey.Scope;
@@ -49,6 +50,7 @@
 import com.cloud.offering.NetworkOffering;
 import com.cloud.user.Account;
 import com.cloud.user.User;
+import com.cloud.utils.fsm.NoTransitionException;
 import com.cloud.utils.Pair;
 import com.cloud.vm.Nic;
 import com.cloud.vm.NicProfile;
@@ -267,6 +269,8 @@
 
     Map<String, String> finalizeServicesAndProvidersForNetwork(NetworkOffering offering, Long physicalNetworkId);
 
+    boolean stateTransitTo(Network network, Network.Event e) throws NoTransitionException;
+
     List<Provider> getProvidersForServiceInNetwork(Network network, Service service);
 
     StaticNatServiceProvider getStaticNatProviderForNetwork(Network network);
@@ -338,7 +342,7 @@
      */
     void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile);
 
-    Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException;
+    Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter datacenter, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException;
 
     void unmanageNics(VirtualMachineProfile vm);
 }
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
index 7bf845d..481d0eb 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
@@ -24,4 +24,6 @@
 
 public interface StorageOrchestrationService {
     MigrationResponse migrateData(Long srcDataStoreId, List<Long> destDatastores, MigrationPolicy migrationPolicy);
+
+    MigrationResponse migrateResources(Long srcImgStoreId, Long destImgStoreId, List<Long> templateIdList, List<Long> snapshotIdList);
 }
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 15f5b23..8a9d5fe 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
@@ -130,6 +130,8 @@
 
     boolean canVmRestartOnAnotherServer(long vmId);
 
+    void saveVolumeDetails(Long diskOfferingId, Long volumeId);
+
     /**
      * Allocate a volume or multiple volumes in case of template is registered with the 'deploy-as-is' option, allowing multiple disks
      */
@@ -153,7 +155,7 @@
      * @param type Type of the volume - ROOT, DATADISK, etc
      * @param name Name of the volume
      * @param offering DiskOffering for the volume
-     * @param size DiskOffering for the volume
+     * @param sizeInBytes size of the volume in bytes
      * @param minIops minimum IOPS for the disk, if not passed DiskOffering value will be used
      * @param maxIops maximum IOPS for the disk, if not passed DiskOffering value will be used
      * @param vm VirtualMachine this volume is attached to
@@ -165,9 +167,12 @@
      * @param chainInfo chain info for the volume. Hypervisor specific.
      * @return  DiskProfile of imported volume
      */
-    DiskProfile importVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template,
+    DiskProfile importVolume(Type type, String name, DiskOffering offering, Long sizeInBytes, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template,
                              Account owner, Long deviceId, Long poolId, String path, String chainInfo);
 
+    DiskProfile updateImportedVolume(Type type, DiskOffering offering, VirtualMachine vm, VirtualMachineTemplate template,
+                                     Long deviceId, Long poolId, String path, String chainInfo, DiskProfile diskProfile);
+
     /**
      * Unmanage VM volumes
      */
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/DirectoryService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/DirectoryService.java
deleted file mode 100644
index e507d99..0000000
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/DirectoryService.java
+++ /dev/null
@@ -1,37 +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.engine.service.api;
-
-import java.net.URI;
-import java.util.List;
-
-import com.cloud.utils.component.PluggableService;
-
-public interface DirectoryService {
-    void registerService(String serviceName, URI endpoint);
-
-    void unregisterService(String serviceName, URI endpoint);
-
-    List<URI> getEndPoints(String serviceName);
-
-    URI getLoadBalancedEndPoint(String serviceName);
-
-    List<PluggableService> listServices();
-
-}
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/EntityService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/EntityService.java
deleted file mode 100644
index fec0b99..0000000
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/EntityService.java
+++ /dev/null
@@ -1,57 +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.engine.service.api;
-
-import java.util.List;
-
-import javax.ws.rs.Path;
-
-import com.cloud.network.Network;
-import com.cloud.storage.Volume;
-import com.cloud.vm.VirtualMachine;
-
-/**
- * Service to retrieve CloudStack entities
- * very likely to change
- */
-@Path("resources")
-public interface EntityService {
-    List<String> listVirtualMachines();
-
-    List<String> listVolumes();
-
-    List<String> listNetworks();
-
-    List<String> listNics();
-
-    List<String> listSnapshots();
-
-    List<String> listTemplates();
-
-    List<String> listStoragePools();
-
-    List<String> listHosts();
-
-    VirtualMachine getVirtualMachine(String vm);
-
-    Volume getVolume(String volume);
-
-    Network getNetwork(String network);
-
-}
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OperationsServices.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OperationsServices.java
deleted file mode 100644
index 7b7abe8..0000000
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OperationsServices.java
+++ /dev/null
@@ -1,56 +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.engine.service.api;
-
-import java.net.URL;
-import java.util.List;
-
-import com.cloud.alert.Alert;
-
-public interface OperationsServices {
-//    List<AsyncJob> listJobs();
-//
-//    List<AsyncJob> listJobsInProgress();
-//
-//    List<AsyncJob> listJobsCompleted();
-//
-//    List<AsyncJob> listJobsCompleted(Long from);
-//
-//    List<AsyncJob> listJobsInWaiting();
-
-    void cancelJob(String job);
-
-    List<Alert> listAlerts();
-
-    Alert getAlert(String uuid);
-
-    void cancelAlert(String alert);
-
-    void registerForAlerts();
-
-    String registerForEventNotifications(String type, String topic, URL url);
-
-    boolean deregisterForEventNotifications(String notificationId);
-
-    /**
-     * @return the list of event topics someone can register for
-     */
-    List<String> listEventTopics();
-
-}
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/BucketInfo.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/BucketInfo.java
new file mode 100644
index 0000000..366c7c8
--- /dev/null
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/BucketInfo.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.engine.subsystem.api.storage;
+
+import org.apache.cloudstack.storage.object.Bucket;
+
+public interface BucketInfo extends DataObject, Bucket {
+
+    void addPayload(Object data);
+
+    Object getPayload();
+
+    Bucket getBucket();
+}
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreManager.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreManager.java
index 80e3ce1..de26a09 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreManager.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreManager.java
@@ -35,6 +35,8 @@
 
     List<DataStore> getImageStoresByScopeExcludingReadOnly(ZoneScope scope);
 
+    List<DataStore> getImageStoresByZoneIds(Long ... zoneIds);
+
     DataStore getRandomImageStore(long zoneId);
 
     DataStore getRandomUsableImageStore(long zoneId);
@@ -54,4 +56,8 @@
     List<DataStore> listImageCacheStores();
 
     boolean isRegionStore(DataStore store);
+
+    DataStore getImageStoreByUuid(String uuid);
+
+    Long getStoreZoneId(long storeId, DataStoreRole role);
 }
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 3e5761e..41c1d94 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
@@ -31,7 +31,7 @@
     String DEFAULT_PRIMARY = "DefaultPrimary";
 
     enum DataStoreProviderType {
-        PRIMARY, IMAGE, ImageCache
+        PRIMARY, IMAGE, ImageCache, OBJECT
     }
 
     DataStoreLifeCycle getDataStoreLifeCycle();
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProviderManager.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProviderManager.java
index e476d8f..379911a 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProviderManager.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProviderManager.java
@@ -33,4 +33,6 @@
     DataStoreProvider getDefaultCacheDataStoreProvider();
 
     List<DataStoreProvider> getProviders();
+
+    DataStoreProvider getDefaultObjectStoreProvider();
 }
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectStorageService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectStorageService.java
new file mode 100644
index 0000000..691da7e
--- /dev/null
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectStorageService.java
@@ -0,0 +1,22 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.engine.subsystem.api.storage;
+
+public interface ObjectStorageService {
+
+}
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectStoreProvider.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectStoreProvider.java
new file mode 100644
index 0000000..71b3762
--- /dev/null
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectStoreProvider.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.engine.subsystem.api.storage;
+
+public interface ObjectStoreProvider extends DataStoreProvider {
+
+}
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 4ab88c1..2c7d3c6 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
@@ -23,6 +23,8 @@
 
 import com.cloud.host.Host;
 import com.cloud.storage.StoragePool;
+import com.cloud.storage.Volume;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.utils.Pair;
 
 public interface PrimaryDataStoreDriver extends DataStoreDriver {
@@ -136,4 +138,23 @@
      * @param tagValue The value of the VM's tag
      */
     void provideVmTags(long vmId, long volumeId, String tagValue);
+
+    boolean isStorageSupportHA(StoragePoolType type);
+
+    void detachVolumeFromAllStorageNodes(Volume volume);
+    /**
+     * Data store driver needs its grantAccess() method called for volumes in order for them to be used with a host.
+     * @return true if we should call grantAccess() to use a volume
+     */
+    default boolean volumesRequireGrantAccessWhenUsed() {
+        return false;
+    }
+
+    /**
+     * Zone-wide data store supports using a volume across clusters without the need for data motion
+     * @return true if we don't need to data motion volumes across clusters for zone-wide use
+     */
+    default boolean zoneWideVolumesAvailableWithoutClusterMotion() {
+        return false;
+    }
 }
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 1dbff59..1b18264 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
@@ -43,6 +43,8 @@
     private boolean managed;
     private Long capacityIops;
 
+    private Boolean isTagARule;
+
     /**
      * @return the userInfo
      */
@@ -277,4 +279,12 @@
     public void setUsedBytes(long usedBytes) {
         this.usedBytes = usedBytes;
     }
+
+    public Boolean isTagARule() {
+        return isTagARule;
+    }
+
+    public void setIsTagARule(Boolean isTagARule) {
+        this.isTagARule = isTagARule;
+    }
 }
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotDataFactory.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotDataFactory.java
index 86f0ab8..2a2db4a 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotDataFactory.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotDataFactory.java
@@ -27,11 +27,17 @@
 
     SnapshotInfo getSnapshot(DataObject obj, DataStore store);
 
-    SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role);
+    SnapshotInfo getSnapshot(long snapshotId, long storeId, DataStoreRole role);
 
-    SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, boolean retrieveAnySnapshotFromVolume);
+    SnapshotInfo getSnapshotWithRoleAndZone(long snapshotId, DataStoreRole role, long zoneId);
 
-    List<SnapshotInfo> getSnapshots(long volumeId, DataStoreRole store);
+    SnapshotInfo getSnapshotOnPrimaryStore(long snapshotId);
+
+    SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, long zoneId, boolean retrieveAnySnapshotFromVolume);
+
+    List<SnapshotInfo> getSnapshotsForVolumeAndStoreRole(long volumeId, DataStoreRole store);
+
+    List<SnapshotInfo> getSnapshots(long snapshotId, Long zoneId);
 
     List<SnapshotInfo> listSnapshotOnCache(long snapshotId);
 
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 ecc412a..3213484 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
@@ -57,4 +57,6 @@
     void markBackedUp() throws CloudRuntimeException;
 
     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 053e0cd..d2e085f 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
@@ -17,6 +17,9 @@
 
 package org.apache.cloudstack.engine.subsystem.api.storage;
 
+import org.apache.cloudstack.framework.async.AsyncCallFuture;
+
+import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.storage.Snapshot.Event;
 
 public interface SnapshotService {
@@ -35,4 +38,8 @@
     void processEventOnSnapshotObject(SnapshotInfo snapshot, Event event);
 
     void cleanupOnSnapshotBackupFailure(SnapshotInfo snapshot);
+
+    AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore dataStore) throws ResourceUnavailableException;
+
+    AsyncCallFuture<CreateCmdResult> queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException;
 }
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 62c4c20..f3aa8f5 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
@@ -28,11 +28,11 @@
 
     SnapshotInfo backupSnapshot(SnapshotInfo snapshot);
 
-    boolean deleteSnapshot(Long snapshotId);
+    boolean deleteSnapshot(Long snapshotId, Long zoneId);
 
     boolean revertSnapshot(SnapshotInfo snapshot);
 
-    StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op);
+    StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op);
 
     void postSnapshotCreation(SnapshotInfo snapshot);
 }
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 fde71fe..6a78f6f 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
@@ -52,9 +52,12 @@
      *            avoid
      * @param int returnUpTo (use -1 to return all possible pools)
      * @param boolean bypassStorageTypeCheck allows bypassing useLocalStorage check for provided DiskProfile when true
+     * @param String keyword if passed, will only return storage pools that contain this keyword in the name
      * @return List<StoragePool> List of storage pools that are suitable for the
      *         VM
      **/
+    List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword);
+
     List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck);
 
 
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageStrategyFactory.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageStrategyFactory.java
index e309b98..deaee43 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageStrategyFactory.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageStrategyFactory.java
@@ -34,6 +34,8 @@
 
     SnapshotStrategy getSnapshotStrategy(Snapshot snapshot, SnapshotOperation op);
 
+    SnapshotStrategy getSnapshotStrategy(Snapshot snapshot, Long zoneId, SnapshotOperation op);
+
     VMSnapshotStrategy getVmSnapshotStrategy(VMSnapshot vmSnapshot);
 
     /**
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 a6ebf95..7c4d56e 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
@@ -18,13 +18,13 @@
  */
 package org.apache.cloudstack.engine.subsystem.api.storage;
 
-import com.cloud.agent.api.Answer;
 import java.util.Map;
 
 import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
 import org.apache.cloudstack.framework.async.AsyncCallFuture;
 import org.apache.cloudstack.storage.command.CommandResult;
 
+import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.exception.StorageAccessException;
 import com.cloud.host.Host;
@@ -35,6 +35,9 @@
 import com.cloud.utils.Pair;
 
 public interface VolumeService {
+
+    String SNAPSHOT_ID = "SNAPSHOT_ID";
+
     class VolumeApiResult extends CommandResult {
         private final VolumeInfo volume;
 
@@ -114,4 +117,8 @@
       VolumeInfo sourceVolume, VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync);
 
     void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount);
+
+    Pair<String, String> checkAndRepairVolume(VolumeInfo volume);
+
+    void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host);
 }
diff --git a/engine/api/src/main/java/org/apache/cloudstack/storage/object/ObjectStoreEntity.java b/engine/api/src/main/java/org/apache/cloudstack/storage/object/ObjectStoreEntity.java
new file mode 100644
index 0000000..9ee94b0
--- /dev/null
+++ b/engine/api/src/main/java/org/apache/cloudstack/storage/object/ObjectStoreEntity.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.storage.object;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ObjectStoreEntity extends DataStore, ObjectStore {
+    Bucket createBucket(Bucket bucket, boolean objectLock);
+
+    List<Bucket> listBuckets();
+
+    boolean createUser(long accountId);
+
+    boolean deleteBucket(String name);
+
+    boolean setBucketEncryption(String name);
+
+    boolean deleteBucketEncryption(String name);
+
+    boolean setBucketVersioning(String name);
+
+    boolean deleteBucketVersioning(String name);
+
+    void setBucketPolicy(String name, String policy);
+
+    void setQuota(String name, int quota);
+
+    Map<String, Long> getAllBucketsUsage();
+}
diff --git a/engine/components-api/pom.xml b/engine/components-api/pom.xml
index 20e9e5d..5811699 100644
--- a/engine/components-api/pom.xml
+++ b/engine/components-api/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
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 818e0a7..2182dfc 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
@@ -39,6 +39,16 @@
 public interface AgentManager {
     static final ConfigKey<Integer> Wait = new ConfigKey<Integer>("Advanced", Integer.class, "wait", "1800", "Time in seconds to wait for control commands to return",
             true);
+    ConfigKey<Boolean> EnableKVMAutoEnableDisable = new ConfigKey<>(Boolean.class,
+                    "enable.kvm.host.auto.enable.disable",
+                    "Advanced",
+                    "false",
+                    "(KVM only) Enable Auto Disable/Enable KVM hosts in the cluster " +
+                            "according to the hosts health check results",
+                    true, ConfigKey.Scope.Cluster, null);
+
+    ConfigKey<Integer> ReadyCommandWait = new ConfigKey<Integer>("Advanced", Integer.class, "ready.command.wait",
+            "60", "Time in seconds to wait for Ready command to return", true);
 
     public enum TapAgentsAction {
         Add, Del, Contains,
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 2ce7631..0232d07 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
@@ -20,6 +20,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO;
 
 import com.cloud.dc.ClusterVO;
@@ -59,6 +60,10 @@
     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";
 
+    static final String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length";
+    static final ConfigKey<Integer> VM_USERDATA_MAX_LENGTH = new ConfigKey<>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768",
+            "Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true);
+
     /**
      * @param offering
      * @return
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 ec305fe..1a2fab1 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
@@ -240,7 +240,7 @@
         try {
             s_eventBus.publish(event);
         } catch (EventBusException e) {
-            s_logger.warn("Failed to publish usage event on the the event bus.");
+            s_logger.warn("Failed to publish usage event on the event bus.");
         }
     }
 
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 1dd999d..72737d0 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
@@ -53,7 +53,7 @@
     ConfigKey<Long> TimeBetweenCleanup = new ConfigKey<>("Advanced", Long.class,
         "time.between.cleanup", "86400", "The time in seconds to wait before the"
         + " cleanup thread runs for the different HA-Worker-Threads. The cleanup thread finds all the work items "
-        + "that were successful and is now ready to be purged from the the database (table: op_ha_work).",
+        + "that were successful and is now ready to be purged from the database (table: op_ha_work).",
         true, Cluster);
 
     ConfigKey<Integer> MaxRetries = new ConfigKey<>("Advanced", Integer.class, "max.retries",
@@ -72,6 +72,9 @@
         + " which are registered for the HA event that were successful and are now ready to be purged.",
         true, Cluster);
 
+    public static final ConfigKey<Boolean> 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 {
         Migration,  // Migrating VMs off of a host.
         Stop,       // Stops a VM for storage pool migration purposes.  This should be obsolete now.
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 73d3de0..3693746 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
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.network;
 
+import java.util.Date;
 import java.util.List;
 
 import org.apache.cloudstack.api.response.AcquirePodIpCmdResponse;
@@ -237,4 +238,53 @@
 
     public static final String MESSAGE_ASSIGN_IPADDR_EVENT = "Message.AssignIpAddr.Event";
     public static final String MESSAGE_RELEASE_IPADDR_EVENT = "Message.ReleaseIpAddr.Event";
+
+
+    /**
+     * Checks if the given public IP address is not in active quarantine.
+     * It returns `true` if:
+     *  <ul>
+     *   <li>The IP was never in quarantine;</li>
+     *   <li>The IP was in quarantine, but the quarantine expired;</li>
+     *   <li>The IP is still in quarantine; however, the new owner is the same as the previous owner, therefore, the IP can be allocated.</li>
+     * </ul>
+     *
+     * It returns `false` if:
+     * <ul>
+     *   <li>The IP is in active quarantine and the new owner is different from the previous owner.</li>
+     * </ul>
+     *
+     * @param ip used to check if it is in active quarantine.
+     * @param account used to identify the new owner of the public IP.
+     * @return true if the IP can be allocated, and false otherwise.
+     */
+    boolean canPublicIpAddressBeAllocated(IpAddress ip, Account account);
+
+    /**
+     * Adds the given public IP address to quarantine for the duration of the global configuration `public.ip.address.quarantine.duration` value.
+     *
+     * @param publicIpAddress to be quarantined.
+     * @param domainId used to retrieve the quarantine duration.
+     * @return the {@link PublicIpQuarantine} persisted in the database.
+     */
+    PublicIpQuarantine addPublicIpAddressToQuarantine(IpAddress publicIpAddress, Long domainId);
+
+    /**
+     * Prematurely removes a public IP address from quarantine. It is required to provide a reason for removing it.
+     *
+     * @param quarantineProcessId the ID of the active quarantine process.
+     * @param removalReason       for prematurely removing the public IP address from quarantine.
+     */
+    void removePublicIpAddressFromQuarantine(Long quarantineProcessId, String removalReason);
+
+    /**
+     * Updates the end date of a public IP address in active quarantine. It can increase and decrease the duration of the quarantine.
+     *
+     * @param quarantineProcessId the ID of the quarantine process.
+     * @param endDate             the new end date for the quarantine.
+     * @return the updated quarantine object.
+     */
+    PublicIpQuarantine updatePublicIpAddressInQuarantine(Long quarantineProcessId, Date endDate);
+
+    void updateSourceNatIpAddress(IPAddressVO requestedIp, List<IPAddressVO> userIps) throws Exception;
 }
diff --git a/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java b/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java
index ef8dddc..1e1251d 100644
--- a/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java
+++ b/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java
@@ -94,7 +94,7 @@
         try {
             s_eventBus.publish(eventMsg);
         } catch (EventBusException e) {
-            s_logger.warn("Failed to publish state change event on the the event bus.");
+            s_logger.warn("Failed to publish state change event on the event bus.");
         }
     }
 
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 7fdec90..b1594e3 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
@@ -96,6 +96,14 @@
             true,
             ConfigKey.Scope.Global,
             null);
+    ConfigKey<Integer> ConvertVmwareInstanceToKvmTimeout = new ConfigKey<>(Integer.class,
+            "convert.vmware.instance.to.kvm.timeout",
+            "Storage",
+            "8",
+            "Timeout (in hours) for the instance conversion process from VMware through the virt-v2v binary on a KVM host",
+            true,
+            ConfigKey.Scope.Global,
+            null);
     ConfigKey<Boolean> KvmAutoConvergence = new ConfigKey<>(Boolean.class,
             "kvm.auto.convergence",
             "Storage",
@@ -196,6 +204,9 @@
             "Whether HTTP redirect is followed during store downloads for objects such as template, volume etc.",
             true, ConfigKey.Scope.Global);
 
+    ConfigKey<Long> 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);
+
     /**
      * should we execute in sequence not involving any storages?
      * @return tru if commands should execute in sequence
diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java b/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java
index a7b5a64..e59ec92 100644
--- a/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java
+++ b/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java
@@ -73,7 +73,7 @@
      * With managed storage on XenServer and vSphere, CloudStack needs to use an iSCSI SR (XenServer) or datastore (vSphere) per CloudStack
      * volume. Since XenServer and vSphere are limited to the hundreds with regards to how many SRs or datastores can be leveraged per
      * compute cluster, this method is used to check a Global Setting (that specifies the maximum number of SRs or datastores per compute cluster)
-     * against what is being requested. KVM does not apply here here because it does not suffer from the same scalability limits as XenServer and
+     * against what is being requested. KVM does not apply here because it does not suffer from the same scalability limits as XenServer and
      * vSphere do. With XenServer and vSphere, each host is configured to see all the SRs/datastores of the cluster. With KVM, each host typically
      * is only configured to see the managed volumes of the VMs that are currently running on that host.
      *
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 a3d9c1b..3b3537d 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
@@ -18,6 +18,7 @@
 
 import java.util.List;
 
+import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -29,9 +30,11 @@
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.StorageUnavailableException;
 import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Storage.TemplateType;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.VMTemplateStoragePoolVO;
 import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
 import com.cloud.utils.Pair;
 import com.cloud.vm.VirtualMachineProfile;
 
@@ -114,7 +117,7 @@
 
     Long getTemplateSize(long templateId, long zoneId);
 
-    DataStore getImageStore(String storeUuid, Long zoneId);
+    DataStore getImageStore(String storeUuid, Long zoneId, VolumeVO volume);
 
     String getChecksum(DataStore store, String templatePath, String algorithm);
 
@@ -133,5 +136,7 @@
     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);
+
     List<DatadiskTO> getTemplateDisksOnImageStore(Long templateId, DataStoreRole role, String configurationId);
 }
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 bc689fe..2d51c3c 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
@@ -26,7 +26,6 @@
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.network.element.NetworkElement;
 import com.cloud.offering.ServiceOffering;
-import com.cloud.service.ServiceOfferingVO;
 import com.cloud.template.VirtualMachineTemplate;
 import com.cloud.template.VirtualMachineTemplate.BootloaderType;
 import com.cloud.user.Account;
@@ -260,7 +259,8 @@
         return _params;
     }
 
-    public void setServiceOffering(ServiceOfferingVO offering) {
+    @Override
+    public void setServiceOffering(ServiceOffering offering) {
         _offering = offering;
     }
 
diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWork.java b/engine/components-api/src/main/java/com/cloud/vm/VmWork.java
index a0b5e97..3333829 100644
--- a/engine/components-api/src/main/java/com/cloud/vm/VmWork.java
+++ b/engine/components-api/src/main/java/com/cloud/vm/VmWork.java
@@ -17,9 +17,21 @@
 package com.cloud.vm;
 
 import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.cloud.serializer.GsonHelper;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
 
 public class VmWork implements Serializable {
     private static final long serialVersionUID = -6946320465729853589L;
+    private static final Gson gsonLogger = GsonHelper.getGsonLogger();
 
     long userId;
     long accountId;
@@ -56,4 +68,31 @@
     public String getHandlerName() {
         return handlerName;
     }
+
+    @Override
+    public String toString() {
+        return gsonLogger.toJson(this);
+    }
+
+    protected String toStringAfterRemoveParams(String paramsObjName, List<String> params) {
+        String ObjJsonStr = gsonLogger.toJson(this);
+        if (StringUtils.isBlank(ObjJsonStr) || StringUtils.isBlank(paramsObjName) || CollectionUtils.isEmpty(params)) {
+            return ObjJsonStr;
+        }
+
+        try {
+            Map<String, Object> ObjMap = new ObjectMapper().readValue(ObjJsonStr, HashMap.class);
+            if (ObjMap != null && ObjMap.containsKey(paramsObjName)) {
+                for (String param : params) {
+                    ((Map<String, String>)ObjMap.get(paramsObjName)).remove(param);
+                }
+                String resultJson = new ObjectMapper().writeValueAsString(ObjMap);
+                return resultJson;
+            }
+        } catch (final JsonProcessingException e) {
+            // Ignore json exception
+        }
+
+        return ObjJsonStr;
+    }
 }
diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkCheckAndRepairVolume.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkCheckAndRepairVolume.java
new file mode 100644
index 0000000..eaee4d1
--- /dev/null
+++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkCheckAndRepairVolume.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.vm;
+
+public class VmWorkCheckAndRepairVolume extends VmWork {
+
+    private static final long serialVersionUID = 341816293003023824L;
+
+    private Long volumeId;
+
+    private String repair;
+
+    public VmWorkCheckAndRepairVolume(long userId, long accountId, long vmId, String handlerName,
+                                      Long volumeId, String repair) {
+        super(userId, accountId, vmId, handlerName);
+        this.repair = repair;
+        this.volumeId = volumeId;
+    }
+
+    public Long getVolumeId() {
+        return volumeId;
+    }
+
+    public String getRepair() {
+        return repair;
+    }
+}
diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkJobHandlerProxy.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkJobHandlerProxy.java
index ce10a83..a542da6 100644
--- a/engine/components-api/src/main/java/com/cloud/vm/VmWorkJobHandlerProxy.java
+++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkJobHandlerProxy.java
@@ -21,15 +21,13 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.log4j.Logger;
-
-import com.google.gson.Gson;
-
 import org.apache.cloudstack.framework.jobs.impl.JobSerializerHelper;
 import org.apache.cloudstack.jobs.JobInfo;
+import org.apache.log4j.Logger;
 
 import com.cloud.serializer.GsonHelper;
 import com.cloud.utils.Pair;
+import com.google.gson.Gson;
 
 /**
  * VmWorkJobHandlerProxy can not be used as standalone due to run-time
@@ -102,12 +100,12 @@
 
             try {
                 if (s_logger.isDebugEnabled())
-                    s_logger.debug("Execute VM work job: " + work.getClass().getName() + _gsonLogger.toJson(work));
+                    s_logger.debug("Execute VM work job: " + work.getClass().getName() + work);
 
                 Object obj = method.invoke(_target, work);
 
                 if (s_logger.isDebugEnabled())
-                    s_logger.debug("Done executing VM work job: " + work.getClass().getName() + _gsonLogger.toJson(work));
+                    s_logger.debug("Done executing VM work job: " + work.getClass().getName() + work);
 
                 assert (obj instanceof Pair);
                 return (Pair<JobInfo.Status, String>)obj;
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 6d62644..8474052 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
@@ -16,6 +16,8 @@
 // under the License.
 package com.cloud.vm;
 
+import java.util.List;
+
 import com.cloud.storage.Snapshot;
 
 public class VmWorkTakeVolumeSnapshot extends VmWork {
@@ -29,8 +31,11 @@
     private Snapshot.LocationType locationType;
     private boolean asyncBackup;
 
+    private List<Long> zoneIds;
+
     public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String handlerName,
-            Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType, boolean asyncBackup) {
+            Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType,
+            boolean asyncBackup, List<Long> zoneIds) {
         super(userId, accountId, vmId, handlerName);
         this.volumeId = volumeId;
         this.policyId = policyId;
@@ -38,6 +43,7 @@
         this.quiesceVm = quiesceVm;
         this.locationType = locationType;
         this.asyncBackup = asyncBackup;
+        this.zoneIds = zoneIds;
     }
 
     public Long getVolumeId() {
@@ -61,4 +67,8 @@
     public boolean isAsyncBackup() {
         return asyncBackup;
     }
+
+    public List<Long> getZoneIds() {
+        return zoneIds;
+    }
 }
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
new file mode 100644
index 0000000..feb7ee4
--- /dev/null
+++ b/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java
@@ -0,0 +1,36 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.vm;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VmWorkTakeVolumeSnapshotTest {
+
+    @Test
+    public void testVmWorkTakeVolumeSnapshotZoneIds() {
+        List<Long> zoneIds = List.of(10L, 20L);
+        VmWorkTakeVolumeSnapshot work = new VmWorkTakeVolumeSnapshot(1L, 1L, 1L, "handler",
+                1L, 1L, 1L, false, null, false, zoneIds);
+        Assert.assertNotNull(work.getZoneIds());
+        Assert.assertEquals(zoneIds.size(), work.getZoneIds().size());
+        Assert.assertEquals(zoneIds.get(0), work.getZoneIds().get(0));
+        Assert.assertEquals(zoneIds.get(1), work.getZoneIds().get(1));
+    }
+}
diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml
index 2eda6b0..a48d502 100755
--- a/engine/orchestration/pom.xml
+++ b/engine/orchestration/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -68,6 +68,11 @@
             <artifactId>cloud-server</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-shutdown</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
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 26e8718..606a902 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
@@ -52,6 +52,7 @@
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 import org.apache.log4j.MDC;
 
@@ -595,6 +596,7 @@
 
         final Long dcId = host.getDataCenterId();
         final ReadyCommand ready = new ReadyCommand(dcId, host.getId(), NumbersUtil.enableHumanReadableSizes);
+        ready.setWait(ReadyCommandWait.value());
         final Answer answer = easySend(hostId, ready);
         if (answer == null || !answer.getResult()) {
             // this is tricky part for secondary storage
@@ -1282,6 +1284,52 @@
             super(type, link, data);
         }
 
+        private void processHostHealthCheckResult(Boolean hostHealthCheckResult, long hostId) {
+            if (hostHealthCheckResult == null) {
+                return;
+            }
+            HostVO host = _hostDao.findById(hostId);
+            if (host == null) {
+                s_logger.error(String.format("Unable to find host with ID: %s", hostId));
+                return;
+            }
+            if (!BooleanUtils.toBoolean(EnableKVMAutoEnableDisable.valueIn(host.getClusterId()))) {
+                s_logger.debug(String.format("%s is disabled for the cluster %s, cannot process the health check result " +
+                        "received for the host %s", EnableKVMAutoEnableDisable.key(), host.getClusterId(), host.getName()));
+                return;
+            }
+
+            ResourceState.Event resourceEvent = hostHealthCheckResult ? ResourceState.Event.Enable : ResourceState.Event.Disable;
+
+            try {
+                s_logger.info(String.format("Host health check %s, auto %s KVM host: %s",
+                        hostHealthCheckResult ? "succeeds" : "fails",
+                        hostHealthCheckResult ? "enabling" : "disabling",
+                        host.getName()));
+                _resourceMgr.autoUpdateHostAllocationState(hostId, resourceEvent);
+            } catch (NoTransitionException e) {
+                s_logger.error(String.format("Cannot Auto %s host: %s", resourceEvent, host.getName()), e);
+            }
+        }
+
+        private void processStartupRoutingCommand(StartupRoutingCommand startup, long hostId) {
+            if (startup == null) {
+                s_logger.error("Empty StartupRoutingCommand received");
+                return;
+            }
+            Boolean hostHealthCheckResult = startup.getHostHealthCheckResult();
+            processHostHealthCheckResult(hostHealthCheckResult, hostId);
+        }
+
+        private void processPingRoutingCommand(PingRoutingCommand pingRoutingCommand, long hostId) {
+            if (pingRoutingCommand == null) {
+                s_logger.error("Empty PingRoutingCommand received");
+                return;
+            }
+            Boolean hostHealthCheckResult = pingRoutingCommand.getHostHealthCheckResult();
+            processHostHealthCheckResult(hostHealthCheckResult, hostId);
+        }
+
         protected void processRequest(final Link link, final Request request) {
             final AgentAttache attache = (AgentAttache)link.attachment();
             final Command[] cmds = request.getCommands();
@@ -1325,6 +1373,7 @@
                 try {
                     if (cmd instanceof StartupRoutingCommand) {
                         final StartupRoutingCommand startup = (StartupRoutingCommand) cmd;
+                        processStartupRoutingCommand(startup, hostId);
                         answer = new StartupAnswer(startup, attache.getId(), mgmtServiceConf.getPingInterval());
                     } else if (cmd instanceof StartupProxyCommand) {
                         final StartupProxyCommand startup = (StartupProxyCommand) cmd;
@@ -1359,7 +1408,9 @@
                             // if the router is sending a ping, verify the
                             // gateway was pingable
                             if (cmd instanceof PingRoutingCommand) {
+                                processPingRoutingCommand((PingRoutingCommand) cmd, hostId);
                                 gatewayAccessible = ((PingRoutingCommand)cmd).isGatewayAccessible();
+
                                 if (host != null) {
                                     if (!gatewayAccessible) {
                                         // alert that host lost connection to
@@ -1786,8 +1837,8 @@
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] { CheckTxnBeforeSending, Workers, Port, Wait, AlertWait, DirectAgentLoadSize, DirectAgentPoolSize,
-            DirectAgentThreadCap };
+        return new ConfigKey<?>[] { CheckTxnBeforeSending, Workers, Port, Wait, AlertWait, DirectAgentLoadSize,
+                DirectAgentPoolSize, DirectAgentThreadCap, EnableKVMAutoEnableDisable, ReadyCommandWait };
     }
 
     protected class SetHostParamsListener implements Listener {
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 749a738..bd4e259 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
@@ -50,6 +50,11 @@
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
 import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao;
+import org.apache.cloudstack.shutdown.ShutdownManager;
+import org.apache.cloudstack.shutdown.command.CancelShutdownManagementServerHostCommand;
+import org.apache.cloudstack.shutdown.command.PrepareForShutdownManagementServerHostCommand;
+import org.apache.cloudstack.shutdown.command.BaseShutdownManagementServerHostCommand;
+import org.apache.cloudstack.shutdown.command.TriggerShutdownManagementServerHostCommand;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
 import org.apache.cloudstack.utils.security.SSLUtils;
 import org.apache.log4j.Logger;
@@ -129,6 +134,8 @@
     private HAConfigDao haConfigDao;
     @Inject
     private CAManager caService;
+    @Inject
+    private ShutdownManager shutdownManager;
 
     protected ClusteredAgentManagerImpl() {
         super();
@@ -1341,8 +1348,10 @@
                 return _gson.toJson(answers);
             } else if (cmds.length == 1 && cmds[0] instanceof ScheduleHostScanTaskCommand) {
                 final ScheduleHostScanTaskCommand cmd = (ScheduleHostScanTaskCommand)cmds[0];
-                final String response = handleScheduleHostScanTaskCommand(cmd);
-                return response;
+                return handleScheduleHostScanTaskCommand(cmd);
+            } else if (cmds.length == 1 && cmds[0] instanceof BaseShutdownManagementServerHostCommand) {
+                final BaseShutdownManagementServerHostCommand cmd = (BaseShutdownManagementServerHostCommand)cmds[0];
+                return handleShutdownManagementServerHostCommand(cmd);
             }
 
             try {
@@ -1376,6 +1385,36 @@
             return null;
         }
 
+        private String handleShutdownManagementServerHostCommand(BaseShutdownManagementServerHostCommand cmd) {
+            if (cmd instanceof PrepareForShutdownManagementServerHostCommand) {
+                s_logger.debug("Received BaseShutdownManagementServerHostCommand - preparing to shut down");
+                try {
+                    shutdownManager.prepareForShutdown();
+                    return "Successfully prepared for shutdown";
+                } catch(CloudRuntimeException e) {
+                    return e.getMessage();
+                }
+            }
+            if (cmd instanceof TriggerShutdownManagementServerHostCommand) {
+                s_logger.debug("Received TriggerShutdownManagementServerHostCommand - triggering a shut down");
+                try {
+                    shutdownManager.triggerShutdown();
+                    return "Successfully triggered shutdown";
+                } catch(CloudRuntimeException e) {
+                    return e.getMessage();
+                }
+            }
+            if (cmd instanceof CancelShutdownManagementServerHostCommand) {
+                s_logger.debug("Received CancelShutdownManagementServerHostCommand - cancelling shut down");
+                try {
+                    shutdownManager.cancelShutdown();
+                    return "Successfully prepared for shutdown";
+                } catch(CloudRuntimeException e) {
+                    return e.getMessage();
+                }
+            }
+            throw new CloudRuntimeException("Unknown BaseShutdownManagementServerHostCommand command received : " + cmd);
+        }
     }
 
     public boolean executeAgentUserRequest(final long agentId, final Event event) throws AgentUnavailableException {
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 a49609f..2436139 100755
--- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -213,8 +213,8 @@
 import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.ScopeType;
-import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage;
+import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.VMTemplateVO;
@@ -2208,9 +2208,11 @@
 
             boolean result = stateTransitTo(vm, Event.OperationSucceeded, null);
             if (result) {
+                vm.setPowerState(PowerState.PowerOff);
+                _vmDao.update(vm.getId(), vm);
                 if (VirtualMachine.Type.User.equals(vm.type) && ResourceCountRunningVMsonly.value()) {
                     ServiceOfferingVO offering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId());
-                    resourceCountDecrement(vm.getAccountId(),new Long(offering.getCpu()), new Long(offering.getRamSize()));
+                    resourceCountDecrement(vm.getAccountId(), offering.getCpu().longValue(), offering.getRamSize().longValue());
                 }
             } else {
                 throw new CloudRuntimeException("unable to stop " + vm);
@@ -2761,6 +2763,7 @@
         }
 
         vm.setLastHostId(srcHostId);
+        _vmDao.resetVmPowerStateTracking(vm.getId());
         try {
             if (vm.getHostId() == null || vm.getHostId() != srcHostId || !changeState(vm, Event.MigrationRequested, dstHostId, work, Step.Migrating)) {
                 _networkMgr.rollbackNicForMigration(vmSrc, profile);
@@ -2981,6 +2984,7 @@
      *  <ul>
      *      <li> If the current storage pool of the volume is not a managed storage, we do not need to validate anything here.
      *      <li> If the current storage pool is a managed storage and the target storage pool ID is different from the current one, we throw an exception.
+     *      <li> If the current storage pool is a managed storage and explicitly declared its capable of migration to alternate storage pools
      *  </ul>
      */
     protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) {
@@ -2990,6 +2994,11 @@
         if (currentPool.getId() == targetPool.getId()) {
             return;
         }
+
+        Map<String, String> details = _storagePoolDao.getDetails(currentPool.getId());
+        if (details != null && Boolean.parseBoolean(details.get(Storage.Capability.ALLOW_MIGRATE_OTHER_POOLS.toString()))) {
+            return;
+        }
         throw new CloudRuntimeException(String.format("Currently, a volume on managed storage can only be 'migrated' to itself " + "[volumeId=%s, currentStoragePoolId=%s, targetStoragePoolId=%s].",
                 volume.getUuid(), currentPool.getUuid(), targetPool.getUuid()));
     }
@@ -3770,7 +3779,7 @@
             if (cmd instanceof PingRoutingCommand) {
                 final PingRoutingCommand ping = (PingRoutingCommand)cmd;
                 if (ping.getHostVmStateReport() != null) {
-                    _syncMgr.processHostVmStatePingReport(agentId, ping.getHostVmStateReport());
+                    _syncMgr.processHostVmStatePingReport(agentId, ping.getHostVmStateReport(), ping.getOutOfBand());
                 }
 
                 scanStalledVMInTransitionStateOnUpHost(agentId);
@@ -4743,7 +4752,7 @@
                 VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval,
                 VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool,
                 HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize,
-                AllowExposeDomainInMetadata
+                AllowExposeDomainInMetadata, MetadataCustomCloudName
         };
     }
 
@@ -5614,20 +5623,20 @@
     }
 
     @Override
-    public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException {
+    public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map<String, String> details) throws ResourceUnavailableException, InsufficientCapacityException {
         final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
         if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
             VmWorkJobVO placeHolder = null;
             placeHolder = createPlaceHolderWork(vmId);
             try {
-                return orchestrateRestoreVirtualMachine(vmId, newTemplateId);
+                return orchestrateRestoreVirtualMachine(vmId, newTemplateId, rootDiskOfferingId, expunge, details);
             } finally {
                 if (placeHolder != null) {
                     _workJobDao.expunge(placeHolder.getId());
                 }
             }
         } else {
-            final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId);
+            final Outcome<VirtualMachine> outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details);
 
             retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine");
 
@@ -5644,14 +5653,14 @@
         }
     }
 
-    private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException {
-        s_logger.debug("Restoring vm " + vmId + " with new templateId " + newTemplateId);
+    private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map<String, String> details) throws ResourceUnavailableException, InsufficientCapacityException {
+        s_logger.debug("Restoring vm " + vmId + " with templateId : " + newTemplateId + " diskOfferingId : " + rootDiskOfferingId + " details : " + details);
         final CallContext context = CallContext.current();
         final Account account = context.getCallingAccount();
-        return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId);
+        return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId, rootDiskOfferingId, expunge, details);
     }
 
-    public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId) {
+    public Outcome<VirtualMachine> restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, Map<String, String> details) {
         String commandName = VmWorkRestore.class.getName();
         Pair<VmWorkJobVO, Long> pendingWorkJob = retrievePendingWorkJob(vmId, commandName);
 
@@ -5661,7 +5670,7 @@
             Pair<VmWorkJobVO, VmWork> newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId);
 
             workJob = newVmWorkJobAndInfo.first();
-            VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId);
+            VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId, rootDiskOfferingId, expunge, details);
 
             setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId);
         }
@@ -5673,7 +5682,7 @@
     @ReflectionUse
     private Pair<JobInfo.Status, String> orchestrateRestoreVirtualMachine(final VmWorkRestore work) throws Exception {
         VMInstanceVO vm = findVmById(work.getVmId());
-        UserVm uservm = orchestrateRestoreVirtualMachine(vm.getId(), work.getTemplateId());
+        UserVm uservm = orchestrateRestoreVirtualMachine(vm.getId(), work.getTemplateId(), work.getRootDiskOfferingId(), work.getExpunge(), work.getDetails());
         HashMap<Long, String> passwordMap = new HashMap<>();
         passwordMap.put(uservm.getId(), uservm.getPassword());
         return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(passwordMap));
diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSync.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSync.java
index 152d0d8..b2a48a0 100644
--- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSync.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSync.java
@@ -27,7 +27,7 @@
     void processHostVmStateReport(long hostId, Map<String, HostVmStateReportEntry> report);
 
     // to adapt legacy ping report
-    void processHostVmStatePingReport(long hostId, Map<String, HostVmStateReportEntry> report);
+    void processHostVmStatePingReport(long hostId, Map<String, HostVmStateReportEntry> report, boolean force);
 
     Map<Long, VirtualMachine.PowerState> convertVmStateReport(Map<String, HostVmStateReportEntry> states);
 }
diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java
index 815206a..3eb3569 100644
--- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java
@@ -55,19 +55,19 @@
             s_logger.debug("Process host VM state report. host: " + hostId);
 
         Map<Long, VirtualMachine.PowerState> translatedInfo = convertVmStateReport(report);
-        processReport(hostId, translatedInfo);
+        processReport(hostId, translatedInfo, false);
     }
 
     @Override
-    public void processHostVmStatePingReport(long hostId, Map<String, HostVmStateReportEntry> report) {
+    public void processHostVmStatePingReport(long hostId, Map<String, HostVmStateReportEntry> report, boolean force) {
         if (s_logger.isDebugEnabled())
             s_logger.debug("Process host VM state report from ping process. host: " + hostId);
 
         Map<Long, VirtualMachine.PowerState> translatedInfo = convertVmStateReport(report);
-        processReport(hostId, translatedInfo);
+        processReport(hostId, translatedInfo, force);
     }
 
-    private void processReport(long hostId, Map<Long, VirtualMachine.PowerState> translatedInfo) {
+    private void processReport(long hostId, Map<Long, VirtualMachine.PowerState> translatedInfo, boolean force) {
 
         if (s_logger.isDebugEnabled()) {
             s_logger.debug("Process VM state report. host: " + hostId + ", number of records in report: " + translatedInfo.size());
@@ -117,7 +117,7 @@
 
                 // Make sure powerState is up to date for missing VMs
                 try {
-                    if (!_instanceDao.isPowerStateUpToDate(instance.getId())) {
+                    if (!force && !_instanceDao.isPowerStateUpToDate(instance.getId())) {
                         s_logger.warn("Detected missing VM but power state is outdated, wait for another process report run for VM id: " + instance.getId());
                         _instanceDao.resetVmPowerStateTracking(instance.getId());
                         continue;
@@ -150,7 +150,7 @@
 
                 long milliSecondsSinceLastStateUpdate = currentTime.getTime() - vmStateUpdateTime.getTime();
 
-                if (milliSecondsSinceLastStateUpdate > milliSecondsGracefullPeriod) {
+                if (force || milliSecondsSinceLastStateUpdate > milliSecondsGracefullPeriod) {
                     s_logger.debug("vm id: " + instance.getId() + " - time since last state update(" + milliSecondsSinceLastStateUpdate + "ms) has passed graceful period");
 
                     // this is were a race condition might have happened if we don't re-fetch the instance;
diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkReboot.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkReboot.java
index 6a903b3..3813a8d 100644
--- a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkReboot.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkReboot.java
@@ -17,7 +17,9 @@
 package com.cloud.vm;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.cloudstack.framework.jobs.impl.JobSerializerHelper;
@@ -62,4 +64,11 @@
             }
         }
     }
+
+    @Override
+    public String toString() {
+        List<String> params = new ArrayList<>();
+        params.add(VirtualMachineProfile.Param.VmPassword.getName());
+        return super.toStringAfterRemoveParams("rawParams", params);
+    }
 }
diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java
index cb3adae..ab5425a 100644
--- a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRestore.java
@@ -16,23 +16,38 @@
 // under the License.
 package com.cloud.vm;
 
+import java.util.Map;
+
 public class VmWorkRestore extends VmWork {
     private static final long serialVersionUID = 195901782359759635L;
 
     private Long templateId;
+    private Long rootDiskOfferingId;
+    private Map<String,String> details;
 
-    public VmWorkRestore(long userId, long accountId, long vmId, String handlerName, Long templateId) {
-        super(userId, accountId, vmId, handlerName);
+    private boolean expunge;
 
-        this.templateId = templateId;
-    }
-
-    public VmWorkRestore(VmWork vmWork, Long templateId) {
+    public VmWorkRestore(VmWork vmWork, Long templateId, Long rootDiskOfferingId, boolean expunge, Map<String,String> details) {
         super(vmWork);
         this.templateId = templateId;
+        this.rootDiskOfferingId = rootDiskOfferingId;
+        this.expunge = expunge;
+        this.details = details;
     }
 
     public Long getTemplateId() {
         return templateId;
     }
+
+    public Long getRootDiskOfferingId() {
+        return rootDiskOfferingId;
+    }
+
+    public boolean getExpunge() {
+        return expunge;
+    }
+
+    public Map<String, String> getDetails() {
+        return details;
+    }
 }
diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStart.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStart.java
index 1b2a719..5a7acdd 100644
--- a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStart.java
+++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStart.java
@@ -18,7 +18,9 @@
 package com.cloud.vm;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.cloudstack.context.CallContext;
@@ -135,4 +137,11 @@
             }
         }
     }
+
+    @Override
+    public String toString() {
+        List<String> params = new ArrayList<>();
+        params.add(VirtualMachineProfile.Param.VmPassword.getName());
+        return super.toStringAfterRemoveParams("rawParams", params);
+    }
 }
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 d639b45..6763a13 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
@@ -61,6 +61,9 @@
 import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.VMInstanceDao;
 
+import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
+import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
+
 @Component
 public class CloudOrchestrator implements OrchestrationService {
 
@@ -196,8 +199,8 @@
             Map<String, String> userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId());
 
             if (userVmDetails != null) {
-                String minIops = userVmDetails.get("minIops");
-                String maxIops = userVmDetails.get("maxIops");
+                String minIops = userVmDetails.get(MIN_IOPS);
+                String maxIops = userVmDetails.get(MAX_IOPS);
 
                 rootDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null);
                 rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null);
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 71b1281..0a761cb 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
@@ -46,6 +46,7 @@
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
+import org.apache.log4j.Logger;
 
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
@@ -61,7 +62,6 @@
 import com.cloud.vm.SecondaryStorageVmVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.dao.SecondaryStorageVmDao;
-import org.apache.log4j.Logger;
 
 public class DataMigrationUtility {
     private static Logger LOGGER = Logger.getLogger(DataMigrationUtility.class);
@@ -91,20 +91,17 @@
      *  "Ready" "Allocated", "Destroying", "Destroyed", "Failed". If this is the case, and if the migration policy is complete,
      *  the migration is terminated.
      */
-    private boolean filesReadyToMigrate(Long srcDataStoreId) {
+    public boolean filesReadyToMigrate(Long srcDataStoreId, List<TemplateDataStoreVO> templates, List<SnapshotDataStoreVO> snapshots, List<VolumeDataStoreVO> volumes) {
         State[] validStates = {State.Ready, State.Allocated, State.Destroying, State.Destroyed, State.Failed};
         boolean isReady = true;
-        List<TemplateDataStoreVO> templates = templateDataStoreDao.listByStoreId(srcDataStoreId);
         for (TemplateDataStoreVO template : templates) {
             isReady &= (Arrays.asList(validStates).contains(template.getState()));
             LOGGER.trace(String.format("template state: %s", template.getState()));
         }
-        List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreId(srcDataStoreId, DataStoreRole.Image);
         for (SnapshotDataStoreVO snapshot : snapshots) {
             isReady &= (Arrays.asList(validStates).contains(snapshot.getState()));
             LOGGER.trace(String.format("snapshot state: %s", snapshot.getState()));
         }
-        List<VolumeDataStoreVO> volumes = volumeDataStoreDao.listByStoreId(srcDataStoreId);
         for (VolumeDataStoreVO volume : volumes) {
             isReady &= (Arrays.asList(validStates).contains(volume.getState()));
             LOGGER.trace(String.format("volume state: %s", volume.getState()));
@@ -112,6 +109,13 @@
         return isReady;
     }
 
+    private boolean filesReadyToMigrate(Long srcDataStoreId) {
+        List<TemplateDataStoreVO> templates = templateDataStoreDao.listByStoreId(srcDataStoreId);
+        List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreId(srcDataStoreId, DataStoreRole.Image);
+        List<VolumeDataStoreVO> volumes = volumeDataStoreDao.listByStoreId(srcDataStoreId);
+        return filesReadyToMigrate(srcDataStoreId, templates, snapshots, volumes);
+    }
+
     protected void checkIfCompleteMigrationPossible(ImageStoreService.MigrationPolicy policy, Long srcDataStoreId) {
         if (policy == ImageStoreService.MigrationPolicy.COMPLETE) {
             if (!filesReadyToMigrate(srcDataStoreId)) {
@@ -158,7 +162,19 @@
     }
 
     protected List<DataObject> getSortedValidSourcesList(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains,
-                                                         Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates) {
+            Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates, List<TemplateDataStoreVO> templates, List<SnapshotDataStoreVO> snapshots) {
+        List<DataObject> files = new ArrayList<>();
+
+        files.addAll(getAllReadyTemplates(srcDataStore, childTemplates, templates));
+        files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshots));
+
+        files = sortFilesOnSize(files, snapshotChains);
+
+        return files;
+    }
+
+    protected List<DataObject> getSortedValidSourcesList(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains,
+            Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates) {
         List<DataObject> files = new ArrayList<>();
         files.addAll(getAllReadyTemplates(srcDataStore, childTemplates));
         files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains));
@@ -187,10 +203,8 @@
         return files;
     }
 
-    protected List<DataObject> getAllReadyTemplates(DataStore srcDataStore, Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates) {
-
+    protected List<DataObject> getAllReadyTemplates(DataStore srcDataStore, Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates, List<TemplateDataStoreVO> templates) {
         List<TemplateInfo> files = new LinkedList<>();
-        List<TemplateDataStoreVO> templates = templateDataStoreDao.listByStoreId(srcDataStore.getId());
         for (TemplateDataStoreVO template : templates) {
             VMTemplateVO templateVO = templateDao.findById(template.getTemplateId());
             if (template.getState() == ObjectInDataStoreStateMachine.State.Ready && templateVO != null &&
@@ -211,19 +225,23 @@
         return (List<DataObject>) (List<?>) files;
     }
 
+    protected List<DataObject> getAllReadyTemplates(DataStore srcDataStore, Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates) {
+        List<TemplateDataStoreVO> templates = templateDataStoreDao.listByStoreId(srcDataStore.getId());
+        return getAllReadyTemplates(srcDataStore, childTemplates, templates);
+    }
+
     /** Returns parent snapshots and snapshots that do not have any children; snapshotChains comprises of the snapshot chain info
      * for each parent snapshot and the cumulative size of the chain - this is done to ensure that all the snapshots in a chain
      * are migrated to the same datastore
      */
-    protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains) {
+    protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains, List<SnapshotDataStoreVO> snapshots) {
         List<SnapshotInfo> files = new LinkedList<>();
-        List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreId(srcDataStore.getId(), DataStoreRole.Image);
         for (SnapshotDataStoreVO snapshot : snapshots) {
             SnapshotVO snapshotVO = snapshotDao.findById(snapshot.getSnapshotId());
             if (snapshot.getState() == ObjectInDataStoreStateMachine.State.Ready &&
                     snapshotVO != null && snapshotVO.getHypervisorType() != Hypervisor.HypervisorType.Simulator
                     && snapshot.getParentSnapshotId() == 0 ) {
-                SnapshotInfo snap = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), DataStoreRole.Image);
+                SnapshotInfo snap = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), snapshot.getDataStoreId(), snapshot.getRole());
                 if (snap != null) {
                     files.add(snap);
                 }
@@ -246,6 +264,11 @@
         return (List<DataObject>) (List<?>) files;
     }
 
+    protected List<DataObject> getAllReadySnapshotsAndChains(DataStore srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains) {
+        List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreId(srcDataStore.getId(), DataStoreRole.Image);
+        return getAllReadySnapshotsAndChains(srcDataStore, snapshotChains, snapshots);
+    }
+
     protected Long getTotalChainSize(List<? extends DataObject> chain) {
         Long size = 0L;
         for (DataObject dataObject : chain) {
@@ -254,9 +277,8 @@
         return size;
     }
 
-    protected List<DataObject> getAllReadyVolumes(DataStore srcDataStore) {
+    protected List<DataObject> getAllReadyVolumes(DataStore srcDataStore, List<VolumeDataStoreVO> volumes) {
         List<DataObject> files = new LinkedList<>();
-        List<VolumeDataStoreVO> volumes = volumeDataStoreDao.listByStoreId(srcDataStore.getId());
         for (VolumeDataStoreVO volume : volumes) {
             if (volume.getState() == ObjectInDataStoreStateMachine.State.Ready) {
                 VolumeInfo volumeInfo = volumeFactory.getVolume(volume.getVolumeId(), srcDataStore);
@@ -268,6 +290,11 @@
         return files;
     }
 
+    protected List<DataObject> getAllReadyVolumes(DataStore srcDataStore) {
+        List<VolumeDataStoreVO> volumes = volumeDataStoreDao.listByStoreId(srcDataStore.getId());
+        return getAllReadyVolumes(srcDataStore, volumes);
+    }
+
     /** Returns the count of active SSVMs - SSVM with agents in connected state, so as to dynamically increase the thread pool
      * size when SSVMs scale
      */
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 ba9ef03..57f6f99 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
@@ -21,7 +21,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -1180,7 +1179,7 @@
     }
 
     /**
-     * Validates the locked IP, throwing an exeption if the locked IP is null or the locked IP is not in 'Free' state.
+     * Validates the locked IP, throwing an exception if the locked IP is null or the locked IP is not in 'Free' state.
      */
     protected void validateLockedRequestedIp(IPAddressVO ipVO, IPAddressVO lockedIpVO) {
         if (lockedIpVO == null) {
@@ -1602,7 +1601,7 @@
                 }
 
                 if (s_logger.isDebugEnabled()) {
-                    s_logger.debug("Asking " + element.getName() + " to implemenet " + network);
+                    s_logger.debug("Asking " + element.getName() + " to implement " + network);
                 }
 
                 if (!element.implement(network, offering, dest, context)) {
@@ -2311,12 +2310,12 @@
 
     @DB
     protected void releaseNic(final VirtualMachineProfile vmProfile, final long nicId) throws ConcurrentOperationException, ResourceUnavailableException {
-        final Pair<Network, NicProfile> networkToRelease = Transaction.execute(new TransactionCallback<Pair<Network, NicProfile>>() {
+        final Pair<Network, NicProfile> networkToRelease = Transaction.execute(new TransactionCallback<>() {
             @Override
             public Pair<Network, NicProfile> doInTransaction(final TransactionStatus status) {
                 final NicVO nic = _nicDao.lockRow(nicId, true);
                 if (nic == null) {
-                    throw new ConcurrentOperationException("Unable to acquire lock on nic " + nic);
+                    throw new ConcurrentOperationException(String.format("Unable to acquire lock on nic id=%d", nicId));
                 }
 
                 final Nic.State originalState = nic.getState();
@@ -2330,6 +2329,9 @@
                         final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel
                                 .isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vmProfile.getHypervisorType(), network));
                         if (guru.release(profile, vmProfile, nic.getReservationId())) {
+                            if (s_logger.isDebugEnabled()) {
+                                s_logger.debug(String.format("The nic %s on %s was released according to %s by guru %s, now updating record.", nic, profile, vmProfile, guru));
+                            }
                             applyProfileToNicForRelease(nic, profile);
                             nic.setState(Nic.State.Allocated);
                             if (originalState == Nic.State.Reserved) {
@@ -2339,7 +2341,7 @@
                             }
                         }
                         // Perform release on network elements
-                        return new Pair<Network, NicProfile>(network, profile);
+                        return new Pair<>(network, profile);
                     } else {
                         nic.setState(Nic.State.Allocated);
                         updateNic(nic, network.getId(), -1);
@@ -2436,7 +2438,7 @@
             for (final NetworkElement element : networkElements) {
                 if (providersToImplement.contains(element.getProvider())) {
                     if (s_logger.isDebugEnabled()) {
-                        s_logger.debug("Asking " + element.getName() + " to release " + nic);
+                        s_logger.debug(String.format("Asking %s to release %s, according to the reservation strategy %s", element.getName(), nic, nic.getReservationStrategy()));
                     }
                     try {
                         element.release(network, profile, vm, null);
@@ -2493,9 +2495,9 @@
                     IPv6Address.class.getName(), null);
         }
 
-        //remove the secondary ip addresses corresponding to to this nic
+        //remove the secondary ip addresses corresponding to this nic
         if (!removeVmSecondaryIpsOfNic(nic.getId())) {
-            s_logger.debug("Removing nic " + nic.getId() + " secondary ip addreses failed");
+            s_logger.debug("Removing nic " + nic.getId() + " secondary ip addresses failed");
         }
     }
 
@@ -3249,7 +3251,7 @@
         // get updated state for the network
         network = _networksDao.findById(networkId);
         if (network.getState() != Network.State.Allocated && network.getState() != Network.State.Setup && !forced) {
-            s_logger.debug("Network is not not in the correct state to be destroyed: " + network.getState());
+            s_logger.debug("Network is not in the correct state to be destroyed: " + network.getState());
             return false;
         }
 
@@ -4425,7 +4427,8 @@
         return accessDetails;
     }
 
-    protected boolean stateTransitTo(final NetworkVO network, final Network.Event e) throws NoTransitionException {
+    @Override
+    public boolean stateTransitTo(final Network network, final Network.Event e) throws NoTransitionException {
         return _stateMachine.transitTo(network, e, null, _networksDao);
     }
 
@@ -4565,48 +4568,39 @@
 
     @DB
     @Override
-    public Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final boolean forced)
+    public Pair<NicProfile, Integer> importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter dataCenter, final boolean forced)
             throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
         s_logger.debug("Allocating nic for vm " + vm.getUuid() + " in network " + network + " during import");
-        String guestIp = null;
+        String selectedIp = null;
         if (ipAddresses != null && StringUtils.isNotEmpty(ipAddresses.getIp4Address())) {
             if (ipAddresses.getIp4Address().equals("auto")) {
                 ipAddresses.setIp4Address(null);
             }
-            if (network.getGuestType() != GuestType.L2) {
-                guestIp = _ipAddrMgr.acquireGuestIpAddress(network, ipAddresses.getIp4Address());
-            } else {
-                guestIp = null;
-            }
-            if (guestIp == null && network.getGuestType() != GuestType.L2 && !_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) {
+            selectedIp = getSelectedIpForNicImport(network, dataCenter, ipAddresses);
+            if (selectedIp == null && network.getGuestType() != GuestType.L2 && !_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) {
                 throw new InsufficientVirtualNetworkCapacityException("Unable to acquire Guest IP  address for network " + network, DataCenter.class,
                         network.getDataCenterId());
             }
         }
-        final String finalGuestIp = guestIp;
+        final String finalSelectedIp = selectedIp;
         final NicVO vo = Transaction.execute(new TransactionCallback<NicVO>() {
             @Override
             public NicVO doInTransaction(TransactionStatus status) {
                 NicVO existingNic = _nicDao.findByNetworkIdAndMacAddress(network.getId(), macAddress);
+                String macAddressToPersist = macAddress;
                 if (existingNic != null) {
-                    if (!forced) {
-                        throw new CloudRuntimeException("NIC with MAC address = " + macAddress + " exists on network with ID = " + network.getId() +
-                                " and forced flag is disabled");
-                    }
-                    s_logger.debug("Removing existing NIC with MAC address = " + macAddress + " on network with ID = " + network.getId());
-                    existingNic.setState(Nic.State.Deallocating);
-                    existingNic.setRemoved(new Date());
-                    _nicDao.update(existingNic.getId(), existingNic);
+                    macAddressToPersist = generateNewMacAddressIfForced(network, macAddress, forced);
                 }
                 NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType());
-                vo.setMacAddress(macAddress);
+                vo.setMacAddress(macAddressToPersist);
                 vo.setAddressFormat(Networks.AddressFormat.Ip4);
-                if (NetUtils.isValidIp4(finalGuestIp) && StringUtils.isNotEmpty(network.getGateway())) {
-                    vo.setIPv4Address(finalGuestIp);
-                    vo.setIPv4Gateway(network.getGateway());
-                    if (StringUtils.isNotEmpty(network.getCidr())) {
-                        vo.setIPv4Netmask(NetUtils.cidr2Netmask(network.getCidr()));
-                    }
+                Pair<String, String> pair = getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, finalSelectedIp);
+                String gateway = pair.first();
+                String netmask = pair.second();
+                if (NetUtils.isValidIp4(finalSelectedIp) && StringUtils.isNotEmpty(gateway)) {
+                    vo.setIPv4Address(finalSelectedIp);
+                    vo.setIPv4Gateway(gateway);
+                    vo.setIPv4Netmask(netmask);
                 }
                 vo.setBroadcastUri(network.getBroadcastUri());
                 vo.setMode(network.getMode());
@@ -4643,6 +4637,62 @@
         return new Pair<NicProfile, Integer>(vmNic, Integer.valueOf(deviceId));
     }
 
+    protected String getSelectedIpForNicImport(Network network, DataCenter dataCenter, Network.IpAddresses ipAddresses) {
+        if (network.getGuestType() == GuestType.L2) {
+            return null;
+        }
+        return dataCenter.getNetworkType() == NetworkType.Basic ?
+                getSelectedIpForNicImportOnBasicZone(ipAddresses.getIp4Address(), network, dataCenter):
+                _ipAddrMgr.acquireGuestIpAddress(network, ipAddresses.getIp4Address());
+    }
+
+    protected String getSelectedIpForNicImportOnBasicZone(String requestedIp, Network network, DataCenter dataCenter) {
+        IPAddressVO ipAddressVO = StringUtils.isBlank(requestedIp) ?
+                _ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(network.getId(), dataCenter.getId(), IpAddress.State.Free):
+                _ipAddressDao.findByIp(requestedIp);
+        if (ipAddressVO == null || ipAddressVO.getState() != IpAddress.State.Free) {
+            String msg = String.format("Cannot find a free IP to assign to VM NIC on network %s", network.getName());
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+        return ipAddressVO.getAddress() != null ? ipAddressVO.getAddress().addr() : null;
+    }
+
+    /**
+     * Obtain the gateway and netmask for a VM NIC to import
+     * If the VM to import is on a Basic Zone, then obtain the information from the vlan table instead of the network
+     */
+    protected Pair<String, String> getNetworkGatewayAndNetmaskForNicImport(Network network, DataCenter dataCenter, String selectedIp) {
+        String gateway = network.getGateway();
+        String netmask = StringUtils.isNotEmpty(network.getCidr()) ? NetUtils.cidr2Netmask(network.getCidr()) : null;
+        if (dataCenter.getNetworkType() == NetworkType.Basic) {
+            IPAddressVO freeIp = _ipAddressDao.findByIp(selectedIp);
+            if (freeIp != null) {
+                VlanVO vlan = _vlanDao.findById(freeIp.getVlanId());
+                gateway = vlan != null ? vlan.getVlanGateway() : null;
+                netmask = vlan != null ? vlan.getVlanNetmask() : null;
+            }
+        }
+        return new Pair<>(gateway, netmask);
+    }
+
+    private String generateNewMacAddressIfForced(Network network, String macAddress, boolean forced) {
+        if (!forced) {
+            throw new CloudRuntimeException("NIC with MAC address = " + macAddress + " exists on network with ID = " + network.getId() +
+                    " and forced flag is disabled");
+        }
+        try {
+            s_logger.debug(String.format("Generating a new mac address on network %s as the mac address %s already exists", network.getName(), macAddress));
+            String newMacAddress = _networkModel.getNextAvailableMacAddressInNetwork(network.getId());
+            s_logger.debug(String.format("Successfully generated the mac address %s, using it instead of the conflicting address %s", newMacAddress, macAddress));
+            return newMacAddress;
+        } catch (InsufficientAddressCapacityException e) {
+            String msg = String.format("Could not generate a new mac address on network %s", network.getName());
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
     @Override
     public void unmanageNics(VirtualMachineProfile vm) {
         if (s_logger.isDebugEnabled()) {
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 01c7f72..873ddb5 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
@@ -17,8 +17,8 @@
 
 package org.apache.cloudstack.engine.orchestration;
 
-import com.cloud.capacity.CapacityManager;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Hashtable;
@@ -31,6 +31,7 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
@@ -53,11 +54,14 @@
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.math3.stat.descriptive.moment.Mean;
 import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
 import org.apache.log4j.Logger;
 
+import com.cloud.capacity.CapacityManager;
 import com.cloud.server.StatsCollector;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.SnapshotVO;
@@ -219,6 +223,82 @@
         return handleResponse(futures, migrationPolicy, message, success);
     }
 
+    @Override
+    public MigrationResponse migrateResources(Long srcImgStoreId, Long destImgStoreId, List<Long> templateIdList,
+            List<Long> snapshotIdList) {
+        List<DataObject> files;
+        boolean success = true;
+        String message = null;
+
+        DataStore srcDatastore = dataStoreManager.getDataStore(srcImgStoreId, DataStoreRole.Image);
+        Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains = new HashMap<>();
+        Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates = new HashMap<>();
+
+        List<TemplateDataStoreVO> templates = templateDataStoreDao.listByStoreIdAndTemplateIds(srcImgStoreId, templateIdList);
+        List<SnapshotDataStoreVO> snapshots = snapshotDataStoreDao.listByStoreAndSnapshotIds(srcImgStoreId, DataStoreRole.Image, snapshotIdList);
+
+        if (!migrationHelper.filesReadyToMigrate(srcImgStoreId, templates, snapshots, 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);
+
+        if (files.isEmpty()) {
+            return new MigrationResponse(String.format("No files in Image store: %s to migrate", srcDatastore.getUuid()), null, true);
+        }
+
+        Map<Long, Pair<Long, Long>> storageCapacities = new Hashtable<>();
+        storageCapacities.put(srcImgStoreId, new Pair<>(null, null));
+        storageCapacities.put(destImgStoreId, new Pair<>(null, null));
+        storageCapacities = getStorageCapacities(storageCapacities, srcImgStoreId);
+        storageCapacities = getStorageCapacities(storageCapacities, destImgStoreId);
+
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(numConcurrentCopyTasksPerSSVM, numConcurrentCopyTasksPerSSVM, 30,
+                TimeUnit.MINUTES, new MigrateBlockingQueue<>(numConcurrentCopyTasksPerSSVM));
+        List<Future<AsyncCallFuture<DataObjectResult>>> futures = new ArrayList<>();
+        Date start = new Date();
+
+        while (true) {
+            DataObject chosenFileForMigration = null;
+            if (!files.isEmpty()) {
+                chosenFileForMigration = files.remove(0);
+            }
+
+            if (chosenFileForMigration == null) {
+                message = "Migration completed";
+                break;
+            }
+
+            if (chosenFileForMigration.getPhysicalSize() > storageCapacities.get(destImgStoreId).first()) {
+                s_logger.debug(String.format("%s: %s too large to be migrated to %s", chosenFileForMigration.getType().name(), chosenFileForMigration.getUuid(), destImgStoreId));
+                continue;
+            }
+
+            if (storageCapacityBelowThreshold(storageCapacities, destImgStoreId)) {
+                storageCapacities = migrateAway(chosenFileForMigration, storageCapacities, snapshotChains, childTemplates, srcDatastore, destImgStoreId, executor, futures);
+            } else {
+                message = "Migration failed. Destination store doesn't have enough capacity for migration";
+                success = false;
+                break;
+            }
+        }
+        Date end = new Date();
+
+        // Migrate any new child snapshots if created during migration
+        List<Long> migratedSnapshotIdList = snapshotChains.keySet().stream().map(DataObject::getId).collect(Collectors.toList());
+        if (!CollectionUtils.isEmpty(migratedSnapshotIdList)) {
+            List<SnapshotDataStoreVO> snaps = snapshotDataStoreDao.findSnapshots(srcImgStoreId, start, end);
+            snaps.forEach(snap -> {
+                SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snap.getSnapshotId(), snap.getDataStoreId(), DataStoreRole.Image);
+                SnapshotInfo parentSnapshot = snapshotInfo.getParent();
+                if (snapshotInfo.getDataStore().getId() == srcImgStoreId && parentSnapshot != null && migratedSnapshotIdList.contains(parentSnapshot.getSnapshotId())) {
+                    futures.add(executor.submit(new MigrateDataTask(snapshotInfo, srcDatastore, dataStoreManager.getDataStore(destImgStoreId, DataStoreRole.Image))));
+                }
+            });
+        }
+
+        return handleResponse(futures, null, message, success);
+    }
+
     protected Pair<String, Boolean> migrateCompleted(Long destDatastoreId, DataStore srcDatastore, List<DataObject> files, MigrationPolicy migrationPolicy, int skipped) {
         String message = "";
         boolean success = true;
@@ -295,7 +375,8 @@
             }
         }
         message += ". successful migrations: "+successCount;
-        return new MigrationResponse(message, migrationPolicy.toString(), success);
+        String policy = migrationPolicy != null ? migrationPolicy.toString() : null;
+        return new MigrationResponse(message, policy, success);
     }
 
     private void handleSnapshotMigration(Long srcDataStoreId, Date start, Date end, MigrationPolicy policy,
@@ -305,7 +386,7 @@
         if (!snaps.isEmpty()) {
             for (SnapshotDataStoreVO snap : snaps) {
                 SnapshotVO snapshotVO = snapshotDao.findById(snap.getSnapshotId());
-                SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), DataStoreRole.Image);
+                SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), snap.getDataStoreId(), DataStoreRole.Image);
                 SnapshotInfo parentSnapshot = snapshotInfo.getParent();
 
                 if (parentSnapshot == null && policy == MigrationPolicy.COMPLETE) {
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 ec10c34..e49616d 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
@@ -558,7 +558,7 @@
         VolumeInfo vol = volFactory.getVolume(volume.getId());
         DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
         DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot);
-        SnapshotInfo snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), dataStoreRole);
+        SnapshotInfo snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, volume.getDataCenterId());
 
         boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole);
 
@@ -863,18 +863,7 @@
         vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType()));
         vol = _volsDao.persist(vol);
 
-        List<VolumeDetailVO> volumeDetailsVO = new ArrayList<VolumeDetailVO>();
-        DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS);
-        if (bandwidthLimitDetail != null) {
-            volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false));
-        }
-        DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.IOPS_LIMIT);
-        if (iopsLimitDetail != null) {
-            volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false));
-        }
-        if (!volumeDetailsVO.isEmpty()) {
-            _volDetailDao.saveDetails(volumeDetailsVO);
-        }
+        saveVolumeDetails(offering.getId(), vol.getId());
 
         // Save usage event and update resource count for user vm volumes
         if (vm.getType() == VirtualMachine.Type.User) {
@@ -934,18 +923,7 @@
 
         vol = _volsDao.persist(vol);
 
-        List<VolumeDetailVO> volumeDetailsVO = new ArrayList<VolumeDetailVO>();
-        DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS);
-        if (bandwidthLimitDetail != null) {
-            volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false));
-        }
-        DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(offering.getId(), Volume.IOPS_LIMIT);
-        if (iopsLimitDetail != null) {
-            volumeDetailsVO.add(new VolumeDetailVO(vol.getId(), Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false));
-        }
-        if (!volumeDetailsVO.isEmpty()) {
-            _volDetailDao.saveDetails(volumeDetailsVO);
-        }
+        saveVolumeDetails(offering.getId(), vol.getId());
 
         if (StringUtils.isNotBlank(configurationId)) {
             VolumeDetailVO deployConfigurationDetail = new VolumeDetailVO(vol.getId(), VmDetailConstants.DEPLOY_AS_IS_CONFIGURATION, configurationId, false);
@@ -970,6 +948,32 @@
         return toDiskProfile(vol, offering);
     }
 
+    @Override
+    public void saveVolumeDetails(Long diskOfferingId, Long volumeId) {
+        List<VolumeDetailVO> volumeDetailsVO = new ArrayList<>();
+        DiskOfferingDetailVO bandwidthLimitDetail = _diskOfferingDetailDao.findDetail(diskOfferingId, Volume.BANDWIDTH_LIMIT_IN_MBPS);
+        if (bandwidthLimitDetail != null) {
+            volumeDetailsVO.add(new VolumeDetailVO(volumeId, Volume.BANDWIDTH_LIMIT_IN_MBPS, bandwidthLimitDetail.getValue(), false));
+        } else {
+            VolumeDetailVO bandwidthLimit = _volDetailDao.findDetail(volumeId, Volume.BANDWIDTH_LIMIT_IN_MBPS);
+            if (bandwidthLimit != null) {
+                _volDetailDao.remove(bandwidthLimit.getId());
+            }
+        }
+        DiskOfferingDetailVO iopsLimitDetail = _diskOfferingDetailDao.findDetail(diskOfferingId, Volume.IOPS_LIMIT);
+        if (iopsLimitDetail != null) {
+            volumeDetailsVO.add(new VolumeDetailVO(volumeId, Volume.IOPS_LIMIT, iopsLimitDetail.getValue(), false));
+        } else {
+            VolumeDetailVO iopsLimit = _volDetailDao.findDetail(volumeId, Volume.IOPS_LIMIT);
+            if (iopsLimit != null) {
+                _volDetailDao.remove(iopsLimit.getId());
+            }
+        }
+        if (!volumeDetailsVO.isEmpty()) {
+            _volDetailDao.saveDetails(volumeDetailsVO);
+        }
+    }
+
     @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true)
     @Override
     public List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
@@ -1856,6 +1860,18 @@
         return _volsDao.persist(volume);
     }
 
+    protected void grantVolumeAccessToHostIfNeeded(PrimaryDataStore volumeStore, long volumeId, Host host, String volToString) {
+        PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver)volumeStore.getDriver();
+        if (!driver.volumesRequireGrantAccessWhenUsed()) {
+            return;
+        }
+        try {
+            volService.grantAccess(volFactory.getVolume(volumeId), host, volumeStore);
+        } catch (Exception e) {
+            throw new StorageAccessException(String.format("Unable to grant access to volume [%s] on host [%s].", volToString, host));
+        }
+    }
+
     @Override
     public void prepare(VirtualMachineProfile vm, DeployDestination dest) throws StorageUnavailableException, InsufficientStorageCapacityException, ConcurrentOperationException, StorageAccessException {
         if (dest == null) {
@@ -1873,18 +1889,18 @@
 
         List<VolumeTask> tasks = getTasks(vols, dest.getStorageForDisks(), vm);
         Volume vol = null;
-        StoragePool pool;
+        PrimaryDataStore store;
         for (VolumeTask task : tasks) {
             if (task.type == VolumeTaskType.NOP) {
                 vol = task.volume;
 
                 String volToString = getReflectOnlySelectedFields(vol);
 
-                pool = (StoragePool)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary);
+                store = (PrimaryDataStore)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary);
 
                 // For zone-wide managed storage, it is possible that the VM can be started in another
                 // cluster. In that case, make sure that the volume is in the right access group.
-                if (pool.isManaged()) {
+                if (store.isManaged()) {
                     Host lastHost = _hostDao.findById(vm.getVirtualMachine().getLastHostId());
                     Host host = _hostDao.findById(vm.getVirtualMachine().getHostId());
 
@@ -1893,35 +1909,27 @@
 
                     if (lastClusterId != clusterId) {
                         if (lastHost != null) {
-                            storageMgr.removeStoragePoolFromCluster(lastHost.getId(), vol.get_iScsiName(), pool);
-
-                            DataStore storagePool = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
-
-                            volService.revokeAccess(volFactory.getVolume(vol.getId()), lastHost, storagePool);
+                            storageMgr.removeStoragePoolFromCluster(lastHost.getId(), vol.get_iScsiName(), store);
+                            volService.revokeAccess(volFactory.getVolume(vol.getId()), lastHost, store);
                         }
 
                         try {
-                            volService.grantAccess(volFactory.getVolume(vol.getId()), host, (DataStore)pool);
+                            volService.grantAccess(volFactory.getVolume(vol.getId()), host, store);
                         } catch (Exception e) {
                             throw new StorageAccessException(String.format("Unable to grant access to volume [%s] on host [%s].", volToString, host));
                         }
                     } else {
-                        // This might impact other managed storages, grant access for PowerFlex and Iscsi/Solidfire storage pool only
-                        if (pool.getPoolType() == Storage.StoragePoolType.PowerFlex || pool.getPoolType() == Storage.StoragePoolType.Iscsi) {
-                            try {
-                                volService.grantAccess(volFactory.getVolume(vol.getId()), host, (DataStore)pool);
-                            } catch (Exception e) {
-                                throw new StorageAccessException(String.format("Unable to grant access to volume [%s] on host [%s].", volToString, host));
-                            }
-                        }
+                        grantVolumeAccessToHostIfNeeded(store, vol.getId(), host, volToString);
                     }
+                } else {
+                    handleCheckAndRepairVolume(vol, vm.getVirtualMachine().getHostId());
                 }
             } else if (task.type == VolumeTaskType.MIGRATE) {
-                pool = (StoragePool)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary);
-                vol = migrateVolume(task.volume, pool);
+                store = (PrimaryDataStore) dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary);
+                vol = migrateVolume(task.volume, store);
             } else if (task.type == VolumeTaskType.RECREATE) {
                 Pair<VolumeVO, DataStore> result = recreateVolume(task.volume, vm, dest);
-                pool = (StoragePool)dataStoreMgr.getDataStore(result.second().getId(), DataStoreRole.Primary);
+                store = (PrimaryDataStore) dataStoreMgr.getDataStore(result.second().getId(), DataStoreRole.Primary);
                 vol = result.first();
             }
 
@@ -1957,6 +1965,16 @@
         }
     }
 
+    private void handleCheckAndRepairVolume(Volume vol, Long hostId) {
+        Host host = _hostDao.findById(hostId);
+        try {
+            volService.checkAndRepairVolumeBasedOnConfig(volFactory.getVolume(vol.getId()), host);
+        } catch (Exception e) {
+            String volumeToString = getReflectOnlySelectedFields(vol);
+            s_logger.debug(String.format("Unable to check and repair volume [%s] on host [%s], due to %s.", volumeToString, host, e.getMessage()));
+        }
+    }
+
     private boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException {
         return _volStateMachine.transitTo(vol, event, null, _volsDao);
     }
@@ -2172,19 +2190,17 @@
     }
 
     @Override
-    public DiskProfile importVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops,
+    public DiskProfile importVolume(Type type, String name, DiskOffering offering, Long sizeInBytes, Long minIops, Long maxIops,
                                     VirtualMachine vm, VirtualMachineTemplate template, Account owner,
                                     Long deviceId, Long poolId, String path, String chainInfo) {
-        if (size == null) {
-            size = offering.getDiskSize();
-        } else {
-            size = (size * 1024 * 1024 * 1024);
+        if (sizeInBytes == null) {
+            sizeInBytes = offering.getDiskSize();
         }
 
         minIops = minIops != null ? minIops : offering.getMinIops();
         maxIops = maxIops != null ? maxIops : offering.getMaxIops();
 
-        VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), owner.getDomainId(), owner.getId(), offering.getId(), offering.getProvisioningType(), size, minIops, maxIops, null);
+        VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), owner.getDomainId(), owner.getId(), offering.getId(), offering.getProvisioningType(), sizeInBytes, minIops, maxIops, null);
         if (vm != null) {
             vol.setInstanceId(vm.getId());
         }
@@ -2225,6 +2241,51 @@
     }
 
     @Override
+    public DiskProfile updateImportedVolume(Type type, DiskOffering offering, VirtualMachine vm, VirtualMachineTemplate template,
+                                    Long deviceId, Long poolId, String path, String chainInfo, DiskProfile diskProfile) {
+
+        VolumeVO vol = _volsDao.findById(diskProfile.getVolumeId());
+        if (vm != null) {
+            vol.setInstanceId(vm.getId());
+        }
+
+        if (deviceId != null) {
+            vol.setDeviceId(deviceId);
+        } else if (type.equals(Type.ROOT)) {
+            vol.setDeviceId(0l);
+        } else {
+            vol.setDeviceId(1l);
+        }
+
+        if (template != null) {
+            if (ImageFormat.ISO.equals(template.getFormat())) {
+                vol.setIsoId(template.getId());
+            } else if (Storage.TemplateType.DATADISK.equals(template.getTemplateType())) {
+                vol.setTemplateId(template.getId());
+            }
+            if (type == Type.ROOT) {
+                vol.setTemplateId(template.getId());
+            }
+        }
+
+        // display flag matters only for the User vms
+        if (VirtualMachine.Type.User.equals(vm.getType())) {
+            UserVmVO userVm = _userVmDao.findById(vm.getId());
+            vol.setDisplayVolume(userVm.isDisplayVm());
+        }
+
+        vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType()));
+        vol.setPoolId(poolId);
+        vol.setPath(path);
+        vol.setChainInfo(chainInfo);
+        vol.setSize(diskProfile.getSize());
+        vol.setState(Volume.State.Ready);
+        vol.setAttached(new Date());
+        _volsDao.update(vol.getId(), vol);
+        return toDiskProfile(vol, offering);
+    }
+
+    @Override
     public void unmanageVolumes(long vmId) {
         if (s_logger.isDebugEnabled()) {
             s_logger.debug(String.format("Unmanaging storage for VM [%s].", _userVmDao.findById(vmId)));
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 20af10e..cf19446 100644
--- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
+++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
@@ -66,8 +66,10 @@
 import org.mockito.InOrder;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 
 import com.cloud.agent.AgentManager;
@@ -107,15 +109,9 @@
 import com.cloud.vm.VirtualMachine.State;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class VirtualMachineManagerImplTest {
 
     @Spy
@@ -1014,15 +1010,16 @@
         doReturn(false).when(virtualMachineManagerImpl).areAllVolumesAllocated(Mockito.anyLong());
 
         CallContext callContext = mock(CallContext.class);
-        Mockito.when(callContext.getCallingAccount()).thenReturn(account);
-        Mockito.when(callContext.getCallingUser()).thenReturn(user);
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContext);
+        when(callContext.getCallingAccount()).thenReturn(account);
+        when(callContext.getCallingUser()).thenReturn(user);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            when(CallContext.current()).thenReturn(callContext);
 
-        try {
-            virtualMachineManagerImpl.orchestrateStart("vm-uuid", params, plan, planner);
-        } catch (CloudRuntimeException e) {
-            assertEquals(e.getMessage(), "Error while transitioning");
+            try {
+                virtualMachineManagerImpl.orchestrateStart("vm-uuid", params, plan, planner);
+            } catch (CloudRuntimeException e) {
+                assertEquals(e.getMessage(), "Error while transitioning");
+            }
         }
 
         assertEquals(vmInstance.getPodIdToDeployIn(), (Long) destPod.getId());
@@ -1106,15 +1103,16 @@
         doReturn(true).when(virtualMachineManagerImpl).areAllVolumesAllocated(Mockito.anyLong());
 
         CallContext callContext = mock(CallContext.class);
-        Mockito.when(callContext.getCallingAccount()).thenReturn(account);
-        Mockito.when(callContext.getCallingUser()).thenReturn(user);
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContext);
+        when(callContext.getCallingAccount()).thenReturn(account);
+        when(callContext.getCallingUser()).thenReturn(user);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            when(CallContext.current()).thenReturn(callContext);
 
-        try {
-            virtualMachineManagerImpl.orchestrateStart("vm-uuid", params, plan, planner);
-        } catch (CloudRuntimeException e) {
-            assertEquals(e.getMessage(), "Error while transitioning");
+            try {
+                virtualMachineManagerImpl.orchestrateStart("vm-uuid", params, plan, planner);
+            } catch (CloudRuntimeException e) {
+                assertEquals(e.getMessage(), "Error while transitioning");
+            }
         }
 
         assertNull(vmInstance.getPodIdToDeployIn());
diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmWorkRebootTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmWorkRebootTest.java
new file mode 100644
index 0000000..75ffd94
--- /dev/null
+++ b/engine/orchestration/src/test/java/com/cloud/vm/VmWorkRebootTest.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.vm;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.framework.jobs.impl.JobSerializerHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VmWorkRebootTest {
+
+    @Test
+    public void testToString() {
+        VmWork vmWork = new VmWork(1l, 1l, 1l, "testhandler");
+        Map<VirtualMachineProfile.Param, Object> params = new HashMap<>();
+        String lastHost = "rO0ABXQABHRydWU";
+        String lastHostSerialized = JobSerializerHelper.toObjectSerializedString(lastHost);
+        params.put(VirtualMachineProfile.Param.ConsiderLastHost, lastHost);
+        params.put(VirtualMachineProfile.Param.VmPassword, "rO0ABXQADnNhdmVkX3Bhc3N3b3Jk");
+        VmWorkReboot workInfo = new VmWorkReboot(vmWork, params);
+        String expectedVmWorkRebootStr = "{\"accountId\":1,\"vmId\":1,\"handlerName\":\"testhandler\",\"userId\":1,\"rawParams\":{\"ConsiderLastHost\":\"" + lastHostSerialized + "\"}}";
+
+        String vmWorkRebootStr = workInfo.toString();
+        Assert.assertEquals(expectedVmWorkRebootStr, vmWorkRebootStr);
+    }
+}
diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmWorkStartTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmWorkStartTest.java
new file mode 100644
index 0000000..a0644b1
--- /dev/null
+++ b/engine/orchestration/src/test/java/com/cloud/vm/VmWorkStartTest.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.vm;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.framework.jobs.impl.JobSerializerHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VmWorkStartTest {
+
+    @Test
+    public void testToStringWithParams() {
+        VmWork vmWork = new VmWork(1l,  1l, 1l, "testhandler");
+        VmWorkStart workInfo = new VmWorkStart(vmWork);
+        Map<VirtualMachineProfile.Param, Object> params = new HashMap<>();
+        String lastHost = "rO0ABXQABHRydWU";
+        String lastHostSerialized = JobSerializerHelper.toObjectSerializedString(lastHost);
+        params.put(VirtualMachineProfile.Param.ConsiderLastHost, lastHost);
+        params.put(VirtualMachineProfile.Param.VmPassword, "rO0ABXQADnNhdmVkX3Bhc3N3b3Jk");
+        workInfo.setParams(params);
+        String expectedVmWorkStartStr = "{\"accountId\":1,\"dcId\":0,\"vmId\":1,\"handlerName\":\"testhandler\",\"userId\":1,\"rawParams\":{\"ConsiderLastHost\":\"" + lastHostSerialized + "\"}}";
+
+        String vmWorkStartStr = workInfo.toString();
+        Assert.assertEquals(expectedVmWorkStartStr, vmWorkStartStr);
+    }
+
+    @Test
+    public void testToStringWithRawParams() {
+        VmWork vmWork = new VmWork(1l,  1l, 1l, "testhandler");
+        VmWorkStart workInfo = new VmWorkStart(vmWork);
+        Map<String, String> rawParams = new HashMap<>();
+        rawParams.put(VirtualMachineProfile.Param.ConsiderLastHost.getName(), "rO0ABXQABHRydWU");
+        rawParams.put(VirtualMachineProfile.Param.VmPassword.getName(), "rO0ABXQADnNhdmVkX3Bhc3N3b3Jk");
+        workInfo.setRawParams(rawParams);
+        String expectedVmWorkStartStr = "{\"accountId\":1,\"dcId\":0,\"vmId\":1,\"handlerName\":\"testhandler\",\"userId\":1,\"rawParams\":{\"ConsiderLastHost\":\"rO0ABXQABHRydWU\"}}";
+
+        String vmWorkStartStr = workInfo.toString();
+        Assert.assertEquals(expectedVmWorkStartStr, vmWorkStartStr);
+    }
+}
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 0aed5a2..9e64eff 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
@@ -29,6 +29,9 @@
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.dc.DataCenter;
+import com.cloud.network.IpAddressManager;
+import com.cloud.utils.Pair;
 import org.apache.log4j.Logger;
 import org.junit.Assert;
 import org.junit.Before;
@@ -125,6 +128,7 @@
         testOrchastrator.routerNetworkDao = mock(RouterNetworkDao.class);
         testOrchastrator._vpcMgr = mock(VpcManager.class);
         testOrchastrator.routerJoinDao = mock(DomainRouterJoinDao.class);
+        testOrchastrator._ipAddrMgr = mock(IpAddressManager.class);
         DhcpServiceProvider provider = mock(DhcpServiceProvider.class);
 
         Map<Network.Capability, String> capabilities = new HashMap<Network.Capability, String>();
@@ -708,4 +712,134 @@
         Assert.assertEquals(ip6Dns[0], profile.getIPv6Dns1());
         Assert.assertEquals(ip6Dns[1], profile.getIPv6Dns2());
     }
+
+    @Test
+    public void testGetNetworkGatewayAndNetmaskForNicImportAdvancedZone() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        String ipAddress = "10.1.1.10";
+
+        String networkGateway = "10.1.1.1";
+        String networkNetmask = "255.255.255.0";
+        String networkCidr = "10.1.1.0/24";
+        Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
+        Mockito.when(network.getGateway()).thenReturn(networkGateway);
+        Mockito.when(network.getCidr()).thenReturn(networkCidr);
+        Pair<String, String> pair = testOrchastrator.getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, ipAddress);
+        Assert.assertNotNull(pair);
+        Assert.assertEquals(networkGateway, pair.first());
+        Assert.assertEquals(networkNetmask, pair.second());
+    }
+
+    @Test
+    public void testGetNetworkGatewayAndNetmaskForNicImportBasicZone() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class);
+        String ipAddress = "172.1.1.10";
+
+        String defaultNetworkGateway = "172.1.1.1";
+        String defaultNetworkNetmask = "255.255.255.0";
+        VlanVO vlan = Mockito.mock(VlanVO.class);
+        Mockito.when(vlan.getVlanGateway()).thenReturn(defaultNetworkGateway);
+        Mockito.when(vlan.getVlanNetmask()).thenReturn(defaultNetworkNetmask);
+        Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
+        Mockito.when(ipAddressVO.getVlanId()).thenReturn(1L);
+        Mockito.when(testOrchastrator._vlanDao.findById(1L)).thenReturn(vlan);
+        Mockito.when(testOrchastrator._ipAddressDao.findByIp(ipAddress)).thenReturn(ipAddressVO);
+        Pair<String, String> pair = testOrchastrator.getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, ipAddress);
+        Assert.assertNotNull(pair);
+        Assert.assertEquals(defaultNetworkGateway, pair.first());
+        Assert.assertEquals(defaultNetworkNetmask, pair.second());
+    }
+
+    @Test
+    public void testGetGuestIpForNicImportL2Network() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class);
+        Mockito.when(network.getGuestType()).thenReturn(GuestType.L2);
+        Assert.assertNull(testOrchastrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses));
+    }
+
+    @Test
+    public void testGetGuestIpForNicImportAdvancedZone() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class);
+        Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated);
+        Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
+        String ipAddress = "10.1.10.10";
+        Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress);
+        Mockito.when(testOrchastrator._ipAddrMgr.acquireGuestIpAddress(network, ipAddress)).thenReturn(ipAddress);
+        String guestIp = testOrchastrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses);
+        Assert.assertEquals(ipAddress, guestIp);
+    }
+
+    @Test
+    public void testGetGuestIpForNicImportBasicZoneAutomaticIP() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class);
+        Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared);
+        Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
+        long networkId = 1L;
+        long dataCenterId = 1L;
+        String freeIp = "172.10.10.10";
+        IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class);
+        Ip ip = mock(Ip.class);
+        Mockito.when(ip.addr()).thenReturn(freeIp);
+        Mockito.when(ipAddressVO.getAddress()).thenReturn(ip);
+        Mockito.when(ipAddressVO.getState()).thenReturn(State.Free);
+        Mockito.when(network.getId()).thenReturn(networkId);
+        Mockito.when(dataCenter.getId()).thenReturn(dataCenterId);
+        Mockito.when(testOrchastrator._ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(networkId, dataCenterId, State.Free)).thenReturn(ipAddressVO);
+        String ipAddress = testOrchastrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses);
+        Assert.assertEquals(freeIp, ipAddress);
+    }
+
+    @Test
+    public void testGetGuestIpForNicImportBasicZoneManualIP() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class);
+        Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared);
+        Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
+        long networkId = 1L;
+        long dataCenterId = 1L;
+        String requestedIp = "172.10.10.10";
+        IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class);
+        Ip ip = mock(Ip.class);
+        Mockito.when(ip.addr()).thenReturn(requestedIp);
+        Mockito.when(ipAddressVO.getAddress()).thenReturn(ip);
+        Mockito.when(ipAddressVO.getState()).thenReturn(State.Free);
+        Mockito.when(network.getId()).thenReturn(networkId);
+        Mockito.when(dataCenter.getId()).thenReturn(dataCenterId);
+        Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp);
+        Mockito.when(testOrchastrator._ipAddressDao.findByIp(requestedIp)).thenReturn(ipAddressVO);
+        String ipAddress = testOrchastrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses);
+        Assert.assertEquals(requestedIp, ipAddress);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testGetGuestIpForNicImportBasicUsedIP() {
+        Network network = Mockito.mock(Network.class);
+        DataCenter dataCenter = Mockito.mock(DataCenter.class);
+        Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class);
+        Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared);
+        Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
+        long networkId = 1L;
+        long dataCenterId = 1L;
+        String requestedIp = "172.10.10.10";
+        IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class);
+        Ip ip = mock(Ip.class);
+        Mockito.when(ip.addr()).thenReturn(requestedIp);
+        Mockito.when(ipAddressVO.getAddress()).thenReturn(ip);
+        Mockito.when(ipAddressVO.getState()).thenReturn(State.Allocated);
+        Mockito.when(network.getId()).thenReturn(networkId);
+        Mockito.when(dataCenter.getId()).thenReturn(dataCenterId);
+        Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp);
+        Mockito.when(testOrchastrator._ipAddressDao.findByIp(requestedIp)).thenReturn(ipAddressVO);
+        testOrchastrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses);
+    }
 }
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 b2f4c43..e817f61 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
@@ -18,6 +18,13 @@
 
 import java.util.ArrayList;
 
+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;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 import org.apache.commons.lang3.ObjectUtils;
 import org.junit.Assert;
 import org.junit.Before;
@@ -31,14 +38,22 @@
 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.user.ResourceLimitService;
+import com.cloud.utils.exception.CloudRuntimeException;
 
 @RunWith(MockitoJUnitRunner.class)
 public class VolumeOrchestratorTest {
 
     @Mock
     protected ResourceLimitService resourceLimitMgr;
+    @Mock
+    protected VolumeService volumeService;
+    @Mock
+    protected VolumeDataFactory volumeDataFactory;
 
     @Spy
     @InjectMocks
@@ -100,4 +115,44 @@
     public void testCheckAndUpdateVolumeAccountResourceCountLessSize() {
         runCheckAndUpdateVolumeAccountResourceCountTest(20L, 10L);
     }
+
+    @Test
+    public void testGrantVolumeAccessToHostIfNeededDriverNoNeed() {
+        PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class);
+        PrimaryDataStoreDriver driver = Mockito.mock(PrimaryDataStoreDriver.class);
+        Mockito.when(driver.volumesRequireGrantAccessWhenUsed()).thenReturn(false);
+        Mockito.when(store.getDriver()).thenReturn(driver);
+        volumeOrchestrator.grantVolumeAccessToHostIfNeeded(store, 1L,
+                Mockito.mock(HostVO.class), "");
+        Mockito.verify(volumeService, Mockito.never())
+                .grantAccess(Mockito.any(DataObject.class), Mockito.any(Host.class), Mockito.any(DataStore.class));
+    }
+
+    @Test
+    public void testGrantVolumeAccessToHostIfNeededDriverNeeds() {
+        PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class);
+        PrimaryDataStoreDriver driver = Mockito.mock(PrimaryDataStoreDriver.class);
+        Mockito.when(driver.volumesRequireGrantAccessWhenUsed()).thenReturn(true);
+        Mockito.when(store.getDriver()).thenReturn(driver);
+        Mockito.when(volumeDataFactory.getVolume(Mockito.anyLong())).thenReturn(Mockito.mock(VolumeInfo.class));
+        Mockito.doReturn(true).when(volumeService)
+                .grantAccess(Mockito.any(DataObject.class), Mockito.any(Host.class), Mockito.any(DataStore.class));
+        volumeOrchestrator.grantVolumeAccessToHostIfNeeded(store, 1L,
+                Mockito.mock(HostVO.class), "");
+        Mockito.verify(volumeService, Mockito.times(1))
+                .grantAccess(Mockito.any(DataObject.class), Mockito.any(Host.class), Mockito.any(DataStore.class));
+    }
+
+    @Test(expected = StorageAccessException.class)
+    public void testGrantVolumeAccessToHostIfNeededDriverNeedsButException() {
+        PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class);
+        PrimaryDataStoreDriver driver = Mockito.mock(PrimaryDataStoreDriver.class);
+        Mockito.when(driver.volumesRequireGrantAccessWhenUsed()).thenReturn(true);
+        Mockito.when(store.getDriver()).thenReturn(driver);
+        Mockito.when(volumeDataFactory.getVolume(Mockito.anyLong())).thenReturn(Mockito.mock(VolumeInfo.class));
+        Mockito.doThrow(CloudRuntimeException.class).when(volumeService)
+                .grantAccess(Mockito.any(DataObject.class), Mockito.any(Host.class), Mockito.any(DataStore.class));
+        volumeOrchestrator.grantVolumeAccessToHostIfNeeded(store, 1L,
+                Mockito.mock(HostVO.class), "");
+    }
 }
diff --git a/engine/orchestration/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/engine/orchestration/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/engine/orchestration/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/engine/pom.xml b/engine/pom.xml
index c21d0a2..c4a2fc8 100644
--- a/engine/pom.xml
+++ b/engine/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <build>
@@ -55,8 +55,11 @@
         <module>storage/configdrive</module>
         <module>storage/datamotion</module>
         <module>storage/image</module>
+        <module>storage/object</module>
         <module>storage/snapshot</module>
         <module>storage/volume</module>
+        <module>userdata/cloud-init</module>
+        <module>userdata</module>
     </modules>
     <profiles>
         <profile>
diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml
index fc859fe..62911f1 100644
--- a/engine/schema/pom.xml
+++ b/engine/schema/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -49,8 +49,8 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
         </dependency>
         <dependency>
             <groupId>org.ini4j</groupId>
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 f14cef9..ca6f13d 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
@@ -36,7 +36,6 @@
 import com.cloud.configuration.ResourceLimit;
 import com.cloud.domain.DomainVO;
 import com.cloud.domain.dao.DomainDao;
-import com.cloud.exception.UnsupportedServiceException;
 import com.cloud.user.AccountVO;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.db.DB;
@@ -171,9 +170,6 @@
 
         ResourceType[] resourceTypes = Resource.ResourceType.values();
         for (ResourceType resourceType : resourceTypes) {
-            if (!resourceType.supportsOwner(ownerType)) {
-                continue;
-            }
             ResourceCountVO resourceCountVO = new ResourceCountVO(resourceType, 0, ownerId, ownerType);
             persist(resourceCountVO);
         }
@@ -218,17 +214,6 @@
     }
 
     @Override
-    public ResourceCountVO persist(ResourceCountVO resourceCountVO) {
-        ResourceOwnerType ownerType = resourceCountVO.getResourceOwnerType();
-        ResourceType resourceType = resourceCountVO.getType();
-        if (!resourceType.supportsOwner(ownerType)) {
-            throw new UnsupportedServiceException("Resource type " + resourceType + " is not supported for owner of type " + ownerType.getName());
-        }
-
-        return super.persist(resourceCountVO);
-    }
-
-    @Override
     public long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType) {
         SearchCriteria<ResourceCountVO> sc = TypeSearch.create();
 
diff --git a/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.java b/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.java
new file mode 100644
index 0000000..6390d92
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.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 com.cloud.dc;
+
+import java.util.UUID;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.db.Encrypt;
+
+/**
+ * VmwareDatacenterVO contains information of Vmware Datacenter associated with a CloudStack zone.
+ */
+
+@Entity
+@Table(name = "vmware_data_center")
+public class VmwareDatacenterVO implements VmwareDatacenter {
+
+    private static final long serialVersionUID = -9114941929893819232L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "guid")
+    private String guid;
+
+    @Column(name = "name")
+    private String vmwareDatacenterName;
+
+    @Column(name = "vcenter_host")
+    private String vcenterHost;
+
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "username")
+    private String user;
+
+    @Encrypt
+    @Column(name = "password")
+    private String password;
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public String getVmwareDatacenterName() {
+        return vmwareDatacenterName;
+    }
+
+    @Override
+    public String getGuid() {
+        return guid;
+    }
+
+    @Override
+    public String getUser() {
+        return user;
+    }
+
+    @Override
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public String getVcenterHost() {
+        return vcenterHost;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public void setGuid(String guid) {
+        this.guid = guid;
+    }
+
+    public void setVmwareDatacenterName(String name) {
+        vmwareDatacenterName = name;
+    }
+
+    public void setVcenterHost(String vCenterHost) {
+        vcenterHost = vCenterHost;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+        ;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("VmwareDatacenter[").append(guid).append("]").toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return NumbersUtil.hash(id);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof VmwareDatacenterVO) {
+            return ((VmwareDatacenterVO)obj).getId() == getId();
+        } else {
+            return false;
+        }
+    }
+
+    public VmwareDatacenterVO(String guid, String name, String vCenterHost, String user, String password) {
+        uuid = UUID.randomUUID().toString();
+        vmwareDatacenterName = name;
+        this.guid = guid;
+        vcenterHost = vCenterHost;
+        this.user = user;
+        this.password = password;
+    }
+
+    public VmwareDatacenterVO(long id, String guid, String name, String vCenterHost, String user, String password) {
+        this(guid, name, vCenterHost, user, password);
+        this.id = id;
+    }
+
+    public VmwareDatacenterVO() {
+        uuid = UUID.randomUUID().toString();
+    }
+
+}
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 aea5192..dddbce3 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
@@ -115,4 +115,6 @@
     List<DataCenterVO> findByKeyword(String keyword);
 
     List<DataCenterVO> listAllZones();
+
+    List<DataCenterVO> listByIds(List<Long> ids);
 }
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 0c75568..491919b 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
@@ -433,4 +433,14 @@
 
         return dcs;
     }
+
+    @Override
+    public List<DataCenterVO> listByIds(List<Long> ids) {
+        SearchBuilder<DataCenterVO> idsSearch = createSearchBuilder();
+        idsSearch.and("ids", idsSearch.entity().getId(), SearchCriteria.Op.IN);
+        idsSearch.done();
+        SearchCriteria<DataCenterVO> sc = idsSearch.create();
+        sc.setParameters("ids", ids.toArray());
+        return listBy(sc);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VmwareDatacenterDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/VmwareDatacenterDao.java
new file mode 100644
index 0000000..1ed1f8d
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/dc/dao/VmwareDatacenterDao.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.dc.dao;
+
+import java.util.List;
+
+import com.cloud.dc.VmwareDatacenterVO;
+import com.cloud.utils.db.GenericDao;
+
+public interface VmwareDatacenterDao extends GenericDao<VmwareDatacenterVO, Long> {
+
+    /**
+     * Return a VMware Datacenter given guid
+     * @param guid of VMware datacenter
+     * @return VmwareDatacenterVO for the VMware datacenter having the specified guid.
+     */
+    VmwareDatacenterVO getVmwareDatacenterByGuid(String guid);
+
+    /**
+     * Return a VMware Datacenter given name and vCenter host.
+     * For legacy zones multiple records will be present in the table.
+     * @param name of VMware datacenter
+     * @param vCenter host
+     * @return VmwareDatacenterVO for the VMware datacenter with given name and
+     * belonging to specified vCenter host.
+     */
+    List<VmwareDatacenterVO> getVmwareDatacenterByNameAndVcenter(String name, String vCenterHost);
+
+    /**
+     * Return a list of VMware Datacenter given name.
+     * @param name of Vmware datacenter
+     * @return list of VmwareDatacenterVO for VMware datacenters having the specified name.
+     */
+    List<VmwareDatacenterVO> listVmwareDatacenterByName(String name);
+
+    /**
+     * Return a list of VMware Datacenters belonging to specified vCenter
+     * @param vCenter Host
+     * @return list of VmwareDatacenterVO for all VMware datacenters belonging to
+     * specified vCenter
+     */
+    List<VmwareDatacenterVO> listVmwareDatacenterByVcenter(String vCenterHost);
+
+    /**
+     * Lists all associated VMware datacenter on the management server.
+     * @return list of VmwareDatacenterVO for all associated VMware datacenters
+     */
+    List<VmwareDatacenterVO> listAllVmwareDatacenters();
+
+}
diff --git a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java
index 61eb6cf..df5a228 100644
--- a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java
+++ b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java
@@ -23,7 +23,6 @@
 import javax.persistence.Id;
 import javax.persistence.Table;
 
-import com.cloud.utils.db.Encrypt;
 import org.apache.cloudstack.api.InternalIdentity;
 
 @Entity
@@ -40,7 +39,6 @@
     @Column(name = "name")
     private String name;
 
-    @Encrypt
     @Column(name = "value")
     private String value;
 
diff --git a/engine/schema/src/main/java/com/cloud/host/HostTagVO.java b/engine/schema/src/main/java/com/cloud/host/HostTagVO.java
index 6b8b754..cd4ac29 100644
--- a/engine/schema/src/main/java/com/cloud/host/HostTagVO.java
+++ b/engine/schema/src/main/java/com/cloud/host/HostTagVO.java
@@ -24,6 +24,7 @@
 import javax.persistence.Table;
 
 import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.commons.lang3.BooleanUtils;
 
 @Entity
 @Table(name = "host_tags")
@@ -39,12 +40,22 @@
     @Column(name = "tag")
     private String tag;
 
+    @Column(name = "is_tag_a_rule")
+    private boolean isTagARule;
+
     protected HostTagVO() {
     }
 
     public HostTagVO(long hostId, String tag) {
         this.hostId = hostId;
         this.tag = tag;
+        this.isTagARule = false;
+    }
+
+    public HostTagVO(long hostId, String tag, Boolean isTagARule) {
+        this.hostId = hostId;
+        this.tag = tag;
+        this.isTagARule = BooleanUtils.toBooleanDefaultIfNull(isTagARule, false);
     }
 
     public long getHostId() {
@@ -59,6 +70,11 @@
         this.tag = tag;
     }
 
+    public boolean getIsTagARule() {
+        return isTagARule;
+    }
+
+
     @Override
     public long getId() {
         return id;
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 d6e7ea1..697401a 100644
--- a/engine/schema/src/main/java/com/cloud/host/HostVO.java
+++ b/engine/schema/src/main/java/com/cloud/host/HostVO.java
@@ -39,6 +39,7 @@
 import javax.persistence.Transient;
 
 import com.cloud.agent.api.VgpuTypesInfo;
+import com.cloud.host.dao.HostTagsDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.resource.ResourceState;
@@ -46,7 +47,10 @@
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.db.GenericDao;
 import java.util.Arrays;
+
+import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
 import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+import org.apache.commons.lang.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 
 @Entity
@@ -159,6 +163,14 @@
     @Transient
     List<String> hostTags;
 
+    /**
+     * 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.
+     */
+    @Transient
+    Boolean isTagARule;
+
     // This value is only for saving and current cannot be loaded.
     @Transient
     HashMap<String, HashMap<String, VgpuTypesInfo>> groupDetails = new HashMap<String, HashMap<String, VgpuTypesInfo>>();
@@ -322,8 +334,13 @@
         return hostTags;
     }
 
-    public void setHostTags(List<String> hostTags) {
+    public void setHostTags(List<String> hostTags, Boolean isTagARule) {
         this.hostTags = hostTags;
+        this.isTagARule = isTagARule;
+    }
+
+    public Boolean getIsTagARule() {
+        return isTagARule;
     }
 
     public  HashMap<String, HashMap<String, VgpuTypesInfo>> getGpuGroupDetails() {
@@ -748,6 +765,11 @@
         if (serviceOffering == null) {
             return false;
         }
+
+        if (BooleanUtils.isTrue(this.getIsTagARule())) {
+            return TagAsRuleHelper.interpretTagAsRule(this.getHostTags().get(0), serviceOffering.getHostTag(), HostTagsDao.hostTagRuleExecutionTimeout.value());
+        }
+
         if (StringUtils.isEmpty(serviceOffering.getHostTag())) {
             return true;
         }
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 6dfe75c..fe30722 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
@@ -108,6 +108,8 @@
 
     List<HostVO> listAllHostsByZoneAndHypervisorType(long zoneId, HypervisorType hypervisorType);
 
+    List<HostVO> listAllHostsThatHaveNoRuleTag(Host.Type type, Long clusterId, Long podId, Long dcId);
+
     List<HostVO> listAllHostsByType(Host.Type type);
 
     HostVO findByPublicIp(String publicIp);
@@ -143,6 +145,8 @@
 
     HostVO findByName(String name);
 
+    HostVO findHostByHypervisorTypeAndVersion(HypervisorType hypervisorType, String hypervisorVersion);
+
     List<HostVO> listHostsWithActiveVMs(long offeringId);
 
     /**
@@ -159,4 +163,8 @@
      * @return ordered list of hypervisor versions
      */
     List<String> listOrderedHostsHypervisorVersionsInDatacenter(long datacenterId, HypervisorType hypervisorType);
+
+    List<HostVO> findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags);
+
+    List<Long> findClustersThatMatchHostTagRule(String computeOfferingTags);
 }
diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
index 15c87d6..1363515 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
@@ -22,9 +22,11 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.stream.Collectors;
 
@@ -32,6 +34,8 @@
 import javax.inject.Inject;
 import javax.persistence.TableGenerator;
 
+import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.VgpuTypesInfo;
@@ -80,8 +84,8 @@
     private static final Logger state_logger = Logger.getLogger(ResourceState.class);
 
     private static final String LIST_HOST_IDS_BY_COMPUTETAGS = "SELECT filtered.host_id, COUNT(filtered.tag) AS tag_count "
-                                                             + "FROM (SELECT host_id, tag FROM host_tags GROUP BY host_id,tag) AS filtered "
-                                                             + "WHERE tag IN(%s) "
+                                                             + "FROM (SELECT host_id, tag, is_tag_a_rule FROM host_tags GROUP BY host_id,tag) AS filtered "
+                                                             + "WHERE tag IN(%s) AND is_tag_a_rule = 0 "
                                                              + "GROUP BY host_id "
                                                              + "HAVING tag_count = %s ";
     private static final String SEPARATOR = ",";
@@ -116,6 +120,7 @@
     protected SearchBuilder<HostVO> ResourceStateSearch;
     protected SearchBuilder<HostVO> NameLikeSearch;
     protected SearchBuilder<HostVO> NameSearch;
+    protected SearchBuilder<HostVO> hostHypervisorTypeAndVersionSearch;
     protected SearchBuilder<HostVO> SequenceSearch;
     protected SearchBuilder<HostVO> DirectlyConnectedSearch;
     protected SearchBuilder<HostVO> UnmanagedDirectConnectSearch;
@@ -147,6 +152,10 @@
     protected GenericSearchBuilder<ClusterVO, Long> AllClustersSearch;
     protected SearchBuilder<HostVO> HostsInClusterSearch;
 
+    protected SearchBuilder<HostVO> searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag;
+
+    protected SearchBuilder<HostTagVO> searchBuilderFindByRuleTag;
+
     protected Attribute _statusAttr;
     protected Attribute _resourceStateAttr;
     protected Attribute _msIdAttr;
@@ -311,6 +320,13 @@
         NameSearch.and("name", NameSearch.entity().getName(), SearchCriteria.Op.EQ);
         NameSearch.done();
 
+        hostHypervisorTypeAndVersionSearch = createSearchBuilder();
+        hostHypervisorTypeAndVersionSearch.and("hypervisorType", hostHypervisorTypeAndVersionSearch.entity().getHypervisorType(), SearchCriteria.Op.EQ);
+        hostHypervisorTypeAndVersionSearch.and("hypervisorVersion", hostHypervisorTypeAndVersionSearch.entity().getHypervisorVersion(), SearchCriteria.Op.EQ);
+        hostHypervisorTypeAndVersionSearch.and("type", hostHypervisorTypeAndVersionSearch.entity().getType(), SearchCriteria.Op.EQ);
+        hostHypervisorTypeAndVersionSearch.and("status", hostHypervisorTypeAndVersionSearch.entity().getStatus(), SearchCriteria.Op.EQ);
+        hostHypervisorTypeAndVersionSearch.done();
+
         SequenceSearch = createSearchBuilder();
         SequenceSearch.and("id", SequenceSearch.entity().getId(), SearchCriteria.Op.EQ);
         // SequenceSearch.addRetrieve("sequence", SequenceSearch.entity().getSequence());
@@ -447,6 +463,22 @@
         HostIdSearch.and("dataCenterId", HostIdSearch.entity().getDataCenterId(), Op.EQ);
         HostIdSearch.done();
 
+        searchBuilderFindByRuleTag = _hostTagsDao.createSearchBuilder();
+        searchBuilderFindByRuleTag.and("is_tag_a_rule", searchBuilderFindByRuleTag.entity().getIsTagARule(), Op.EQ);
+        searchBuilderFindByRuleTag.or("tagDoesNotExist", searchBuilderFindByRuleTag.entity().getIsTagARule(), Op.NULL);
+
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag = createSearchBuilder();
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.and("id", searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.entity().getId(), Op.EQ);
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.and("type", searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.entity().getType(), Op.EQ);
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.and("cluster_id", searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.entity().getClusterId(), Op.EQ);
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.and("pod_id", searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.entity().getPodId(), Op.EQ);
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.and("data_center_id", searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.entity().getDataCenterId(), Op.EQ);
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.join("id", searchBuilderFindByRuleTag, searchBuilderFindByRuleTag.entity().getHostId(),
+                searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.entity().getId(), JoinType.LEFTOUTER);
+
+        searchBuilderFindByRuleTag.done();
+        searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.done();
+
         _statusAttr = _allAttributes.get("status");
         _msIdAttr = _allAttributes.get("managementServerId");
         _pingTimeAttr = _allAttributes.get("lastPinged");
@@ -784,9 +816,12 @@
 
     @Override
     public List<HostVO> listAllUpAndEnabledNonHAHosts(Type type, Long clusterId, Long podId, long dcId, String haTag) {
-        SearchBuilder<HostTagVO> hostTagSearch = null;
+        SearchBuilder<HostTagVO> hostTagSearch = _hostTagsDao.createSearchBuilder();
+        hostTagSearch.and();
+        hostTagSearch.op("isTagARule", hostTagSearch.entity().getIsTagARule(), Op.EQ);
+        hostTagSearch.or("tagDoesNotExist", hostTagSearch.entity().getIsTagARule(), Op.NULL);
+        hostTagSearch.cp();
         if (haTag != null && !haTag.isEmpty()) {
-            hostTagSearch = _hostTagsDao.createSearchBuilder();
             hostTagSearch.and().op("tag", hostTagSearch.entity().getTag(), SearchCriteria.Op.NEQ);
             hostTagSearch.or("tagNull", hostTagSearch.entity().getTag(), SearchCriteria.Op.NULL);
             hostTagSearch.cp();
@@ -801,12 +836,14 @@
         hostSearch.and("status", hostSearch.entity().getStatus(), SearchCriteria.Op.EQ);
         hostSearch.and("resourceState", hostSearch.entity().getResourceState(), SearchCriteria.Op.EQ);
 
-        if (haTag != null && !haTag.isEmpty()) {
-            hostSearch.join("hostTagSearch", hostTagSearch, hostSearch.entity().getId(), hostTagSearch.entity().getHostId(), JoinBuilder.JoinType.LEFTOUTER);
-        }
+
+        hostSearch.join("hostTagSearch", hostTagSearch, hostSearch.entity().getId(), hostTagSearch.entity().getHostId(), JoinBuilder.JoinType.LEFTOUTER);
+
 
         SearchCriteria<HostVO> sc = hostSearch.create();
 
+        sc.setJoinParameters("hostTagSearch", "isTagARule", false);
+
         if (haTag != null && !haTag.isEmpty()) {
             sc.setJoinParameters("hostTagSearch", "tag", haTag);
         }
@@ -838,8 +875,13 @@
 
     @Override
     public void loadHostTags(HostVO host) {
-        List<String> hostTags = _hostTagsDao.getHostTags(host.getId());
-        host.setHostTags(hostTags);
+        List<HostTagVO> hostTagVOList = _hostTagsDao.getHostTags(host.getId());
+        if (CollectionUtils.isNotEmpty(hostTagVOList)) {
+            List<String> hostTagList = hostTagVOList.parallelStream().map(HostTagVO::getTag).collect(Collectors.toList());
+            host.setHostTags(hostTagList, hostTagVOList.get(0).getIsTagARule());
+        } else {
+            host.setHostTags(null, null);
+        }
     }
 
     @DB
@@ -873,10 +915,10 @@
 
     protected void saveHostTags(HostVO host) {
         List<String> hostTags = host.getHostTags();
-        if (hostTags == null || (hostTags != null && hostTags.isEmpty())) {
+        if (CollectionUtils.isEmpty(hostTags)) {
             return;
         }
-        _hostTagsDao.persist(host.getId(), hostTags);
+        _hostTagsDao.persist(host.getId(), hostTags, host.getIsTagARule());
     }
 
     protected void saveGpuRecords(HostVO host) {
@@ -1237,6 +1279,26 @@
     }
 
     @Override
+    public List<HostVO> listAllHostsThatHaveNoRuleTag(Type type, Long clusterId, Long podId, Long dcId) {
+        SearchCriteria<HostVO> sc = searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.create();
+        if (type != null) {
+            sc.setParameters("type", type);
+        }
+        if (clusterId != null) {
+            sc.setParameters("cluster_id", clusterId);
+        }
+        if (podId != null) {
+            sc.setParameters("pod_id", podId);
+        }
+        if (dcId != null) {
+            sc.setParameters("data_center_id", dcId);
+        }
+        sc.setJoinParameters("id", "is_tag_a_rule", false);
+
+        return search(sc, null);
+    }
+
+    @Override
     public List<Long> listClustersByHostTag(String computeOfferingTags) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         String sql = this.LIST_CLUSTERID_FOR_HOST_TAG;
@@ -1258,9 +1320,6 @@
                 result.add(rs.getLong(1));
             }
             pstmt.close();
-            if(result.isEmpty()){
-                throw new CloudRuntimeException("No suitable host found for follow compute offering tags: " + computeOfferingTags);
-            }
             return result;
         } catch (SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + sql, e);
@@ -1285,15 +1344,33 @@
                 result.add(rs.getLong(1));
             }
             pstmt.close();
-            if(result.isEmpty()){
-                throw new CloudRuntimeException("No suitable host found for follow compute offering tags: " + computeOfferingTags);
-            }
             return result;
         } catch (SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + select, e);
         }
     }
 
+    public List<HostVO> findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags) {
+        List<HostTagVO> hostTagVOList = _hostTagsDao.findHostRuleTags();
+        List<HostVO> result = new ArrayList<>();
+        for (HostTagVO rule: hostTagVOList) {
+            if (TagAsRuleHelper.interpretTagAsRule(rule.getTag(), computeOfferingTags, HostTagsDao.hostTagRuleExecutionTimeout.value())) {
+                result.add(findById(rule.getHostId()));
+            }
+        }
+
+        return result;
+    }
+
+    public List<Long> findClustersThatMatchHostTagRule(String computeOfferingTags) {
+        Set<Long> result = new HashSet<>();
+        List<HostVO> hosts = findHostsWithTagRuleThatMatchComputeOferringTags(computeOfferingTags);
+        for (HostVO host: hosts) {
+            result.add(host.getClusterId());
+        }
+        return new ArrayList<>(result);
+    }
+
     private String getHostIdsByComputeTags(List<String> offeringTags){
         List<String> questionMarks = new ArrayList();
         offeringTags.forEach((tag) -> { questionMarks.add("?"); });
@@ -1446,6 +1523,16 @@
         return findOneBy(sc);
     }
 
+    @Override
+    public HostVO findHostByHypervisorTypeAndVersion(HypervisorType hypervisorType, String hypervisorVersion) {
+        SearchCriteria<HostVO> sc = hostHypervisorTypeAndVersionSearch.create();
+        sc.setParameters("hypervisorType", hypervisorType);
+        sc.setParameters("hypervisorVersion", hypervisorVersion);
+        sc.setParameters("type", Host.Type.Routing);
+        sc.setParameters("status", Status.Up);
+        return findOneBy(sc);
+    }
+
     private ResultSet executeSqlGetResultsetForMethodFindHostInZoneToExecuteCommand(HypervisorType hypervisorType, long zoneId, TransactionLegacy tx, String sql) throws SQLException {
         PreparedStatement pstmt = tx.prepareAutoCloseStatement(sql);
         pstmt.setString(1, Objects.toString(hypervisorType));
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 0fb5370d..d134db3 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
@@ -20,15 +20,21 @@
 
 import com.cloud.host.HostTagVO;
 import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.framework.config.ConfigKey;
 
 public interface HostTagsDao extends GenericDao<HostTagVO, Long> {
 
-    void persist(long hostId, List<String> hostTags);
+    ConfigKey<Long> hostTagRuleExecutionTimeout = new ConfigKey<>("Advanced", Long.class, "host.tag.rule.execution.timeout", "2000", "The maximum runtime, in milliseconds, " +
+        "to execute a host tag rule; if it is reached, a timeout will happen.", true);
 
-    List<String> getHostTags(long hostId);
+    void persist(long hostId, List<String> hostTags, Boolean isTagARule);
+
+    List<HostTagVO> getHostTags(long hostId);
 
     List<String> getDistinctImplicitHostTags(List<Long> hostIds, String[] implicitHostTags);
 
     void deleteTags(long hostId);
 
+    List<HostTagVO> findHostRuleTags();
+
 }
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 a73899b..65deb1d 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
@@ -16,10 +16,10 @@
 // under the License.
 package com.cloud.host.dao;
 
-import java.util.ArrayList;
 import java.util.List;
 
-
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
 import org.springframework.stereotype.Component;
 
 import com.cloud.host.HostTagVO;
@@ -31,13 +31,14 @@
 import com.cloud.utils.db.SearchCriteria.Func;
 
 @Component
-public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements HostTagsDao {
+public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements HostTagsDao, Configurable {
     protected final SearchBuilder<HostTagVO> HostSearch;
     protected final GenericSearchBuilder<HostTagVO, String> DistinctImplictTagsSearch;
 
     public HostTagsDaoImpl() {
         HostSearch = createSearchBuilder();
         HostSearch.and("hostId", HostSearch.entity().getHostId(), SearchCriteria.Op.EQ);
+        HostSearch.and("isTagARule", HostSearch.entity().getIsTagARule(), SearchCriteria.Op.EQ);
         HostSearch.done();
 
         DistinctImplictTagsSearch = createSearchBuilder(String.class);
@@ -48,17 +49,11 @@
     }
 
     @Override
-    public List<String> getHostTags(long hostId) {
+    public List<HostTagVO> getHostTags(long hostId) {
         SearchCriteria<HostTagVO> sc = HostSearch.create();
         sc.setParameters("hostId", hostId);
 
-        List<HostTagVO> results = search(sc, null);
-        List<String> hostTags = new ArrayList<String>(results.size());
-        for (HostTagVO result : results) {
-            hostTags.add(result.getTag());
-        }
-
-        return hostTags;
+        return search(sc, null);
     }
 
     @Override
@@ -80,7 +75,15 @@
     }
 
     @Override
-    public void persist(long hostId, List<String> hostTags) {
+    public List<HostTagVO> findHostRuleTags() {
+        SearchCriteria<HostTagVO> sc = HostSearch.create();
+        sc.setParameters("isTagARule", true);
+
+        return search(sc, null);
+    }
+
+    @Override
+    public void persist(long hostId, List<String> hostTags, Boolean isTagARule) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
 
         txn.start();
@@ -91,10 +94,20 @@
         for (String tag : hostTags) {
             tag = tag.trim();
             if (tag.length() > 0) {
-                HostTagVO vo = new HostTagVO(hostId, tag);
+                HostTagVO vo = new HostTagVO(hostId, tag, isTagARule);
                 persist(vo);
             }
         }
         txn.commit();
     }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {hostTagRuleExecutionTimeout};
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return HostTagsDaoImpl.class.getSimpleName();
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java b/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java
index e827282..a4ec0a6 100644
--- a/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java
@@ -75,11 +75,17 @@
         sc.setParameters("hypervisorType", hypervisorType);
         sc.setParameters("hypervisorVersion", hypervisorVersion);
         HypervisorCapabilitiesVO result = findOneBy(sc);
+        String parentVersion = CloudStackVersion.getVMwareParentVersion(hypervisorVersion);
         if (result != null || !HypervisorType.VMware.equals(hypervisorType) ||
-                CloudStackVersion.getVMwareParentVersion(hypervisorVersion) == null) {
+                parentVersion == null) {
             return result;
         }
-        sc.setParameters("hypervisorVersion", CloudStackVersion.getVMwareParentVersion(hypervisorVersion));
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("Hypervisor capabilities for hypervisor: %s, version: %s can not be found. " +
+                            "Trying to find capabilities for the parent version: %s",
+                    hypervisorType, hypervisorVersion, parentVersion));
+        }
+        sc.setParameters("hypervisorVersion", parentVersion);
         return findOneBy(sc);
     }
 
diff --git a/engine/schema/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDaoImpl.java b/engine/schema/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDaoImpl.java
new file mode 100644
index 0000000..b4bd56f
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDaoImpl.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 com.cloud.hypervisor.vmware.dao;
+
+import java.util.List;
+
+
+import com.cloud.dc.dao.VmwareDatacenterDao;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import com.cloud.dc.VmwareDatacenterVO;
+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 com.cloud.utils.db.SearchCriteria.Op;
+
+@Component
+@DB
+public class VmwareDatacenterDaoImpl extends GenericDaoBase<VmwareDatacenterVO, Long> implements VmwareDatacenterDao {
+    protected static final Logger s_logger = Logger.getLogger(VmwareDatacenterDaoImpl.class);
+
+    final SearchBuilder<VmwareDatacenterVO> nameSearch;
+    final SearchBuilder<VmwareDatacenterVO> guidSearch;
+    final SearchBuilder<VmwareDatacenterVO> vcSearch;
+    final SearchBuilder<VmwareDatacenterVO> nameVcSearch;
+    final SearchBuilder<VmwareDatacenterVO> fullTableSearch;
+
+    public VmwareDatacenterDaoImpl() {
+        super();
+
+        nameSearch = createSearchBuilder();
+        nameSearch.and("name", nameSearch.entity().getVmwareDatacenterName(), Op.EQ);
+        nameSearch.done();
+
+        nameVcSearch = createSearchBuilder();
+        nameVcSearch.and("name", nameVcSearch.entity().getVmwareDatacenterName(), Op.EQ);
+        nameVcSearch.and("vCenterHost", nameVcSearch.entity().getVcenterHost(), Op.EQ);
+        nameVcSearch.done();
+
+        vcSearch = createSearchBuilder();
+        vcSearch.and("vCenterHost", vcSearch.entity().getVcenterHost(), Op.EQ);
+        vcSearch.done();
+
+        guidSearch = createSearchBuilder();
+        guidSearch.and("guid", guidSearch.entity().getGuid(), Op.EQ);
+        guidSearch.done();
+
+        fullTableSearch = createSearchBuilder();
+        fullTableSearch.done();
+    }
+
+    @Override
+    public VmwareDatacenterVO getVmwareDatacenterByGuid(String guid) {
+        SearchCriteria<VmwareDatacenterVO> sc = guidSearch.create();
+        sc.setParameters("guid", guid);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public List<VmwareDatacenterVO> getVmwareDatacenterByNameAndVcenter(String name, String vCenterHost) {
+        SearchCriteria<VmwareDatacenterVO> sc = nameVcSearch.create();
+        sc.setParameters("name", name);
+        sc.setParameters("vCenterHost", vCenterHost);
+        return search(sc, null);
+    }
+
+    @Override
+    public List<VmwareDatacenterVO> listVmwareDatacenterByName(String name) {
+        SearchCriteria<VmwareDatacenterVO> sc = nameSearch.create();
+        sc.setParameters("name", name);
+        return search(sc, null);
+    }
+
+    @Override
+    public List<VmwareDatacenterVO> listVmwareDatacenterByVcenter(String vCenterHost) {
+        SearchCriteria<VmwareDatacenterVO> sc = vcSearch.create();
+        sc.setParameters("vCenterHost", vCenterHost);
+        return search(sc, null);
+    }
+
+    @Override
+    public List<VmwareDatacenterVO> listAllVmwareDatacenters() {
+        SearchCriteria<VmwareDatacenterVO> sc = fullTableSearch.create();
+        return search(sc, null);
+    }
+
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceDao.java b/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceDao.java
index b67130b..efb1b56 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceDao.java
@@ -34,14 +34,14 @@
     /**
      * list the firewall devices added in to this physical network of certain provider type?
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      */
     List<ExternalFirewallDeviceVO> listByPhysicalNetworkAndProvider(long physicalNetworkId, String providerName);
 
     /**
      * list the firewall devices added in to this physical network by their allocation state
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      * @param allocationState firewall device allocation state
      * @return list of ExternalFirewallDeviceVO for the devices in the physical network with a device allocation state
      */
@@ -50,7 +50,7 @@
     /**
      * list the load balancer devices added in to this physical network by the device status (enabled/disabled)
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      * @param state firewall device status
      * @return list of ExternalFirewallDeviceVO for the devices in the physical network with a device state
      */
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceDao.java b/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceDao.java
index 9125447..9d0f3d6 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceDao.java
@@ -34,14 +34,14 @@
     /**
      * list the load balancer devices added in to this physical network of certain provider type?
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      */
     List<ExternalLoadBalancerDeviceVO> listByPhysicalNetworkAndProvider(long physicalNetworkId, String providerName);
 
     /**
      * list the load balancer devices added in to this physical network by their allocation state
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      * @param allocationState load balancer device allocation state
      * @return list of ExternalLoadBalancerDeviceVO for the devices in the physical network with a device allocation state
      */
@@ -50,7 +50,7 @@
     /**
      * list the load balancer devices added in to this physical network by the device status (enabled/disabled)
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      * @param state load balancer device status
      * @return list of ExternalLoadBalancerDeviceVO for the devices in the physical network with a device state
      */
@@ -59,7 +59,7 @@
     /**
      * list the load balancer devices added in to this physical network by the managed type (external/cloudstack managed)
      * @param physicalNetworkId physical Network Id
-     * @param providerName netwrok service provider name
+     * @param providerName network service provider name
      * @param managed managed type
      * @return list of ExternalLoadBalancerDeviceVO for the devices in to this physical network of a managed type
      */
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 f2e9bcb..21200db 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
@@ -53,6 +53,12 @@
 
     List<FirewallRuleVO> listByIpAndNotRevoked(long ipAddressId);
 
+    /**
+     * counts the number of portforwarding rules for an IP address
+     *
+     * @param sourceIpId the id of the IP record
+     * @return the number of portforwarding rules for this IP
+     */
     long countRulesByIpId(long sourceIpId);
 
     long countRulesByIpIdAndState(long sourceIpId, FirewallRule.State state);
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java
index 51dfa91..b1b1e1c 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java
@@ -21,6 +21,7 @@
 import com.cloud.dc.Vlan.VlanType;
 import com.cloud.network.IpAddress.State;
 import com.cloud.utils.db.GenericDao;
+import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.net.Ip;
 
 public interface IPAddressDao extends GenericDao<IPAddressVO, Long> {
@@ -58,7 +59,7 @@
     IPAddressVO findByAssociatedVmId(long vmId);
 
     // for vm secondary ips case mapping is  IP1--> vmIp1, IP2-->vmIp2, etc
-    // This method is used when one vm is mapped to muliple to public ips
+    // This method is used when one vm is mapped to multiple to public ips
     List<IPAddressVO> findAllByAssociatedVmId(long vmId);
 
     IPAddressVO findByIpAndSourceNetworkId(long networkId, String ipAddress);
@@ -75,7 +76,7 @@
 
     long countFreeIPsInNetwork(long networkId);
 
-    IPAddressVO findByVmIp(String vmIp);
+    IPAddressVO findByIp(String ipAddress);
 
     IPAddressVO findByAssociatedVmIdAndVmIp(long vmId, String vmIp);
 
@@ -100,4 +101,8 @@
     List<IPAddressVO> listByDcIdAndAssociatedNetwork(long dcId);
 
     List<IPAddressVO> listByNetworkId(long networkId);
+
+    void buildQuarantineSearchCriteria(SearchCriteria<IPAddressVO> sc);
+
+    IPAddressVO findBySourceNetworkIdAndDatacenterIdAndState(long sourceNetworkId, long dataCenterId, State state);
 }
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 b995959..d142752 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
@@ -24,6 +24,7 @@
 import javax.annotation.PostConstruct;
 import javax.inject.Inject;
 
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.resourcedetail.dao.UserIpAddressDetailsDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -32,6 +33,7 @@
 import com.cloud.dc.VlanVO;
 import com.cloud.dc.dao.VlanDao;
 import com.cloud.network.IpAddress.State;
+import com.cloud.network.vo.PublicIpQuarantineVO;
 import com.cloud.server.ResourceTag.ResourceObjectType;
 import com.cloud.tags.dao.ResourceTagDao;
 import com.cloud.utils.db.DB;
@@ -69,6 +71,9 @@
     @Inject
     UserIpAddressDetailsDao _detailsDao;
 
+    @Inject
+    PublicIpQuarantineDao publicIpQuarantineDao;
+
     // make it public for JUnit test
     public IPAddressDaoImpl() {
     }
@@ -304,7 +309,7 @@
 
 
     // for vm secondary ips case mapping is  IP1--> vmIp1, IP2-->vmIp2, etc
-    // Used when vm is mapped to muliple to public ips
+    // Used when vm is mapped to multiple to public ips
     @Override
     public List<IPAddressVO> findAllByAssociatedVmId(long vmId) {
         SearchCriteria<IPAddressVO> sc = AllFieldsSearch.create();
@@ -314,9 +319,9 @@
     }
 
     @Override
-    public IPAddressVO findByVmIp(String vmIp) {
+    public IPAddressVO findByIp(String ipAddress) {
         SearchCriteria<IPAddressVO> sc = AllFieldsSearch.create();
-        sc.setParameters("associatedVmIp", vmIp);
+        sc.setParameters("ipAddress", ipAddress);
         return findOneBy(sc);
     }
 
@@ -534,4 +539,28 @@
         sc.setParameters("state", State.Allocated);
         return listBy(sc);
     }
+
+    @Override
+    public void buildQuarantineSearchCriteria(SearchCriteria<IPAddressVO> sc) {
+        long accountId = CallContext.current().getCallingAccount().getAccountId();
+        SearchBuilder<PublicIpQuarantineVO> listAllIpsInQuarantine = publicIpQuarantineDao.createSearchBuilder();
+        listAllIpsInQuarantine.and("quarantineEndDate", listAllIpsInQuarantine.entity().getEndDate(), SearchCriteria.Op.GT);
+        listAllIpsInQuarantine.and("previousOwnerId", listAllIpsInQuarantine.entity().getPreviousOwnerId(), Op.NEQ);
+
+        SearchCriteria<PublicIpQuarantineVO> searchCriteria = listAllIpsInQuarantine.create();
+        searchCriteria.setParameters("quarantineEndDate", new Date());
+        searchCriteria.setParameters("previousOwnerId", accountId);
+        Object[] quarantinedIpsIdsAllowedToUser = publicIpQuarantineDao.search(searchCriteria, null).stream().map(PublicIpQuarantineVO::getPublicIpAddressId).toArray();
+
+        sc.setParametersIfNotNull("quarantinedPublicIpsIdsNIN", quarantinedIpsIdsAllowedToUser);
+    }
+
+    @Override
+    public IPAddressVO findBySourceNetworkIdAndDatacenterIdAndState(long sourceNetworkId, long dataCenterId, State state) {
+        SearchCriteria<IPAddressVO> sc = AllFieldsSearch.create();
+        sc.setParameters("sourcenetwork", sourceNetworkId);
+        sc.setParameters("dataCenterId", dataCenterId);
+        sc.setParameters("state", State.Free);
+        return findOneBy(sc);
+    }
 }
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 7c4d56b..4c7569a 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
@@ -29,7 +29,6 @@
 import javax.persistence.Table;
 import javax.persistence.Temporal;
 import javax.persistence.TemporalType;
-import javax.persistence.Transient;
 
 import com.cloud.network.IpAddress;
 import com.cloud.utils.db.GenericDao;
@@ -97,14 +96,6 @@
     @Column(name = "is_system")
     private boolean system;
 
-    @Column(name = "account_id")
-    @Transient
-    private Long accountId = null;
-
-    @Transient
-    @Column(name = "domain_id")
-    private Long domainId = null;
-
     @Column(name = "vpc_id")
     private Long vpcId;
 
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java
index f4d0ad7..43d4d25 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java
@@ -17,6 +17,7 @@
 package com.cloud.network.dao;
 
 import java.util.List;
+import java.util.Map;
 
 import com.cloud.utils.db.GenericDao;
 
@@ -26,4 +27,6 @@
     NetworkDomainVO getDomainNetworkMapByNetworkId(long networkId);
 
     List<Long> listNetworkIdsByDomain(long domainId);
+
+    Map<Long, List<String>> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath);
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java
index 188f306..ce86a86 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java
@@ -16,10 +16,17 @@
 // under the License.
 package com.cloud.network.dao;
 
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
-
+import com.cloud.utils.db.TransactionLegacy;
+import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import com.cloud.utils.db.DB;
@@ -31,9 +38,23 @@
 @Component
 @DB()
 public class NetworkDomainDaoImpl extends GenericDaoBase<NetworkDomainVO, Long> implements NetworkDomainDao {
+    public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName());
     final SearchBuilder<NetworkDomainVO> AllFieldsSearch;
     final SearchBuilder<NetworkDomainVO> DomainsSearch;
 
+    private static final String LIST_DOMAINS_OF_SHARED_NETWORKS_USED_BY_DOMAIN_PATH = "SELECT shared_nw.domain_id, \n" +
+            "GROUP_CONCAT('VM:', vm.uuid, ' | NW:' , network.uuid) \n" +
+            "FROM   cloud.domain_network_ref AS shared_nw\n" +
+            "INNER  JOIN cloud.nics AS nic ON (nic.network_id = shared_nw.network_id AND nic.removed IS NULL)\n" +
+            "INNER  JOIN cloud.vm_instance AS vm ON (vm.id = nic.instance_id)\n" +
+            "INNER  JOIN cloud.domain AS domain ON (domain.id = vm.domain_id)\n" +
+            "INNER  JOIN cloud.domain AS domain_sn ON (domain_sn.id = shared_nw.domain_id)\n" +
+            "INNER  JOIN cloud.networks AS network ON (shared_nw.network_id = network.id)\n" +
+            "WHERE  shared_nw.subdomain_access = 1\n" +
+            "AND    domain.path LIKE ?\n" +
+            "AND    domain_sn.path NOT LIKE ?\n" +
+            "GROUP  BY shared_nw.network_id";
+
     protected NetworkDomainDaoImpl() {
         super();
 
@@ -71,4 +92,37 @@
         }
         return networkIdsToReturn;
     }
+
+    @Override
+    public Map<Long, List<String>> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath) {
+        logger.debug(String.format("Retrieving the domains of the shared networks with subdomain access used by domain with path [%s].", domainPath));
+
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_SHARED_NETWORKS_USED_BY_DOMAIN_PATH)) {
+            Map<Long, List<String>> domainsOfSharedNetworksUsedByDomainPath = new HashMap<>();
+
+            String domainSearch = domainPath.concat("%");
+            pstmt.setString(1, domainSearch);
+            pstmt.setString(2, domainSearch);
+
+            try (ResultSet rs = pstmt.executeQuery()) {
+                while (rs.next()) {
+                    Long domainId = rs.getLong(1);
+                    List<String> vmUuidsAndNetworkUuids = Arrays.asList(rs.getString(2).split(","));
+
+                    domainsOfSharedNetworksUsedByDomainPath.put(domainId, vmUuidsAndNetworkUuids);
+                }
+            }
+
+            return domainsOfSharedNetworksUsedByDomainPath;
+        } catch (SQLException e) {
+            logger.error(String.format("Failed to retrieve the domains of the shared networks with subdomain access used by domain with path [%s] due to [%s]. Returning an empty "
+                    + "list of domains.", domainPath, e.getMessage()));
+
+            logger.debug(String.format("Failed to retrieve the domains of the shared networks with subdomain access used by domain with path [%s]. Returning an empty "
+                    + "list of domains.", domainPath), e);
+
+            return new HashMap<>();
+        }
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java
new file mode 100644
index 0000000..ccba6bb
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.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.network.dao;
+
+import com.cloud.network.vo.PublicIpQuarantineVO;
+import com.cloud.utils.db.GenericDao;
+
+public interface PublicIpQuarantineDao extends GenericDao<PublicIpQuarantineVO, Long> {
+
+    PublicIpQuarantineVO findByPublicIpAddressId(long publicIpAddressId);
+
+    PublicIpQuarantineVO findByIpAddress(String publicIpAddress);
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java
new file mode 100644
index 0000000..a1b789b
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.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.network.dao;
+
+import com.cloud.network.vo.PublicIpQuarantineVO;
+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.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+@Component
+public class PublicIpQuarantineDaoImpl extends GenericDaoBase<PublicIpQuarantineVO, Long> implements PublicIpQuarantineDao {
+    private SearchBuilder<PublicIpQuarantineVO> publicIpAddressByIdSearch;
+
+    private SearchBuilder<IPAddressVO> ipAddressSearchBuilder;
+
+    @Inject
+    IPAddressDao ipAddressDao;
+
+    @PostConstruct
+    public void init() {
+        publicIpAddressByIdSearch = createSearchBuilder();
+        publicIpAddressByIdSearch.and("publicIpAddressId", publicIpAddressByIdSearch.entity().getPublicIpAddressId(), SearchCriteria.Op.EQ);
+
+        ipAddressSearchBuilder = ipAddressDao.createSearchBuilder();
+        ipAddressSearchBuilder.and("ipAddress", ipAddressSearchBuilder.entity().getAddress(), SearchCriteria.Op.EQ);
+        ipAddressSearchBuilder.and("removed", ipAddressSearchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL);
+        publicIpAddressByIdSearch.join("quarantineJoin", ipAddressSearchBuilder, ipAddressSearchBuilder.entity().getId(),
+                publicIpAddressByIdSearch.entity().getPublicIpAddressId(), JoinBuilder.JoinType.INNER);
+
+        ipAddressSearchBuilder.done();
+        publicIpAddressByIdSearch.done();
+    }
+
+    @Override
+    public PublicIpQuarantineVO findByPublicIpAddressId(long publicIpAddressId) {
+        SearchCriteria<PublicIpQuarantineVO> sc = publicIpAddressByIdSearch.create();
+        sc.setParameters("publicIpAddressId", publicIpAddressId);
+        final Filter filter = new Filter(PublicIpQuarantineVO.class, "created", false);
+
+        return findOneBy(sc, filter);
+    }
+
+    @Override
+    public PublicIpQuarantineVO findByIpAddress(String publicIpAddress) {
+        SearchCriteria<PublicIpQuarantineVO> sc = publicIpAddressByIdSearch.create();
+        sc.setJoinParameters("quarantineJoin", "ipAddress", publicIpAddress);
+        final Filter filter = new Filter(PublicIpQuarantineVO.class, "created", false);
+
+        return findOneBy(sc, filter);
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java b/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java
new file mode 100644
index 0000000..89e0261
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.network.vo;
+
+import com.cloud.network.PublicIpQuarantine;
+import com.cloud.utils.db.GenericDao;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "quarantined_ips")
+public class PublicIpQuarantineVO implements PublicIpQuarantine {
+
+    @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 = "public_ip_address_id", nullable = false)
+    private Long publicIpAddressId;
+
+    @Column(name = "previous_owner_id", nullable = false)
+    private Long previousOwnerId;
+
+    @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;
+
+    @Column(name = "end_date", nullable = false)
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date endDate;
+
+    @Column(name = "removal_reason")
+    private String removalReason = null;
+
+    @Column(name = "remover_account_id")
+    private Long removerAccountId = null;
+
+    public PublicIpQuarantineVO() {
+    }
+
+    public PublicIpQuarantineVO(Long publicIpAddressId, Long previousOwnerId, Date created, Date endDate) {
+        this.publicIpAddressId = publicIpAddressId;
+        this.previousOwnerId = previousOwnerId;
+        this.created = created;
+        this.endDate = endDate;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public Long getPublicIpAddressId() {
+        return publicIpAddressId;
+    }
+
+    @Override
+    public Long getPreviousOwnerId() {
+        return previousOwnerId;
+    }
+
+    @Override
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    @Override
+    public String getRemovalReason() {
+        return removalReason;
+    }
+
+    @Override
+    public Long getRemoverAccountId() {
+        return this.removerAccountId;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    public void setRemovalReason(String removalReason) {
+        this.removalReason = removalReason;
+    }
+
+    public void setRemoverAccountId(Long removerAccountId) {
+        this.removerAccountId = removerAccountId;
+    }
+
+    @Override
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(Date removed) {
+        this.removed = removed;
+    }
+
+    @Override
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java
index 4eaa2b5..280d5df 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java
@@ -17,6 +17,8 @@
 
 package com.cloud.network.vpc;
 
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
 import java.util.UUID;
 
 import javax.persistence.Column;
@@ -85,6 +87,11 @@
         return name;
     }
 
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "vpcId");
+    }
+
     public void setUuid(String uuid) {
         this.uuid = uuid;
     }
diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java
index a35e791..823ea36 100644
--- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java
@@ -228,7 +228,7 @@
     }
 
     /**
-     * Persist L2 deafult Network offering
+     * Persist L2 default Network offering
      */
     private void persistL2DefaultNetworkOffering(String name, String displayText, boolean specifyVlan, boolean configDriveEnabled) {
         NetworkOfferingVO offering = new NetworkOfferingVO(name, displayText, TrafficType.Guest, false, specifyVlan,
diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java
index 560f8dc..5deb858 100644
--- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java
@@ -33,7 +33,6 @@
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.db.SearchCriteria.Func;
-import com.cloud.utils.db.TransactionLegacy;
 
 @Component
 public class ProjectDaoImpl extends GenericDaoBase<ProjectVO, Long> implements ProjectDao {
@@ -71,22 +70,8 @@
     @Override
     @DB
     public boolean remove(Long projectId) {
-        boolean result = false;
-        TransactionLegacy txn = TransactionLegacy.currentTxn();
-        txn.start();
-        ProjectVO projectToRemove = findById(projectId);
-        projectToRemove.setName(null);
-        if (!update(projectId, projectToRemove)) {
-            s_logger.warn("Failed to reset name for the project id=" + projectId + " as a part of project remove");
-            return false;
-        }
-
         _tagsDao.removeByIdAndType(projectId, ResourceObjectType.Project);
-        result = super.remove(projectId);
-        txn.commit();
-
-        return result;
-
+        return super.remove(projectId);
     }
 
     @Override
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 31e4b07..7f5c1a7 100644
--- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java
+++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java
@@ -194,7 +194,7 @@
         limitCpuUse = offering.getLimitCpuUse();
         volatileVm = offering.isVolatileVm();
         hostTag = offering.getHostTag();
-        vmType = offering.getSystemVmType();
+        vmType = offering.getVmType();
         systemUse = offering.isSystemUse();
         dynamicScalingEnabled = offering.isDynamicScalingEnabled();
         diskOfferingStrictness = offering.diskOfferingStrictness;
@@ -278,7 +278,7 @@
     }
 
     @Override
-    public String getSystemVmType() {
+    public String getVmType() {
         return vmType;
     }
 
diff --git a/engine/schema/src/main/java/com/cloud/storage/BucketVO.java b/engine/schema/src/main/java/com/cloud/storage/BucketVO.java
new file mode 100644
index 0000000..181b02e
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/BucketVO.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 com.cloud.storage;
+
+import com.cloud.utils.db.GenericDao;
+import com.google.gson.annotations.Expose;
+import org.apache.cloudstack.storage.object.Bucket;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+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.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "bucket")
+public class BucketVO implements Bucket {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "account_id")
+    long accountId;
+
+    @Column(name = "domain_id")
+    long domainId;
+
+    @Column(name = "object_store_id")
+    long objectStoreId;
+
+    @Expose
+    @Column(name = "name")
+    String name;
+
+    @Expose
+    @Column(name = "state", updatable = true, nullable = false)
+    @Enumerated(value = EnumType.STRING)
+    private State state;
+
+    @Column(name = "size")
+    Long size;
+
+    @Column(name = "quota")
+    Integer quota;
+
+    @Column(name = "versioning")
+    boolean versioning;
+
+    @Column(name = "encryption")
+    boolean encryption;
+
+    @Column(name = "object_lock")
+    boolean objectLock;
+
+    @Column(name = "policy")
+    String policy;
+
+    @Column(name = "bucket_url")
+    String bucketURL;
+
+    @Column(name = "access_key")
+    String accessKey;
+
+    @Column(name = "secret_key")
+    String secretKey;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    Date created;
+
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    Date removed;
+
+    @Column(name = "uuid")
+    String uuid;
+
+    public BucketVO() {
+    }
+
+    public BucketVO(long accountId, long domainId, long objectStoreId, String name, Integer quota, boolean versioning,
+                    boolean encryption, boolean objectLock, String policy)
+    {
+        this.accountId = accountId;
+        this.domainId = domainId;
+        this.objectStoreId = objectStoreId;
+        this.name = name;
+        state = State.Allocated;
+        uuid = UUID.randomUUID().toString();
+        this.quota = quota;
+        this.versioning = versioning;
+        this.encryption = encryption;
+        this.objectLock = objectLock;
+        this.policy = policy;
+        this.size = 0L;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public long getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public long getDomainId() {
+        return domainId;
+    }
+
+    @Override
+    public long getObjectStoreId() {
+        return objectStoreId;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public Long getSize() {
+        return size;
+    }
+
+    @Override
+    public Date getCreated() {
+        return created;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    @Override
+    public State getState() {
+        return state;
+    }
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setState(State state) {
+        this.state = state;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public Integer getQuota() {
+        return quota;
+    }
+
+    public void setQuota(Integer quota) {
+        this.quota = quota;
+    }
+
+    public boolean isVersioning() {
+        return versioning;
+    }
+
+    public void setVersioning(boolean versioning) {
+        this.versioning = versioning;
+    }
+
+    public boolean isEncryption() {
+        return encryption;
+    }
+
+    public void setEncryption(boolean encryption) {
+        this.encryption = encryption;
+    }
+
+    public boolean isObjectLock() {
+        return objectLock;
+    }
+
+    public void setObjectLock(boolean objectLock) {
+        this.objectLock = objectLock;
+    }
+
+    public String getPolicy() {
+        return policy;
+    }
+
+    public void setPolicy(String policy) {
+        this.policy = policy;
+    }
+
+    public String getBucketURL() {
+        return bucketURL;
+    }
+    public void setBucketURL(String bucketURL) {
+        this.bucketURL = bucketURL;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    public void setSize(Long size) {
+        this.size = size;
+    }
+
+    @Override
+    public Class<?> getEntityType() {
+        return Bucket.class;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Bucket %s", new ToStringBuilder(this, ToStringStyle.JSON_STYLE).append("uuid", getUuid()).append("name", getName())
+                .append("ObjectStoreId", getObjectStoreId()).toString());
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java b/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java
index 087649b..e900d28 100644
--- a/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java
+++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java
@@ -26,6 +26,7 @@
 import javax.persistence.Id;
 import javax.persistence.Table;
 
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.utils.db.GenericDao;
 
 @Entity
@@ -72,7 +73,7 @@
 
     @Override
     public String getHypervisorType() {
-        return hypervisorType;
+        return Hypervisor.HypervisorType.getType(hypervisorType).toString();
     }
 
     @Override
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 ebfad66..e9d6df8 100644
--- a/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java
+++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java
@@ -16,9 +16,8 @@
 // under the License.
 package com.cloud.storage;
 
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.utils.db.GenericDao;
-import com.google.gson.annotations.Expose;
+import java.util.Date;
+import java.util.UUID;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -32,8 +31,9 @@
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
-import java.util.Date;
-import java.util.UUID;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.utils.db.GenericDao;
+import com.google.gson.annotations.Expose;
 
 @Entity
 @Table(name = "snapshots")
diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java
new file mode 100644
index 0000000..82860de
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java
@@ -0,0 +1,118 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES 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 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;
+
+import com.cloud.utils.db.GenericDao;
+import com.cloud.utils.db.GenericDaoBase;
+
+@Entity
+@Table(name = "snapshot_zone_ref")
+public class SnapshotZoneVO implements InternalIdentity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    Long id;
+
+    @Column(name = "zone_id")
+    private long zoneId;
+
+    @Column(name = "snapshot_id")
+    private long snapshotId;
+
+    @Column(name = GenericDaoBase.CREATED_COLUMN)
+    private Date created = null;
+
+    @Column(name = "last_updated")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date lastUpdated = null;
+
+    @Temporal(value = TemporalType.TIMESTAMP)
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    private Date removed;
+
+    protected SnapshotZoneVO() {
+
+    }
+
+    public SnapshotZoneVO(long zoneId, long snapshotId, Date lastUpdated) {
+        this.zoneId = zoneId;
+        this.snapshotId = snapshotId;
+        this.lastUpdated = lastUpdated;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public long getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(long zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public long getSnapshotId() {
+        return snapshotId;
+    }
+
+    public void setSnapshotId(long snapshotId) {
+        this.snapshotId = snapshotId;
+    }
+
+    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 void setRemoved(Date removed) {
+        this.removed = removed;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java
index 18c0dc3..2675c36 100755
--- a/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java
+++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java
@@ -23,7 +23,9 @@
 import javax.persistence.Id;
 import javax.persistence.Table;
 
+import com.cloud.utils.NumbersUtil;
 import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.commons.lang3.BooleanUtils;
 
 @Entity
 @Table(name = "storage_pool_tags")
@@ -43,9 +45,19 @@
     @Column(name = "tag")
     private String tag;
 
+    @Column(name = "is_tag_a_rule")
+    private boolean isTagARule;
+
     public StoragePoolTagVO(long poolId, String tag) {
         this.poolId = poolId;
         this.tag = tag;
+        this.isTagARule = false;
+    }
+
+    public StoragePoolTagVO(long poolId, String tag, Boolean isTagARule) {
+        this.poolId = poolId;
+        this.tag = tag;
+        this.isTagARule = BooleanUtils.toBooleanDefaultIfNull(isTagARule, false);
     }
 
     @Override
@@ -61,4 +73,20 @@
         return tag;
     }
 
+    public boolean isTagARule() {
+        return this.isTagARule;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof StoragePoolTagVO) {
+            return this.poolId == ((StoragePoolTagVO)obj).getPoolId();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return NumbersUtil.hash(id);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java
new file mode 100644
index 0000000..24d8191
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java
@@ -0,0 +1,101 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.storage;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+
+import org.apache.cloudstack.api.ResourceDetail;
+
+@Entity
+@Table(name = "vnf_template_details")
+public class VnfTemplateDetailVO implements ResourceDetail {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "template_id")
+    private long resourceId;
+
+    @Column(name = "name")
+    private String name;
+
+    @Lob
+    @Column(name = "value", length = 65535)
+    private String value;
+
+    @Column(name = "display")
+    private boolean display = true;
+
+    public VnfTemplateDetailVO() {
+    }
+
+    public VnfTemplateDetailVO(long templateId, String name, String value, boolean display) {
+        this.resourceId = templateId;
+        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/com/cloud/storage/VnfTemplateNicVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java
new file mode 100644
index 0000000..1f5054c
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java
@@ -0,0 +1,101 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.storage;
+
+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;
+
+@Entity
+@Table(name = "vnf_template_nics")
+public class VnfTemplateNicVO implements InternalIdentity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "template_id")
+    private long templateId;
+
+    @Column(name = "device_id")
+    private long deviceId;
+
+    @Column(name = "device_name")
+    private String deviceName;
+
+    @Column(name = "required")
+    private boolean required = true;
+
+    @Column(name = "management")
+    private boolean management = true;
+
+    @Column(name = "description")
+    private String description;
+
+    public VnfTemplateNicVO() {
+    }
+
+    public VnfTemplateNicVO(long templateId, long deviceId, String deviceName, boolean required, boolean management, String description) {
+        this.templateId = templateId;
+        this.deviceId = deviceId;
+        this.deviceName = deviceName;
+        this.required = required;
+        this.management = management;
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Template %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "templateId", "deviceId", "required"));
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public long getTemplateId() {
+        return templateId;
+    }
+
+    public long getDeviceId() {
+        return deviceId;
+    }
+
+    public String getDeviceName() {
+        return deviceName;
+    }
+
+    public boolean isRequired() {
+        return required;
+    }
+
+    public boolean isManagement() {
+        return management;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java
index 6723f0b..42980e0 100644
--- a/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java
+++ b/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java
@@ -80,4 +80,7 @@
         return display;
     }
 
+    public void setValue(String value) {
+        this.value = value;
+    }
 }
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
new file mode 100644
index 0000000..f45f28b
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDao.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.storage.dao;
+
+import com.cloud.storage.BucketVO;
+import com.cloud.utils.db.GenericDao;
+
+import java.util.List;
+
+public interface BucketDao extends GenericDao<BucketVO, Long> {
+    List<BucketVO> listByObjectStoreId(long objectStoreId);
+
+    List<BucketVO> listByObjectStoreIdAndAccountId(long objectStoreId, long accountId);
+
+    List<BucketVO> searchByIds(Long[] ids);
+}
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
new file mode 100644
index 0000000..83b5f6b
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.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 com.cloud.storage.dao;
+
+import com.cloud.storage.BucketVO;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class BucketDaoImpl extends GenericDaoBase<BucketVO, Long> implements BucketDao {
+    public static final Logger s_logger = Logger.getLogger(BucketDaoImpl.class.getName());
+    private SearchBuilder<BucketVO> searchFilteringStoreId;
+
+    private SearchBuilder<BucketVO> bucketSearch;
+
+    private static final String STORE_ID = "store_id";
+    private static final String STATE = "state";
+    private static final String ACCOUNT_ID = "account_id";
+
+    protected BucketDaoImpl() {
+
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        super.configure(name, params);
+
+        searchFilteringStoreId = createSearchBuilder();
+        searchFilteringStoreId.and(STORE_ID, searchFilteringStoreId.entity().getObjectStoreId(), SearchCriteria.Op.EQ);
+        searchFilteringStoreId.and(ACCOUNT_ID, searchFilteringStoreId.entity().getAccountId(), SearchCriteria.Op.EQ);
+        searchFilteringStoreId.and(STATE, searchFilteringStoreId.entity().getState(), SearchCriteria.Op.NEQ);
+        searchFilteringStoreId.done();
+
+        bucketSearch = createSearchBuilder();
+        bucketSearch.and("idIN", bucketSearch.entity().getId(), SearchCriteria.Op.IN);
+        bucketSearch.done();
+
+        return true;
+    }
+    @Override
+    public List<BucketVO> listByObjectStoreId(long objectStoreId) {
+        SearchCriteria<BucketVO> sc = searchFilteringStoreId.create();
+        sc.setParameters(STORE_ID, objectStoreId);
+        sc.setParameters(STATE, BucketVO.State.Destroyed);
+        return listBy(sc);
+    }
+
+    @Override
+    public List<BucketVO> listByObjectStoreIdAndAccountId(long objectStoreId, long accountId) {
+        SearchCriteria<BucketVO> sc = searchFilteringStoreId.create();
+        sc.setParameters(STORE_ID, objectStoreId);
+        sc.setParameters(ACCOUNT_ID, accountId);
+        sc.setParameters(STATE, BucketVO.State.Destroyed);
+        return listBy(sc);
+    }
+
+    @Override
+    public List<BucketVO> searchByIds(Long[] ids) {
+        SearchCriteria<BucketVO> sc = bucketSearch.create();
+        sc.setParameters("idIN", ids);
+        return search(sc, null, null, false);
+    }
+}
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 eeae0bd..13cd398 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
@@ -28,6 +28,7 @@
 
     GuestOSVO findOneByDisplayName(String displayName);
 
+    List<GuestOSVO> listLikeDisplayName(String displayName);
     GuestOSVO findByCategoryIdAndDisplayNameOrderByCreatedDesc(long categoryId, String displayName);
 
     Set<String> findDoubleNames();
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 19c4e90..efcaa48 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
@@ -44,12 +44,19 @@
 
     protected final SearchBuilder<GuestOSVO> Search;
 
+    protected final SearchBuilder<GuestOSVO> displayNameSearch;
+
     public GuestOSDaoImpl() {
         Search = createSearchBuilder();
         Search.and("category_id", Search.entity().getCategoryId(), SearchCriteria.Op.EQ);
         Search.and("display_name", Search.entity().getDisplayName(), SearchCriteria.Op.EQ);
         Search.and("is_user_defined", Search.entity().getIsUserDefined(), SearchCriteria.Op.EQ);
         Search.done();
+
+        displayNameSearch = createSearchBuilder();
+        displayNameSearch.and("display_name", displayNameSearch.entity().getDisplayName(), SearchCriteria.Op.LIKE);
+        displayNameSearch.done();
+
     }
 
     @Override
@@ -60,6 +67,13 @@
     }
 
     @Override
+    public List<GuestOSVO> listLikeDisplayName(String displayName) {
+        SearchCriteria<GuestOSVO> sc = displayNameSearch.create();
+        sc.setParameters("display_name", "%" + displayName + "%");
+        return listBy(sc);
+    }
+
+    @Override
     public GuestOSVO findByCategoryIdAndDisplayNameOrderByCreatedDesc(long categoryId, String displayName) {
         SearchCriteria<GuestOSVO> sc = Search.create();
         sc.setParameters("category_id", categoryId);
@@ -67,9 +81,9 @@
         sc.setParameters("is_user_defined", false);
 
         Filter orderByFilter = new Filter(GuestOSVO.class, "created", false, null, 1L);
-        List<GuestOSVO> guestOSes = listBy(sc, orderByFilter);
-        if (CollectionUtils.isNotEmpty(guestOSes)) {
-            return guestOSes.get(0);
+        List<GuestOSVO> guestOSlist = listBy(sc, orderByFilter);
+        if (CollectionUtils.isNotEmpty(guestOSlist)) {
+            return guestOSlist.get(0);
         }
         return null;
     }
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotZoneDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotZoneDao.java
new file mode 100644
index 0000000..186047c
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotZoneDao.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.SnapshotZoneVO;
+import com.cloud.utils.db.GenericDao;
+
+public interface SnapshotZoneDao extends GenericDao<SnapshotZoneVO, Long> {
+    SnapshotZoneVO findByZoneSnapshot(long zoneId, long templateId);
+    void addSnapshotToZone(long snapshotId, long zoneId);
+    void removeSnapshotFromZone(long snapshotId, long zoneId);
+    void removeSnapshotFromZones(long snapshotId);
+    List<SnapshotZoneVO> listBySnapshot(long snapshotId);
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotZoneDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotZoneDaoImpl.java
new file mode 100644
index 0000000..1ed8a547
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotZoneDaoImpl.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 com.cloud.storage.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import com.cloud.storage.SnapshotZoneVO;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+public class SnapshotZoneDaoImpl extends GenericDaoBase<SnapshotZoneVO, Long> implements SnapshotZoneDao {
+    public static final Logger s_logger = Logger.getLogger(SnapshotZoneDaoImpl.class.getName());
+    protected final SearchBuilder<SnapshotZoneVO> ZoneSnapshotSearch;
+
+    public SnapshotZoneDaoImpl() {
+
+        ZoneSnapshotSearch = createSearchBuilder();
+        ZoneSnapshotSearch.and("zone_id", ZoneSnapshotSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
+        ZoneSnapshotSearch.and("snapshot_id", ZoneSnapshotSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
+        ZoneSnapshotSearch.done();
+    }
+
+    @Override
+    public SnapshotZoneVO findByZoneSnapshot(long zoneId, long snapshotId) {
+        SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
+        sc.setParameters("zone_id", zoneId);
+        sc.setParameters("snapshot_id", snapshotId);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public void addSnapshotToZone(long snapshotId, long zoneId) {
+        SnapshotZoneVO snapshotZone = findByZoneSnapshot(zoneId, snapshotId);
+        if (snapshotZone == null) {
+            snapshotZone = new SnapshotZoneVO(zoneId, snapshotId, new Date());
+            persist(snapshotZone);
+        } else {
+            snapshotZone.setRemoved(GenericDaoBase.DATE_TO_NULL);
+            snapshotZone.setLastUpdated(new Date());
+            update(snapshotZone.getId(), snapshotZone);
+        }
+    }
+
+    @Override
+    public void removeSnapshotFromZone(long snapshotId, long zoneId) {
+        SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
+        sc.setParameters("zone_id", zoneId);
+        sc.setParameters("snapshot_id", snapshotId);
+        remove(sc);
+    }
+
+    @Override
+    public void removeSnapshotFromZones(long snapshotId) {
+        SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
+        sc.setParameters("snapshot_id", snapshotId);
+        remove(sc);
+    }
+
+    @Override
+    public List<SnapshotZoneVO> listBySnapshot(long snapshotId) {
+        SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
+        sc.setParameters("snapshot_id", snapshotId);
+        return listBy(sc);
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDao.java
index 946b46b..9352ee2 100755
--- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDao.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDao.java
@@ -25,10 +25,14 @@
 
 public interface StoragePoolTagsDao extends GenericDao<StoragePoolTagVO, Long> {
 
-    void persist(long poolId, List<String> storagePoolTags);
+    void persist(long poolId, List<String> storagePoolTags, Boolean isTagARule);
+
+    void persist(List<StoragePoolTagVO> storagePoolTags);
     List<String> getStoragePoolTags(long poolId);
     void deleteTags(long poolId);
     List<StoragePoolTagVO> searchByIds(Long... stIds);
     StorageTagResponse newStorageTagResponse(StoragePoolTagVO tag);
 
+    List<StoragePoolTagVO> findStoragePoolTags(long poolId);
+
 }
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java
index f20e0c4..c01c667 100755
--- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java
@@ -21,6 +21,9 @@
 
 import javax.inject.Inject;
 
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallbackNoReturn;
+import com.cloud.utils.db.TransactionStatus;
 import org.apache.cloudstack.api.response.StorageTagResponse;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 
@@ -50,7 +53,7 @@
     }
 
     @Override
-    public void persist(long poolId, List<String> storagePoolTags) {
+    public void persist(long poolId, List<String> storagePoolTags, Boolean isTagARule) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
 
         txn.start();
@@ -61,13 +64,23 @@
         for (String tag : storagePoolTags) {
             tag = tag.trim();
             if (tag.length() > 0) {
-                StoragePoolTagVO vo = new StoragePoolTagVO(poolId, tag);
+                StoragePoolTagVO vo = new StoragePoolTagVO(poolId, tag, isTagARule);
                 persist(vo);
             }
         }
         txn.commit();
     }
 
+    public void persist(List<StoragePoolTagVO> storagePoolTags) {
+        Transaction.execute(TransactionLegacy.CLOUD_DB, new TransactionCallbackNoReturn() {
+            @Override public void doInTransactionWithoutResult(TransactionStatus status) {
+                for (StoragePoolTagVO storagePoolTagVO : storagePoolTags) {
+                    persist(storagePoolTagVO);
+                }
+            }
+        });
+    }
+
     @Override
     public List<String> getStoragePoolTags(long poolId) {
         SearchCriteria<StoragePoolTagVO> sc = StoragePoolSearch.create();
@@ -157,4 +170,12 @@
         return tagResponse;
     }
 
+    @Override
+    public List<StoragePoolTagVO> findStoragePoolTags(long poolId) {
+        SearchCriteria<StoragePoolTagVO> sc = StoragePoolSearch.create();
+        sc.setParameters("poolId", poolId);
+
+        return search(sc, null);
+    }
+
 }
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 b1d7f21..708a77a 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
@@ -88,4 +88,6 @@
     VMTemplateVO findLatestTemplateByName(String name);
 
     List<VMTemplateVO> findTemplatesLinkedToUserdata(long userdataId);
+
+    List<VMTemplateVO> listByIds(List<Long> ids);
 }
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 08b98f9..031bcb3 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.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -26,6 +27,7 @@
 
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -99,6 +101,7 @@
     private SearchBuilder<VMTemplateVO> InactiveUnremovedTmpltSearch;
     private SearchBuilder<VMTemplateVO> LatestTemplateByHypervisorTypeSearch;
     private SearchBuilder<VMTemplateVO> userDataSearch;
+    private SearchBuilder<VMTemplateVO> templateIdSearch;
     @Inject
     ResourceTagDao _tagsDao;
 
@@ -427,6 +430,11 @@
         userDataSearch.and("state", userDataSearch.entity().getState(), SearchCriteria.Op.EQ);
         userDataSearch.done();
 
+
+        templateIdSearch = createSearchBuilder();
+        templateIdSearch.and("idIN", templateIdSearch.entity().getId(), SearchCriteria.Op.IN);
+        templateIdSearch.done();
+
         return result;
     }
 
@@ -649,6 +657,16 @@
     }
 
     @Override
+    public List<VMTemplateVO> listByIds(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<VMTemplateVO> sc = templateIdSearch.create();
+        sc.setParameters("idIN", ids.toArray());
+        return listBy(sc, null);
+    }
+
+    @Override
     @DB
     public boolean remove(Long id) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java
index d00eece..a3ce03a 100644
--- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java
@@ -52,4 +52,8 @@
     VMTemplateStoragePoolVO findByPoolPath(Long poolId, String path);
 
     List<VMTemplateStoragePoolVO> listByTemplatePath(String templatePath);
+
+    List<VMTemplateStoragePoolVO> listByPoolIdAndInstallPath(Long poolId, List<String> pathList);
+
+    List<VMTemplateStoragePoolVO> listByTemplateId(long templateId, List<Long> poolIds);
 }
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java
index 479e02e..d938beb 100644
--- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java
@@ -19,6 +19,7 @@
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -28,6 +29,7 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -86,7 +88,7 @@
         TemplateSearch.done();
 
         PoolTemplateSearch = createSearchBuilder();
-        PoolTemplateSearch.and("pool_id", PoolTemplateSearch.entity().getPoolId(), SearchCriteria.Op.EQ);
+        PoolTemplateSearch.and("pool_id", PoolTemplateSearch.entity().getPoolId(), Op.IN);
         PoolTemplateSearch.and("template_id", PoolTemplateSearch.entity().getTemplateId(), SearchCriteria.Op.EQ);
         PoolTemplateSearch.and("configuration", PoolTemplateSearch.entity().getDeploymentOption(), SearchCriteria.Op.EQ);
         PoolTemplateSearch.done();
@@ -120,8 +122,8 @@
 
         templatePathSearch = createSearchBuilder();
         templatePathSearch.and("pool_id", templatePathSearch.entity().getPoolId(), Op.EQ);
-        templatePathSearch.and("local_path", templatePathSearch.entity().getLocalDownloadPath(), Op.EQ);
-        templatePathSearch.and("install_path", templatePathSearch.entity().getInstallPath(), Op.EQ);
+        templatePathSearch.and("local_path", templatePathSearch.entity().getLocalDownloadPath(), Op.IN);
+        templatePathSearch.and("install_path", templatePathSearch.entity().getInstallPath(), Op.IN);
         templatePathSearch.done();
     }
 
@@ -294,6 +296,28 @@
     }
 
     @Override
+    public List<VMTemplateStoragePoolVO> listByPoolIdAndInstallPath(Long poolId, List<String> pathList) {
+        if (CollectionUtils.isEmpty(pathList)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<VMTemplateStoragePoolVO> sc = templatePathSearch.create();
+        sc.setParameters("pool_id", poolId);
+        sc.setParameters("install_path", pathList.toArray());
+        return listBy(sc);
+    }
+
+    @Override
+    public List<VMTemplateStoragePoolVO> listByTemplateId(long templateId, List<Long> poolIds) {
+        if (CollectionUtils.isEmpty(poolIds)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<VMTemplateStoragePoolVO> sc = PoolTemplateSearch.create();
+        sc.setParameters("template_id", templateId);
+        sc.setParameters("pool_id", poolIds.toArray());
+        return listBy(sc);
+    }
+
+    @Override
     public boolean updateState(State currentState, Event event, State nextState, DataObjectInStore vo, Object data) {
         VMTemplateStoragePoolVO templatePool = (VMTemplateStoragePoolVO)vo;
         Long oldUpdated = templatePool.getUpdatedCount();
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDao.java
new file mode 100644
index 0000000..c492240
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDao.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.storage.dao;
+
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.utils.db.GenericDao;
+
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
+
+public interface VnfTemplateDetailsDao extends GenericDao<VnfTemplateDetailVO, Long>, ResourceDetailsDao<VnfTemplateDetailVO> {
+
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDaoImpl.java
new file mode 100644
index 0000000..a4cbfa0
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDaoImpl.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 com.cloud.storage.VnfTemplateDetailVO;
+
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
+import org.springframework.stereotype.Component;
+
+@Component
+public class VnfTemplateDetailsDaoImpl extends ResourceDetailsDaoBase<VnfTemplateDetailVO> implements VnfTemplateDetailsDao {
+
+    @Override
+    public void addDetail(long resourceId, String key, String value, boolean display) {
+        super.addDetail(new VnfTemplateDetailVO(resourceId, key, value, display));
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDao.java
new file mode 100644
index 0000000..b076f14
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDao.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 com.cloud.storage.dao;
+
+import java.util.List;
+
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.utils.db.GenericDao;
+
+public interface VnfTemplateNicDao extends GenericDao<VnfTemplateNicVO, Long> {
+
+    List<VnfTemplateNicVO> listByTemplateId(long templateId);
+
+    void deleteByTemplateId(long templateId);
+}
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDaoImpl.java
new file mode 100644
index 0000000..990ef44
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDaoImpl.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.storage.dao;
+
+import java.util.List;
+
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.TransactionLegacy;
+
+public class VnfTemplateNicDaoImpl extends GenericDaoBase<VnfTemplateNicVO, Long> implements VnfTemplateNicDao {
+
+    protected SearchBuilder<VnfTemplateNicVO> TemplateSearch;
+
+    public VnfTemplateNicDaoImpl() {
+        TemplateSearch = createSearchBuilder();
+        TemplateSearch.and("templateId", TemplateSearch.entity().getTemplateId(), SearchCriteria.Op.EQ);
+        TemplateSearch.done();
+    }
+
+    @Override
+    public List<VnfTemplateNicVO> listByTemplateId(long templateId) {
+        SearchCriteria<VnfTemplateNicVO> sc = TemplateSearch.create();
+        sc.setParameters("templateId", templateId);
+        return listBy(sc);
+    }
+
+    @Override
+    public void deleteByTemplateId(long templateId) {
+        SearchCriteria<VnfTemplateNicVO> sc = TemplateSearch.create();
+        sc.setParameters("templateId", templateId);
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        txn.start();
+        remove(sc);
+        txn.commit();
+    }
+}
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 3cdaa3b..be6588e 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
@@ -149,4 +149,10 @@
     VolumeVO getInstanceRootVolume(long instanceId);
 
     void updateAndRemoveVolume(VolumeVO volume);
+
+    List<VolumeVO> listByPoolIdAndPaths(long id, List<String> pathList);
+
+    VolumeVO findByPoolIdAndPath(long id, String path);
+
+    List<VolumeVO> listByIds(List<Long> ids);
 }
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 2b5e34c..a773a95 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
@@ -20,11 +20,13 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
 import javax.inject.Inject;
 
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -63,10 +65,13 @@
     protected final SearchBuilder<VolumeVO> AllFieldsSearch;
     protected final SearchBuilder<VolumeVO> diskOfferingSearch;
     protected final SearchBuilder<VolumeVO> RootDiskStateSearch;
+    private final SearchBuilder<VolumeVO> storeAndInstallPathSearch;
+    private final SearchBuilder<VolumeVO> volumeIdSearch;
     protected GenericSearchBuilder<VolumeVO, Long> CountByAccount;
     protected GenericSearchBuilder<VolumeVO, SumCount> primaryStorageSearch;
     protected GenericSearchBuilder<VolumeVO, SumCount> primaryStorageSearch2;
     protected GenericSearchBuilder<VolumeVO, SumCount> secondaryStorageSearch;
+    private final SearchBuilder<VolumeVO> poolAndPathSearch;
     @Inject
     ResourceTagDao _tagsDao;
 
@@ -78,8 +83,9 @@
     protected static final String SELECT_HYPERTYPE_FROM_ZONE_VOLUME = "SELECT s.hypervisor from volumes v, storage_pool s where v.pool_id = s.id and v.id = ?";
     protected static final String SELECT_POOLSCOPE = "SELECT s.scope from storage_pool s, volumes v where s.id = v.pool_id and v.id = ?";
 
-    private static final String ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT = "SELECT pool.id, SUM(IF(vol.state='Ready' AND vol.account_id = ?, 1, 0)) FROM `cloud`.`storage_pool` pool LEFT JOIN `cloud`.`volumes` vol ON pool.id = vol.pool_id WHERE pool.data_center_id = ? "
-            + " AND pool.pod_id = ? AND pool.cluster_id = ? " + " GROUP BY pool.id ORDER BY 2 ASC ";
+    private static final String ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_PART1 = "SELECT pool.id, SUM(IF(vol.state='Ready' AND vol.account_id = ?, 1, 0)) FROM `cloud`.`storage_pool` pool LEFT JOIN `cloud`.`volumes` vol ON pool.id = vol.pool_id WHERE pool.data_center_id = ? ";
+    private static final String ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_PART2 = " GROUP BY pool.id ORDER BY 2 ASC ";
+
     private static final String ORDER_ZONE_WIDE_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT = "SELECT pool.id, SUM(IF(vol.state='Ready' AND vol.account_id = ?, 1, 0)) FROM `cloud`.`storage_pool` pool LEFT JOIN `cloud`.`volumes` vol ON pool.id = vol.pool_id WHERE pool.data_center_id = ? "
             + " AND pool.scope = 'ZONE' AND pool.status='Up' " + " GROUP BY pool.id ORDER BY 2 ASC ";
 
@@ -473,6 +479,21 @@
         secondaryStorageSearch.and("states", secondaryStorageSearch.entity().getState(), Op.NIN);
         secondaryStorageSearch.and("isRemoved", secondaryStorageSearch.entity().getRemoved(), Op.NULL);
         secondaryStorageSearch.done();
+
+        storeAndInstallPathSearch = createSearchBuilder();
+        storeAndInstallPathSearch.and("poolId", storeAndInstallPathSearch.entity().getPoolId(), Op.EQ);
+        storeAndInstallPathSearch.and("pathIN", storeAndInstallPathSearch.entity().getPath(), Op.IN);
+        storeAndInstallPathSearch.done();
+
+        volumeIdSearch = createSearchBuilder();
+        volumeIdSearch.and("idIN", volumeIdSearch.entity().getId(), Op.IN);
+        volumeIdSearch.done();
+
+        poolAndPathSearch = createSearchBuilder();
+        poolAndPathSearch.and("poolId", poolAndPathSearch.entity().getPoolId(), Op.EQ);
+        poolAndPathSearch.and("path", poolAndPathSearch.entity().getPath(), Op.EQ);
+        poolAndPathSearch.done();
+
     }
 
     @Override
@@ -592,14 +613,27 @@
     public List<Long> listPoolIdsByVolumeCount(long dcId, Long podId, Long clusterId, long accountId) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         PreparedStatement pstmt = null;
-        List<Long> result = new ArrayList<Long>();
+        List<Long> result = new ArrayList<>();
+        StringBuilder sql = new StringBuilder(ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_PART1);
         try {
-            String sql = ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT;
-            pstmt = txn.prepareAutoCloseStatement(sql);
-            pstmt.setLong(1, accountId);
-            pstmt.setLong(2, dcId);
-            pstmt.setLong(3, podId);
-            pstmt.setLong(4, clusterId);
+            List<Long> resourceIdList = new ArrayList<>();
+            resourceIdList.add(accountId);
+            resourceIdList.add(dcId);
+
+            if (podId != null) {
+                sql.append(" AND pool.pod_id = ?");
+                resourceIdList.add(podId);
+            }
+            if (clusterId != null) {
+                sql.append(" AND pool.cluster_id = ?");
+                resourceIdList.add(clusterId);
+            }
+            sql.append(ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_PART2);
+
+            pstmt = txn.prepareAutoCloseStatement(sql.toString());
+            for (int i = 0; i < resourceIdList.size(); i++) {
+                pstmt.setLong(i + 1, resourceIdList.get(i));
+            }
 
             ResultSet rs = pstmt.executeQuery();
             while (rs.next()) {
@@ -607,9 +641,11 @@
             }
             return result;
         } catch (SQLException e) {
-            throw new CloudRuntimeException("DB Exception on: " + ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT, e);
+            s_logger.debug("DB Exception on: " + sql.toString(), e);
+            throw new CloudRuntimeException(e);
         } catch (Throwable e) {
-            throw new CloudRuntimeException("Caught: " + ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT, e);
+            s_logger.debug("Caught: " + sql.toString(), e);
+            throw new CloudRuntimeException(e);
         }
     }
 
@@ -775,4 +811,34 @@
             remove(volume.getId());
         }
     }
+
+    @Override
+    public List<VolumeVO> listByPoolIdAndPaths(long id, List<String> pathList) {
+        if (CollectionUtils.isEmpty(pathList)) {
+            return Collections.emptyList();
+        }
+
+        SearchCriteria<VolumeVO> sc = storeAndInstallPathSearch.create();
+        sc.setParameters("poolId", id);
+        sc.setParameters("pathIN", pathList.toArray());
+        return listBy(sc);
+    }
+
+    @Override
+    public VolumeVO findByPoolIdAndPath(long id, String path) {
+        SearchCriteria<VolumeVO> sc = poolAndPathSearch.create();
+        sc.setParameters("poolId", id);
+        sc.setParameters("path", path);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public List<VolumeVO> listByIds(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<VolumeVO> sc = volumeIdSearch.create();
+        sc.setParameters("idIN", ids.toArray());
+        return listBy(sc, null);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java
index 7bf5cf6..154a8d1 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java
@@ -74,7 +74,7 @@
 
     private static void runQuery(String host, String port, String rootPassword, String query, boolean dryRun) {
         System.out.println("============> Running query: " + query);
-        try (Connection conn = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/", host, port), "root", rootPassword);
+        try (Connection conn = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/?" + TransactionLegacy.CONNECTION_PARAMS, host, port), "root", rootPassword);
              Statement stmt = conn.createStatement();){
              if (!dryRun)
                 stmt.executeUpdate(query);
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java
index bb75aac..1fc8b7e 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java
@@ -86,7 +86,7 @@
                     boolean noDuplicate = true;
                     StringBuffer helpInfo = new StringBuffer();
                     String note =
-                        "DATABASE INTEGRITY ERROR\nManagement server detected there are some hosts connect to the same loacal storage, please contact CloudStack support team for solution. Below are detialed info, please attach all of them to CloudStack support. Thank you\n";
+                        "DATABASE INTEGRITY ERROR\nManagement server detected there are some hosts connect to the same local storage, please contact CloudStack support team for solution. Below are detailed info, please attach all of them to CloudStack support. Thank you\n";
                     helpInfo.append(note);
                     while (rs.next()) {
                         try ( PreparedStatement sel_pstmt =
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 e5b2df7..614e605 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
@@ -22,15 +22,18 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.nio.file.Paths;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.List;
 
 import javax.inject.Inject;
 
+import com.cloud.utils.FileUtil;
 import org.apache.cloudstack.utils.CloudStackVersion;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
@@ -81,6 +84,8 @@
 import com.cloud.upgrade.dao.Upgrade41710to41720;
 import com.cloud.upgrade.dao.Upgrade41720to41800;
 import com.cloud.upgrade.dao.Upgrade41800to41810;
+import com.cloud.upgrade.dao.Upgrade41810to41900;
+import com.cloud.upgrade.dao.Upgrade41900to41910;
 import com.cloud.upgrade.dao.Upgrade420to421;
 import com.cloud.upgrade.dao.Upgrade421to430;
 import com.cloud.upgrade.dao.Upgrade430to440;
@@ -122,6 +127,7 @@
 public class DatabaseUpgradeChecker implements SystemIntegrityChecker {
     private static final Logger s_logger = Logger.getLogger(DatabaseUpgradeChecker.class);
     private final DatabaseVersionHierarchy hierarchy;
+    private static final String VIEWS_DIRECTORY = Paths.get("META-INF", "db", "views").toString();
 
     @Inject
     VersionDao _dao;
@@ -218,6 +224,8 @@
                 .next("4.17.1.0", new Upgrade41710to41720())
                 .next("4.17.2.0", new Upgrade41720to41800())
                 .next("4.18.0.0", new Upgrade41800to41810())
+                .next("4.18.1.0", new Upgrade41810to41900())
+                .next("4.19.0.0", new Upgrade41900to41910())
                 .build();
     }
 
@@ -361,9 +369,33 @@
                 txn.close();
             }
         }
+
+        executeViewScripts();
         updateSystemVmTemplates(upgrades);
     }
 
+    protected void executeViewScripts() {
+        s_logger.info(String.format("Executing VIEW scripts that are under resource directory [%s].", VIEWS_DIRECTORY));
+        List<String> filesPathUnderViewsDirectory = FileUtil.getFilesPathsUnderResourceDirectory(VIEWS_DIRECTORY);
+
+        try (TransactionLegacy txn = TransactionLegacy.open("execute-view-scripts")) {
+            Connection conn = txn.getConnection();
+
+            for (String filePath : filesPathUnderViewsDirectory) {
+                s_logger.debug(String.format("Executing VIEW script [%s].", filePath));
+
+                InputStream viewScript = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);
+                runScript(conn, viewScript);
+            }
+
+            s_logger.info(String.format("Finished execution of VIEW scripts that are under resource directory [%s].", VIEWS_DIRECTORY));
+        } catch (SQLException e) {
+            String message = String.format("Unable to execute VIEW scripts due to [%s].", e.getMessage());
+            s_logger.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
     @Override
     public void check() {
         GlobalLock lock = GlobalLock.getInternLock("DatabaseUpgrade");
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java b/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java
index 20f9d85..4aabaa3 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java
@@ -303,7 +303,7 @@
             return false;
         }
 
-        if (StringUtils.isBlank(srcVersion) || StringUtils.isBlank(destVersion)) {
+        if (StringUtils.isAnyBlank(srcVersion, destVersion)) {
             LOG.warn("Unable to copy, invalid hypervisor version details");
             return false;
         }
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/BasicTemplateDataStoreDaoImpl.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/BasicTemplateDataStoreDaoImpl.java
index 3ea63d0..431686f 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/BasicTemplateDataStoreDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/BasicTemplateDataStoreDaoImpl.java
@@ -230,6 +230,16 @@
     }
 
     @Override
+    public List<TemplateDataStoreVO> listByStoreIdAndInstallPaths(long storeId, List<String> installPaths) {
+       return null;
+    }
+
+    @Override
+    public List<TemplateDataStoreVO> listByStoreIdAndTemplateIds(long storeId, List<Long> templateIds) {
+        return null;
+    }
+
+    @Override
     public boolean updateState(ObjectInDataStoreStateMachine.State currentState, ObjectInDataStoreStateMachine.Event event, ObjectInDataStoreStateMachine.State nextState, DataObjectInStore vo, Object data) {
         return false;
     }
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 0b38acb..de161af 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
@@ -21,6 +21,7 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 public class DatabaseAccessObject {
@@ -85,8 +86,8 @@
         return columnExists;
     }
 
-    public String generateIndexName(String tableName, String columnName) {
-        return String.format("i_%s__%s", tableName, columnName);
+    public String generateIndexName(String tableName, String... columnName) {
+        return String.format("i_%s__%s", tableName, StringUtils.join(columnName, "__"));
     }
 
     public boolean indexExists(Connection conn, String tableName, String indexName) {
@@ -101,8 +102,8 @@
         return false;
     }
 
-    public void createIndex(Connection conn, String tableName, String columnName, String indexName) {
-        String stmt = String.format("CREATE INDEX %s on %s (%s)", indexName, tableName, columnName);
+    public void createIndex(Connection conn, String tableName, String indexName, String... columnNames) {
+        String stmt = String.format("CREATE INDEX %s ON %s (%s)", indexName, tableName, StringUtils.join(columnNames, ", "));
         s_logger.debug("Statement: " + stmt);
         try (PreparedStatement pstmt = conn.prepareStatement(stmt)) {
             pstmt.execute();
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 6b4e181..51e6ac7 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
@@ -23,11 +23,11 @@
 
     private static DatabaseAccessObject dao = new DatabaseAccessObject();
 
-    public static void addIndexIfNeeded(Connection conn, String tableName, String columnName) {
-        String indexName = dao.generateIndexName(tableName, columnName);
+    public static void addIndexIfNeeded(Connection conn, String tableName, String... columnNames) {
+        String indexName = dao.generateIndexName(tableName, columnNames);
 
         if (!dao.indexExists(conn, tableName, indexName)) {
-            dao.createIndex(conn, tableName, columnName, indexName);
+            dao.createIndex(conn, tableName, indexName, columnNames);
         }
     }
 
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade218to22.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade218to22.java
index 90070dc..bc58794 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade218to22.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade218to22.java
@@ -1168,7 +1168,7 @@
             pstmt.executeUpdate();
             s_logger.debug("Upgraded userStatistcis with device_type=DomainRouter");
 
-            // update device_id infrormation
+            // update device_id information
             try (
                     PreparedStatement selectUserStatistics = conn.prepareStatement("SELECT id, account_id, data_center_id FROM user_statistics");
                     ResultSet rs = selectUserStatistics.executeQuery();
@@ -1204,7 +1204,7 @@
                                     selectnonRemovedVms.setLong(2, dataCenterId);
                                     try (ResultSet nonRemovedVms = selectnonRemovedVms.executeQuery();) {
                                         if (nonRemovedVms.next()) {
-                                            s_logger.warn("Failed to find domR for for account id=" + accountId + " in zone id=" + dataCenterId +
+                                            s_logger.warn("Failed to find domR for account id=" + accountId + " in zone id=" + dataCenterId +
                                                     "; will try to locate domR based on user_vm info");
                                             //try to get domR information from the user_vm belonging to the account
                                             try (PreparedStatement selectNetworkType =
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade224to225.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade224to225.java
index 1e23377..48908f5 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade224to225.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade224to225.java
@@ -332,7 +332,7 @@
             pstmt.close();
         } catch (SQLException e) {
             s_logger.error("Unable to add missing foreign key; following statement was executed:" + pstmt);
-            throw new CloudRuntimeException("Unable to add missign keys due to exception", e);
+            throw new CloudRuntimeException("Unable to add missing keys due to exception", e);
         }
     }
 
@@ -348,7 +348,7 @@
             }
         } catch (SQLException e) {
             s_logger.error("Unable to add missing ovs tunnel account due to ", e);
-            throw new CloudRuntimeException("Unable to add missign ovs tunnel account due to ", e);
+            throw new CloudRuntimeException("Unable to add missing ovs tunnel account due to ", e);
         }
     }
 }
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade301to302.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade301to302.java
index cafd025..ba479b5 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade301to302.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade301to302.java
@@ -66,7 +66,7 @@
         keys.add("i_host__allocation_state");
         uniqueKeys.put("host", keys);
 
-        s_logger.debug("Droping i_host__allocation_state key in host table");
+        s_logger.debug("Dropping i_host__allocation_state key in host table");
         for (String tableName : uniqueKeys.keySet()) {
             DbUpgradeUtils.dropKeysIfExist(conn, tableName, uniqueKeys.get(tableName), false);
         }
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 915f22b..2e7eee1 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
@@ -1884,7 +1884,7 @@
             //Update all snapshots except KVM snapshots
             int rowCount = snapshotStoreInsert.executeUpdate();
             s_logger.debug("Inserted " + rowCount + " snapshots into snapshot_store_ref");
-            //backsnap_id for KVM snapshots is complate path. CONCAT is not required
+            //backsnap_id for KVM snapshots is complete path. CONCAT is not required
             try(PreparedStatement snapshotStoreInsert_2 =
                     conn.prepareStatement("INSERT INTO `cloud`.`snapshot_store_ref` (store_id,  snapshot_id, created, size, parent_snapshot_id, install_path, volume_id, update_count, ref_cnt, store_role, state) select sechost_id, id, created, size, prev_snap_id, backup_snap_id, volume_id, 0, 0, 'Image', 'Ready' from `cloud`.`snapshots` where status = 'BackedUp' and hypervisor_type = 'KVM' and sechost_id is not null and removed is null");) {
                 rowCount = snapshotStoreInsert_2.executeUpdate();
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
new file mode 100644
index 0000000..13e30c0
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java
@@ -0,0 +1,269 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.upgrade.dao;
+
+import com.cloud.upgrade.SystemVmTemplateRegistration;
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.log4j.Logger;
+import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
+
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Upgrade41810to41900 implements DbUpgrade, DbUpgradeSystemVmTemplate {
+    final static Logger LOG = Logger.getLogger(Upgrade41810to41900.class);
+    private SystemVmTemplateRegistration systemVmTemplateRegistration;
+
+    private static final String ACCOUNT_DETAILS = "account_details";
+
+    private static final String DOMAIN_DETAILS = "domain_details";
+
+    private final SimpleDateFormat[] formats = {
+            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"), new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"),
+            new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy")};
+
+    @Override
+    public String[] getUpgradableVersionRange() {
+        return new String[] {"4.18.1.0", "4.19.0.0"};
+    }
+
+    @Override
+    public String getUpgradedVersion() {
+        return "4.19.0.0";
+    }
+
+    @Override
+    public boolean supportsRollingUpgrade() {
+        return false;
+    }
+
+    @Override
+    public InputStream[] getPrepareScripts() {
+        final String scriptFile = "META-INF/db/schema-41810to41900.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) {
+        decryptConfigurationValuesFromAccountAndDomainScopesNotInSecureHiddenCategories(conn);
+        migrateBackupDates(conn);
+        addIndexes(conn);
+        addRemoverAccountIdForeignKeyToQuarantinedIps(conn);
+    }
+
+    @Override
+    public InputStream[] getCleanupScripts() {
+        final String scriptFile = "META-INF/db/schema-41810to41900-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) {
+        LOG.debug("Updating System Vm template IDs");
+        initSystemVmTemplateRegistration();
+        try {
+            systemVmTemplateRegistration.updateSystemVmTemplates(conn);
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Failed to find / register SystemVM template(s)");
+        }
+    }
+
+    protected void decryptConfigurationValuesFromAccountAndDomainScopesNotInSecureHiddenCategories(Connection conn) {
+        LOG.info("Decrypting global configuration values from the following tables: account_details and domain_details.");
+
+        Map<Long, String> accountsMap = getConfigsWithScope(conn, ACCOUNT_DETAILS);
+        updateConfigValuesWithScope(conn, accountsMap, ACCOUNT_DETAILS);
+        LOG.info("Successfully decrypted configurations from account_details table.");
+
+        Map<Long, String> domainsMap = getConfigsWithScope(conn, DOMAIN_DETAILS);
+        updateConfigValuesWithScope(conn, domainsMap, DOMAIN_DETAILS);
+        LOG.info("Successfully decrypted configurations from domain_details table.");
+    }
+
+    protected Map<Long, String> getConfigsWithScope(Connection conn, String table) {
+        Map<Long, String> configsToBeUpdated = new HashMap<>();
+        String selectDetails = String.format("SELECT details.id, details.value from cloud.%s details, cloud.configuration c " +
+                "WHERE details.name = c.name AND c.category NOT IN ('Hidden', 'Secure') AND details.value <> \"\" ORDER BY details.id;", table);
+
+        try (PreparedStatement pstmt = conn.prepareStatement(selectDetails)) {
+            try (ResultSet result = pstmt.executeQuery()) {
+                while (result.next()) {
+                    configsToBeUpdated.put(result.getLong("id"), result.getString("value"));
+                }
+            }
+            return configsToBeUpdated;
+        } catch (SQLException e) {
+            String message = String.format("Unable to retrieve data from table [%s] due to [%s].", table, e.getMessage());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
+    public void migrateBackupDates(Connection conn) {
+        LOG.info("Trying to convert backups' date column from varchar(255) to datetime type.");
+
+        modifyDateColumnNameAndCreateNewOne(conn);
+        fetchDatesAndMigrateToNewColumn(conn);
+        dropOldColumn(conn);
+
+        LOG.info("Finished converting backups' date column from varchar(255) to datetime.");
+    }
+
+    private void modifyDateColumnNameAndCreateNewOne(Connection conn) {
+        String alterColumnName = "ALTER TABLE `cloud`.`backups` CHANGE COLUMN `date` `old_date` varchar(255);";
+        try (PreparedStatement pstmt = conn.prepareStatement(alterColumnName)) {
+            pstmt.execute();
+        } catch (SQLException e) {
+            String message = String.format("Unable to alter backups' date column name due to [%s].", e.getMessage());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+
+        String createNewColumn = "ALTER TABLE `cloud`.`backups` ADD COLUMN `date` DATETIME;";
+        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());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
+    protected void updateConfigValuesWithScope(Connection conn, Map<Long, String> configsToBeUpdated, String table) {
+        String updateConfigValues = String.format("UPDATE cloud.%s SET value = ? WHERE id = ?;", table);
+
+        for (Map.Entry<Long, String> config : configsToBeUpdated.entrySet()) {
+            try (PreparedStatement pstmt = conn.prepareStatement(updateConfigValues)) {
+                String decryptedValue = DBEncryptionUtil.decrypt(config.getValue());
+
+                pstmt.setString(1, decryptedValue);
+                pstmt.setLong(2, config.getKey());
+
+                LOG.info(String.format("Updating config with ID [%s] to value [%s].", config.getKey(), decryptedValue));
+                pstmt.executeUpdate();
+            } catch (SQLException | EncryptionOperationNotPossibleException e) {
+                String message = String.format("Unable to update config value with ID [%s] on table [%s] due to [%s]. The config value may already be decrypted.",
+                        config.getKey(), table, e);
+                LOG.error(message);
+                throw new CloudRuntimeException(message, e);
+            }
+        }
+    }
+
+    private void fetchDatesAndMigrateToNewColumn(Connection conn) {
+        String selectBackupDates = "SELECT `id`, `old_date` FROM `cloud`.`backups` WHERE 1;";
+        String date;
+        java.sql.Date reformatedDate;
+
+        try (PreparedStatement pstmt = conn.prepareStatement(selectBackupDates)) {
+            try (ResultSet result = pstmt.executeQuery()) {
+                while (result.next()) {
+                    date = result.getString("old_date");
+                    reformatedDate = tryToTransformStringToDate(date);
+                    updateBackupDate(conn, result.getLong("id"), reformatedDate);
+                }
+            }
+        } catch (SQLException e) {
+            String message = String.format("Unable to retrieve backup dates due to [%s].", e.getMessage());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
+    private java.sql.Date tryToTransformStringToDate(String date) {
+        Date parsedDate = null;
+        try {
+            parsedDate = DateUtil.parseTZDateString(date);
+        } catch (ParseException e) {
+            for (SimpleDateFormat sdf: formats) {
+                try {
+                    parsedDate = sdf.parse(date);
+                } catch (ParseException ex) {
+                    continue;
+                }
+                break;
+            }
+        }
+        if (parsedDate == null) {
+            String msg = String.format("Unable to parse date [%s]. Will change backup date to null.", date);
+            LOG.error(msg);
+            return null;
+        }
+
+        return new java.sql.Date(parsedDate.getTime());
+    }
+
+    private void updateBackupDate(Connection conn, long id, java.sql.Date date) {
+        String updateBackupDate = "UPDATE `cloud`.`backups` SET `date` = ? WHERE `id` = ?;";
+        try (PreparedStatement pstmt = conn.prepareStatement(updateBackupDate)) {
+            pstmt.setDate(1, date);
+            pstmt.setLong(2, id);
+
+            pstmt.executeUpdate();
+        } catch (SQLException e) {
+            String message = String.format("Unable to update backup date with id [%s] to date [%s] due to [%s].", id, date, e.getMessage());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
+    private void dropOldColumn(Connection conn) {
+        String dropOldColumn = "ALTER TABLE `cloud`.`backups` DROP COLUMN `old_date`;";
+        try (PreparedStatement pstmt = conn.prepareStatement(dropOldColumn)) {
+            pstmt.execute();
+        } catch (SQLException e) {
+            String message = String.format("Unable to drop old_date column due to [%s].", e.getMessage());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
+    private void addIndexes(Connection conn) {
+        DbUpgradeUtils.addIndexIfNeeded(conn, "alert", "archived", "created");
+        DbUpgradeUtils.addIndexIfNeeded(conn, "alert", "type", "data_center_id", "pod_id");
+
+        DbUpgradeUtils.addIndexIfNeeded(conn, "event", "resource_type", "resource_id");
+    }
+
+    private void addRemoverAccountIdForeignKeyToQuarantinedIps(Connection conn) {
+        DbUpgradeUtils.addForeignKey(conn, "quarantined_ips", "remover_account_id", "account", "id");
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41900to41910.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41900to41910.java
new file mode 100644
index 0000000..5c57fb3
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41900to41910.java
@@ -0,0 +1,90 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.upgrade.dao;
+
+import com.cloud.upgrade.SystemVmTemplateRegistration;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.log4j.Logger;
+
+import java.io.InputStream;
+import java.sql.Connection;
+
+public class Upgrade41900to41910 implements DbUpgrade, DbUpgradeSystemVmTemplate {
+    final static Logger LOG = Logger.getLogger(Upgrade41900to41910.class);
+    private SystemVmTemplateRegistration systemVmTemplateRegistration;
+
+    @Override
+    public String[] getUpgradableVersionRange() {
+        return new String[]{"4.19.0.0", "4.19.1.0"};
+    }
+
+    @Override
+    public String getUpgradedVersion() {
+        return "4.19.1.0";
+    }
+
+    @Override
+    public boolean supportsRollingUpgrade() {
+        return false;
+    }
+
+    @Override
+    public InputStream[] getPrepareScripts() {
+        final String scriptFile = "META-INF/db/schema-41900to41910.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) {
+        addIndexes(conn);
+    }
+
+    @Override
+    public InputStream[] getCleanupScripts() {
+        final String scriptFile = "META-INF/db/schema-41900to41910-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 addIndexes(Connection conn) {
+        DbUpgradeUtils.addIndexIfNeeded(conn, "vm_stats", "vm_id");
+    }
+
+    @Override
+    public void updateSystemVmTemplates(Connection conn) {
+        LOG.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 initSystemVmTemplateRegistration() {
+        systemVmTemplateRegistration = new SystemVmTemplateRegistration("");
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.java b/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.java
new file mode 100644
index 0000000..ab5fcfc
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.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.usage;
+
+import org.apache.cloudstack.api.InternalIdentity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "bucket_statistics")
+public class BucketStatisticsVO implements InternalIdentity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id;
+
+    @Column(name = "account_id", updatable = false)
+    private long accountId;
+
+    @Column(name = "bucket_id", updatable = false)
+    private long bucketId;
+
+    @Column(name = "size")
+    private long size;
+
+    protected BucketStatisticsVO() {
+    }
+
+    public BucketStatisticsVO(long accountId, long bucketId) {
+        this.accountId = accountId;
+        this.bucketId = bucketId;
+    }
+
+    public long getAccountId() {
+        return accountId;
+    }
+
+    public long getBucketId() {
+        return bucketId;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+}
diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/BucketStatisticsDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/BucketStatisticsDao.java
new file mode 100644
index 0000000..48388af
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/usage/dao/BucketStatisticsDao.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.usage.dao;
+
+import com.cloud.usage.BucketStatisticsVO;
+import com.cloud.utils.db.GenericDao;
+
+import java.util.List;
+
+public interface BucketStatisticsDao extends GenericDao<BucketStatisticsVO, Long> {
+    BucketStatisticsVO findBy(long accountId, long bucketId);
+
+    BucketStatisticsVO lock(long accountId, long bucketId);
+
+    List<BucketStatisticsVO> listBy(long accountId);
+}
diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/BucketStatisticsDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/BucketStatisticsDaoImpl.java
new file mode 100644
index 0000000..2261389
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/usage/dao/BucketStatisticsDaoImpl.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 com.cloud.usage.dao;
+
+import com.cloud.usage.BucketStatisticsVO;
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class BucketStatisticsDaoImpl extends GenericDaoBase<BucketStatisticsVO, Long> implements BucketStatisticsDao {
+    private static final Logger s_logger = Logger.getLogger(BucketStatisticsDaoImpl.class);
+    private final SearchBuilder<BucketStatisticsVO> AllFieldsSearch;
+    private final SearchBuilder<BucketStatisticsVO> AccountSearch;
+
+    public BucketStatisticsDaoImpl() {
+        AccountSearch = createSearchBuilder();
+        AccountSearch.and("account", AccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+        AccountSearch.done();
+
+        AllFieldsSearch = createSearchBuilder();
+        AllFieldsSearch.and("account", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.and("bucket", AllFieldsSearch.entity().getBucketId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.done();
+    }
+
+    @Override
+    public BucketStatisticsVO findBy(long accountId, long bucketId) {
+        SearchCriteria<BucketStatisticsVO> sc = AllFieldsSearch.create();
+        sc.setParameters("account", accountId);
+        sc.setParameters("bucket", bucketId);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public BucketStatisticsVO lock(long accountId, long bucketId) {
+        SearchCriteria<BucketStatisticsVO> sc = AllFieldsSearch.create();
+        sc.setParameters("account", accountId);
+        sc.setParameters("bucket", bucketId);
+        return lockOneRandomRow(sc, true);
+    }
+
+    @Override
+    public List<BucketStatisticsVO> listBy(long accountId) {
+        SearchCriteria<BucketStatisticsVO> sc = AccountSearch.create();
+        sc.setParameters("account", accountId);
+        return search(sc, null);
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java
index 4099b3a..ea490e6 100644
--- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java
+++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.usage.dao;
 
+import com.cloud.usage.BucketStatisticsVO;
 import com.cloud.usage.UsageVO;
 import com.cloud.user.AccountVO;
 import com.cloud.user.UserStatisticsVO;
@@ -45,6 +46,12 @@
 
     Long getLastUserStatsId();
 
+    Long getLastBucketStatsId();
+
+    void saveBucketStats(List<BucketStatisticsVO> userStats);
+
+    void updateBucketStats(List<BucketStatisticsVO> userStats);
+
     List<Long> listPublicTemplatesByAccount(long accountId);
 
     Long getLastVmDiskStatsId();
diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java
index 4553ed8..0d9e727 100644
--- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.usage.dao;
 
+import com.cloud.usage.BucketStatisticsVO;
 import com.cloud.usage.UsageVO;
 import com.cloud.user.AccountVO;
 import com.cloud.user.UserStatisticsVO;
@@ -82,6 +83,13 @@
             + "WHERE   cloud_usage.usage_type = ? AND cloud_usage.account_id = ? AND cloud_usage.start_date >= ? AND cloud_usage.end_date <= ? "
             + "GROUP   BY cloud_usage.usage_id ";
 
+    private static final String GET_LAST_BUCKET_STATS_ID = "SELECT id FROM cloud_usage.bucket_statistics ORDER BY id DESC LIMIT 1";
+
+    private static final String INSERT_BUCKET_STATS = "INSERT INTO cloud_usage.bucket_statistics (id, account_id, bucket_id, size) VALUES (?,?,?,?)";
+
+    private static final String UPDATE_BUCKET_STATS = "UPDATE cloud_usage.bucket_statistics SET size=? WHERE id=?";
+
+
     public UsageDaoImpl() {
     }
 
@@ -286,6 +294,69 @@
     }
 
     @Override
+    public Long getLastBucketStatsId() {
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        PreparedStatement pstmt = null;
+        String sql = GET_LAST_BUCKET_STATS_ID;
+        try {
+            pstmt = txn.prepareAutoCloseStatement(sql);
+            ResultSet rs = pstmt.executeQuery();
+            if (rs.next()) {
+                return Long.valueOf(rs.getLong(1));
+            }
+        } catch (Exception ex) {
+            s_logger.error("error getting last bucket stats id", ex);
+        }
+        return null;
+    }
+
+    @Override
+    public void saveBucketStats(List<BucketStatisticsVO> bucketStats) {
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try {
+            txn.start();
+            String sql = INSERT_BUCKET_STATS;
+            PreparedStatement pstmt = null;
+            pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection
+            for (BucketStatisticsVO bucketStat : bucketStats) {
+                pstmt.setLong(1, bucketStat.getId());
+                pstmt.setLong(2, bucketStat.getAccountId());
+                pstmt.setLong(3, bucketStat.getBucketId());
+                pstmt.setLong(4, bucketStat.getSize());
+                pstmt.addBatch();
+            }
+            pstmt.executeBatch();
+            txn.commit();
+        } catch (Exception ex) {
+            txn.rollback();
+            s_logger.error("error saving bucket stats to cloud_usage db", ex);
+            throw new CloudRuntimeException(ex.getMessage());
+        }
+    }
+
+    @Override
+    public void updateBucketStats(List<BucketStatisticsVO> bucketStats) {
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try {
+            txn.start();
+            String sql = UPDATE_BUCKET_STATS;
+            PreparedStatement pstmt = null;
+            pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection
+            for (BucketStatisticsVO bucketStat : bucketStats) {
+                pstmt.setLong(1, bucketStat.getSize());
+                pstmt.setLong(2, bucketStat.getId());
+                pstmt.addBatch();
+            }
+            pstmt.executeBatch();
+            txn.commit();
+        } catch (Exception ex) {
+            txn.rollback();
+            s_logger.error("error updating bucket stats to cloud_usage db", ex);
+            throw new CloudRuntimeException(ex.getMessage());
+        }
+    }
+
+    @Override
     public List<Long> listPublicTemplatesByAccount(long accountId) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         PreparedStatement pstmt = null;
diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java
index 71ad765..863f6c9 100644
--- a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java
+++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java
@@ -25,8 +25,6 @@
 
 import org.apache.cloudstack.api.InternalIdentity;
 
-import com.cloud.utils.db.Encrypt;
-
 @Entity
 @Table(name = "account_details")
 public class AccountDetailVO implements InternalIdentity {
@@ -41,7 +39,6 @@
     @Column(name = "name")
     private String name;
 
-    @Encrypt
     @Column(name = "value", length=4096)
     private String value;
 
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 1d005b2..de3b769 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
@@ -27,6 +27,8 @@
 
     UserAccount getUserAccount(String username, Long domainId);
 
+    List<UserAccountVO> 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 e0cf48d..c9de9a3 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
@@ -60,6 +60,18 @@
     }
 
     @Override
+    public List<UserAccountVO> getUserAccountByEmail(String email, Long domainId) {
+        if (email == null) {
+            return null;
+        }
+
+        SearchCriteria<UserAccountVO> sc = createSearchCriteria();
+        sc.addAnd("email", SearchCriteria.Op.EQ, email);
+        sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId);
+        return listBy(sc);
+    }
+
+    @Override
     public boolean validateUsernameInDomain(String username, Long domainId) {
         UserAccount userAcct = getUserAccount(username, domainId);
         if (userAcct == null) {
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 4b476af..81a1124 100644
--- a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java
+++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java
@@ -19,13 +19,16 @@
 
 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 java.util.Date;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 
 @Entity
 @Table(name = "console_session")
@@ -55,7 +58,8 @@
     private long hostId;
 
     @Column(name = "acquired")
-    private boolean acquired;
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date acquired;
 
     @Column(name = "removed")
     private Date removed;
@@ -124,11 +128,11 @@
         this.removed = removed;
     }
 
-    public boolean isAcquired() {
+    public Date getAcquired() {
         return acquired;
     }
 
-    public void setAcquired(boolean acquired) {
+    public void setAcquired(Date acquired) {
         this.acquired = acquired;
     }
 }
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 fba7c96..936efd1 100644
--- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java
+++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java
@@ -30,6 +30,7 @@
 import javax.persistence.Table;
 import javax.persistence.Transient;
 
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 
@@ -329,15 +330,7 @@
 
     @Override
     public String toString() {
-        return new StringBuilder("Nic[").append(id)
-            .append("-")
-            .append(instanceId)
-            .append("-")
-            .append(reservationId)
-            .append("-")
-            .append(iPv4Address)
-            .append("]")
-            .toString();
+        return String.format("Nic %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "instanceId", "deviceId", "broadcastUri", "reservationId", "iPv4Address"));
     }
 
     @Override
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java
index dcf6505..5b5c350 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java
@@ -43,7 +43,7 @@
     private static final Logger s_logger = Logger.getLogger(ConsoleProxyDaoImpl.class);
 
     //
-    // query SQL for returnning console proxy assignment info as following
+    // query SQL for returning console proxy assignment info as following
     //         proxy vm id, count of assignment
     //
     private static final String PROXY_ASSIGNMENT_MATRIX = "SELECT c.id, count(runningVm.id) AS count "
@@ -63,7 +63,7 @@
         + " WHERE v.type='ConsoleProxy' AND (v.state='Creating' OR v.state='Starting' OR v.state='Running' OR v.state='Migrating')" + " GROUP BY d.id, d.name";
 
     //
-    // query SQL for returnning running console proxy count at data center basis
+    // query SQL for returning running console proxy count at data center basis
     //
     private static final String DATACENTER_PROXY_MATRIX =
         "SELECT d.id, d.name, count(dcid) as count"
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 f2f4703..8e7e229 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
@@ -19,12 +19,12 @@
 
 package com.cloud.vm.dao;
 
+import java.util.Date;
+
+import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.vm.ConsoleSessionVO;
-import com.cloud.utils.db.GenericDaoBase;
-
-import java.util.Date;
 
 public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long> implements ConsoleSessionDao {
 
@@ -48,7 +48,7 @@
         if (consoleSessionVO == null) {
             return false;
         }
-        return !consoleSessionVO.isAcquired();
+        return consoleSessionVO.getAcquired() == null;
     }
 
     @Override
@@ -61,7 +61,7 @@
     @Override
     public void acquireSession(String sessionUuid) {
         ConsoleSessionVO consoleSessionVO = findByUuid(sessionUuid);
-        consoleSessionVO.setAcquired(true);
+        consoleSessionVO.setAcquired(new Date());
         update(consoleSessionVO.getId(), consoleSessionVO);
     }
 
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java
index abc8d80..39c6586 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java
@@ -103,4 +103,5 @@
 
     List<UserVmVO> findByUserDataId(long userdataId);
 
+    List<UserVmVO> listByIds(List<Long> ids);
 }
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 8a1039e..80fabf6 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
@@ -20,6 +20,7 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Hashtable;
 import java.util.List;
@@ -71,6 +72,7 @@
     protected SearchBuilder<UserVmVO> RunningSearch;
     protected SearchBuilder<UserVmVO> StateChangeSearch;
     protected SearchBuilder<UserVmVO> AccountHostSearch;
+    protected SearchBuilder<UserVmVO> IdsSearch;
 
     protected SearchBuilder<UserVmVO> DestroySearch;
     protected SearchBuilder<UserVmVO> AccountDataCenterVirtualSearch;
@@ -135,6 +137,10 @@
         AccountSearch.and("account", AccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
         AccountSearch.done();
 
+        IdsSearch = createSearchBuilder();
+        IdsSearch.and("ids", IdsSearch.entity().getId(), SearchCriteria.Op.IN);
+        IdsSearch.done();
+
         HostSearch = createSearchBuilder();
         HostSearch.and("host", HostSearch.entity().getHostId(), SearchCriteria.Op.EQ);
         HostSearch.done();
@@ -778,4 +784,14 @@
         sc.setParameters("userDataId", userdataId);
         return listBy(sc);
     }
+
+    @Override
+    public List<UserVmVO> listByIds(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<UserVmVO> sc = IdsSearch.create();
+        sc.setParameters("ids", ids.toArray());
+        return listBy(sc);
+    }
 }
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 916687b..322895f 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
@@ -66,7 +66,7 @@
 public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implements VMInstanceDao {
 
     public static final Logger s_logger = Logger.getLogger(VMInstanceDaoImpl.class);
-    private static final int MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT = 3;
+    static final int MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT = 3;
 
     protected SearchBuilder<VMInstanceVO> VMClusterSearch;
     protected SearchBuilder<VMInstanceVO> LHVMClusterSearch;
@@ -897,17 +897,19 @@
 
     @Override
     public boolean updatePowerState(final long instanceId, final long powerHostId, final VirtualMachine.PowerState powerState, Date wisdomEra) {
-        return Transaction.execute(new TransactionCallback<Boolean>() {
+        return Transaction.execute(new TransactionCallback<>() {
             @Override
             public Boolean doInTransaction(TransactionStatus status) {
                 boolean needToUpdate = false;
                 VMInstanceVO instance = findById(instanceId);
                 if (instance != null
-                &&  (null == instance.getPowerStateUpdateTime()
+                        && (null == instance.getPowerStateUpdateTime()
                         || instance.getPowerStateUpdateTime().before(wisdomEra))) {
                     Long savedPowerHostId = instance.getPowerHostId();
-                    if (instance.getPowerState() != powerState || savedPowerHostId == null
-                            || savedPowerHostId.longValue() != powerHostId) {
+                    if (instance.getPowerState() != powerState
+                            || savedPowerHostId == null
+                            || savedPowerHostId != powerHostId
+                            || !isPowerStateInSyncWithInstanceState(powerState, powerHostId, instance)) {
                         instance.setPowerState(powerState);
                         instance.setPowerHostId(powerHostId);
                         instance.setPowerStateUpdateCount(1);
@@ -929,6 +931,17 @@
         });
     }
 
+    private boolean isPowerStateInSyncWithInstanceState(final VirtualMachine.PowerState powerState, final long powerHostId, final VMInstanceVO instance) {
+        State instanceState = instance.getState();
+        if ((powerState == VirtualMachine.PowerState.PowerOff && instanceState == State.Running)
+                || (powerState == VirtualMachine.PowerState.PowerOn && instanceState == State.Stopped)) {
+            s_logger.debug(String.format("VM id: %d on host id: %d and power host id: %d is in %s state, but power state is %s",
+                    instance.getId(), instance.getHostId(), powerHostId, instanceState, powerState));
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public boolean isPowerStateUpToDate(final long instanceId) {
         VMInstanceVO instance = findById(instanceId);
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java
index f5a0ceb..d464725 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java
@@ -55,6 +55,9 @@
     @Column(name = "is_default")
     private boolean isDefault = false;
 
+    @Column(name = "public_role")
+    private boolean publicRole = true;
+
     @Column(name = GenericDao.REMOVED_COLUMN)
     private Date removed;
 
@@ -120,4 +123,12 @@
     public String toString() {
         return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "name", "uuid", "roleType");
     }
+
+    public boolean isPublicRole() {
+        return publicRole;
+    }
+
+    public void setPublicRole(boolean publicRole) {
+        this.publicRole = publicRole;
+    }
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java
index 36833d5..2d4151a 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java
@@ -26,13 +26,17 @@
 import java.util.List;
 
 public interface RoleDao extends GenericDao<RoleVO, Long> {
-    List<RoleVO> findAllByName(String roleName);
+    List<RoleVO> findAllByName(String roleName, boolean showPrivateRole);
 
-    Pair<List<RoleVO>, Integer> findAllByName(final String roleName, String keyword, Long offset, Long limit);
+    Pair<List<RoleVO>, Integer> findAllByName(final String roleName, String keyword, Long offset, Long limit, boolean showPrivateRole);
 
-    List<RoleVO> findAllByRoleType(RoleType type);
-    List<RoleVO> findByName(String roleName);
-    RoleVO findByNameAndType(String roleName, RoleType type);
+    List<RoleVO> findAllByRoleType(RoleType type, boolean showPrivateRole);
+    List<RoleVO> findByName(String roleName, boolean showPrivateRole);
+    RoleVO findByNameAndType(String roleName, RoleType type, boolean showPrivateRole);
 
-    Pair<List<RoleVO>, Integer> findAllByRoleType(RoleType type, Long offset, Long limit);
+    Pair<List<RoleVO>, Integer> findAllByRoleType(RoleType type, Long offset, Long limit, boolean showPrivateRole);
+
+    Pair<List<RoleVO>, Integer> listAllRoles(Long startIndex, Long limit, boolean showPrivateRole);
+
+    List<RoleVO> searchByIds(Long... ids);
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java
index b4938a1..2e8fdd5 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java
@@ -28,39 +28,55 @@
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Component;
 
+import java.util.Collections;
 import java.util.List;
 
 @Component
 public class RoleDaoImpl extends GenericDaoBase<RoleVO, Long> implements RoleDao {
+
+    private final SearchBuilder<RoleVO> RoleByIdsSearch;
     private final SearchBuilder<RoleVO> RoleByNameSearch;
     private final SearchBuilder<RoleVO> RoleByTypeSearch;
     private final SearchBuilder<RoleVO> RoleByNameAndTypeSearch;
+    private final SearchBuilder<RoleVO> RoleByIsPublicSearch;
 
     public RoleDaoImpl() {
         super();
 
+        RoleByIdsSearch = createSearchBuilder();
+        RoleByIdsSearch.and("idIN", RoleByIdsSearch.entity().getId(), SearchCriteria.Op.IN);
+        RoleByIdsSearch.done();
+
         RoleByNameSearch = createSearchBuilder();
         RoleByNameSearch.and("roleName", RoleByNameSearch.entity().getName(), SearchCriteria.Op.LIKE);
+        RoleByNameSearch.and("isPublicRole", RoleByNameSearch.entity().isPublicRole(), SearchCriteria.Op.EQ);
         RoleByNameSearch.done();
 
         RoleByTypeSearch = createSearchBuilder();
         RoleByTypeSearch.and("roleType", RoleByTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ);
+        RoleByTypeSearch.and("isPublicRole", RoleByTypeSearch.entity().isPublicRole(), SearchCriteria.Op.EQ);
         RoleByTypeSearch.done();
 
         RoleByNameAndTypeSearch = createSearchBuilder();
         RoleByNameAndTypeSearch.and("roleName", RoleByNameAndTypeSearch.entity().getName(), SearchCriteria.Op.EQ);
         RoleByNameAndTypeSearch.and("roleType", RoleByNameAndTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ);
+        RoleByNameAndTypeSearch.and("isPublicRole", RoleByNameAndTypeSearch.entity().isPublicRole(), SearchCriteria.Op.EQ);
         RoleByNameAndTypeSearch.done();
+
+        RoleByIsPublicSearch = createSearchBuilder();
+        RoleByIsPublicSearch.and("isPublicRole", RoleByIsPublicSearch.entity().isPublicRole(), SearchCriteria.Op.EQ);
+        RoleByIsPublicSearch.done();
     }
 
     @Override
-    public List<RoleVO> findAllByName(final String roleName) {
-        return findAllByName(roleName, null, null, null).first();
+    public List<RoleVO> findAllByName(final String roleName, boolean showPrivateRole) {
+        return findAllByName(roleName, null, null, null, showPrivateRole).first();
     }
 
     @Override
-    public Pair<List<RoleVO>, Integer> findAllByName(final String roleName, String keyword, Long offset, Long limit) {
+    public Pair<List<RoleVO>, Integer> findAllByName(final String roleName, String keyword, Long offset, Long limit, boolean showPrivateRole) {
         SearchCriteria<RoleVO> sc = RoleByNameSearch.create();
+        filterPrivateRolesIfNeeded(sc, showPrivateRole);
         if (StringUtils.isNotEmpty(roleName)) {
             sc.setParameters("roleName", roleName);
         }
@@ -72,28 +88,54 @@
     }
 
     @Override
-    public List<RoleVO> findAllByRoleType(final RoleType type) {
-        return findAllByRoleType(type, null, null).first();
+    public List<RoleVO> findAllByRoleType(final RoleType type, boolean showPrivateRole) {
+        return findAllByRoleType(type, null, null, showPrivateRole).first();
     }
 
-    public Pair<List<RoleVO>, Integer> findAllByRoleType(final RoleType type, Long offset, Long limit) {
+    public Pair<List<RoleVO>, Integer> findAllByRoleType(final RoleType type, Long offset, Long limit, boolean showPrivateRole) {
         SearchCriteria<RoleVO> sc = RoleByTypeSearch.create();
+        filterPrivateRolesIfNeeded(sc, showPrivateRole);
         sc.setParameters("roleType", type);
         return searchAndCount(sc, new Filter(RoleVO.class, "id", true, offset, limit));
     }
 
     @Override
-    public List<RoleVO> findByName(String roleName) {
+    public List<RoleVO> findByName(String roleName, boolean showPrivateRole) {
         SearchCriteria<RoleVO> sc = RoleByNameSearch.create();
+        filterPrivateRolesIfNeeded(sc, showPrivateRole);
         sc.setParameters("roleName", roleName);
         return listBy(sc);
     }
 
     @Override
-    public RoleVO findByNameAndType(String roleName, RoleType type) {
+    public RoleVO findByNameAndType(String roleName, RoleType type, boolean showPrivateRole) {
         SearchCriteria<RoleVO> sc = RoleByNameAndTypeSearch.create();
+        filterPrivateRolesIfNeeded(sc, showPrivateRole);
         sc.setParameters("roleName", roleName);
         sc.setParameters("roleType", type);
         return findOneBy(sc);
     }
+
+    @Override
+    public Pair<List<RoleVO>, Integer> listAllRoles(Long startIndex, Long limit, boolean showPrivateRole) {
+        SearchCriteria<RoleVO> sc = RoleByIsPublicSearch.create();
+        filterPrivateRolesIfNeeded(sc, showPrivateRole);
+        return searchAndCount(sc, new Filter(RoleVO.class, "id", true, startIndex, limit));
+    }
+
+    @Override
+    public List<RoleVO> searchByIds(Long... ids) {
+        if (ids == null || ids.length == 0) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<RoleVO> sc = RoleByIdsSearch.create();
+        sc.setParameters("idIN", ids);
+        return listBy(sc);
+    }
+
+    public void filterPrivateRolesIfNeeded(SearchCriteria<RoleVO> sc, boolean showPrivateRole) {
+        if (!showPrivateRole) {
+            sc.setParameters("isPublicRole", true);
+        }
+    }
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java
index 07be976..27040c2 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.affinity.dao;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
 
@@ -28,4 +29,6 @@
 
     List<AffinityGroupDomainMapVO> listByDomain(Object... domainId);
 
+    Map<Long, List<String>> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath);
+
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java
index 5ecf63d..1dd22df 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java
@@ -16,23 +16,46 @@
 // under the License.
 package org.apache.cloudstack.affinity.dao;
 
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.PostConstruct;
 
 import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
+import org.apache.log4j.Logger;
 
+import com.cloud.network.dao.NetworkDomainDaoImpl;
 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 com.cloud.utils.db.TransactionLegacy;
 
 public class AffinityGroupDomainMapDaoImpl extends GenericDaoBase<AffinityGroupDomainMapVO, Long> implements AffinityGroupDomainMapDao {
+    public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName());
 
     private SearchBuilder<AffinityGroupDomainMapVO> ListByAffinityGroup;
 
     private SearchBuilder<AffinityGroupDomainMapVO> DomainsSearch;
 
+    private static final String LIST_DOMAINS_WITH_AFFINITY_GROUPS_WITH_SUBDOMAIN_ACCESS_USED_BY_DOMAIN_PATH = "SELECT affinity_group_domain_map.domain_id, \n" +
+            "GROUP_CONCAT('VM:', vm.uuid, ' | AG:' , affinity_group.uuid) \n" +
+            "FROM  cloud.affinity_group_domain_map AS affinity_group_domain_map\n" +
+            "INNER JOIN cloud.affinity_group_vm_map AS affinity_group_vm_map ON (cloud.affinity_group_domain_map.affinity_group_id = affinity_group_vm_map.affinity_group_id)\n" +
+            "INNER JOIN cloud.vm_instance AS vm ON (vm.id = affinity_group_vm_map.instance_id)\n" +
+            "INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id)\n" +
+            "INNER  JOIN cloud.domain AS domain_sn ON (domain_sn.id = affinity_group_domain_map.domain_id)\n" +
+            "INNER JOIN cloud.affinity_group AS affinity_group ON (affinity_group.id = affinity_group_domain_map.affinity_group_id)\n" +
+            "WHERE affinity_group_domain_map.subdomain_access = 1\n" +
+            "AND   domain.path LIKE ?\n" +
+            "AND   domain_sn.path NOT LIKE ?\n" +
+            "GROUP BY affinity_group.id";
+
     public AffinityGroupDomainMapDaoImpl() {
     }
 
@@ -62,4 +85,38 @@
         return listBy(sc);
     }
 
+    @Override
+    public Map<Long, List<String>> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath) {
+        logger.debug(String.format("Retrieving the domains of the affinity groups with subdomain access used by domain with path [%s].", domainPath));
+
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_WITH_AFFINITY_GROUPS_WITH_SUBDOMAIN_ACCESS_USED_BY_DOMAIN_PATH)) {
+            Map<Long, List<String>> domainsOfAffinityGroupsUsedByDomainPath = new HashMap<>();
+
+            String domainSearch = domainPath.concat("%");
+            pstmt.setString(1, domainSearch);
+            pstmt.setString(2, domainSearch);
+
+
+            try (ResultSet rs = pstmt.executeQuery()) {
+                while (rs.next()) {
+                    Long domainId = rs.getLong(1);
+                    List<String> vmUuidsAndAffinityGroupUuids = Arrays.asList(rs.getString(2).split(","));
+
+                    domainsOfAffinityGroupsUsedByDomainPath.put(domainId, vmUuidsAndAffinityGroupUuids);
+                }
+            }
+
+            return domainsOfAffinityGroupsUsedByDomainPath;
+        } catch (SQLException e) {
+            logger.error(String.format("Failed to retrieve the domains of the affinity groups with subdomain access used by domain with path [%s] due to [%s]. Returning an " +
+                    "empty list of domains.", domainPath, e.getMessage()));
+
+            logger.debug(String.format("Failed to retrieve the domains of the affinity groups with subdomain access used by domain with path [%s]. Returning an empty "
+                    + "list of domains.", domainPath), e);
+
+            return new HashMap<>();
+        }
+    }
+
 }
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 2ecbfd5..3e5db04 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java
@@ -30,6 +30,8 @@
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 
 @Entity
 @Table(name = "backups")
@@ -52,7 +54,8 @@
     private String backupType;
 
     @Column(name = "date")
-    private String date;
+    @Temporal(value = TemporalType.DATE)
+    private Date date;
 
     @Column(name = GenericDao.REMOVED_COLUMN)
     private Date removed;
@@ -120,11 +123,11 @@
     }
 
     @Override
-    public String getDate() {
-        return date;
+    public Date getDate() {
+        return this.date;
     }
 
-    public void setDate(String date) {
+    public void setDate(Date date) {
         this.date = date;
     }
 
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.java
new file mode 100644
index 0000000..eab2e55
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.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.cluster;
+
+import org.apache.cloudstack.jobs.JobInfo;
+
+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 = "cluster_drs_plan_migration")
+public class ClusterDrsPlanMigrationVO implements ClusterDrsPlanMigration {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id", nullable = false)
+    Long id;
+
+    @Column(name = "plan_id", nullable = false)
+    private long planId;
+
+    @Column(name = "vm_id", nullable = false)
+    private long vmId;
+
+    @Column(name = "src_host_id", nullable = false)
+    private long srcHostId;
+
+    @Column(name = "dest_host_id", nullable = false)
+    private long destHostId;
+
+    @Column(name = "job_id")
+    private Long jobId;
+
+    @Column(name = "status")
+    private JobInfo.Status status;
+
+
+    public ClusterDrsPlanMigrationVO(long planId, long vmId, long srcHostId, long destHostId) {
+        this.planId = planId;
+        this.vmId = vmId;
+        this.srcHostId = srcHostId;
+        this.destHostId = destHostId;
+    }
+
+    protected ClusterDrsPlanMigrationVO() {
+
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public long getPlanId() {
+        return planId;
+    }
+
+    public long getVmId() {
+        return vmId;
+    }
+
+    public long getSrcHostId() {
+        return srcHostId;
+    }
+
+    public long getDestHostId() {
+        return destHostId;
+    }
+
+    public Long getJobId() {
+        return jobId;
+    }
+
+    public void setJobId(long jobId) {
+        this.jobId = jobId;
+    }
+
+    public JobInfo.Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(JobInfo.Status status) {
+        this.status = status;
+    }
+
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java
new file mode 100644
index 0000000..0ce25ae
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster;
+
+import com.cloud.utils.db.GenericDao;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "cluster_drs_plan")
+public class ClusterDrsPlanVO implements ClusterDrsPlan {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id", nullable = false)
+    Long id;
+
+    @Column(name = "uuid", nullable = false)
+    String uuid;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    Date created;
+
+    @Column(name = "cluster_id")
+    private long clusterId;
+
+    @Column(name = "event_id")
+    private long eventId;
+
+    @Column(name = "type")
+    private Type type;
+
+    @Column(name = "status")
+    private Status status;
+
+    public ClusterDrsPlanVO(long clusterId, long eventId, Type type, Status status) {
+        uuid = UUID.randomUUID().toString();
+        this.clusterId = clusterId;
+        this.eventId = eventId;
+        this.type = type;
+        this.status = status;
+    }
+
+    protected ClusterDrsPlanVO() {
+        uuid = UUID.randomUUID().toString();
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public long getEventId() {
+        return eventId;
+    }
+
+    public long getClusterId() {
+        return clusterId;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    @Override
+    public Status getStatus() {
+        return status;
+    }
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanDao.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanDao.java
new file mode 100644
index 0000000..2fa5248
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanDao.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.cluster.dao;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.cluster.ClusterDrsPlan;
+import org.apache.cloudstack.cluster.ClusterDrsPlanVO;
+
+import java.util.Date;
+import java.util.List;
+
+public interface ClusterDrsPlanDao extends GenericDao<ClusterDrsPlanVO, Long> {
+    List<ClusterDrsPlanVO> listByStatus(ClusterDrsPlan.Status status);
+
+    List<ClusterDrsPlanVO> listByClusterIdAndStatus(Long clusterId, ClusterDrsPlan.Status status);
+
+    ClusterDrsPlanVO listLatestPlanForClusterId(Long clusterId);
+
+    Pair<List<ClusterDrsPlanVO>, Integer> searchAndCount(Long clusterId, Long planId, Long startIndex,
+                                                         Long pageSizeVal);
+
+    int expungeBeforeDate(Date date);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanDaoImpl.java
new file mode 100644
index 0000000..8683258
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanDaoImpl.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster.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.cluster.ClusterDrsPlan;
+import org.apache.cloudstack.cluster.ClusterDrsPlanVO;
+
+import java.util.Date;
+import java.util.List;
+
+public class ClusterDrsPlanDaoImpl extends GenericDaoBase<ClusterDrsPlanVO, Long> implements ClusterDrsPlanDao {
+    public ClusterDrsPlanDaoImpl() {
+    }
+
+    @Override
+    public List<ClusterDrsPlanVO> listByStatus(ClusterDrsPlan.Status status) {
+        SearchBuilder<ClusterDrsPlanVO> sb;
+        sb = createSearchBuilder();
+        sb.and(ApiConstants.STATUS, sb.entity().getStatus(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanVO> sc = sb.create();
+        sc.setParameters(ApiConstants.STATUS, status);
+        return search(sc, null);
+    }
+
+    @Override
+    public List<ClusterDrsPlanVO> listByClusterIdAndStatus(Long clusterId, ClusterDrsPlan.Status status) {
+        SearchBuilder<ClusterDrsPlanVO> sb;
+        sb = createSearchBuilder();
+        sb.and(ApiConstants.CLUSTER_ID, sb.entity().getClusterId(), SearchCriteria.Op.EQ);
+        sb.and(ApiConstants.STATUS, sb.entity().getStatus(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanVO> sc = sb.create();
+        sc.setParameters(ApiConstants.CLUSTER_ID, clusterId);
+        sc.setParameters(ApiConstants.STATUS, status);
+        return search(sc, null);
+    }
+
+    @Override
+    public ClusterDrsPlanVO listLatestPlanForClusterId(Long clusterId) {
+        SearchBuilder<ClusterDrsPlanVO> sb;
+        sb = createSearchBuilder();
+        sb.and(ApiConstants.CLUSTER_ID, sb.entity().getClusterId(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanVO> sc = sb.create();
+        sc.setParameters(ApiConstants.CLUSTER_ID, clusterId);
+        Filter filter = new Filter(ClusterDrsPlanVO.class, "id", false, 0L, 1L);
+        List<ClusterDrsPlanVO> plans = listBy(sc, filter);
+        if (plans != null && !plans.isEmpty()) {
+            return plans.get(0);
+        }
+        return null;
+    }
+
+    @Override
+    public Pair<List<ClusterDrsPlanVO>, Integer> searchAndCount(Long clusterId, Long planId, Long startIndex,
+                                                                Long pageSizeVal) {
+        SearchBuilder<ClusterDrsPlanVO> sb;
+        sb = createSearchBuilder();
+        sb.and(ApiConstants.CLUSTER_ID, sb.entity().getClusterId(), SearchCriteria.Op.EQ);
+        sb.and(ApiConstants.ID, sb.entity().getId(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanVO> sc = sb.create();
+        if (clusterId != null) {
+            sc.setParameters(ApiConstants.CLUSTER_ID, clusterId);
+        }
+        if (planId != null) {
+            sc.setParameters(ApiConstants.ID, planId);
+        }
+        Filter filter = new Filter(ClusterDrsPlanVO.class, "id", false, startIndex, pageSizeVal);
+        return searchAndCount(sc, filter);
+    }
+
+    @Override
+    public int expungeBeforeDate(Date date) {
+        SearchBuilder<ClusterDrsPlanVO> sb;
+        sb = createSearchBuilder();
+        sb.and(ApiConstants.CREATED, sb.entity().getCreated(), SearchCriteria.Op.LT);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanVO> sc = sb.create();
+        sc.setParameters(ApiConstants.CREATED, date);
+        return expunge(sc);
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanMigrationDao.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanMigrationDao.java
new file mode 100644
index 0000000..a6f51ca
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanMigrationDao.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.cluster.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.cluster.ClusterDrsPlanMigrationVO;
+
+import java.util.List;
+
+public interface ClusterDrsPlanMigrationDao extends GenericDao<ClusterDrsPlanMigrationVO, Long> {
+    List<ClusterDrsPlanMigrationVO> listByPlanId(long planId);
+
+    List<ClusterDrsPlanMigrationVO> listPlanMigrationsToExecute(Long id);
+
+    List<ClusterDrsPlanMigrationVO> listPlanMigrationsInProgress(Long id);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanMigrationDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanMigrationDaoImpl.java
new file mode 100644
index 0000000..c1edce1
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/dao/ClusterDrsPlanMigrationDaoImpl.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.cluster.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.cluster.ClusterDrsPlanMigrationVO;
+import org.apache.cloudstack.jobs.JobInfo;
+
+import java.util.List;
+
+public class ClusterDrsPlanMigrationDaoImpl extends GenericDaoBase<ClusterDrsPlanMigrationVO, Long> implements ClusterDrsPlanMigrationDao {
+    public ClusterDrsPlanMigrationDaoImpl() {
+    }
+
+    @Override
+    public List<ClusterDrsPlanMigrationVO> listByPlanId(long planId) {
+        SearchBuilder<ClusterDrsPlanMigrationVO> sb = createSearchBuilder();
+        sb.and("planId", sb.entity().getPlanId(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanMigrationVO> sc = sb.create();
+        sc.setParameters("planId", planId);
+        Filter filter = new Filter(ClusterDrsPlanMigrationVO.class, "id", true, null, null);
+        return search(sc, filter);
+    }
+
+    @Override
+    public List<ClusterDrsPlanMigrationVO> listPlanMigrationsToExecute(Long id) {
+        SearchBuilder<ClusterDrsPlanMigrationVO> sb = createSearchBuilder();
+        sb.and("planId", sb.entity().getPlanId(), SearchCriteria.Op.EQ);
+        sb.and("status", sb.entity().getStatus(), SearchCriteria.Op.NULL);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanMigrationVO> sc = sb.create();
+        sc.setParameters("planId", id);
+        Filter filter = new Filter(ClusterDrsPlanMigrationVO.class, "id", true, null, null);
+        return search(sc, filter);
+    }
+
+    @Override
+    public List<ClusterDrsPlanMigrationVO> listPlanMigrationsInProgress(Long id) {
+        SearchBuilder<ClusterDrsPlanMigrationVO> sb = createSearchBuilder();
+        sb.and("planId", sb.entity().getPlanId(), SearchCriteria.Op.EQ);
+        sb.and("status", sb.entity().getStatus(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<ClusterDrsPlanMigrationVO> sc = sb.create();
+        sc.setParameters("planId", id);
+        sc.setParameters("status", JobInfo.Status.IN_PROGRESS);
+        Filter filter = new Filter(ClusterDrsPlanMigrationVO.class, "id", true, null, null);
+        return search(sc, filter);
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java
index f4d98c3..7b05006 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java
@@ -65,6 +65,10 @@
         return name;
     }
 
+    public void setName(String name) {
+        this.name = name;
+    }
+
     @Override
     public String getValue() {
         return value;
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java
new file mode 100644
index 0000000..b0da0c5
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java
@@ -0,0 +1,125 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.secstorage;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "heuristics")
+public class HeuristicVO implements Heuristic {
+    @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")
+    private String name;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "zone_id", nullable = false)
+    private Long zoneId;
+
+    @Column(name = "type", nullable = false)
+    private String type;
+
+    @Column(name = "heuristic_rule", nullable = false, length = 65535)
+    private String heuristicRule;
+
+    @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 HeuristicVO() {
+    }
+
+    public HeuristicVO(String name, String description, Long zoneId, String type, String heuristicRule) {
+        this.name = name;
+        this.description = description;
+        this.zoneId = zoneId;
+        this.type = type;
+        this.heuristicRule = heuristicRule;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getHeuristicRule() {
+        return heuristicRule;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public void setHeuristicRule(String heuristicRule) {
+        this.heuristicRule = heuristicRule;
+    }
+
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "name", "heuristicRule", "type");
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDao.java b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDao.java
new file mode 100644
index 0000000..1c4057a
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDao.java
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.secstorage.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.secstorage.HeuristicVO;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+
+public interface SecondaryStorageHeuristicDao extends GenericDao<HeuristicVO, Long> {
+
+    HeuristicVO findByZoneIdAndType(long zoneId, HeuristicType type);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.java
new file mode 100644
index 0000000..0b51b2a
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.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.secstorage.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.secstorage.HeuristicVO;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+@Component
+public class SecondaryStorageHeuristicDaoImpl extends GenericDaoBase<HeuristicVO, Long> implements SecondaryStorageHeuristicDao {
+    private SearchBuilder<HeuristicVO> zoneAndTypeSearch;
+
+    @PostConstruct
+    public void init() {
+        zoneAndTypeSearch = createSearchBuilder();
+        zoneAndTypeSearch.and("zoneId", zoneAndTypeSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
+        zoneAndTypeSearch.and("type", zoneAndTypeSearch.entity().getType(), SearchCriteria.Op.IN);
+        zoneAndTypeSearch.done();
+    }
+
+    @Override
+    public HeuristicVO findByZoneIdAndType(long zoneId, HeuristicType type) {
+        SearchCriteria<HeuristicVO> searchCriteria = zoneAndTypeSearch.create();
+        searchCriteria.setParameters("zoneId", zoneId);
+        searchCriteria.setParameters("type", type.toString());
+        final Filter filter = new Filter(HeuristicVO.class, "created", false);
+
+        return findOneBy(searchCriteria, filter);
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java
index ba9825c..7aab5bb 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java
@@ -49,4 +49,8 @@
     List<ImageStoreVO> findByProtocol(String protocol);
 
     ImageStoreVO findOneByZoneAndProtocol(long zoneId, String protocol);
+
+    List<ImageStoreVO> listImageStoresByZoneIds(Long... zoneIds);
+
+    List<ImageStoreVO> listByIds(List<Long> ids);
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java
index 3468b60..84b88c2 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java
@@ -18,12 +18,14 @@
  */
 package org.apache.cloudstack.storage.datastore.db;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
 import javax.naming.ConfigurationException;
 
 import com.cloud.utils.db.Filter;
+import org.apache.commons.collections.CollectionUtils;
 import org.springframework.stereotype.Component;
 
 import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
@@ -43,6 +45,9 @@
     private SearchBuilder<ImageStoreVO> protocolSearch;
     private SearchBuilder<ImageStoreVO> zoneProtocolSearch;
 
+    private SearchBuilder<ImageStoreVO> zonesInSearch;
+    private SearchBuilder<ImageStoreVO> IdsSearch;
+
     public ImageStoreDaoImpl() {
         super();
         protocolSearch = createSearchBuilder();
@@ -55,6 +60,14 @@
         zoneProtocolSearch.and("protocol", zoneProtocolSearch.entity().getProtocol(), SearchCriteria.Op.EQ);
         zoneProtocolSearch.and("role", zoneProtocolSearch.entity().getRole(), SearchCriteria.Op.EQ);
         zoneProtocolSearch.done();
+
+        zonesInSearch = createSearchBuilder();
+        zonesInSearch.and("zonesIn", zonesInSearch.entity().getDcId(), SearchCriteria.Op.IN);
+        zonesInSearch.done();
+
+        IdsSearch = createSearchBuilder();
+        IdsSearch.and("ids", IdsSearch.entity().getId(), SearchCriteria.Op.IN);
+        IdsSearch.done();
     }
     @Override
     public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
@@ -191,4 +204,22 @@
         List<ImageStoreVO> results =  listBy(sc, filter);
         return results.size() == 0 ? null : results.get(0);
     }
+
+
+    @Override
+    public List<ImageStoreVO> listImageStoresByZoneIds(Long... zoneIds) {
+        SearchCriteria<ImageStoreVO> sc = zonesInSearch.create();
+        sc.setParametersIfNotNull("zonesIn", zoneIds);
+        return listBy(sc);
+    }
+
+    @Override
+    public List<ImageStoreVO> listByIds(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<ImageStoreVO> sc = IdsSearch.create();
+        sc.setParameters("ids", ids.toArray());
+        return listBy(sc);
+    }
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadDao.java
new file mode 100644
index 0000000..964e729
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadDao.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.storage.datastore.db;
+
+import com.cloud.utils.db.GenericDao;
+
+import java.util.Date;
+import java.util.List;
+
+public interface ImageStoreObjectDownloadDao extends GenericDao<ImageStoreObjectDownloadVO, Long> {
+    ImageStoreObjectDownloadVO findByStoreIdAndPath(long storeId, String path);
+
+    List<ImageStoreObjectDownloadVO> listToExpire(Date date);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadDaoImpl.java
new file mode 100644
index 0000000..918ab8f
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadDaoImpl.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.datastore.db;
+
+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.naming.ConfigurationException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ImageStoreObjectDownloadDaoImpl extends GenericDaoBase<ImageStoreObjectDownloadVO, Long> implements ImageStoreObjectDownloadDao {
+    private SearchBuilder<ImageStoreObjectDownloadVO> storeIdPathSearch;
+
+    private SearchBuilder<ImageStoreObjectDownloadVO> createdSearch;
+
+    public ImageStoreObjectDownloadDaoImpl() {
+    }
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        super.configure(name, params);
+
+        storeIdPathSearch = createSearchBuilder();
+        storeIdPathSearch.and("store_id", storeIdPathSearch.entity().getStoreId(), SearchCriteria.Op.EQ);
+        storeIdPathSearch.and("path", storeIdPathSearch.entity().getPath(), SearchCriteria.Op.EQ);
+        storeIdPathSearch.done();
+
+        createdSearch = createSearchBuilder();
+        createdSearch.and("created", createdSearch.entity().getCreated(), SearchCriteria.Op.LTEQ);
+        createdSearch.done();
+
+        return true;
+    }
+
+    @Override
+    public ImageStoreObjectDownloadVO findByStoreIdAndPath(long storeId, String path) {
+        SearchCriteria<ImageStoreObjectDownloadVO> sc = storeIdPathSearch.create();
+        sc.setParameters("store_id", storeId);
+        sc.setParameters("path", path);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public List<ImageStoreObjectDownloadVO> listToExpire(Date date) {
+        SearchCriteria<ImageStoreObjectDownloadVO> sc = createdSearch.create();
+        sc.setParameters("created", date);
+        return listBy(sc);
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.java
new file mode 100644
index 0000000..a698184
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.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.storage.datastore.db;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.storage.ImageStoreObjectDownload;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+@Entity
+@Table(name = "image_store_object_download")
+public class ImageStoreObjectDownloadVO implements ImageStoreObjectDownload {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id", nullable = false)
+    private long id;
+
+    @Column(name = "store_id", nullable = false)
+    private Long storeId;
+
+    @Column(name = "path", nullable = false)
+    private String path;
+
+    @Column(name = "download_url", nullable = false)
+    private String downloadUrl;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    private Date created;
+
+    public ImageStoreObjectDownloadVO() {
+    }
+
+    public ImageStoreObjectDownloadVO(Long storeId, String path, String downloadUrl) {
+        this.storeId = storeId;
+        this.path = path;
+        this.downloadUrl = downloadUrl;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getDownloadUrl() {
+        return downloadUrl;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+}
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
new file mode 100644
index 0000000..94f6b5e
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDao.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.storage.datastore.db;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+
+import java.util.List;
+
+public interface ObjectStoreDao extends GenericDao<ObjectStoreVO, Long> {
+    ObjectStoreVO findByName(String name);
+
+    List<ObjectStoreVO> findByProvider(String provider);
+
+    ObjectStoreVO findByUrl(String url);
+
+    List<ObjectStoreVO> listObjectStores();
+
+    List<ObjectStoreVO> searchByIds(Long[] osIds);
+
+    ObjectStoreResponse newObjectStoreResponse(ObjectStoreVO store);
+
+    ObjectStoreResponse setObjectStoreResponse(ObjectStoreResponse storeData, ObjectStoreVO store);
+
+    Integer countAllObjectStores();
+}
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
new file mode 100644
index 0000000..51abde0
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.db;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ObjectStoreDaoImpl extends GenericDaoBase<ObjectStoreVO, Long> implements ObjectStoreDao {
+    private SearchBuilder<ObjectStoreVO> nameSearch;
+    private SearchBuilder<ObjectStoreVO> providerSearch;
+    @Inject
+    private ConfigurationDao _configDao;
+    private final SearchBuilder<ObjectStoreVO> osSearch;
+
+    private SearchBuilder<ObjectStoreVO> urlSearch;
+
+    protected ObjectStoreDaoImpl() {
+        osSearch = createSearchBuilder();
+        osSearch.and("idIN", osSearch.entity().getId(), SearchCriteria.Op.IN);
+        osSearch.done();
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        super.configure(name, params);
+
+        nameSearch = createSearchBuilder();
+        nameSearch.and("name", nameSearch.entity().getName(), SearchCriteria.Op.EQ);
+        nameSearch.done();
+
+        providerSearch = createSearchBuilder();
+        providerSearch.and("providerName", providerSearch.entity().getProviderName(), SearchCriteria.Op.EQ);
+        providerSearch.done();
+
+        urlSearch = createSearchBuilder();
+        urlSearch.and("url", urlSearch.entity().getUrl(), SearchCriteria.Op.EQ);
+        urlSearch.done();
+
+        return true;
+    }
+
+    @Override
+    public ObjectStoreVO findByName(String name) {
+        SearchCriteria<ObjectStoreVO> sc = nameSearch.create();
+        sc.setParameters("name", name);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public List<ObjectStoreVO> findByProvider(String provider) {
+        SearchCriteria<ObjectStoreVO> sc = providerSearch.create();
+        sc.setParameters("providerName", provider);
+        return listBy(sc);
+    }
+
+    @Override
+    public ObjectStoreVO findByUrl(String url) {
+        SearchCriteria<ObjectStoreVO> sc = urlSearch.create();
+        sc.setParameters("url", url);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public List<ObjectStoreVO> listObjectStores() {
+        SearchCriteria<ObjectStoreVO> sc = createSearchCriteria();
+        return listBy(sc);
+    }
+
+    @Override
+    public List<ObjectStoreVO> searchByIds(Long[] osIds) {
+        // set detail batch query size
+        int DETAILS_BATCH_SIZE = 2000;
+        String batchCfg = _configDao.getValue("detail.batch.query.size");
+        if (batchCfg != null) {
+            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
+        }
+        // query details by batches
+        List<ObjectStoreVO> osList = new ArrayList<>();
+        // query details by batches
+        int curr_index = 0;
+        if (osIds.length > DETAILS_BATCH_SIZE) {
+            while ((curr_index + DETAILS_BATCH_SIZE) <= osIds.length) {
+                Long[] ids = new Long[DETAILS_BATCH_SIZE];
+                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
+                    ids[k] = osIds[j];
+                }
+                SearchCriteria<ObjectStoreVO> sc = osSearch.create();
+                sc.setParameters("idIN", ids);
+                List<ObjectStoreVO> stores = searchIncludingRemoved(sc, null, null, false);
+                if (stores != null) {
+                    osList.addAll(stores);
+                }
+                curr_index += DETAILS_BATCH_SIZE;
+            }
+        }
+        if (curr_index < osIds.length) {
+            int batch_size = (osIds.length - curr_index);
+            // set the ids value
+            Long[] ids = new Long[batch_size];
+            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
+                ids[k] = osIds[j];
+            }
+            SearchCriteria<ObjectStoreVO> sc = osSearch.create();
+            sc.setParameters("idIN", ids);
+            List<ObjectStoreVO> stores = searchIncludingRemoved(sc, null, null, false);
+            if (stores != null) {
+                osList.addAll(stores);
+            }
+        }
+        return osList;
+    }
+
+    @Override
+    public ObjectStoreResponse newObjectStoreResponse(ObjectStoreVO store) {
+        ObjectStoreResponse osResponse = new ObjectStoreResponse();
+        osResponse.setId(store.getUuid());
+        osResponse.setName(store.getName());
+        osResponse.setProviderName(store.getProviderName());
+        String url = store.getUrl();
+        osResponse.setUrl(url);
+        osResponse.setObjectName("objectstore");
+        return osResponse;
+    }
+
+    @Override
+    public ObjectStoreResponse setObjectStoreResponse(ObjectStoreResponse storeData, ObjectStoreVO store) {
+        return storeData;
+    }
+
+    @Override
+    public Integer countAllObjectStores() {
+        SearchCriteria<ObjectStoreVO> sc = createSearchCriteria();
+        return getCount(sc);
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.java
new file mode 100644
index 0000000..1f4047f
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.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.storage.datastore.db;
+
+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 = "object_store_details")
+public class ObjectStoreDetailVO implements ResourceDetail {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    long id;
+
+    @Column(name = "store_id")
+    long resourceId;
+
+    @Column(name = "name")
+    String name;
+
+    @Column(name = "value")
+    String value;
+
+    public ObjectStoreDetailVO() {
+    }
+    public ObjectStoreDetailVO(long storeId, String name, String value) {
+        this.resourceId = storeId;
+        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;
+    }
+
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailsDao.java
new file mode 100644
index 0000000..170a28a
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailsDao.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.storage.datastore.db;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
+
+import java.util.Map;
+
+public interface ObjectStoreDetailsDao extends GenericDao<ObjectStoreDetailVO, Long>, ResourceDetailsDao<ObjectStoreDetailVO> {
+
+    void update(long storeId, Map<String, String> details);
+
+    Map<String, String> getDetails(long storeId);
+
+    void deleteDetails(long storeId);
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailsDaoImpl.java
new file mode 100644
index 0000000..e1000e5
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailsDaoImpl.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.storage.datastore.db;
+
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import com.cloud.utils.db.QueryBuilder;
+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 org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ObjectStoreDetailsDaoImpl extends ResourceDetailsDaoBase<ObjectStoreDetailVO> implements ObjectStoreDetailsDao {
+
+    protected final SearchBuilder<ObjectStoreDetailVO> storeSearch;
+
+    public ObjectStoreDetailsDaoImpl() {
+        super();
+        storeSearch = createSearchBuilder();
+        storeSearch.and("store", storeSearch.entity().getResourceId(), Op.EQ);
+        storeSearch.done();
+    }
+
+    @Override
+    public void update(long storeId, Map<String, String> details) {
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        SearchCriteria<ObjectStoreDetailVO> sc = storeSearch.create();
+        sc.setParameters("store", storeId);
+
+        txn.start();
+        expunge(sc);
+        for (Map.Entry<String, String> entry : details.entrySet()) {
+            ObjectStoreDetailVO detail = new ObjectStoreDetailVO(storeId, entry.getKey(), entry.getValue());
+            persist(detail);
+        }
+        txn.commit();
+    }
+
+    @Override
+    public Map<String, String> getDetails(long storeId) {
+        SearchCriteria<ObjectStoreDetailVO> sc = storeSearch.create();
+        sc.setParameters("store", storeId);
+
+        List<ObjectStoreDetailVO> details = listBy(sc);
+        Map<String, String> detailsMap = new HashMap<String, String>();
+        for (ObjectStoreDetailVO detail : details) {
+            String name = detail.getName();
+            String value = detail.getValue();
+            if (name.equals(ApiConstants.KEY)) {
+                value = DBEncryptionUtil.decrypt(value);
+            }
+            detailsMap.put(name, value);
+        }
+
+        return detailsMap;
+    }
+
+    @Override
+    public void deleteDetails(long storeId) {
+        SearchCriteria<ObjectStoreDetailVO> sc = storeSearch.create();
+        sc.setParameters("store", storeId);
+
+        List<ObjectStoreDetailVO> results = search(sc, null);
+        for (ObjectStoreDetailVO result : results) {
+            remove(result.getId());
+        }
+    }
+
+    @Override
+    public ObjectStoreDetailVO findDetail(long storeId, String name) {
+        QueryBuilder<ObjectStoreDetailVO> sc = QueryBuilder.create(ObjectStoreDetailVO.class);
+        sc.and(sc.entity().getResourceId(), Op.EQ, storeId);
+        sc.and(sc.entity().getName(), Op.EQ, name);
+        return sc.find();
+    }
+
+    @Override
+    public void addDetail(long resourceId, String key, String value, boolean display) {
+        // ToDo: Add Display
+        super.addDetail(new ObjectStoreDetailVO(resourceId, key, value));
+    }
+
+}
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
new file mode 100644
index 0000000..885cbfd
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.storage.datastore.db;
+
+import org.apache.cloudstack.storage.object.ObjectStore;
+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.TableGenerator;
+import javax.persistence.Transient;
+import java.util.Date;
+import java.util.Map;
+
+@Entity
+@Table(name = "object_store")
+public class ObjectStoreVO implements ObjectStore {
+    @Id
+    @TableGenerator(name = "object_store_sq", table = "sequence", pkColumnName = "name", valueColumnName = "value", pkColumnValue = "object_store_seq", allocationSize = 1)
+    @Column(name = "id", nullable = false)
+    private long id;
+
+    @Column(name = "name", nullable = false)
+    private String name;
+
+    @Column(name = "uuid", nullable = false)
+    private String uuid;
+
+    @Column(name = "url", nullable = false, length = 2048)
+    private String url;
+
+    @Column(name = "object_provider_name", nullable = false)
+    private String providerName;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    private Date created;
+
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    private Date removed;
+
+    @Column(name = "total_size")
+    private Long totalSize;
+
+    @Column(name = "used_bytes")
+    private Long usedBytes;
+
+    @Transient
+    Map<String, String> details;
+
+    @Override
+    public long getId() {
+        return this.id;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public String getProviderName() {
+        return this.providerName;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setProviderName(String provider) {
+        this.providerName = provider;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    @Override
+    public String getUuid() {
+        return this.uuid;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    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 Long getTotalSize() {
+        return totalSize;
+    }
+
+    public void setTotalSize(Long totalSize) {
+        this.totalSize = totalSize;
+    }
+
+    public Long getUsedBytes() {
+        return usedBytes;
+    }
+
+    public void setUsedBytes(Long usedBytes) {
+        this.usedBytes = usedBytes;
+    }
+
+    public void setDetails(Map<String, String> details) {
+        this.details = details;
+    }
+}
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 80fddc9..e97f463 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
@@ -22,6 +22,8 @@
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.StoragePoolStatus;
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.Filter;
 import com.cloud.utils.db.GenericDao;
 
 /**
@@ -39,6 +41,8 @@
      */
     List<StoragePoolVO> listBy(long datacenterId, Long podId, Long clusterId, ScopeType scope);
 
+    List<StoragePoolVO> listBy(long datacenterId, Long podId, Long clusterId, ScopeType scope, String keyword);
+
     /**
      * Set capacity of storage pool in bytes
      * @param id pool id.
@@ -53,7 +57,7 @@
      */
     void updateCapacityIops(long id, long capacityIops);
 
-    StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags);
+    StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags, Boolean isTagARule);
 
     /**
      * Find pool by name.
@@ -77,7 +81,7 @@
      */
     List<StoragePoolVO> findPoolsByDetails(long dcId, long podId, Long clusterId, Map<String, String> details, ScopeType scope);
 
-    List<StoragePoolVO> findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags);
+    List<StoragePoolVO> findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule, long ruleExecuteTimeout);
 
     List<StoragePoolVO> findDisabledPoolsByScope(long dcId, Long podId, Long clusterId, ScopeType scope);
 
@@ -112,17 +116,21 @@
 
     List<StoragePoolVO> listPoolsByCluster(long clusterId);
 
-    List<StoragePoolVO> findLocalStoragePoolsByTags(long dcId, long podId, Long clusterId, String[] tags);
+    List<StoragePoolVO> findLocalStoragePoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule);
 
-    List<StoragePoolVO> findZoneWideStoragePoolsByTags(long dcId, String[] tags);
+    List<StoragePoolVO> findLocalStoragePoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule, String keyword);
+
+    List<StoragePoolVO> findZoneWideStoragePoolsByTags(long dcId, String[] tags, boolean validateTagRule);
 
     List<StoragePoolVO> findZoneWideStoragePoolsByHypervisor(long dataCenterId, HypervisorType hypervisorType);
 
+    List<StoragePoolVO> findZoneWideStoragePoolsByHypervisor(long dataCenterId, HypervisorType hypervisorType, String keyword);
+
     List<StoragePoolVO> findLocalStoragePoolsByHostAndTags(long hostId, String[] tags);
 
     List<StoragePoolVO> listLocalStoragePoolByPath(long datacenterId, String path);
 
-    List<StoragePoolVO> findPoolsInClusters(List<Long> clusterIds);
+    List<StoragePoolVO> findPoolsInClusters(List<Long> clusterIds, String keyword);
 
     void deletePoolTags(long poolId);
 
@@ -133,4 +141,10 @@
     List<StoragePoolVO> findPoolsByStorageType(String storageType);
 
     List<StoragePoolVO> listStoragePoolsWithActiveVolumesByOfferingId(long offeringid);
+
+    Pair<List<Long>, Integer> searchForIdsAndCount(Long storagePoolId, String storagePoolName, Long zoneId,
+            String path, Long podId, Long clusterId, String address, ScopeType scopeType, StoragePoolStatus status,
+            String keyword, Filter searchFilter);
+
+    List<StoragePoolVO> listByIds(List<Long> ids);
 }
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 2fc52b3..90a6924 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
@@ -20,12 +20,16 @@
 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 java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.Filter;
 import org.apache.commons.collections.CollectionUtils;
 
 import com.cloud.host.Status;
@@ -57,6 +61,7 @@
     private final SearchBuilder<StoragePoolVO> DcLocalStorageSearch;
     private final GenericSearchBuilder<StoragePoolVO, Long> StatusCountSearch;
     private final SearchBuilder<StoragePoolVO> ClustersSearch;
+    private final SearchBuilder<StoragePoolVO> IdsSearch;
 
     @Inject
     private StoragePoolDetailsDao _detailsDao;
@@ -67,11 +72,11 @@
 
     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) >= ?";
-    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.data_center_id = ? and storage_pool.scope = ? and (";
+    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) >= ?";
 
     // 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.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and (";
+    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) >= ?";
 
     private static final String GET_STORAGE_POOLS_OF_VOLUMES_WITHOUT_OR_NOT_HAVING_TAGS = "SELECT s.* " +
@@ -143,6 +148,11 @@
         ClustersSearch = createSearchBuilder();
         ClustersSearch.and("clusterIds", ClustersSearch.entity().getClusterId(), Op.IN);
         ClustersSearch.and("status", ClustersSearch.entity().getStatus(), Op.EQ);
+        ClustersSearch.done();
+
+        IdsSearch = createSearchBuilder();
+        IdsSearch.and("ids", IdsSearch.entity().getId(), SearchCriteria.Op.IN);
+        IdsSearch.done();
 
     }
 
@@ -243,6 +253,11 @@
 
     @Override
     public List<StoragePoolVO> listBy(long datacenterId, Long podId, Long clusterId, ScopeType scope) {
+        return listBy(datacenterId, podId, clusterId, scope, null);
+    }
+
+    @Override
+    public List<StoragePoolVO> listBy(long datacenterId, Long podId, Long clusterId, ScopeType scope, String keyword) {
         SearchCriteria<StoragePoolVO> sc = null;
         if (clusterId != null) {
             sc = DcPodSearch.create();
@@ -254,6 +269,9 @@
         sc.setParameters("datacenterId", datacenterId);
         sc.setParameters("podId", podId);
         sc.setParameters("status", Status.Up);
+        if (keyword != null) {
+            sc.addAnd("name", Op.LIKE,  "%" + keyword + "%");
+        }
         if (scope != null) {
             sc.setParameters("scope", scope);
         }
@@ -278,7 +296,7 @@
 
     @Override
     @DB
-    public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags) {
+    public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags, Boolean isTagARule) {
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         txn.start();
         pool = super.persist(pool);
@@ -289,7 +307,7 @@
             }
         }
         if (CollectionUtils.isNotEmpty(tags)) {
-            _tagsDao.persist(pool.getId(), tags);
+            _tagsDao.persist(pool.getId(), tags, isTagARule);
         }
         txn.commit();
         return pool;
@@ -404,10 +422,15 @@
     }
 
     @Override
-    public List<StoragePoolVO> findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags) {
+    public List<StoragePoolVO> findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule, long ruleExecuteTimeout) {
         List<StoragePoolVO> storagePools = null;
         if (tags == null || tags.length == 0) {
             storagePools = listBy(dcId, podId, clusterId, ScopeType.CLUSTER);
+
+            if (validateTagRule) {
+                storagePools = getPoolsWithoutTagRule(storagePools);
+            }
+
         } else {
             String sqlValues = getSqlValuesFromStorageTags(tags);
             storagePools = findPoolsByDetailsOrTagsInternal(dcId, podId, clusterId, ScopeType.CLUSTER, sqlValues, ValueType.TAGS, tags.length);
@@ -437,10 +460,19 @@
     }
 
     @Override
-    public List<StoragePoolVO> findLocalStoragePoolsByTags(long dcId, long podId, Long clusterId, String[] tags) {
+    public List<StoragePoolVO> findLocalStoragePoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule) {
+        return findLocalStoragePoolsByTags(dcId, podId, clusterId, tags, validateTagRule, null);
+    }
+
+    @Override
+    public List<StoragePoolVO> findLocalStoragePoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule, String keyword) {
         List<StoragePoolVO> storagePools = null;
         if (tags == null || tags.length == 0) {
-            storagePools = listBy(dcId, podId, clusterId, ScopeType.HOST);
+            storagePools = listBy(dcId, podId, clusterId, ScopeType.HOST, keyword);
+
+            if (validateTagRule) {
+                storagePools = getPoolsWithoutTagRule(storagePools);
+            }
         } else {
             String sqlValues = getSqlValuesFromStorageTags(tags);
             storagePools = findPoolsByDetailsOrTagsInternal(dcId, podId, clusterId, ScopeType.HOST, sqlValues, ValueType.TAGS, tags.length);
@@ -483,13 +515,20 @@
     }
 
     @Override
-    public List<StoragePoolVO> findZoneWideStoragePoolsByTags(long dcId, String[] tags) {
+    public List<StoragePoolVO> findZoneWideStoragePoolsByTags(long dcId, String[] tags, boolean validateTagRule) {
         if (tags == null || tags.length == 0) {
             QueryBuilder<StoragePoolVO> 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();
+
+            List<StoragePoolVO> storagePools = sc.list();
+
+            if (validateTagRule) {
+                storagePools = getPoolsWithoutTagRule(storagePools);
+            }
+
+            return storagePools;
         } else {
             String sqlValues = getSqlValuesFromStorageTags(tags);
             String sql = getSqlPreparedStatement(ZoneWideTagsSqlPrefix, ZoneWideTagsSqlSuffix, sqlValues, null);
@@ -497,6 +536,20 @@
         }
     }
 
+    protected List<StoragePoolVO> getPoolsWithoutTagRule(List<StoragePoolVO> storagePools) {
+        List<StoragePoolVO> storagePoolsToReturn = new ArrayList<>();
+        for (StoragePoolVO storagePool : storagePools) {
+
+            List<StoragePoolTagVO> poolTags = _tagsDao.findStoragePoolTags(storagePool.getId());
+
+            if (CollectionUtils.isEmpty(poolTags) || !poolTags.get(0).isTagARule()) {
+                storagePoolsToReturn.add(storagePool);
+            }
+        }
+
+        return storagePoolsToReturn;
+    }
+
     @Override
     public List<String> searchForStoragePoolTags(long poolId) {
         return _tagsDao.getStoragePoolTags(poolId);
@@ -552,11 +605,19 @@
 
     @Override
     public List<StoragePoolVO> findZoneWideStoragePoolsByHypervisor(long dataCenterId, HypervisorType hypervisorType) {
+        return findZoneWideStoragePoolsByHypervisor(dataCenterId, hypervisorType, null);
+    }
+
+    @Override
+    public List<StoragePoolVO> findZoneWideStoragePoolsByHypervisor(long dataCenterId, HypervisorType hypervisorType, String keyword) {
         QueryBuilder<StoragePoolVO> sc = QueryBuilder.create(StoragePoolVO.class);
         sc.and(sc.entity().getDataCenterId(), Op.EQ, dataCenterId);
         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, hypervisorType);
+        if (keyword != null) {
+            sc.and(sc.entity().getName(), Op.LIKE,  "%" + keyword + "%");
+        }
         return sc.list();
     }
 
@@ -581,10 +642,13 @@
     }
 
     @Override
-    public List<StoragePoolVO> findPoolsInClusters(List<Long> clusterIds) {
+    public List<StoragePoolVO> findPoolsInClusters(List<Long> clusterIds, String keyword) {
         SearchCriteria<StoragePoolVO> sc = ClustersSearch.create();
         sc.setParameters("clusterIds", clusterIds.toArray());
         sc.setParameters("status", StoragePoolStatus.Up);
+        if (keyword != null) {
+            sc.addAnd("name", Op.LIKE, "%" + keyword + "%");
+        }
         return listBy(sc);
     }
 
@@ -615,4 +679,92 @@
             throw new CloudRuntimeException("Caught: " + sql, e);
         }
     }
+
+    @Override
+    public Pair<List<Long>, 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<StoragePoolVO> sc = createStoragePoolSearchCriteria(storagePoolId, storagePoolName, zoneId, path, podId, clusterId, address, scopeType, status, keyword);
+        Pair<List<StoragePoolVO>, Integer> uniquePair = searchAndCount(sc, searchFilter);
+        List<Long> idList = uniquePair.first().stream().map(StoragePoolVO::getId).collect(Collectors.toList());
+        return new Pair<>(idList, uniquePair.second());
+    }
+
+    @Override
+    public List<StoragePoolVO> listByIds(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<StoragePoolVO> sc = IdsSearch.create();
+        sc.setParameters("ids", ids.toArray());
+        return listBy(sc);
+    }
+
+    private SearchCriteria<StoragePoolVO> createStoragePoolSearchCriteria(Long storagePoolId, String storagePoolName,
+            Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType,
+            StoragePoolStatus status, String keyword) {
+        SearchBuilder<StoragePoolVO> sb = createSearchBuilder();
+        sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getId()); // select distinct
+        // ids
+        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
+        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
+        sb.and("path", sb.entity().getPath(), SearchCriteria.Op.EQ);
+        sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ);
+        sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ);
+        sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ);
+        sb.and("hostAddress", sb.entity().getHostAddress(), SearchCriteria.Op.EQ);
+        sb.and("scope", sb.entity().getScope(), SearchCriteria.Op.EQ);
+        sb.and("status", sb.entity().getStatus(), SearchCriteria.Op.EQ);
+        sb.and("parent", sb.entity().getParent(), SearchCriteria.Op.EQ);
+
+        SearchCriteria<StoragePoolVO> sc = sb.create();
+
+        if (keyword != null) {
+            SearchCriteria<StoragePoolVO> ssc = createSearchCriteria();
+            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+            ssc.addOr("poolType", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+
+            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+        }
+
+        if (storagePoolId != null) {
+            sc.setParameters("id", storagePoolId);
+        }
+
+        if (storagePoolName != null) {
+            sc.setParameters("name", storagePoolName);
+        }
+
+        if (path != null) {
+            sc.setParameters("path", path);
+        }
+        if (zoneId != null) {
+            sc.setParameters("dataCenterId", zoneId);
+        }
+        if (podId != null) {
+            SearchCriteria<StoragePoolVO> ssc = createSearchCriteria();
+            ssc.addOr("podId", SearchCriteria.Op.EQ, podId);
+            ssc.addOr("podId", SearchCriteria.Op.NULL);
+
+            sc.addAnd("podId", SearchCriteria.Op.SC, ssc);
+        }
+        if (address != null) {
+            sc.setParameters("hostAddress", address);
+        }
+        if (clusterId != null) {
+            SearchCriteria<StoragePoolVO> ssc = createSearchCriteria();
+            ssc.addOr("clusterId", SearchCriteria.Op.EQ, clusterId);
+            ssc.addOr("clusterId", SearchCriteria.Op.NULL);
+
+            sc.addAnd("clusterId", SearchCriteria.Op.SC, ssc);
+        }
+        if (scopeType != null) {
+            sc.setParameters("scope", scopeType.toString());
+        }
+        if (status != null) {
+            sc.setParameters("status", status.toString());
+        }
+        sc.setParameters("parent", 0);
+        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 2ce1589..344ff8b 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
@@ -23,6 +23,7 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 
 import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.utils.db.GenericDao;
 import com.cloud.utils.fsm.StateDao;
 
@@ -33,15 +34,21 @@
 
     List<SnapshotDataStoreVO> listByStoreIdAndState(long id, ObjectInDataStoreStateMachine.State state);
 
+    List<SnapshotDataStoreVO> listBySnapshotIdAndState(long id, ObjectInDataStoreStateMachine.State state);
+
     List<SnapshotDataStoreVO> listActiveOnCache(long id);
 
     void deletePrimaryRecordsForStore(long id, DataStoreRole role);
 
     SnapshotDataStoreVO findByStoreSnapshot(DataStoreRole role, long storeId, long snapshotId);
 
+    void removeBySnapshotStore(long snapshotId, long storeId, DataStoreRole role);
+
     SnapshotDataStoreVO findParent(DataStoreRole role, Long storeId, Long volumeId);
 
-    SnapshotDataStoreVO findBySnapshot(long snapshotId, DataStoreRole role);
+    List<SnapshotDataStoreVO> listBySnapshot(long snapshotId, DataStoreRole role);
+
+    List<SnapshotDataStoreVO> listReadyBySnapshot(long snapshotId, DataStoreRole role);
 
     SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole role);
 
@@ -66,9 +73,7 @@
 
     void updateVolumeIds(long oldVolId, long newVolId);
 
-    SnapshotDataStoreVO findByVolume(long volumeId, DataStoreRole role);
-
-    SnapshotDataStoreVO findByVolume(long snapshotId, long volumeId, DataStoreRole role);
+    List<SnapshotDataStoreVO> findByVolume(long snapshotId, long volumeId, 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.
@@ -85,10 +90,20 @@
      * Removes the snapshot reference from the database according to its id and data store role.
      * @return true if success, otherwise, false.
      */
-    boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, DataStoreRole dataStorerole);
+    boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, long storeId, DataStoreRole dataStorerole);
 
     /**
      * List all snapshots in 'snapshot_store_ref' with state 'Ready' by volume ID.
      */
     List<SnapshotDataStoreVO> listReadyByVolumeId(long volumeId);
+
+    List<SnapshotDataStoreVO> listByStoreAndInstallPaths(long storeId, DataStoreRole role, List<String> pathList);
+
+    List<SnapshotDataStoreVO> listByStoreAndSnapshotIds(long storeId, DataStoreRole role, List<Long> snapshotIds);
+
+    List<SnapshotDataStoreVO> listBySnasphotStoreDownloadStatus(long snapshotId, long storeId, VMTemplateStorageResourceAssoc.Status... status);
+
+    SnapshotDataStoreVO findOneBySnapshotAndDatastoreRole(long snapshotId, DataStoreRole role);
+
+    void updateDisplayForSnapshotStoreRole(long snapshotId, long storeId, DataStoreRole role, boolean display);
 }
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 066a36d..98cb6ca 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
@@ -19,6 +19,7 @@
 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;
@@ -37,6 +38,7 @@
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.db.Filter;
@@ -61,8 +63,12 @@
     private SearchBuilder<SnapshotDataStoreVO> searchFilteringStoreIdEqStoreRoleEqStateNeqRefCntNeq;
     protected SearchBuilder<SnapshotDataStoreVO> searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq;
     private SearchBuilder<SnapshotDataStoreVO> stateSearch;
+    private SearchBuilder<SnapshotDataStoreVO> idStateNeqSearch;
     protected SearchBuilder<SnapshotVO> snapshotVOSearch;
     private SearchBuilder<SnapshotDataStoreVO> snapshotCreatedSearch;
+    private SearchBuilder<SnapshotDataStoreVO> dataStoreAndInstallPathSearch;
+    private SearchBuilder<SnapshotDataStoreVO> storeAndSnapshotIdsSearch;
+    private SearchBuilder<SnapshotDataStoreVO> storeSnapshotDownloadStatusSearch;
 
     protected static final List<Hypervisor.HypervisorType> HYPERVISORS_SUPPORTING_SNAPSHOTS_CHAINING = List.of(Hypervisor.HypervisorType.XenServer);
 
@@ -114,6 +120,12 @@
         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();
+
         snapshotVOSearch = snapshotDao.createSearchBuilder();
         snapshotVOSearch.and(VOLUME_ID, snapshotVOSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
         snapshotVOSearch.done();
@@ -123,6 +135,24 @@
         snapshotCreatedSearch.and(CREATED,  snapshotCreatedSearch.entity().getCreated(), SearchCriteria.Op.BETWEEN);
         snapshotCreatedSearch.done();
 
+        dataStoreAndInstallPathSearch = createSearchBuilder();
+        dataStoreAndInstallPathSearch.and(STORE_ID, dataStoreAndInstallPathSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
+        dataStoreAndInstallPathSearch.and(STORE_ROLE, dataStoreAndInstallPathSearch.entity().getRole(), SearchCriteria.Op.EQ);
+        dataStoreAndInstallPathSearch.and("install_pathIN", dataStoreAndInstallPathSearch.entity().getInstallPath(), SearchCriteria.Op.IN);
+        dataStoreAndInstallPathSearch.done();
+
+        storeAndSnapshotIdsSearch = createSearchBuilder();
+        storeAndSnapshotIdsSearch.and(STORE_ID, storeAndSnapshotIdsSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
+        storeAndSnapshotIdsSearch.and(STORE_ROLE, storeAndSnapshotIdsSearch.entity().getRole(), SearchCriteria.Op.EQ);
+        storeAndSnapshotIdsSearch.and("snapshot_idIN", storeAndSnapshotIdsSearch.entity().getSnapshotId(), SearchCriteria.Op.IN);
+        storeAndSnapshotIdsSearch.done();
+
+        storeSnapshotDownloadStatusSearch = createSearchBuilder();
+        storeSnapshotDownloadStatusSearch.and(SNAPSHOT_ID, storeSnapshotDownloadStatusSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
+        storeSnapshotDownloadStatusSearch.and(STORE_ID, storeSnapshotDownloadStatusSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
+        storeSnapshotDownloadStatusSearch.and("downloadState", storeSnapshotDownloadStatusSearch.entity().getDownloadState(), SearchCriteria.Op.IN);
+        storeSnapshotDownloadStatusSearch.done();
+
         return true;
     }
 
@@ -180,6 +210,14 @@
     }
 
     @Override
+    public List<SnapshotDataStoreVO> listBySnapshotIdAndState(long id, ObjectInDataStoreStateMachine.State state) {
+        SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
+        sc.setParameters(SNAPSHOT_ID, id);
+        sc.setParameters(STATE, state);
+        return listBy(sc);
+    }
+
+    @Override
     public void deletePrimaryRecordsForStore(long id, DataStoreRole role) {
         SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStoreRoleEqStateNeqRefCntNeq.create();
         sc.setParameters(STORE_ID, id);
@@ -204,6 +242,15 @@
     }
 
     @Override
+    public void removeBySnapshotStore(long snapshotId, long storeId, DataStoreRole role) {
+        SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
+        sc.setParameters(STORE_ID, storeId);
+        sc.setParameters(SNAPSHOT_ID, snapshotId);
+        sc.setParameters(STORE_ROLE, role);
+        remove(sc);
+    }
+
+    @Override
     public SnapshotDataStoreVO findLatestSnapshotForVolume(Long volumeId, DataStoreRole role) {
         return findOldestOrLatestSnapshotForVolume(volumeId, role, false);
     }
@@ -257,10 +304,16 @@
     }
 
     @Override
-    public SnapshotDataStoreVO findBySnapshot(long snapshotId, DataStoreRole role) {
+    public List<SnapshotDataStoreVO> listBySnapshot(long snapshotId, DataStoreRole role) {
+        SearchCriteria<SnapshotDataStoreVO> sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role);
+        return listBy(sc);
+    }
+
+    @Override
+    public List<SnapshotDataStoreVO> listReadyBySnapshot(long snapshotId, DataStoreRole role) {
         SearchCriteria<SnapshotDataStoreVO> sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role);
         sc.setParameters(STATE, State.Ready);
-        return findOneBy(sc);
+        return listBy(sc);
     }
 
     @Override
@@ -279,26 +332,19 @@
     }
 
     @Override
-    public SnapshotDataStoreVO findByVolume(long volumeId, DataStoreRole role) {
-        SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
-        sc.setParameters(VOLUME_ID, volumeId);
-        sc.setParameters(STORE_ROLE, role);
-        return findOneBy(sc);
-    }
-
-    @Override
-    public SnapshotDataStoreVO findByVolume(long snapshotId, long volumeId, DataStoreRole role) {
+    public List<SnapshotDataStoreVO> findByVolume(long snapshotId, long volumeId, DataStoreRole role) {
         SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
         sc.setParameters(SNAPSHOT_ID, snapshotId);
         sc.setParameters(VOLUME_ID, volumeId);
         sc.setParameters(STORE_ROLE, role);
-        return findOneBy(sc);
+        return listBy(sc);
     }
 
     @Override
     public List<SnapshotDataStoreVO> findBySnapshotId(long snapshotId) {
-        SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
+        SearchCriteria<SnapshotDataStoreVO> sc = idStateNeqSearch.create();
         sc.setParameters(SNAPSHOT_ID, snapshotId);
+        sc.setParameters(STATE, State.Destroyed);
         return listBy(sc);
     }
 
@@ -451,8 +497,8 @@
     }
 
     @Override
-    public boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, DataStoreRole dataStoreRole) {
-        SnapshotDataStoreVO snapshotDataStoreVo = findOneBy(createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, dataStoreRole));
+    public boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, long storeId, DataStoreRole dataStoreRole) {
+        SnapshotDataStoreVO snapshotDataStoreVo = findByStoreSnapshot(dataStoreRole, storeId, snapshotId);
         return snapshotDataStoreVo == null || expunge(snapshotDataStoreVo.getId());
     }
 
@@ -463,4 +509,56 @@
         sc.setParameters(STATE, State.Ready);
         return listBy(sc);
     }
+
+    @Override
+    public List<SnapshotDataStoreVO> listByStoreAndInstallPaths(long storeId, DataStoreRole role, List<String> pathList) {
+        if (CollectionUtils.isEmpty(pathList)) {
+            return Collections.emptyList();
+        }
+
+        SearchCriteria<SnapshotDataStoreVO> sc = dataStoreAndInstallPathSearch.create();
+        sc.setParameters(STORE_ID, storeId);
+        sc.setParameters(STORE_ROLE, role);
+        sc.setParameters("install_pathIN", pathList.toArray());
+        return listBy(sc);
+    }
+
+    @Override
+    public List<SnapshotDataStoreVO> listByStoreAndSnapshotIds(long storeId, DataStoreRole role, List<Long> snapshotIds) {
+        if (CollectionUtils.isEmpty(snapshotIds)) {
+            return Collections.emptyList();
+        }
+
+        SearchCriteria<SnapshotDataStoreVO> sc = storeAndSnapshotIdsSearch.create();
+        sc.setParameters(STORE_ID, storeId);
+        sc.setParameters(STORE_ROLE, role);
+        sc.setParameters("snapshot_idIN", snapshotIds.toArray());
+        return listBy(sc);
+    }
+
+    @Override
+    public List<SnapshotDataStoreVO> listBySnasphotStoreDownloadStatus(long snapshotId, long storeId, VMTemplateStorageResourceAssoc.Status... status) {
+        SearchCriteria<SnapshotDataStoreVO> sc = storeSnapshotDownloadStatusSearch.create();
+        sc.setParameters("snapshot_id", snapshotId);
+        sc.setParameters("store_id", storeId);
+        sc.setParameters("downloadState", (Object[])status);
+        return search(sc, null);
+    }
+
+    @Override
+    public SnapshotDataStoreVO findOneBySnapshotAndDatastoreRole(long snapshotId, DataStoreRole role) {
+        SearchCriteria<SnapshotDataStoreVO> sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role);
+        sc.setParameters(STATE, State.Ready);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public void updateDisplayForSnapshotStoreRole(long snapshotId, long storeId, DataStoreRole role, boolean display) {
+        SnapshotDataStoreVO ref = findByStoreSnapshot(role, storeId, snapshotId);
+        if (ref == null) {
+            return;
+        }
+        ref.setDisplay(display);
+        update(ref.getId(), ref);
+    }
 }
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 f362169..6f6ed4e 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
@@ -36,6 +36,7 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
 
 import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.fsm.StateObject;
 
@@ -95,6 +96,22 @@
     @Enumerated(EnumType.STRING)
     ObjectInDataStoreStateMachine.State state;
 
+    @Column(name = "download_pct")
+    private int downloadPercent;
+
+    @Column(name = "download_state")
+    @Enumerated(EnumType.STRING)
+    private VMTemplateStorageResourceAssoc.Status downloadState;
+
+    @Column(name = "local_path")
+    private String localDownloadPath;
+
+    @Column(name = "error_str")
+    private String errorString;
+
+    @Column(name = "display")
+    private boolean display = true;
+
     @Column(name = "ref_cnt")
     Long refCnt = 0L;
 
@@ -295,4 +312,44 @@
     public void setCreated(Date created) {
         this.created = created;
     }
+
+    public int getDownloadPercent() {
+        return downloadPercent;
+    }
+
+    public void setDownloadPercent(int downloadPercent) {
+        this.downloadPercent = downloadPercent;
+    }
+
+    public VMTemplateStorageResourceAssoc.Status getDownloadState() {
+        return downloadState;
+    }
+
+    public void setDownloadState(VMTemplateStorageResourceAssoc.Status downloadState) {
+        this.downloadState = downloadState;
+    }
+
+    public void setLocalDownloadPath(String localPath) {
+        localDownloadPath = localPath;
+    }
+
+    public String getLocalDownloadPath() {
+        return localDownloadPath;
+    }
+
+    public void setErrorString(String errorString) {
+        this.errorString = errorString;
+    }
+
+    public String getErrorString() {
+        return errorString;
+    }
+
+    public boolean isDisplay() {
+        return display;
+    }
+
+    public void setDisplay(boolean display) {
+        this.display = display;
+    }
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java
index f8e210a..441649e 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java
@@ -93,4 +93,8 @@
     boolean isTemplateMarkedForDirectDownload(long templateId);
 
     List<TemplateDataStoreVO> listTemplateDownloadUrlsByStoreId(long storeId);
+
+    List<TemplateDataStoreVO> listByStoreIdAndInstallPaths(long storeId, List<String> installPaths);
+
+    List<TemplateDataStoreVO> listByStoreIdAndTemplateIds(long storeId, List<Long> templateIds);
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java
index b3b2ece..c3a4b58 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java
@@ -57,4 +57,6 @@
     List<VolumeDataStoreVO> listVolumeDownloadUrlsByZoneId(long zoneId);
 
     List<VolumeDataStoreVO> listByVolume(long volumeId, long storeId);
+
+    List<VolumeDataStoreVO> listByStoreIdAndInstallPaths(Long storeId, List<String> paths);
 }
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java
new file mode 100644
index 0000000..176f88c
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.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.vm.schedule;
+
+import com.cloud.utils.db.GenericDao;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.UUID;
+
+@Entity
+@Table(name = "vm_schedule")
+public class VMScheduleVO implements VMSchedule {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id", nullable = false)
+    Long id;
+
+    @Column(name = "uuid", nullable = false)
+    String uuid;
+
+    @Column(name = "description")
+    String description;
+
+    @Column(name = "vm_id", nullable = false)
+    long vmId;
+
+    @Column(name = "schedule", nullable = false)
+    String schedule;
+
+    @Column(name = "timezone", nullable = false)
+    String timeZone;
+
+    @Column(name = "action", nullable = false)
+    @Enumerated(value = EnumType.STRING)
+    Action action;
+
+    @Column(name = "enabled", nullable = false)
+    boolean enabled;
+
+    @Column(name = "start_date", nullable = false)
+    @Temporal(value = TemporalType.TIMESTAMP)
+    Date startDate;
+
+    @Column(name = "end_date", nullable = true)
+    @Temporal(value = TemporalType.TIMESTAMP)
+    Date endDate;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    Date created;
+
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    Date removed;
+
+    public VMScheduleVO() {
+        uuid = UUID.randomUUID().toString();
+    }
+
+    public VMScheduleVO(long vmId, String description, String schedule, String timeZone, Action action, Date startDate, Date endDate, boolean enabled) {
+        uuid = UUID.randomUUID().toString();
+        this.vmId = vmId;
+        this.description = description;
+        this.schedule = schedule;
+        this.timeZone = timeZone;
+        this.action = action;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.enabled = enabled;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public long getVmId() {
+        return vmId;
+    }
+
+    public void setVmId(long vmId) {
+        this.vmId = vmId;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getSchedule() {
+        return schedule.substring(2);
+    }
+
+    public void setSchedule(String schedule) {
+        this.schedule = schedule;
+    }
+
+    @Override
+    public String getTimeZone() {
+        return timeZone;
+    }
+
+    public void setTimeZone(String timeZone) {
+        this.timeZone = timeZone;
+    }
+
+    public Action getAction() {
+        return action;
+    }
+
+    public void setAction(Action action) {
+        this.action = action;
+    }
+
+    public boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    @Override
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    @Override
+    public ZoneId getTimeZoneId() {
+        return TimeZone.getTimeZone(getTimeZone()).toZoneId();
+    }
+
+    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/vm/schedule/VMScheduledJobVO.java
new file mode 100644
index 0000000..0c2dd94
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.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.vm.schedule;
+
+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;
+
+@Entity
+@Table(name = "vm_scheduled_job")
+public class VMScheduledJobVO implements VMScheduledJob {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    Long id;
+
+    @Column(name = "uuid", nullable = false)
+    String uuid;
+
+    @Column(name = "vm_id", nullable = false)
+    long vmId;
+
+    @Column(name = "vm_schedule_id", nullable = false)
+    long vmScheduleId;
+
+    @Column(name = "async_job_id")
+    Long asyncJobId;
+
+    @Column(name = "action", nullable = false)
+    @Enumerated(value = EnumType.STRING)
+    VMSchedule.Action action;
+
+    @Column(name = "scheduled_timestamp")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    Date scheduledTime;
+
+    public VMScheduledJobVO() {
+        uuid = UUID.randomUUID().toString();
+    }
+
+    public VMScheduledJobVO(long vmId, long vmScheduleId, VMSchedule.Action action, Date scheduledTime) {
+        uuid = UUID.randomUUID().toString();
+        this.vmId = vmId;
+        this.vmScheduleId = vmScheduleId;
+        this.action = action;
+        this.scheduledTime = scheduledTime;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public long getVmId() {
+        return vmId;
+    }
+
+    @Override
+    public long getVmScheduleId() {
+        return vmScheduleId;
+    }
+
+    @Override
+    public Long getAsyncJobId() {
+        return asyncJobId;
+    }
+
+    @Override
+    public void setAsyncJobId(long asyncJobId) {
+        this.asyncJobId = asyncJobId;
+    }
+
+    @Override
+    public VMSchedule.Action getAction() {
+        return action;
+    }
+
+    @Override
+    public Date getScheduledTime() {
+        return scheduledTime;
+    }
+}
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/vm/schedule/dao/VMScheduleDao.java
new file mode 100644
index 0000000..b8c808b
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.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.vm.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 java.util.List;
+
+public interface VMScheduleDao extends GenericDao<VMScheduleVO, Long> {
+    List<VMScheduleVO> listAllActiveSchedules();
+
+    long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids);
+
+    Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit);
+
+    SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId);
+}
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
new file mode 100644
index 0000000..db8c0c3
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.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.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<VMScheduleVO, Long> implements VMScheduleDao {
+
+    private final SearchBuilder<VMScheduleVO> activeScheduleSearch;
+
+    private final SearchBuilder<VMScheduleVO> scheduleSearchByVmIdAndIds;
+
+    private final SearchBuilder<VMScheduleVO> 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<VMScheduleVO> listAllActiveSchedules() {
+        // WHERE enabled = true AND (end_date IS NULL OR end_date > current_date)
+        SearchCriteria<VMScheduleVO> 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<Long> ids) {
+        SearchCriteria<VMScheduleVO> sc = scheduleSearchByVmIdAndIds.create();
+        sc.setParameters(ApiConstants.ID, ids.toArray());
+        sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
+        return remove(sc);
+    }
+
+    @Override
+    public Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit) {
+        SearchCriteria<VMScheduleVO> 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<VMScheduleVO> getSearchCriteriaForVMId(Long vmId) {
+        SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
+        sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
+        return sc;
+    }
+}
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java
new file mode 100644
index 0000000..7b8c01a
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.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 org.apache.cloudstack.vm.schedule.dao;
+
+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<VMScheduledJobVO, Long> {
+
+    List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp);
+
+    int expungeJobsForSchedules(List<Long> scheduleId, Date dateAfter);
+
+    int expungeJobsBefore(Date currentTimestamp);
+}
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/vm/schedule/dao/VMScheduledJobDaoImpl.java
new file mode 100644
index 0000000..50a2b12
--- /dev/null
+++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.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.vm.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.commons.lang3.time.DateUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long> implements VMScheduledJobDao {
+
+    private final SearchBuilder<VMScheduledJobVO> jobsToStartSearch;
+
+    private final SearchBuilder<VMScheduledJobVO> expungeJobsBeforeSearch;
+
+    private final SearchBuilder<VMScheduledJobVO> expungeJobForScheduleSearch;
+
+    static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp";
+
+    static final String VM_SCHEDULE_ID = "vm_schedule_id";
+
+    public VMScheduledJobDaoImpl() {
+        super();
+        jobsToStartSearch = createSearchBuilder();
+        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(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(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ);
+        expungeJobForScheduleSearch.done();
+    }
+
+    /**
+     * Execution of job wouldn't be at exact seconds. So, we round off and then execute.
+     */
+    @Override
+    public List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp) {
+        if (currentTimestamp == null) {
+            currentTimestamp = new Date();
+        }
+        Date truncatedTs = DateUtils.round(currentTimestamp, Calendar.MINUTE);
+
+        SearchCriteria<VMScheduledJobVO> sc = jobsToStartSearch.create();
+        sc.setParameters(SCHEDULED_TIMESTAMP, truncatedTs);
+        Filter filter = new Filter(VMScheduledJobVO.class, "vmScheduleId", true, null, null);
+        return search(sc, filter);
+    }
+
+    @Override
+    public int expungeJobsForSchedules(List<Long> vmScheduleIds, Date dateAfter) {
+        SearchCriteria<VMScheduledJobVO> sc = expungeJobForScheduleSearch.create();
+        sc.setParameters(VM_SCHEDULE_ID, vmScheduleIds.toArray());
+        if (dateAfter != null) {
+            sc.setParameters(SCHEDULED_TIMESTAMP, dateAfter);
+        }
+        return expunge(sc);
+    }
+
+    @Override
+    public int expungeJobsBefore(Date date) {
+        SearchCriteria<VMScheduledJobVO> sc = expungeJobsBeforeSearch.create();
+        sc.setParameters(SCHEDULED_TIMESTAMP, date);
+        return expunge(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 eba4036..0c46c5f 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
@@ -45,6 +45,7 @@
 	<bean id="hostTagsDaoImpl" class="com.cloud.host.dao.HostTagsDaoImpl" />
 	<bean id="hostTransferMapDaoImpl" class="com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl" />
 	<bean id="imageStoreDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ImageStoreDaoImpl" />
+	<bean id="imageStoreObjectDownloadDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadDaoImpl" />
 	<bean id="networkOfferingDaoImpl" class="com.cloud.offerings.dao.NetworkOfferingDaoImpl" />
 	<bean id="networkOfferingDetailsDaoImpl" class="com.cloud.offerings.dao.NetworkOfferingDetailsDaoImpl" />
 	<bean id="networkOfferingServiceMapDaoImpl" class="com.cloud.offerings.dao.NetworkOfferingServiceMapDaoImpl" />
@@ -56,6 +57,7 @@
 	<bean id="serviceOfferingDaoImpl" class="com.cloud.service.dao.ServiceOfferingDaoImpl" />
 	<bean id="serviceOfferingDetailsDaoImpl" class="com.cloud.service.dao.ServiceOfferingDetailsDaoImpl"/>
 	<bean id="snapshotDaoImpl" class="com.cloud.storage.dao.SnapshotDaoImpl" />
+	<bean id="snapshotZoneDaoImpl" class="com.cloud.storage.dao.SnapshotZoneDaoImpl" />
 	<bean id="snapshotDataStoreDaoImpl" class="org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDaoImpl" />
 	<bean id="storagePoolDetailsDaoImpl" class="com.cloud.storage.dao.StoragePoolDetailsDaoImpl" />
 	<bean id="storagePoolHostDaoImpl" class="com.cloud.storage.dao.StoragePoolHostDaoImpl" />
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 51e557f..5d95838 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
@@ -176,6 +176,7 @@
   <bean id="site2SiteCustomerGatewayDaoImpl" class="com.cloud.network.dao.Site2SiteCustomerGatewayDaoImpl" />
   <bean id="site2SiteVpnConnectionDaoImpl" class="com.cloud.network.dao.Site2SiteVpnConnectionDaoImpl" />
   <bean id="site2SiteVpnGatewayDaoImpl" class="com.cloud.network.dao.Site2SiteVpnGatewayDaoImpl" />
+  <bean id="snapshotJoinDaoImpl" class="com.cloud.api.query.dao.SnapshotJoinDaoImpl" />
   <bean id="snapshotDetailsDaoImpl" class="com.cloud.storage.dao.SnapshotDetailsDaoImpl" />
   <bean id="snapshotPolicyDaoImpl" class="com.cloud.storage.dao.SnapshotPolicyDaoImpl" />
   <bean id="snapshotPolicyDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDaoImpl" />
@@ -275,4 +276,16 @@
   <bean id="UserVmDeployAsIsDetailsDaoImpl" class="com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDaoImpl" />
   <bean id="NetworkPermissionDaoImpl" class="org.apache.cloudstack.network.dao.NetworkPermissionDaoImpl" />
   <bean id="PassphraseDaoImpl" class="org.apache.cloudstack.secret.dao.PassphraseDaoImpl" />
+  <bean id="secondaryStorageHeuristicDaoImpl" class="org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDaoImpl" />
+  <bean id="heuristicRuleHelper" class="org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper" />
+  <bean id="publicIpQuarantineDaoImpl" class="com.cloud.network.dao.PublicIpQuarantineDaoImpl" />
+  <bean id="VMScheduleDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduleDaoImpl" />
+  <bean id="VMScheduledJobDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDaoImpl" />
+  <bean id="VmwareDatacenterDaoImpl" class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterDaoImpl" />
+  <bean id="vnfTemplateDetailsDaoImpl" class="com.cloud.storage.dao.VnfTemplateDetailsDaoImpl" />
+  <bean id="vnfTemplateNicDaoImpl" class="com.cloud.storage.dao.VnfTemplateNicDaoImpl" />
+  <bean id="ClusterDrsPlanDaoImpl" class="org.apache.cloudstack.cluster.dao.ClusterDrsPlanDaoImpl" />
+  <bean id="ClusterDrsPlanDetailsDaoImpl" class="org.apache.cloudstack.cluster.dao.ClusterDrsPlanMigrationDaoImpl" />
+  <bean id="objectStoreDetailsDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDaoImpl" />
+  <bean id="bucketStatisticsDaoImpl" class="com.cloud.usage.dao.BucketStatisticsDaoImpl" />
 </beans>
diff --git a/engine/schema/src/main/resources/META-INF/db/data-217to218.sql b/engine/schema/src/main/resources/META-INF/db/data-217to218.sql
index a6bb1ea..5c12531 100755
--- a/engine/schema/src/main/resources/META-INF/db/data-217to218.sql
+++ b/engine/schema/src/main/resources/META-INF/db/data-217to218.sql
@@ -17,4 +17,3 @@
 
 INSERT INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'default.page.size', '500', 'Default page size for API list* commands');
 DELETE FROM `cloud`.`op_host_capacity` WHERE `capacity_type` in (2,6);
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql b/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql
index cc41910..7013046 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-20to21.sql
@@ -198,4 +198,3 @@
   `created` datetime COMMENT 'date the disk offering was created',
   PRIMARY KEY  (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-21to22-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-21to22-cleanup.sql
index 9cfc10a..c875783 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-21to22-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-21to22-cleanup.sql
@@ -79,5 +79,3 @@
 
 UPDATE `cloud`.`vm_instance` SET domain_id=1, account_id=1 where account_id not in (select distinct id from account) or domain_id not in (select distinct id from domain);
 ALTER TABLE `cloud`.`vm_instance` ADD CONSTRAINT `fk_vm_instance__account_id` FOREIGN KEY `fk_vm_instance__account_id` (`account_id`) REFERENCES `account` (`id`);
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-21to22-premium.sql b/engine/schema/src/main/resources/META-INF/db/schema-21to22-premium.sql
index 9fb9859..4520284 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-21to22-premium.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-21to22-premium.sql
@@ -76,6 +76,3 @@
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 update `cloud_usage`.`usage_volume` set size = (size * 1048576);
-
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql b/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql
index 7ab7228..eb473cf 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql
@@ -231,7 +231,7 @@
   `instance_id` bigint unsigned NOT NULL COMMENT 'vm instance id',
   `dest_ip_address` char(40) NOT NULL COMMENT 'id_address',
   `dest_port_start` int(10) NOT NULL COMMENT 'starting port of the port range to map to',
-  `dest_port_end` int(10) NOT NULL COMMENT 'end port of the the port range to map to',
+  `dest_port_end` int(10) NOT NULL COMMENT 'end port of the port range to map to',
   PRIMARY KEY (`id`),
   CONSTRAINT `fk_port_forwarding_rules__id` FOREIGN KEY(`id`) REFERENCES `firewall_rules`(`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -646,7 +646,7 @@
 INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (89, 6, 'Windows Server 2003 Standard Edition(32-bit)');
 INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (90, 6, 'Windows Server 2003 Standard Edition(64-bit)');
 INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (91, 6, 'Windows Server 2003 Web Edition');
-INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (92, 6, 'Microsoft Small Bussiness Server 2003');
+INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (92, 6, 'Microsoft Small Business Server 2003');
 INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (93, 6, 'Windows XP (32-bit)');
 INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (94, 6, 'Windows XP (64-bit)');
 INSERT INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (95, 6, 'Windows 2000 Advanced Server');
@@ -779,7 +779,7 @@
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Server 2003, Standard Edition (32-bit)', 89);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Server 2003, Standard Edition (64-bit)', 90);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Server 2003, Web Edition', 91);
-INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Small Bussiness Server 2003', 92);
+INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Small Business Server 2003', 92);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Vista (32-bit)', 56);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Vista (64-bit)', 101);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows XP Professional (32-bit)', 93);
@@ -1016,4 +1016,3 @@
 
 DELETE FROM load_balancer_vm_map WHERE load_balancer_id NOT IN (SELECT id FROM load_balancer);
 DELETE FROM vm_instance WHERE type='User' AND id NOT IN (SELECT id FROM user_vm);
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2210to2211.sql b/engine/schema/src/main/resources/META-INF/db/schema-2210to2211.sql
index 45ebdf7..01bec02 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-2210to2211.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-2210to2211.sql
@@ -14,4 +14,3 @@
 -- KIND, either express or implied.  See the License for the
 -- specific language governing permissions and limitations
 -- under the License.
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2211to2212.sql b/engine/schema/src/main/resources/META-INF/db/schema-2211to2212.sql
index 71eca10..94c3d75 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-2211to2212.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-2211to2212.sql
@@ -60,4 +60,3 @@
   CONSTRAINT `fk_inline_load_balancer_nic_map__load_balancer_id` FOREIGN KEY(`load_balancer_id`) REFERENCES `load_balancing_rules`(`id`) ON DELETE CASCADE,
   CONSTRAINT `fk_inline_load_balancer_nic_map__nic_id` FOREIGN KEY(`nic_id`) REFERENCES `nics`(`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2212to2213.sql b/engine/schema/src/main/resources/META-INF/db/schema-2212to2213.sql
index cb32e90..2e86599 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-2212to2213.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-2212to2213.sql
@@ -77,7 +77,6 @@
 update host_details set name='privateip' where host_id in (select id from host where hypervisor_type='BareMetal') and name='agentIp';
 
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'vmware.root.disk.controller', 'ide', 'Specify the default disk controller for root volumes, valid values are scsi, ide');
-INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'vm.destory.forcestop', 'false', 'On destory, force-stop takes this value');
+INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'vm.destroy.forcestop', 'false', 'On destroy, force-stop takes this value');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.lock.timeout', '600', 'Lock wait timeout (seconds) while implementing network');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.disable.rpfilter','true','disable rp_filter on Domain Router VM public interfaces.');
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2213to2214.sql b/engine/schema/src/main/resources/META-INF/db/schema-2213to2214.sql
index de391d3..6c0cc4b 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-2213to2214.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-2213to2214.sql
@@ -87,4 +87,3 @@
 
 ALTER TABLE `cloud`.`keystore` ADD seq int;
 ALTER TABLE `cloud`.`keystore` MODIFY `cloud`.`keystore`.`key` text;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2214to30-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-2214to30-cleanup.sql
index 6b05e9a..c90707c 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-2214to30-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-2214to30-cleanup.sql
@@ -65,4 +65,3 @@
 DROP TABLE IF EXISTS `cloud_usage`.`event`;
 
 DELETE from `cloud`.`guest_os` where id=204 or id=205;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql b/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql
index ebcdfb6..22fda61 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql
@@ -445,7 +445,7 @@
   `allocation_state` varchar(32) NOT NULL DEFAULT 'Free' COMMENT 'Allocation state (Free/Shared/Dedicated/Provider) of the device',
   `is_dedicated` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if device/appliance is provisioned for dedicated use only',
   `is_inline` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer will be used in in-line configuration with firewall',
-  `is_managed` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer appliance is provisioned and its life cycle is managed by by cloudstack',
+  `is_managed` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer appliance is provisioned and its life cycle is managed by cloudstack',
   `host_id` bigint unsigned NOT NULL COMMENT 'host id corresponding to the external load balancer device',
   `parent_host_id` bigint unsigned COMMENT 'if the load balancer appliance is cloudstack managed, then host id on which this appliance is provisioned',
   PRIMARY KEY (`id`),
@@ -702,7 +702,7 @@
 ALTER TABLE  `cloud`.`op_dc_vnet_alloc` ADD CONSTRAINT `fk_op_dc_vnet_alloc__data_center_id` FOREIGN KEY (`data_center_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE;
 ALTER TABLE `cloud`.`domain` ADD COLUMN `type` varchar(255) NOT NULL DEFAULT 'Normal' COMMENT 'type of the domain - can be Normal or Project';
 
-UPDATE `cloud`.`configuration` SET name='vm.destroy.forcestop' where name='vm.destory.forcestop';
+UPDATE `cloud`.`configuration` SET name='vm.destroy.forcestop' where name='vm.destroy.forcestop';
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vm.destroy.forcestop', 'false', 'On destroy, force-stop takes this value');
 DELETE FROM `cloud`.`configuration` where name='skip.steps';
 
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-221to222-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-221to222-cleanup.sql
index 5908dbb..d999b93 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-221to222-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-221to222-cleanup.sql
@@ -17,4 +17,3 @@
 
 alter table firewall_rules drop column is_static_nat;
 delete from configuration where name='router.cleanup';
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-221to222.sql b/engine/schema/src/main/resources/META-INF/db/schema-221to222.sql
index c4fb804..0c663b1 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-221to222.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-221to222.sql
@@ -52,5 +52,3 @@
   PRIMARY KEY (`id`),
   INDEX `i_version__version`(`version`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-222to224-premium.sql b/engine/schema/src/main/resources/META-INF/db/schema-222to224-premium.sql
index 931ca42..9a5f627 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-222to224-premium.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-222to224-premium.sql
@@ -21,4 +21,3 @@
 ALTER TABLE `cloud_usage`.`usage_vm_instance` ADD COLUMN `hypervisor_type` varchar(255);
 
 ALTER TABLE `cloud_usage`.`usage_event` ADD COLUMN `resource_type` varchar(32);
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-222to224.sql b/engine/schema/src/main/resources/META-INF/db/schema-222to224.sql
index 439fd6d..8be6416 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-222to224.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-222to224.sql
@@ -193,4 +193,3 @@
 
 UPDATE storage_pool SET cluster_id=(SELECT cluster_id FROM host INNER JOIN storage_pool_host_ref WHERE host.id=storage_pool_host_ref.host_id AND storage_pool_host_ref.pool_id=storage_pool.id) WHERE pool_type='LVM';
 UPDATE `cloud`.`host` SET resource='com.cloud.hypervisor.xen.resource.XenServer56FP1Resource' WHERE resource='com.cloud.hypervisor.xen.resource.XenServer56FP1PremiumResource';
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-224to225.sql b/engine/schema/src/main/resources/META-INF/db/schema-224to225.sql
index a4eff69..65334af 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-224to225.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-224to225.sql
@@ -64,4 +64,3 @@
 ALTER TABLE `cloud`.`user_statistics` MODIFY `device_type` varchar(32) NOT NULL;
 
 ALTER TABLE `cloud`.`nics` MODIFY `ip6_address` char(40);
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-225to226.sql b/engine/schema/src/main/resources/META-INF/db/schema-225to226.sql
index a991ece..ec1baae 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-225to226.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-225to226.sql
@@ -31,7 +31,7 @@
   `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
   `name` varchar(64) NOT NULL COMMENT 'unique name for the certifiation',
   `certificate` text NOT NULL COMMENT 'the actual certificate being stored in the db',
-  `key` text NOT NULL COMMENT 'private key associated wih the certificate',
+  `key` text NOT NULL COMMENT 'private key associated with the certificate',
   `domain_suffix` varchar(256) NOT NULL COMMENT 'DNS domain suffix associated with the certificate',
   PRIMARY KEY (`id`),
   UNIQUE(name)
@@ -49,4 +49,3 @@
   INDEX `i_cmd_exec_log__instance_id`(`instance_id`),
   CONSTRAINT `fk_cmd_exec_log_ref__inst_id` FOREIGN KEY (`instance_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-227to228-premium.sql b/engine/schema/src/main/resources/META-INF/db/schema-227to228-premium.sql
index 26d555e..40fcbfa 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-227to228-premium.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-227to228-premium.sql
@@ -34,4 +34,3 @@
 
 update `cloud_usage`.`cloud_usage` set raw_usage = (raw_usage % 24) where usage_type =6 and raw_usage > 24 and (raw_usage % 24) <> 0;
 update `cloud_usage`.`cloud_usage` set raw_usage = 24 where usage_type =6 and raw_usage > 24 and (raw_usage % 24) = 0;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-227to228.sql b/engine/schema/src/main/resources/META-INF/db/schema-227to228.sql
index c0b3eb1..343c766 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-227to228.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-227to228.sql
@@ -32,7 +32,7 @@
   `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
   `name` varchar(64) NOT NULL COMMENT 'unique name for the certifiation',
   `certificate` text NOT NULL COMMENT 'the actual certificate being stored in the db',
-  `key` text NOT NULL COMMENT 'private key associated wih the certificate',
+  `key` text NOT NULL COMMENT 'private key associated with the certificate',
   `domain_suffix` varchar(256) NOT NULL COMMENT 'DNS domain suffix associated with the certificate',
   PRIMARY KEY (`id`),
   UNIQUE(name)
@@ -164,5 +164,3 @@
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Console Proxy', 'DEFAULT', 'AgentManager', 'consoleproxy.management.state', 'Auto', 'console proxy service management state');
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Console Proxy', 'DEFAULT', 'AgentManager', 'consoleproxy.management.state.last', 'Auto', 'last console proxy service management state');
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'cluster.message.timeout.seconds', '300', 'Time (in seconds) to wait before a inter-management server message post times out.');
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-228to229.sql b/engine/schema/src/main/resources/META-INF/db/schema-228to229.sql
index d448f03..9d5baa4 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-228to229.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-228to229.sql
@@ -68,7 +68,7 @@
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'agent.load.threshold', '0.70', 'Percentage (as a value between 0 and 1) of connected agents after which agent load balancing will start happening');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.visibility', 'global', 'Load Balancer(haproxy) stats visibility, it can take the following four parameters : global,guest-network,link-local,disabled');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.uri','/admin?stats','Load Balancer(haproxy) uri.');
-INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.auth','admin1:AdMiN123','Load Balancer(haproxy) authetication string in the format username:password');
+INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.auth','admin1:AdMiN123','Load Balancer(haproxy) authentication string in the format username:password');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.port','8081','Load Balancer(haproxy) stats port number.');
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'NetworkManager', 'use.external.dns', 'false', 'Bypass the cloudstack DHCP/DNS server vm name service, use zone external dns1 and dns2');
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'network.loadbalancer.basiczone.elb.enabled', 'false', 'Whether the load balancing service is enabled for basic zones');
@@ -92,5 +92,3 @@
   CONSTRAINT `fk_elastic_lb_vm_map__elb_vm_id` FOREIGN KEY `fk_elastic_lb_vm_map__elb_vm_id` (`elb_vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE,
   CONSTRAINT `fk_elastic_lb_vm_map__lb_id` FOREIGN KEY `fk_elastic_lb_vm_map__lb_id` (`lb_id`) REFERENCES `load_balancing_rules` (`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-229to2210.sql b/engine/schema/src/main/resources/META-INF/db/schema-229to2210.sql
index d549e68..9c5c462 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-229to2210.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-229to2210.sql
@@ -50,7 +50,7 @@
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'agent.load.threshold', '0.70', 'Percentage (as a value between 0 and 1) of connected agents after which agent load balancing will start happening');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.visibility', 'global', 'Load Balancer(haproxy) stats visibility, it can take the following four parameters : global,guest-network,link-local,disabled');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.uri','/admin?stats','Load Balancer(haproxy) uri.');
-INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.auth','admin1:AdMiN123','Load Balancer(haproxy) authetication string in the format username:password');
+INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.auth','admin1:AdMiN123','Load Balancer(haproxy) authentication string in the format username:password');
 INSERT IGNORE INTO configuration VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.stats.port','8081','Load Balancer(haproxy) stats port number.');
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'NetworkManager', 'use.external.dns', 'false', 'Bypass the cloudstack DHCP/DNS server vm name service, use zone external dns1 and dns2');
 INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'management-server', 'network.loadbalancer.basiczone.elb.enabled', 'false', 'Whether the load balancing service is enabled for basic zones');
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-22beta3to22beta4.sql b/engine/schema/src/main/resources/META-INF/db/schema-22beta3to22beta4.sql
index 2160878..c73d165 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-22beta3to22beta4.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-22beta3to22beta4.sql
@@ -124,5 +124,3 @@
 
 DROP VIEW `cloud`.`user_ip_address_view`;
 CREATE VIEW `cloud`.`user_ip_address_view` AS SELECT INET_NTOA(user_ip_address.public_ip_address) as ip_address, user_ip_address.data_center_id, user_ip_address.account_id, user_ip_address.domain_id, user_ip_address.source_nat, user_ip_address.allocated, user_ip_address.vlan_db_id, user_ip_address.one_to_one_nat, user_ip_address.state, user_ip_address.mac_address, user_ip_address.network_id as associated_network_id from user_ip_address;
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-302to303.sql b/engine/schema/src/main/resources/META-INF/db/schema-302to303.sql
index 1233f62..b475a8e 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-302to303.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-302to303.sql
@@ -1,196 +1,196 @@
--- Licensed to the Apache Software Foundation (ASF) under one

--- or more contributor license agreements.  See the NOTICE file

--- distributed with this work for additional information

--- regarding copyright ownership.  The ASF licenses this file

--- to you under the Apache License, Version 2.0 (the

--- "License"); you may not use this file except in compliance

--- with the License.  You may obtain a copy of the License at

--- 

---   http://www.apache.org/licenses/LICENSE-2.0

--- 

--- Unless required by applicable law or agreed to in writing,

--- software distributed under the License is distributed on an

--- "AS IS" BASIS, WITHOUT 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 3.0.2 to 3.0.3;

-

-DELETE FROM `cloud`.`configuration` WHERE name='consoleproxy.cpu.mhz';

-DELETE FROM `cloud`.`configuration` WHERE name='secstorage.vm.cpu.mhz';

-DELETE FROM `cloud`.`configuration` WHERE name='consoleproxy.ram.size';

-DELETE FROM `cloud`.`configuration` WHERE name='secstorage.vm.ram.size';

-DELETE FROM `cloud`.`configuration` WHERE name='open.vswitch.vlan.network';

-DELETE FROM `cloud`.`configuration` WHERE name='open.vswitch.tunnel.network';

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'consoleproxy.service.offering', NULL, 'Service offering used by console proxy; if NULL - system offering will be used');

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'secstorage.service.offering', NULL, 'Service offering used by secondary storage; if NULL - system offering will be used');

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'sdn.ovs.controller', NULL, 'Enable/Disable Open vSwitch SDN controller for L2-in-L3 overlay networks');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'sdn.ovs.controller.default.label', NULL, 'Default network label to be used when fetching interface for GRE endpoints');

-

-ALTER TABLE `cloud`.`user_vm` ADD COLUMN `update_parameters` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Defines if the parameters need to be set for the vm';

-UPDATE `cloud`.`user_vm` SET update_parameters=0 where id>0;

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ha.tag', NULL, 'HA tag defining that the host marked with this tag can be used for HA purposes only');

-

-# Changes for Upload Volume

-CREATE TABLE  `cloud`.`volume_host_ref` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `host_id` bigint unsigned NOT NULL,

-  `volume_id` bigint unsigned NOT NULL,

-  `zone_id` bigint unsigned NOT NULL,

-  `created` DATETIME NOT NULL,

-  `last_updated` DATETIME,

-  `job_id` varchar(255),

-  `download_pct` int(10) unsigned,

-  `size` bigint unsigned,

-  `physical_size` bigint unsigned DEFAULT 0,

-  `download_state` varchar(255),

-  `checksum` varchar(255) COMMENT 'checksum for the data disk',

-  `error_str` varchar(255),

-  `local_path` varchar(255),

-  `install_path` varchar(255),

-  `url` varchar(255),

-  `format` varchar(32) NOT NULL COMMENT 'format for the volume', 

-  `destroyed` tinyint(1) COMMENT 'indicates whether the volume_host entry was destroyed by the user or not',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_volume_host_ref__host_id` FOREIGN KEY `fk_volume_host_ref__host_id` (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE,

-  INDEX `i_volume_host_ref__host_id`(`host_id`),

-  CONSTRAINT `fk_volume_host_ref__volume_id` FOREIGN KEY `fk_volume_host_ref__volume_id` (`volume_id`) REFERENCES `volumes` (`id`),

-  INDEX `i_volume_host_ref__volume_id`(`volume_id`)

-) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

-

-INSERT IGNORE INTO `cloud`.`disk_offering` (name, display_text, customized, unique_name, disk_size, system_use, type) VALUES ( 'Custom', 'Custom Disk', 1, 'Cloud.com-Custom', 0, 0, 'Disk');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Storage', 'DEFAULT', 'management-server', 'storage.max.volume.upload.size', 500, 'The maximum size for a uploaded volume(in GB).');

-# Changes for OVS tunnel manager

-

-# The Following tables are not used anymore

-DROP TABLE IF EXISTS `cloud`.`ovs_host_vlan_alloc`;

-DROP TABLE IF EXISTS `cloud`.`ovs_tunnel`;

-DROP TABLE IF EXISTS `cloud`.`ovs_tunnel_alloc`;

-DROP TABLE IF EXISTS `cloud`.`ovs_vlan_mapping_dirty`;

-DROP TABLE IF EXISTS `cloud`.`ovs_vm_flow_log`;

-DROP TABLE IF EXISTS `cloud`.`ovs_work`;

-

-CREATE TABLE `cloud`.`ovs_tunnel_interface` (

-  `id` bigint(20) NOT NULL AUTO_INCREMENT,

-  `ip` varchar(16) DEFAULT NULL,

-  `netmask` varchar(16) DEFAULT NULL,

-  `mac` varchar(18) DEFAULT NULL,

-  `host_id` bigint(20) DEFAULT NULL,

-  `label` varchar(45) DEFAULT NULL,

-  PRIMARY KEY (`id`)

-) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`ovs_tunnel_network`(

-  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT,

-  `from` bigint unsigned COMMENT 'from host id',

-  `to` bigint unsigned COMMENT 'to host id',

-  `network_id` bigint unsigned COMMENT 'network identifier',

-  `key` int unsigned COMMENT 'gre key',

-  `port_name` varchar(32) COMMENT 'in port on open vswitch',

-  `state` varchar(16) default 'FAILED' COMMENT 'result of tunnel creatation',

-  PRIMARY KEY(`from`, `to`, `network_id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-INSERT INTO `cloud`.`ovs_tunnel_interface` (`ip`, `netmask`, `mac`, `host_id`, `label`) VALUES ('0', '0', '0', 0, 'lock');

-

-INSERT INTO `cloud`.`ovs_tunnel_network` (`from`, `to`, `network_id`, `key`, `port_name`, `state`) VALUES (0, 0, 0, 0, 'lock', 'SUCCESS');

-

-UPDATE `cloud`.`configuration` set component='NetworkManager' where name='external.network.stats.interval';

-UPDATE `cloud`.`configuration` set category='Advanced' where name='guest.domain.suffix';

-UPDATE `cloud`.`configuration` set component='NetworkManager' where name='network.guest.cidr.limit';

-UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.cpu.mhz';

-UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.ram.size';

-UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.stats.interval';

-UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.template.id';

-UPDATE `cloud`.`configuration` set category='Advanced' where name='capacity.skipcounting.hours';

-UPDATE `cloud`.`configuration` set category='Advanced' where name='use.local.storage';

-UPDATE `cloud`.`configuration` set description = 'Percentage (as a value between 0 and 1) of local storage utilization above which alerts will be sent about low local storage available.' where name = 'cluster.localStorage.capacity.notificationthreshold';

-

-DELETE FROM `cloud`.`configuration` WHERE name='direct.agent.pool.size';

-DELETE FROM `cloud`.`configuration` WHERE name='xen.max.product.version';

-DELETE FROM `cloud`.`configuration` WHERE name='xen.max.version';

-DELETE FROM `cloud`.`configuration` WHERE name='xen.max.xapi.version';

-DELETE FROM `cloud`.`configuration` WHERE name='xen.min.product.version';

-DELETE FROM `cloud`.`configuration` WHERE name='xen.min.version';

-DELETE FROM `cloud`.`configuration` WHERE name='xen.min.xapi.version';

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'enable.ec2.api', 'false', 'enable EC2 API on CloudStack');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'enable.s3.api', 'false', 'enable Amazon S3 API on CloudStack');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'vmware.use.nexus.vswitch', 'false', 'Enable/Disable Cisco Nexus 1000v vSwitch in VMware environment');

-ALTER TABLE `cloud`.`account` ADD COLUMN `default_zone_id` bigint unsigned;

-ALTER TABLE `cloud`.`account` ADD CONSTRAINT `fk_account__default_zone_id` FOREIGN KEY `fk_account__default_zone_id`(`default_zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE;

-ALTER TABLE `cloud_usage`.`account` ADD COLUMN `default_zone_id` bigint unsigned;

-

-DROP TABLE IF EXISTS `cloud`.`cluster_vsm_map`;

-DROP TABLE IF EXISTS `cloud`.`virtual_supervisor_module`;

-DROP TABLE IF EXISTS `cloud`.`port_profile`;

-

-CREATE TABLE  `cloud`.`cluster_vsm_map` (

-  `cluster_id` bigint unsigned NOT NULL,

-  `vsm_id` bigint unsigned NOT NULL,

-  PRIMARY KEY (`cluster_id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`virtual_supervisor_module` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `host_id` bigint NOT NULL,

-  `vsm_name` varchar(255),

-  `username` varchar(255) NOT NULL,

-  `password` varchar(255) NOT NULL,

-  `ipaddr` varchar(80) NOT NULL,

-  `management_vlan` int(32),

-  `control_vlan` int(32),

-  `packet_vlan` int(32),

-  `storage_vlan` int(32),

-  `vsm_domain_id` bigint unsigned,

-  `config_mode` varchar(20),

-  `config_state` varchar(20),

-  `vsm_device_state` varchar(20) NOT NULL,

-  PRIMARY KEY (`id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`port_profile` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `port_profile_name` varchar(255),

-  `port_mode` varchar(10),

-  `vsm_id` bigint unsigned NOT NULL,

-  `trunk_low_vlan_id` int,

-  `trunk_high_vlan_id` int,

-  `access_vlan_id` int,

-  `port_type` varchar(20) NOT NULL,

-  `port_binding` varchar(20),

-  PRIMARY KEY (`id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-DELETE FROM `cloud`.`storage_pool_host_ref` WHERE pool_id IN (SELECT id FROM storage_pool WHERE removed IS NOT NULL);

-

-ALTER TABLE `cloud`.`service_offering` MODIFY `nw_rate` smallint(5) unsigned DEFAULT '200' COMMENT 'network rate throttle mbits/s';

-

-

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (141, 1, 'CentOS 5.6 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (142, 1, 'CentOS 5.6 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (143, 1, 'CentOS 6.0 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (144, 1, 'CentOS 6.0 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (145, 3, 'Oracle Enterprise Linux 5.6 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (146, 3, 'Oracle Enterprise Linux 5.6 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (147, 3, 'Oracle Enterprise Linux 6.0 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (148, 3, 'Oracle Enterprise Linux 6.0 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (149, 4, 'Red Hat Enterprise Linux 5.6 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (150, 4, 'Red Hat Enterprise Linux 5.6 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (151, 5, 'SUSE Linux Enterprise Server 10 SP3 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (152, 5, 'SUSE Linux Enterprise Server 10 SP4 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (153, 5, 'SUSE Linux Enterprise Server 10 SP4 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (154, 5, 'SUSE Linux Enterprise Server 11 SP1 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (155, 5, 'SUSE Linux Enterprise Server 11 SP1 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (156, 10, 'Ubuntu 10.10 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (157, 10, 'Ubuntu 10.10 (64-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (161, 1, 'CentOS 5.7 (32-bit)');

-INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (162, 1, 'CentOS 5.7 (64-bit)');

+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+-- 
+--   http://www.apache.org/licenses/LICENSE-2.0
+-- 
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT 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 3.0.2 to 3.0.3;
+
+DELETE FROM `cloud`.`configuration` WHERE name='consoleproxy.cpu.mhz';
+DELETE FROM `cloud`.`configuration` WHERE name='secstorage.vm.cpu.mhz';
+DELETE FROM `cloud`.`configuration` WHERE name='consoleproxy.ram.size';
+DELETE FROM `cloud`.`configuration` WHERE name='secstorage.vm.ram.size';
+DELETE FROM `cloud`.`configuration` WHERE name='open.vswitch.vlan.network';
+DELETE FROM `cloud`.`configuration` WHERE name='open.vswitch.tunnel.network';
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'consoleproxy.service.offering', NULL, 'Service offering used by console proxy; if NULL - system offering will be used');
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'secstorage.service.offering', NULL, 'Service offering used by secondary storage; if NULL - system offering will be used');
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'sdn.ovs.controller', NULL, 'Enable/Disable Open vSwitch SDN controller for L2-in-L3 overlay networks');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'sdn.ovs.controller.default.label', NULL, 'Default network label to be used when fetching interface for GRE endpoints');
+
+ALTER TABLE `cloud`.`user_vm` ADD COLUMN `update_parameters` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Defines if the parameters need to be set for the vm';
+UPDATE `cloud`.`user_vm` SET update_parameters=0 where id>0;
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ha.tag', NULL, 'HA tag defining that the host marked with this tag can be used for HA purposes only');
+
+# Changes for Upload Volume
+CREATE TABLE  `cloud`.`volume_host_ref` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `host_id` bigint unsigned NOT NULL,
+  `volume_id` bigint unsigned NOT NULL,
+  `zone_id` bigint unsigned NOT NULL,
+  `created` DATETIME NOT NULL,
+  `last_updated` DATETIME,
+  `job_id` varchar(255),
+  `download_pct` int(10) unsigned,
+  `size` bigint unsigned,
+  `physical_size` bigint unsigned DEFAULT 0,
+  `download_state` varchar(255),
+  `checksum` varchar(255) COMMENT 'checksum for the data disk',
+  `error_str` varchar(255),
+  `local_path` varchar(255),
+  `install_path` varchar(255),
+  `url` varchar(255),
+  `format` varchar(32) NOT NULL COMMENT 'format for the volume', 
+  `destroyed` tinyint(1) COMMENT 'indicates whether the volume_host entry was destroyed by the user or not',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_volume_host_ref__host_id` FOREIGN KEY `fk_volume_host_ref__host_id` (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE,
+  INDEX `i_volume_host_ref__host_id`(`host_id`),
+  CONSTRAINT `fk_volume_host_ref__volume_id` FOREIGN KEY `fk_volume_host_ref__volume_id` (`volume_id`) REFERENCES `volumes` (`id`),
+  INDEX `i_volume_host_ref__volume_id`(`volume_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
+
+INSERT IGNORE INTO `cloud`.`disk_offering` (name, display_text, customized, unique_name, disk_size, system_use, type) VALUES ( 'Custom', 'Custom Disk', 1, 'Cloud.com-Custom', 0, 0, 'Disk');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Storage', 'DEFAULT', 'management-server', 'storage.max.volume.upload.size', 500, 'The maximum size for a uploaded volume(in GB).');
+# Changes for OVS tunnel manager
+
+# The Following tables are not used anymore
+DROP TABLE IF EXISTS `cloud`.`ovs_host_vlan_alloc`;
+DROP TABLE IF EXISTS `cloud`.`ovs_tunnel`;
+DROP TABLE IF EXISTS `cloud`.`ovs_tunnel_alloc`;
+DROP TABLE IF EXISTS `cloud`.`ovs_vlan_mapping_dirty`;
+DROP TABLE IF EXISTS `cloud`.`ovs_vm_flow_log`;
+DROP TABLE IF EXISTS `cloud`.`ovs_work`;
+
+CREATE TABLE `cloud`.`ovs_tunnel_interface` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `ip` varchar(16) DEFAULT NULL,
+  `netmask` varchar(16) DEFAULT NULL,
+  `mac` varchar(18) DEFAULT NULL,
+  `host_id` bigint(20) DEFAULT NULL,
+  `label` varchar(45) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`ovs_tunnel_network`(
+  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT,
+  `from` bigint unsigned COMMENT 'from host id',
+  `to` bigint unsigned COMMENT 'to host id',
+  `network_id` bigint unsigned COMMENT 'network identifier',
+  `key` int unsigned COMMENT 'gre key',
+  `port_name` varchar(32) COMMENT 'in port on open vswitch',
+  `state` varchar(16) default 'FAILED' COMMENT 'result of tunnel creatation',
+  PRIMARY KEY(`from`, `to`, `network_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `cloud`.`ovs_tunnel_interface` (`ip`, `netmask`, `mac`, `host_id`, `label`) VALUES ('0', '0', '0', 0, 'lock');
+
+INSERT INTO `cloud`.`ovs_tunnel_network` (`from`, `to`, `network_id`, `key`, `port_name`, `state`) VALUES (0, 0, 0, 0, 'lock', 'SUCCESS');
+
+UPDATE `cloud`.`configuration` set component='NetworkManager' where name='external.network.stats.interval';
+UPDATE `cloud`.`configuration` set category='Advanced' where name='guest.domain.suffix';
+UPDATE `cloud`.`configuration` set component='NetworkManager' where name='network.guest.cidr.limit';
+UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.cpu.mhz';
+UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.ram.size';
+UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.stats.interval';
+UPDATE `cloud`.`configuration` set component='NetworkManager' where name='router.template.id';
+UPDATE `cloud`.`configuration` set category='Advanced' where name='capacity.skipcounting.hours';
+UPDATE `cloud`.`configuration` set category='Advanced' where name='use.local.storage';
+UPDATE `cloud`.`configuration` set description = 'Percentage (as a value between 0 and 1) of local storage utilization above which alerts will be sent about low local storage available.' where name = 'cluster.localStorage.capacity.notificationthreshold';
+
+DELETE FROM `cloud`.`configuration` WHERE name='direct.agent.pool.size';
+DELETE FROM `cloud`.`configuration` WHERE name='xen.max.product.version';
+DELETE FROM `cloud`.`configuration` WHERE name='xen.max.version';
+DELETE FROM `cloud`.`configuration` WHERE name='xen.max.xapi.version';
+DELETE FROM `cloud`.`configuration` WHERE name='xen.min.product.version';
+DELETE FROM `cloud`.`configuration` WHERE name='xen.min.version';
+DELETE FROM `cloud`.`configuration` WHERE name='xen.min.xapi.version';
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'enable.ec2.api', 'false', 'enable EC2 API on CloudStack');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'enable.s3.api', 'false', 'enable Amazon S3 API on CloudStack');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'vmware.use.nexus.vswitch', 'false', 'Enable/Disable Cisco Nexus 1000v vSwitch in VMware environment');
+ALTER TABLE `cloud`.`account` ADD COLUMN `default_zone_id` bigint unsigned;
+ALTER TABLE `cloud`.`account` ADD CONSTRAINT `fk_account__default_zone_id` FOREIGN KEY `fk_account__default_zone_id`(`default_zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE;
+ALTER TABLE `cloud_usage`.`account` ADD COLUMN `default_zone_id` bigint unsigned;
+
+DROP TABLE IF EXISTS `cloud`.`cluster_vsm_map`;
+DROP TABLE IF EXISTS `cloud`.`virtual_supervisor_module`;
+DROP TABLE IF EXISTS `cloud`.`port_profile`;
+
+CREATE TABLE  `cloud`.`cluster_vsm_map` (
+  `cluster_id` bigint unsigned NOT NULL,
+  `vsm_id` bigint unsigned NOT NULL,
+  PRIMARY KEY (`cluster_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`virtual_supervisor_module` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `host_id` bigint NOT NULL,
+  `vsm_name` varchar(255),
+  `username` varchar(255) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `ipaddr` varchar(80) NOT NULL,
+  `management_vlan` int(32),
+  `control_vlan` int(32),
+  `packet_vlan` int(32),
+  `storage_vlan` int(32),
+  `vsm_domain_id` bigint unsigned,
+  `config_mode` varchar(20),
+  `config_state` varchar(20),
+  `vsm_device_state` varchar(20) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`port_profile` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `port_profile_name` varchar(255),
+  `port_mode` varchar(10),
+  `vsm_id` bigint unsigned NOT NULL,
+  `trunk_low_vlan_id` int,
+  `trunk_high_vlan_id` int,
+  `access_vlan_id` int,
+  `port_type` varchar(20) NOT NULL,
+  `port_binding` varchar(20),
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DELETE FROM `cloud`.`storage_pool_host_ref` WHERE pool_id IN (SELECT id FROM storage_pool WHERE removed IS NOT NULL);
+
+ALTER TABLE `cloud`.`service_offering` MODIFY `nw_rate` smallint(5) unsigned DEFAULT '200' COMMENT 'network rate throttle mbits/s';
+
+
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (141, 1, 'CentOS 5.6 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (142, 1, 'CentOS 5.6 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (143, 1, 'CentOS 6.0 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (144, 1, 'CentOS 6.0 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (145, 3, 'Oracle Enterprise Linux 5.6 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (146, 3, 'Oracle Enterprise Linux 5.6 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (147, 3, 'Oracle Enterprise Linux 6.0 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (148, 3, 'Oracle Enterprise Linux 6.0 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (149, 4, 'Red Hat Enterprise Linux 5.6 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (150, 4, 'Red Hat Enterprise Linux 5.6 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (151, 5, 'SUSE Linux Enterprise Server 10 SP3 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (152, 5, 'SUSE Linux Enterprise Server 10 SP4 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (153, 5, 'SUSE Linux Enterprise Server 10 SP4 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (154, 5, 'SUSE Linux Enterprise Server 11 SP1 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (155, 5, 'SUSE Linux Enterprise Server 11 SP1 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (156, 10, 'Ubuntu 10.10 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (157, 10, 'Ubuntu 10.10 (64-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (161, 1, 'CentOS 5.7 (32-bit)');
+INSERT IGNORE INTO `cloud`.`guest_os` (id, category_id, display_name) VALUES (162, 1, 'CentOS 5.7 (64-bit)');
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-302to40.sql b/engine/schema/src/main/resources/META-INF/db/schema-302to40.sql
index e632fa6..ca99f01 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-302to40.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-302to40.sql
@@ -227,7 +227,7 @@
 
 -- rrq 5839
 -- Remove the unique constraint on physical_network_id, provider_name from physical_network_service_providers
--- Because the name of this contraint is not set we need this roundabout way
+-- Because the name of this constraint is not set we need this roundabout way
 -- The key is also used by the foreign key constraint so drop and recreate that one
 ALTER TABLE `cloud`.`physical_network_service_providers` DROP FOREIGN KEY fk_pnetwork_service_providers__physical_network_id;
 
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-304to305-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-304to305-cleanup.sql
index b019ac2..3b5c8f5 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-304to305-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-304to305-cleanup.sql
@@ -19,4 +19,3 @@
 
 
 ALTER TABLE `cloud`.`domain_router` DROP COLUMN network_id;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-304to305.sql b/engine/schema/src/main/resources/META-INF/db/schema-304to305.sql
index 58f4557..dfeff3f 100755
--- a/engine/schema/src/main/resources/META-INF/db/schema-304to305.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-304to305.sql
@@ -1,389 +1,389 @@
--- Licensed to the Apache Software Foundation (ASF) under one

--- or more contributor license agreements.  See the NOTICE file

--- distributed with this work for additional information

--- regarding copyright ownership.  The ASF licenses this file

--- to you under the Apache License, Version 2.0 (the

--- "License"); you may not use this file except in compliance

--- with the License.  You may obtain a copy of the License at

--- 

---   http://www.apache.org/licenses/LICENSE-2.0

--- 

--- Unless required by applicable law or agreed to in writing,

--- software distributed under the License is distributed on an

--- "AS IS" BASIS, WITHOUT 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 3.0.4 to 3.0.5;

-

-CREATE TABLE `cloud`.`resource_tags` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `key` varchar(255),

-  `value` varchar(255),

-  `resource_id` bigint unsigned NOT NULL,

-  `resource_uuid` varchar(40),

-  `resource_type` varchar(255),

-  `customer` varchar(255),

-  `domain_id` bigint unsigned NOT NULL COMMENT 'foreign key to domain id',

-  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this network',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `fk_tags__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`),

-  CONSTRAINT `fk_tags__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`),

-  UNIQUE `i_tags__resource_id__resource_type__key`(`resource_id`, `resource_type`, `key`),

-  CONSTRAINT `uc_resource_tags__uuid` UNIQUE (`uuid`)

-  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`vpc_offerings` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40) NOT NULL,

-  `unique_name` varchar(64) UNIQUE COMMENT 'unique name of the vpc offering',

-  `name` varchar(255) COMMENT 'vpc name',

-  `display_text` varchar(255) COMMENT 'display text',

-  `state` char(32) COMMENT 'state of the vpc offering that has Disabled value by default',

-  `default` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if vpc offering is default',

-  `removed` datetime COMMENT 'date removed if not null',

-  `created` datetime NOT NULL COMMENT 'date created',

-  `service_offering_id` bigint unsigned COMMENT 'service offering id that virtual router is tied to',

-  PRIMARY KEY  (`id`),

-  INDEX `i_vpc__removed`(`removed`),

-  CONSTRAINT `fk_vpc_offerings__service_offering_id` FOREIGN KEY `fk_vpc_offerings__service_offering_id` (`service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE  `cloud`.`vpc_offering_service_map` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `vpc_offering_id` bigint unsigned NOT NULL COMMENT 'vpc_offering_id',

-  `service` varchar(255) NOT NULL COMMENT 'service',

-  `provider` varchar(255) COMMENT 'service provider',

-  `created` datetime COMMENT 'date created',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `fk_vpc_offering_service_map__vpc_offering_id` FOREIGN KEY(`vpc_offering_id`) REFERENCES `vpc_offerings`(`id`) ON DELETE CASCADE,

-  UNIQUE (`vpc_offering_id`, `service`, `provider`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`vpc` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40) NOT NULL,

-  `name` varchar(255) COMMENT 'vpc name',

-  `display_text` varchar(255) COMMENT 'vpc display text',

-  `cidr` varchar(18) COMMENT 'vpc cidr',

-  `vpc_offering_id` bigint unsigned NOT NULL COMMENT 'vpc offering id that this vpc is created from',

-  `zone_id` bigint unsigned NOT NULL COMMENT 'the id of the zone this Vpc belongs to',

-  `state` varchar(32) NOT NULL COMMENT 'state of the VP (can be Enabled and Disabled)',

-  `domain_id` bigint unsigned NOT NULL COMMENT 'domain the vpc belongs to',

-  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this vpc',

-  `network_domain` varchar(255) COMMENT 'network domain',

-  `removed` datetime COMMENT 'date removed if not null',

-  `created` datetime NOT NULL COMMENT 'date created',

-  `restart_required` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if restart is required for the VPC',

-  PRIMARY KEY  (`id`),

-  INDEX `i_vpc__removed`(`removed`),

-  CONSTRAINT `fk_vpc__zone_id` FOREIGN KEY `fk_vpc__zone_id` (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_vpc__vpc_offering_id` FOREIGN KEY (`vpc_offering_id`) REFERENCES `vpc_offerings`(`id`), 

-  CONSTRAINT `fk_vpc__account_id` FOREIGN KEY `fk_vpc__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_vpc__domain_id` FOREIGN KEY `fk_vpc__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-

-CREATE TABLE `cloud`.`router_network_ref` (

-  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',

-  `router_id` bigint unsigned NOT NULL COMMENT 'router id',

-  `network_id` bigint unsigned NOT NULL COMMENT 'network id',

-  `guest_type` char(32) COMMENT 'type of guest network that can be shared or isolated',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `fk_router_network_ref__networks_id` FOREIGN KEY (`network_id`) REFERENCES `networks`(`id`) ON DELETE CASCADE,

-  UNIQUE `i_router_network_ref__router_id__network_id`(`router_id`, `network_id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-

-CREATE TABLE `cloud`.`vpc_gateways` (

-  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',

-  `uuid` varchar(40),

-  `ip4_address` char(40) COMMENT 'ip4 address of the gateway',

-  `netmask` varchar(15) COMMENT 'netmask of the gateway',

-  `gateway` varchar(15) COMMENT 'gateway',

-  `vlan_tag` varchar(255),

-  `type` varchar(32) COMMENT 'type of gateway; can be Public/Private/Vpn',

-  `network_id` bigint unsigned NOT NULL COMMENT 'network id vpc gateway belongs to',

-  `vpc_id` bigint unsigned NOT NULL COMMENT 'id of the vpc the gateway belongs to',

-  `zone_id` bigint unsigned NOT NULL COMMENT 'id of the zone the gateway belongs to',

-  `created` datetime COMMENT 'date created',

-  `account_id` bigint unsigned NOT NULL COMMENT 'owner id',

-  `domain_id` bigint unsigned NOT NULL COMMENT 'domain id',

-  `state` varchar(32) NOT NULL COMMENT 'what state the vpc gateway in',

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `fk_vpc_gateways__network_id` FOREIGN KEY `fk_vpc_gateways__network_id`(`network_id`) REFERENCES `networks`(`id`),

-  CONSTRAINT `fk_vpc_gateways__vpc_id` FOREIGN KEY `fk_vpc_gateways__vpc_id`(`vpc_id`) REFERENCES `vpc`(`id`),

-  CONSTRAINT `fk_vpc_gateways__zone_id` FOREIGN KEY `fk_vpc_gateways__zone_id`(`zone_id`) REFERENCES `data_center`(`id`),

-  CONSTRAINT `fk_vpc_gateways__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_vpc_gateways__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_vpc_gateways__uuid` UNIQUE (`uuid`),

-  INDEX `i_vpc_gateways__removed`(`removed`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`private_ip_address` (

-  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',

-  `ip_address` char(40) NOT NULL COMMENT 'ip address',

-  `network_id` bigint unsigned NOT NULL COMMENT 'id of the network ip belongs to',

-  `reservation_id` char(40) COMMENT 'reservation id',

-  `mac_address` varchar(17) COMMENT 'mac address',

-  `vpc_id` bigint unsigned COMMENT 'vpc this ip belongs to',

-  `taken` datetime COMMENT 'Date taken',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `fk_private_ip_address__vpc_id` FOREIGN KEY `fk_private_ip_address__vpc_id`(`vpc_id`) REFERENCES `vpc`(`id`),

-  CONSTRAINT `fk_private_ip_address__network_id` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`) ON DELETE CASCADE

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-

-CREATE TABLE `cloud`.`static_routes` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `vpc_gateway_id` bigint unsigned COMMENT 'id of the corresponding ip address',

-  `cidr` varchar(18) COMMENT 'cidr for the static route', 

-  `state` char(32) NOT NULL COMMENT 'current state of this rule',

-  `vpc_id` bigint unsigned COMMENT 'vpc the firewall rule is associated with',

-  `account_id` bigint unsigned NOT NULL COMMENT 'owner id',

-  `domain_id` bigint unsigned NOT NULL COMMENT 'domain id',

-  `created` datetime COMMENT 'Date created',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_static_routes__vpc_gateway_id` FOREIGN KEY(`vpc_gateway_id`) REFERENCES `vpc_gateways`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_static_routes__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_static_routes__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_static_routes__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_static_routes__uuid` UNIQUE (`uuid`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-

-ALTER TABLE `cloud`.`networks` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'vpc this network belongs to';

-ALTER TABLE `cloud`.`networks`ADD CONSTRAINT `fk_networks__vpc_id` FOREIGN KEY(`vpc_id`) REFERENCES `vpc`(`id`);

-

-ALTER TABLE `cloud`.`firewall_rules` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'vpc the firewall rule is associated with';

-ALTER TABLE `cloud`.`firewall_rules` ADD COLUMN `traffic_type` char(32) COMMENT 'the type of the rule, can be Ingress or Egress';

-ALTER TABLE `cloud`.`firewall_rules` MODIFY `ip_address_id` bigint unsigned COMMENT 'id of the corresponding ip address';

-ALTER TABLE `cloud`.`firewall_rules` ADD CONSTRAINT `fk_firewall_rules__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc`(`id`) ON DELETE CASCADE;

-

-

-ALTER TABLE `cloud`.`user_ip_address` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'vpc the ip address is associated with';

-ALTER TABLE `cloud`.`user_ip_address` ADD CONSTRAINT `fk_user_ip_address__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc`(`id`) ON DELETE CASCADE;

-

-ALTER TABLE `cloud`.`domain_router` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'correlated virtual router vpc ID';

-ALTER TABLE `cloud`.`domain_router` ADD CONSTRAINT `fk_domain_router__vpc_id` FOREIGN KEY `fk_domain_router__vpc_id`(`vpc_id`) REFERENCES `vpc`(`id`);

-

-

-ALTER TABLE `cloud`.`physical_network_service_providers` ADD COLUMN `networkacl_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Network ACL service provided';

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vpc.cleanup.interval', '3600', 'The interval (in seconds) between cleanup for Inactive VPCs');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vpc.max.networks', '3', 'Maximum number of networks per vpc');

-

-

-CREATE TABLE `cloud`.`counter` (

-  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',

-  `uuid` varchar(40),

-  `source` varchar(255) NOT NULL COMMENT 'source e.g. netscaler, snmp',

-  `name` varchar(255) NOT NULL COMMENT 'Counter name',

-  `value` varchar(255) NOT NULL COMMENT 'Value in case of source=snmp',

-  `removed` datetime COMMENT 'date removed if not null',

-  `created` datetime NOT NULL COMMENT 'date created',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `uc_counter__uuid` UNIQUE (`uuid`),

-  INDEX `i_counter__removed`(`removed`),

-  INDEX `i_counter__source`(`source`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`conditions` (

-  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',

-  `uuid` varchar(40),

-  `counter_id` bigint unsigned NOT NULL COMMENT 'Counter Id',

-  `threshold` bigint unsigned NOT NULL COMMENT 'threshold value for the given counter',

-  `relational_operator` char(2) COMMENT 'relational operator to be used upon the counter and condition',

-  `domain_id` bigint unsigned NOT NULL COMMENT 'domain the Condition belongs to',

-  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this Condition',

-  `removed` datetime COMMENT 'date removed if not null',

-  `created` datetime NOT NULL COMMENT 'date created',

-  PRIMARY KEY (`id`),

-  CONSTRAINT `fk_conditions__counter_id` FOREIGN KEY `fk_condition__counter_id`(`counter_id`) REFERENCES `counter`(`id`),

-  CONSTRAINT `fk_conditions__account_id` FOREIGN KEY `fk_condition__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_conditions__domain_id` FOREIGN KEY `fk_condition__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_conditions__uuid` UNIQUE (`uuid`),

-  INDEX `i_conditions__removed`(`removed`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`autoscale_vmprofiles` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `uuid` varchar(40),

-  `zone_id` bigint unsigned NOT NULL,

-  `domain_id` bigint unsigned NOT NULL,

-  `account_id` bigint unsigned NOT NULL,

-  `autoscale_user_id` bigint unsigned NOT NULL,

-  `service_offering_id` bigint unsigned NOT NULL,

-  `template_id` bigint unsigned NOT NULL,

-  `other_deploy_params` varchar(1024) COMMENT 'other deployment parameters that is in addition to zoneid,serviceofferingid,domainid',

-  `destroy_vm_grace_period` int unsigned COMMENT 'the time allowed for existing connections to get closed before a vm is destroyed',

-  `counter_params` varchar(1024) COMMENT 'the parameters for the counter to be used to get metric information from VMs',

-  `created` datetime NOT NULL COMMENT 'date created',

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_autoscale_vmprofiles__domain_id` FOREIGN KEY `fk_autoscale_vmprofiles__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_vmprofiles__account_id` FOREIGN KEY `fk_autoscale_vmprofiles__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_vmprofiles__autoscale_user_id` FOREIGN KEY `fk_autoscale_vmprofiles__autoscale_user_id` (`autoscale_user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_autoscale_vmprofiles__uuid` UNIQUE (`uuid`),

-  INDEX `i_autoscale_vmprofiles__removed`(`removed`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`autoscale_policies` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `uuid` varchar(40),

-  `domain_id` bigint unsigned NOT NULL,

-  `account_id` bigint unsigned NOT NULL,

-  `duration` int unsigned NOT NULL,

-  `quiet_time` int unsigned NOT NULL,

-  `action` varchar(15),

-  `created` datetime NOT NULL COMMENT 'date created',

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_autoscale_policies__domain_id` FOREIGN KEY `fk_autoscale_policies__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_policies__account_id` FOREIGN KEY `fk_autoscale_policies__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_autoscale_policies__uuid` UNIQUE (`uuid`),

-  INDEX `i_autoscale_policies__removed`(`removed`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`autoscale_vmgroups` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `uuid` varchar(40),

-  `zone_id` bigint unsigned NOT NULL,

-  `domain_id` bigint unsigned NOT NULL,

-  `account_id` bigint unsigned NOT NULL,

-  `load_balancer_id` bigint unsigned NOT NULL,

-  `min_members` int unsigned DEFAULT 1,

-  `max_members` int unsigned NOT NULL,

-  `member_port` int unsigned NOT NULL,

-  `interval` int unsigned NOT NULL,

-  `profile_id` bigint unsigned NOT NULL,

-  `state` varchar(255) NOT NULL COMMENT 'enabled or disabled, a vmgroup is disabled to stop autoscaling activity',

-  `created` datetime NOT NULL COMMENT 'date created',

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_autoscale_vmgroup__autoscale_vmprofile_id` FOREIGN KEY(`profile_id`) REFERENCES `autoscale_vmprofiles`(`id`),

-  CONSTRAINT `fk_autoscale_vmgroup__load_balancer_id` FOREIGN KEY(`load_balancer_id`) REFERENCES `load_balancing_rules`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_vmgroups__domain_id` FOREIGN KEY `fk_autoscale_vmgroups__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_vmgroups__account_id` FOREIGN KEY `fk_autoscale_vmgroups__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_vmgroups__zone_id` FOREIGN KEY `fk_autoscale_vmgroups__zone_id`(`zone_id`) REFERENCES `data_center`(`id`),

-  CONSTRAINT `uc_autoscale_vmgroups__uuid` UNIQUE (`uuid`),

-  INDEX `i_autoscale_vmgroups__removed`(`removed`),

-  INDEX `i_autoscale_vmgroups__load_balancer_id`(`load_balancer_id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`autoscale_policy_condition_map` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `policy_id` bigint unsigned NOT NULL,

-  `condition_id` bigint unsigned NOT NULL,

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_autoscale_policy_condition_map__policy_id` FOREIGN KEY `fk_autoscale_policy_condition_map__policy_id` (`policy_id`) REFERENCES `autoscale_policies` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_policy_condition_map__condition_id` FOREIGN KEY `fk_autoscale_policy_condition_map__condition_id` (`condition_id`) REFERENCES `conditions` (`id`),

-  INDEX `i_autoscale_policy_condition_map__policy_id`(`policy_id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`autoscale_vmgroup_policy_map` (

-  `id` bigint unsigned NOT NULL auto_increment,

-  `vmgroup_id` bigint unsigned NOT NULL,

-  `policy_id` bigint unsigned NOT NULL,

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_autoscale_vmgroup_policy_map__vmgroup_id` FOREIGN KEY `fk_autoscale_vmgroup_policy_map__vmgroup_id` (`vmgroup_id`) REFERENCES `autoscale_vmgroups` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_autoscale_vmgroup_policy_map__policy_id` FOREIGN KEY `fk_autoscale_vmgroup_policy_map__policy_id` (`policy_id`) REFERENCES `autoscale_policies` (`id`),

-  INDEX `i_autoscale_vmgroup_policy_map__vmgroup_id`(`vmgroup_id`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (1,'snmp','Linux User CPU - percentage', '1.3.6.1.4.1.2021.11.9.0', now());

-INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (2,'snmp','Linux System CPU - percentage', '1.3.6.1.4.1.2021.11.10.0', now());

-INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (3,'snmp','Linux CPU Idle - percentage', '1.3.6.1.4.1.2021.11.11.0', now());

-INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (100,'netscaler','Response Time - microseconds', 'RESPTIME', now());

-

-CREATE TABLE `cloud`.`s2s_vpn_gateway` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `addr_id` bigint unsigned NOT NULL,

-  `vpc_id` bigint unsigned NOT NULL,

-  `domain_id` bigint unsigned NOT NULL,

-  `account_id` bigint unsigned NOT NULL,

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_s2s_vpn_gateway__addr_id` FOREIGN KEY (`addr_id`) REFERENCES `user_ip_address` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_vpn_gateway__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_vpn_gateway__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_vpn_gateway__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_s2s_vpn_gateway__uuid` UNIQUE (`uuid`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`s2s_customer_gateway` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `name` varchar(255) NOT NULL,

-  `gateway_ip` char(40) NOT NULL,

-  `guest_cidr_list` varchar(200) NOT NULL,

-  `ipsec_psk` varchar(256),

-  `ike_policy` varchar(30) NOT NULL,

-  `esp_policy` varchar(30) NOT NULL,

-  `ike_lifetime` int NOT NULL DEFAULT 86400,

-  `esp_lifetime` int NOT NULL DEFAULT 3600,

-  `dpd` int(1) NOT NULL DEFAULT 0,

-  `domain_id` bigint unsigned NOT NULL,

-  `account_id` bigint unsigned NOT NULL,

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_s2s_customer_gateway__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_customer_gateway__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_s2s_customer_gateway__uuid` UNIQUE (`uuid`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-CREATE TABLE `cloud`.`s2s_vpn_connection` (

-  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',

-  `uuid` varchar(40),

-  `vpn_gateway_id` bigint unsigned NULL,

-  `customer_gateway_id` bigint unsigned NULL,

-  `state` varchar(32) NOT NULL,

-  `domain_id` bigint unsigned NOT NULL,

-  `account_id` bigint unsigned NOT NULL,

-  `created` datetime NOT NULL COMMENT 'date created',

-  `removed` datetime COMMENT 'date removed if not null',

-  PRIMARY KEY  (`id`),

-  CONSTRAINT `fk_s2s_vpn_connection__vpn_gateway_id` FOREIGN KEY (`vpn_gateway_id`) REFERENCES `s2s_vpn_gateway` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_vpn_connection__customer_gateway_id` FOREIGN KEY (`customer_gateway_id`) REFERENCES `s2s_customer_gateway` (`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_vpn_connection__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `fk_s2s_vpn_connection__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,

-  CONSTRAINT `uc_s2s_vpn_connection__uuid` UNIQUE (`uuid`)

-) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-

-ALTER TABLE `cloud`.`data_center` ADD COLUMN `is_local_storage_enabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Is local storage offering enabled for this data center; 1: enabled, 0: not';

-UPDATE `cloud`.`data_center` SET `is_local_storage_enabled` = IF ((SELECT `value` FROM `cloud`.`configuration` WHERE `name`='use.local.storage')='true', 1, 0) WHERE `removed` IS NULL;

-DELETE FROM `cloud`.`configuration` where name='use.local.storage';

-

-ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `max_data_volumes_limit` int unsigned DEFAULT 6 COMMENT 'Max. data volumes per VM supported by hypervisor';

-

-UPDATE `cloud`.`hypervisor_capabilities` SET `max_data_volumes_limit`=13 WHERE `hypervisor_type`='XenServer' AND (`hypervisor_version`='6.0' OR `hypervisor_version`='6.0.2');

-

-UPDATE `cloud`.`configuration` SET description='In second, timeout for creating volume from snapshot' WHERE name='create.volume.from.snapshot.wait';

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Account Defaults', 'DEFAULT', 'management-server', 'max.account.vpcs', '20', 'The default maximum number of vpcs that can be created for an account');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Project Defaults', 'DEFAULT', 'management-server', 'max.project.vpcs', '20', 'The default maximum number of vpcs that can be created for a project');

-

-UPDATE `cloud`.`configuration` SET category='Network' WHERE name='guest.domain.suffix';

-UPDATE `cloud`.`configuration` SET component='management-server' WHERE name='agent.lb.enabled';

-UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='backup.snapshot.wait';

-UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='copy.volume.wait';

-UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='create.volume.from.snapshot.wait';

-UPDATE `cloud`.`configuration` SET component='TemplateManager' WHERE name='primary.storage.download.wait';

-UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='storage.cleanup.enabled';

-UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='storage.cleanup.interval';

-UPDATE `cloud`.`configuration` SET description='Comma separated list of cidrs internal to the datacenter that can host template download servers, please note 0.0.0.0 is not a valid site ' WHERE name='secstorage.allowed.internal.sites';

-

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'site2site.vpn.vpngateway.connection.limit', '4', 'The maximum number of VPN connection per VPN gateway');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'site2site.vpn.customergateway.subnets.limit', '10', 'The maximum number of subnets per customer gateway');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Usage', 'DEFAULT', 'management-server', 'traffic.sentinel.include.zones', 'EXTERNAL', 'Traffic going into specified list of zones is metered. For metering all traffic leave this parameter empty');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Usage', 'DEFAULT', 'management-server', 'traffic.sentinel.exclude.zones', '', 'Traffic going into specified list of zones is not metered');

-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ha.workers', '5', 'Number of ha worker threads');

-

-DROP TABLE IF EXISTS `cloud`.`ovs_tunnel_account`;

-UPDATE `cloud`.`snapshots` set swift_id=null where swift_id=0;

-DELETE FROM `cloud`.`host_details` where name in ('storage.network.device1', 'storage.network.device2');

+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+-- 
+--   http://www.apache.org/licenses/LICENSE-2.0
+-- 
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT 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 3.0.4 to 3.0.5;
+
+CREATE TABLE `cloud`.`resource_tags` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `key` varchar(255),
+  `value` varchar(255),
+  `resource_id` bigint unsigned NOT NULL,
+  `resource_uuid` varchar(40),
+  `resource_type` varchar(255),
+  `customer` varchar(255),
+  `domain_id` bigint unsigned NOT NULL COMMENT 'foreign key to domain id',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this network',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_tags__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`),
+  CONSTRAINT `fk_tags__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`),
+  UNIQUE `i_tags__resource_id__resource_type__key`(`resource_id`, `resource_type`, `key`),
+  CONSTRAINT `uc_resource_tags__uuid` UNIQUE (`uuid`)
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`vpc_offerings` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40) NOT NULL,
+  `unique_name` varchar(64) UNIQUE COMMENT 'unique name of the vpc offering',
+  `name` varchar(255) COMMENT 'vpc name',
+  `display_text` varchar(255) COMMENT 'display text',
+  `state` char(32) COMMENT 'state of the vpc offering that has Disabled value by default',
+  `default` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if vpc offering is default',
+  `removed` datetime COMMENT 'date removed if not null',
+  `created` datetime NOT NULL COMMENT 'date created',
+  `service_offering_id` bigint unsigned COMMENT 'service offering id that virtual router is tied to',
+  PRIMARY KEY  (`id`),
+  INDEX `i_vpc__removed`(`removed`),
+  CONSTRAINT `fk_vpc_offerings__service_offering_id` FOREIGN KEY `fk_vpc_offerings__service_offering_id` (`service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE  `cloud`.`vpc_offering_service_map` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `vpc_offering_id` bigint unsigned NOT NULL COMMENT 'vpc_offering_id',
+  `service` varchar(255) NOT NULL COMMENT 'service',
+  `provider` varchar(255) COMMENT 'service provider',
+  `created` datetime COMMENT 'date created',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_vpc_offering_service_map__vpc_offering_id` FOREIGN KEY(`vpc_offering_id`) REFERENCES `vpc_offerings`(`id`) ON DELETE CASCADE,
+  UNIQUE (`vpc_offering_id`, `service`, `provider`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`vpc` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40) NOT NULL,
+  `name` varchar(255) COMMENT 'vpc name',
+  `display_text` varchar(255) COMMENT 'vpc display text',
+  `cidr` varchar(18) COMMENT 'vpc cidr',
+  `vpc_offering_id` bigint unsigned NOT NULL COMMENT 'vpc offering id that this vpc is created from',
+  `zone_id` bigint unsigned NOT NULL COMMENT 'the id of the zone this Vpc belongs to',
+  `state` varchar(32) NOT NULL COMMENT 'state of the VP (can be Enabled and Disabled)',
+  `domain_id` bigint unsigned NOT NULL COMMENT 'domain the vpc belongs to',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this vpc',
+  `network_domain` varchar(255) COMMENT 'network domain',
+  `removed` datetime COMMENT 'date removed if not null',
+  `created` datetime NOT NULL COMMENT 'date created',
+  `restart_required` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if restart is required for the VPC',
+  PRIMARY KEY  (`id`),
+  INDEX `i_vpc__removed`(`removed`),
+  CONSTRAINT `fk_vpc__zone_id` FOREIGN KEY `fk_vpc__zone_id` (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_vpc__vpc_offering_id` FOREIGN KEY (`vpc_offering_id`) REFERENCES `vpc_offerings`(`id`), 
+  CONSTRAINT `fk_vpc__account_id` FOREIGN KEY `fk_vpc__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_vpc__domain_id` FOREIGN KEY `fk_vpc__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `cloud`.`router_network_ref` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `router_id` bigint unsigned NOT NULL COMMENT 'router id',
+  `network_id` bigint unsigned NOT NULL COMMENT 'network id',
+  `guest_type` char(32) COMMENT 'type of guest network that can be shared or isolated',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_router_network_ref__networks_id` FOREIGN KEY (`network_id`) REFERENCES `networks`(`id`) ON DELETE CASCADE,
+  UNIQUE `i_router_network_ref__router_id__network_id`(`router_id`, `network_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `cloud`.`vpc_gateways` (
+  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',
+  `uuid` varchar(40),
+  `ip4_address` char(40) COMMENT 'ip4 address of the gateway',
+  `netmask` varchar(15) COMMENT 'netmask of the gateway',
+  `gateway` varchar(15) COMMENT 'gateway',
+  `vlan_tag` varchar(255),
+  `type` varchar(32) COMMENT 'type of gateway; can be Public/Private/Vpn',
+  `network_id` bigint unsigned NOT NULL COMMENT 'network id vpc gateway belongs to',
+  `vpc_id` bigint unsigned NOT NULL COMMENT 'id of the vpc the gateway belongs to',
+  `zone_id` bigint unsigned NOT NULL COMMENT 'id of the zone the gateway belongs to',
+  `created` datetime COMMENT 'date created',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner id',
+  `domain_id` bigint unsigned NOT NULL COMMENT 'domain id',
+  `state` varchar(32) NOT NULL COMMENT 'what state the vpc gateway in',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_vpc_gateways__network_id` FOREIGN KEY `fk_vpc_gateways__network_id`(`network_id`) REFERENCES `networks`(`id`),
+  CONSTRAINT `fk_vpc_gateways__vpc_id` FOREIGN KEY `fk_vpc_gateways__vpc_id`(`vpc_id`) REFERENCES `vpc`(`id`),
+  CONSTRAINT `fk_vpc_gateways__zone_id` FOREIGN KEY `fk_vpc_gateways__zone_id`(`zone_id`) REFERENCES `data_center`(`id`),
+  CONSTRAINT `fk_vpc_gateways__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_vpc_gateways__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_vpc_gateways__uuid` UNIQUE (`uuid`),
+  INDEX `i_vpc_gateways__removed`(`removed`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`private_ip_address` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+  `ip_address` char(40) NOT NULL COMMENT 'ip address',
+  `network_id` bigint unsigned NOT NULL COMMENT 'id of the network ip belongs to',
+  `reservation_id` char(40) COMMENT 'reservation id',
+  `mac_address` varchar(17) COMMENT 'mac address',
+  `vpc_id` bigint unsigned COMMENT 'vpc this ip belongs to',
+  `taken` datetime COMMENT 'Date taken',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_private_ip_address__vpc_id` FOREIGN KEY `fk_private_ip_address__vpc_id`(`vpc_id`) REFERENCES `vpc`(`id`),
+  CONSTRAINT `fk_private_ip_address__network_id` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `cloud`.`static_routes` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `vpc_gateway_id` bigint unsigned COMMENT 'id of the corresponding ip address',
+  `cidr` varchar(18) COMMENT 'cidr for the static route', 
+  `state` char(32) NOT NULL COMMENT 'current state of this rule',
+  `vpc_id` bigint unsigned COMMENT 'vpc the firewall rule is associated with',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner id',
+  `domain_id` bigint unsigned NOT NULL COMMENT 'domain id',
+  `created` datetime COMMENT 'Date created',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_static_routes__vpc_gateway_id` FOREIGN KEY(`vpc_gateway_id`) REFERENCES `vpc_gateways`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_static_routes__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_static_routes__account_id` FOREIGN KEY(`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_static_routes__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_static_routes__uuid` UNIQUE (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+ALTER TABLE `cloud`.`networks` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'vpc this network belongs to';
+ALTER TABLE `cloud`.`networks`ADD CONSTRAINT `fk_networks__vpc_id` FOREIGN KEY(`vpc_id`) REFERENCES `vpc`(`id`);
+
+ALTER TABLE `cloud`.`firewall_rules` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'vpc the firewall rule is associated with';
+ALTER TABLE `cloud`.`firewall_rules` ADD COLUMN `traffic_type` char(32) COMMENT 'the type of the rule, can be Ingress or Egress';
+ALTER TABLE `cloud`.`firewall_rules` MODIFY `ip_address_id` bigint unsigned COMMENT 'id of the corresponding ip address';
+ALTER TABLE `cloud`.`firewall_rules` ADD CONSTRAINT `fk_firewall_rules__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc`(`id`) ON DELETE CASCADE;
+
+
+ALTER TABLE `cloud`.`user_ip_address` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'vpc the ip address is associated with';
+ALTER TABLE `cloud`.`user_ip_address` ADD CONSTRAINT `fk_user_ip_address__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc`(`id`) ON DELETE CASCADE;
+
+ALTER TABLE `cloud`.`domain_router` ADD COLUMN `vpc_id` bigint unsigned COMMENT 'correlated virtual router vpc ID';
+ALTER TABLE `cloud`.`domain_router` ADD CONSTRAINT `fk_domain_router__vpc_id` FOREIGN KEY `fk_domain_router__vpc_id`(`vpc_id`) REFERENCES `vpc`(`id`);
+
+
+ALTER TABLE `cloud`.`physical_network_service_providers` ADD COLUMN `networkacl_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Network ACL service provided';
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vpc.cleanup.interval', '3600', 'The interval (in seconds) between cleanup for Inactive VPCs');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vpc.max.networks', '3', 'Maximum number of networks per vpc');
+
+
+CREATE TABLE `cloud`.`counter` (
+  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',
+  `uuid` varchar(40),
+  `source` varchar(255) NOT NULL COMMENT 'source e.g. netscaler, snmp',
+  `name` varchar(255) NOT NULL COMMENT 'Counter name',
+  `value` varchar(255) NOT NULL COMMENT 'Value in case of source=snmp',
+  `removed` datetime COMMENT 'date removed if not null',
+  `created` datetime NOT NULL COMMENT 'date created',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `uc_counter__uuid` UNIQUE (`uuid`),
+  INDEX `i_counter__removed`(`removed`),
+  INDEX `i_counter__source`(`source`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`conditions` (
+  `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id',
+  `uuid` varchar(40),
+  `counter_id` bigint unsigned NOT NULL COMMENT 'Counter Id',
+  `threshold` bigint unsigned NOT NULL COMMENT 'threshold value for the given counter',
+  `relational_operator` char(2) COMMENT 'relational operator to be used upon the counter and condition',
+  `domain_id` bigint unsigned NOT NULL COMMENT 'domain the Condition belongs to',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this Condition',
+  `removed` datetime COMMENT 'date removed if not null',
+  `created` datetime NOT NULL COMMENT 'date created',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_conditions__counter_id` FOREIGN KEY `fk_condition__counter_id`(`counter_id`) REFERENCES `counter`(`id`),
+  CONSTRAINT `fk_conditions__account_id` FOREIGN KEY `fk_condition__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_conditions__domain_id` FOREIGN KEY `fk_condition__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_conditions__uuid` UNIQUE (`uuid`),
+  INDEX `i_conditions__removed`(`removed`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`autoscale_vmprofiles` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `uuid` varchar(40),
+  `zone_id` bigint unsigned NOT NULL,
+  `domain_id` bigint unsigned NOT NULL,
+  `account_id` bigint unsigned NOT NULL,
+  `autoscale_user_id` bigint unsigned NOT NULL,
+  `service_offering_id` bigint unsigned NOT NULL,
+  `template_id` bigint unsigned NOT NULL,
+  `other_deploy_params` varchar(1024) COMMENT 'other deployment parameters that is in addition to zoneid,serviceofferingid,domainid',
+  `destroy_vm_grace_period` int unsigned COMMENT 'the time allowed for existing connections to get closed before a vm is destroyed',
+  `counter_params` varchar(1024) COMMENT 'the parameters for the counter to be used to get metric information from VMs',
+  `created` datetime NOT NULL COMMENT 'date created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_autoscale_vmprofiles__domain_id` FOREIGN KEY `fk_autoscale_vmprofiles__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_vmprofiles__account_id` FOREIGN KEY `fk_autoscale_vmprofiles__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_vmprofiles__autoscale_user_id` FOREIGN KEY `fk_autoscale_vmprofiles__autoscale_user_id` (`autoscale_user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_autoscale_vmprofiles__uuid` UNIQUE (`uuid`),
+  INDEX `i_autoscale_vmprofiles__removed`(`removed`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`autoscale_policies` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `uuid` varchar(40),
+  `domain_id` bigint unsigned NOT NULL,
+  `account_id` bigint unsigned NOT NULL,
+  `duration` int unsigned NOT NULL,
+  `quiet_time` int unsigned NOT NULL,
+  `action` varchar(15),
+  `created` datetime NOT NULL COMMENT 'date created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_autoscale_policies__domain_id` FOREIGN KEY `fk_autoscale_policies__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_policies__account_id` FOREIGN KEY `fk_autoscale_policies__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_autoscale_policies__uuid` UNIQUE (`uuid`),
+  INDEX `i_autoscale_policies__removed`(`removed`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`autoscale_vmgroups` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `uuid` varchar(40),
+  `zone_id` bigint unsigned NOT NULL,
+  `domain_id` bigint unsigned NOT NULL,
+  `account_id` bigint unsigned NOT NULL,
+  `load_balancer_id` bigint unsigned NOT NULL,
+  `min_members` int unsigned DEFAULT 1,
+  `max_members` int unsigned NOT NULL,
+  `member_port` int unsigned NOT NULL,
+  `interval` int unsigned NOT NULL,
+  `profile_id` bigint unsigned NOT NULL,
+  `state` varchar(255) NOT NULL COMMENT 'enabled or disabled, a vmgroup is disabled to stop autoscaling activity',
+  `created` datetime NOT NULL COMMENT 'date created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_autoscale_vmgroup__autoscale_vmprofile_id` FOREIGN KEY(`profile_id`) REFERENCES `autoscale_vmprofiles`(`id`),
+  CONSTRAINT `fk_autoscale_vmgroup__load_balancer_id` FOREIGN KEY(`load_balancer_id`) REFERENCES `load_balancing_rules`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_vmgroups__domain_id` FOREIGN KEY `fk_autoscale_vmgroups__domain_id` (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_vmgroups__account_id` FOREIGN KEY `fk_autoscale_vmgroups__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_vmgroups__zone_id` FOREIGN KEY `fk_autoscale_vmgroups__zone_id`(`zone_id`) REFERENCES `data_center`(`id`),
+  CONSTRAINT `uc_autoscale_vmgroups__uuid` UNIQUE (`uuid`),
+  INDEX `i_autoscale_vmgroups__removed`(`removed`),
+  INDEX `i_autoscale_vmgroups__load_balancer_id`(`load_balancer_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`autoscale_policy_condition_map` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `policy_id` bigint unsigned NOT NULL,
+  `condition_id` bigint unsigned NOT NULL,
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_autoscale_policy_condition_map__policy_id` FOREIGN KEY `fk_autoscale_policy_condition_map__policy_id` (`policy_id`) REFERENCES `autoscale_policies` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_policy_condition_map__condition_id` FOREIGN KEY `fk_autoscale_policy_condition_map__condition_id` (`condition_id`) REFERENCES `conditions` (`id`),
+  INDEX `i_autoscale_policy_condition_map__policy_id`(`policy_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`autoscale_vmgroup_policy_map` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `vmgroup_id` bigint unsigned NOT NULL,
+  `policy_id` bigint unsigned NOT NULL,
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_autoscale_vmgroup_policy_map__vmgroup_id` FOREIGN KEY `fk_autoscale_vmgroup_policy_map__vmgroup_id` (`vmgroup_id`) REFERENCES `autoscale_vmgroups` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_autoscale_vmgroup_policy_map__policy_id` FOREIGN KEY `fk_autoscale_vmgroup_policy_map__policy_id` (`policy_id`) REFERENCES `autoscale_policies` (`id`),
+  INDEX `i_autoscale_vmgroup_policy_map__vmgroup_id`(`vmgroup_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (1,'snmp','Linux User CPU - percentage', '1.3.6.1.4.1.2021.11.9.0', now());
+INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (2,'snmp','Linux System CPU - percentage', '1.3.6.1.4.1.2021.11.10.0', now());
+INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (3,'snmp','Linux CPU Idle - percentage', '1.3.6.1.4.1.2021.11.11.0', now());
+INSERT INTO `cloud`.`counter` (id, source, name, value,created) VALUES (100,'netscaler','Response Time - microseconds', 'RESPTIME', now());
+
+CREATE TABLE `cloud`.`s2s_vpn_gateway` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `addr_id` bigint unsigned NOT NULL,
+  `vpc_id` bigint unsigned NOT NULL,
+  `domain_id` bigint unsigned NOT NULL,
+  `account_id` bigint unsigned NOT NULL,
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_s2s_vpn_gateway__addr_id` FOREIGN KEY (`addr_id`) REFERENCES `user_ip_address` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_vpn_gateway__vpc_id` FOREIGN KEY (`vpc_id`) REFERENCES `vpc` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_vpn_gateway__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_vpn_gateway__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_s2s_vpn_gateway__uuid` UNIQUE (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`s2s_customer_gateway` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `name` varchar(255) NOT NULL,
+  `gateway_ip` char(40) NOT NULL,
+  `guest_cidr_list` varchar(200) NOT NULL,
+  `ipsec_psk` varchar(256),
+  `ike_policy` varchar(30) NOT NULL,
+  `esp_policy` varchar(30) NOT NULL,
+  `ike_lifetime` int NOT NULL DEFAULT 86400,
+  `esp_lifetime` int NOT NULL DEFAULT 3600,
+  `dpd` int(1) NOT NULL DEFAULT 0,
+  `domain_id` bigint unsigned NOT NULL,
+  `account_id` bigint unsigned NOT NULL,
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_s2s_customer_gateway__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_customer_gateway__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_s2s_customer_gateway__uuid` UNIQUE (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cloud`.`s2s_vpn_connection` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `vpn_gateway_id` bigint unsigned NULL,
+  `customer_gateway_id` bigint unsigned NULL,
+  `state` varchar(32) NOT NULL,
+  `domain_id` bigint unsigned NOT NULL,
+  `account_id` bigint unsigned NOT NULL,
+  `created` datetime NOT NULL COMMENT 'date created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_s2s_vpn_connection__vpn_gateway_id` FOREIGN KEY (`vpn_gateway_id`) REFERENCES `s2s_vpn_gateway` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_vpn_connection__customer_gateway_id` FOREIGN KEY (`customer_gateway_id`) REFERENCES `s2s_customer_gateway` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_vpn_connection__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_s2s_vpn_connection__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `uc_s2s_vpn_connection__uuid` UNIQUE (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ALTER TABLE `cloud`.`data_center` ADD COLUMN `is_local_storage_enabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Is local storage offering enabled for this data center; 1: enabled, 0: not';
+UPDATE `cloud`.`data_center` SET `is_local_storage_enabled` = IF ((SELECT `value` FROM `cloud`.`configuration` WHERE `name`='use.local.storage')='true', 1, 0) WHERE `removed` IS NULL;
+DELETE FROM `cloud`.`configuration` where name='use.local.storage';
+
+ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `max_data_volumes_limit` int unsigned DEFAULT 6 COMMENT 'Max. data volumes per VM supported by hypervisor';
+
+UPDATE `cloud`.`hypervisor_capabilities` SET `max_data_volumes_limit`=13 WHERE `hypervisor_type`='XenServer' AND (`hypervisor_version`='6.0' OR `hypervisor_version`='6.0.2');
+
+UPDATE `cloud`.`configuration` SET description='In second, timeout for creating volume from snapshot' WHERE name='create.volume.from.snapshot.wait';
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Account Defaults', 'DEFAULT', 'management-server', 'max.account.vpcs', '20', 'The default maximum number of vpcs that can be created for an account');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Project Defaults', 'DEFAULT', 'management-server', 'max.project.vpcs', '20', 'The default maximum number of vpcs that can be created for a project');
+
+UPDATE `cloud`.`configuration` SET category='Network' WHERE name='guest.domain.suffix';
+UPDATE `cloud`.`configuration` SET component='management-server' WHERE name='agent.lb.enabled';
+UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='backup.snapshot.wait';
+UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='copy.volume.wait';
+UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='create.volume.from.snapshot.wait';
+UPDATE `cloud`.`configuration` SET component='TemplateManager' WHERE name='primary.storage.download.wait';
+UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='storage.cleanup.enabled';
+UPDATE `cloud`.`configuration` SET component='StorageManager' WHERE name='storage.cleanup.interval';
+UPDATE `cloud`.`configuration` SET description='Comma separated list of cidrs internal to the datacenter that can host template download servers, please note 0.0.0.0 is not a valid site ' WHERE name='secstorage.allowed.internal.sites';
+
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'site2site.vpn.vpngateway.connection.limit', '4', 'The maximum number of VPN connection per VPN gateway');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'site2site.vpn.customergateway.subnets.limit', '10', 'The maximum number of subnets per customer gateway');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Usage', 'DEFAULT', 'management-server', 'traffic.sentinel.include.zones', 'EXTERNAL', 'Traffic going into specified list of zones is metered. For metering all traffic leave this parameter empty');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Usage', 'DEFAULT', 'management-server', 'traffic.sentinel.exclude.zones', '', 'Traffic going into specified list of zones is not metered');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'ha.workers', '5', 'Number of ha worker threads');
+
+DROP TABLE IF EXISTS `cloud`.`ovs_tunnel_account`;
+UPDATE `cloud`.`snapshots` set swift_id=null where swift_id=0;
+DELETE FROM `cloud`.`host_details` where name in ('storage.network.device1', 'storage.network.device2');
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-305to306-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-305to306-cleanup.sql
index 2afbc27..f15ad4f 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-305to306-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-305to306-cleanup.sql
@@ -23,4 +23,4 @@
 DELETE FROM `cloud`.`storage_pool_host_ref` WHERE `cloud`.`storage_pool_host_ref`.`pool_id` IN (SELECT `cloud`.`storage_pool`.`id` FROM `cloud`.`storage_pool` WHERE `cloud`.`storage_pool`.`removed` IS NOT NULL);
 
 ALTER TABLE `cloud`.`sync_queue` DROP COLUMN queue_proc_msid;
-ALTER TABLE `cloud`.`sync_queue` DROP COLUMN queue_proc_time;
\ No newline at end of file
+ALTER TABLE `cloud`.`sync_queue` DROP COLUMN queue_proc_time;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-306to307.sql b/engine/schema/src/main/resources/META-INF/db/schema-306to307.sql
index bad23c1..a43833e 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-306to307.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-306to307.sql
@@ -19,4 +19,4 @@
 
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'network.loadbalancer.haproxy.max.conn', '4096', 'Load Balancer(haproxy) maximum number of concurrent connections(global max)');
 
-ALTER TABLE `cloud`.`network_offerings` ADD COLUMN `concurrent_connections` int(10) unsigned COMMENT 'concurrent connections supported on this network';
\ No newline at end of file
+ALTER TABLE `cloud`.`network_offerings` ADD COLUMN `concurrent_connections` int(10) unsigned COMMENT 'concurrent connections supported on this network';
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-307to410-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-307to410-cleanup.sql
index 6a9e2af..4b00fde 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-307to410-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-307to410-cleanup.sql
@@ -35,9 +35,3 @@
 
 ALTER TABLE `cloud`.`network_offerings` DROP COLUMN `concurrent_connections`;
 ALTER TABLE `cloud`.`network_offerings` CHANGE COLUMN `concurrent_connections1` `concurrent_connections` int(10) unsigned COMMENT 'Load Balancer(haproxy) maximum number of concurrent connections(global max)';
-
-
-
-
-
-
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 e538785..944d910 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
@@ -51,7 +51,7 @@
 INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `description`) VALUES ('Advanced', 'DEFAULT', 'management-server', 'event.purge.interval', '86400', 'The interval (in seconds) to wait before running the event purge thread');
 -- rrq 5839
 -- Remove the unique constraint on physical_network_id, provider_name from physical_network_service_providers
--- Because the name of this contraint is not set we need this roundabout way
+-- Because the name of this constraint is not set we need this roundabout way
 -- The key is also used by the foreign key constraint so drop and recreate that one
 ALTER TABLE physical_network_service_providers DROP FOREIGN KEY fk_pnetwork_service_providers__physical_network_id;
 SET @constraintname = (select CONCAT(CONCAT('DROP INDEX ', A.CONSTRAINT_NAME), ' ON physical_network_service_providers' )
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-40to410-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-40to410-cleanup.sql
index 411b568..a10361b 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-40to410-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-40to410-cleanup.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema cleanup from 4.0.0 to 4.1.0;
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-40to410.sql b/engine/schema/src/main/resources/META-INF/db/schema-40to410.sql
index 3d6dc65..1b3a29b 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-40to410.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-40to410.sql
@@ -1668,4 +1668,3 @@
 
 UPDATE `cloud`.`configuration` set category='Advanced' where category='Advanced ';
 UPDATE `cloud`.`configuration` set category='Hidden' where category='Hidden ';
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41000to41100-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41000to41100-cleanup.sql
index f8d9ce9..d7a080e 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41000to41100-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41000to41100-cleanup.sql
@@ -66,4 +66,4 @@
             left join
         `cloud`.`async_job` ON async_job.instance_id = user.id
             and async_job.instance_type = 'User'
-            and async_job.job_status = 0;
\ No newline at end of file
+            and async_job.job_status = 0;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-410to420-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-410to420-cleanup.sql
index b65717f..2a7ab6a 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-410to420-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-410to420-cleanup.sql
@@ -24,5 +24,3 @@
 ALTER TABLE `cloud`.`remote_access_vpn` DROP primary key;
 ALTER TABLE `cloud`.`remote_access_vpn` ADD primary key (`id`);
 ALTER TABLE `cloud`.`remote_access_vpn` ADD CONSTRAINT `fk_remote_access_vpn__vpn_server_addr_id` FOREIGN KEY (`vpn_server_addr_id`) REFERENCES `user_ip_address` (`id`);
-
-
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 5fd1ee7..3556e7e 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
@@ -154,7 +154,7 @@
         `cloud`.`image_store_details` ON image_store_details.store_id = image_store.id;
 
             
--- here we have to allow null for store_id to accomodate baremetal case to search for ready templates since template state is only stored in this table
+-- here we have to allow null for store_id to accommodate baremetal case to search for ready templates since template state is only stored in this table
 -- FK also commented out due to this            
 CREATE TABLE  `cloud`.`template_store_ref` (
   `id` bigint unsigned NOT NULL auto_increment,
@@ -525,9 +525,9 @@
             left join
         `cloud`.`event` eve ON event.start_id = eve.id;
 
-ALTER TABLE `cloud`.`region` ADD COLUMN `portableip_service_enabled` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Portable IP service enalbed in the Region';
+ALTER TABLE `cloud`.`region` ADD COLUMN `portableip_service_enabled` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Portable IP service enabled in the Region';
 
-ALTER TABLE `cloud`.`region` ADD COLUMN `gslb_service_enabled` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT 'Is GSLB service enalbed in the Region';
+ALTER TABLE `cloud`.`region` ADD COLUMN `gslb_service_enabled` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT 'Is GSLB service enabled in the Region';
 
 ALTER TABLE `cloud`.`external_load_balancer_devices` ADD COLUMN `is_gslb_provider` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer appliance is acting as gslb service provider in the zone';
 
@@ -1986,7 +1986,7 @@
   `uuid` varchar(255) UNIQUE,
   `vnet_range` varchar(255) NOT NULL COMMENT 'dedicated guest vlan range',
   `account_id` bigint unsigned NOT NULL COMMENT 'account id. foreign key to account table',
-  `physical_network_id` bigint unsigned NOT NULL COMMENT 'physical network id. foreign key to the the physical network table',
+  `physical_network_id` bigint unsigned NOT NULL COMMENT 'physical network id. foreign key to the physical network table',
   PRIMARY KEY (`id`),
   CONSTRAINT `fk_account_vnet_map__physical_network_id` FOREIGN KEY (`physical_network_id`) REFERENCES `physical_network` (`id`) ON DELETE CASCADE,
   INDEX `i_account_vnet_map__physical_network_id`(`physical_network_id`),
@@ -2067,7 +2067,7 @@
 
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'VpcManager', 'blacklisted.routes', NULL, 'Routes that are blacklisted, can not be used for Static Routes creation for the VPC Private Gateway');
 
-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'enable.dynamic.scale.vm', 'false', 'Enables/Diables dynamically scaling a vm');
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'enable.dynamic.scale.vm', 'false', 'Enables/Disables dynamically scaling a vm');
 
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'scale.retry', '2', 'Number of times to retry scaling up the vm');
 
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41110to41120.sql b/engine/schema/src/main/resources/META-INF/db/schema-41110to41120.sql
index 8b1b9d9..110d13d 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41110to41120.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41110to41120.sql
@@ -27,4 +27,4 @@
 ALTER TABLE `cloud`.`user_vm_clone_setting`
 ADD COLUMN `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
 DROP PRIMARY KEY,
-ADD PRIMARY KEY (`id`);
\ No newline at end of file
+ADD PRIMARY KEY (`id`);
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41310to41400.sql b/engine/schema/src/main/resources/META-INF/db/schema-41310to41400.sql
index baa7bcf..fbbf0a2 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41310to41400.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41310to41400.sql
@@ -20,7 +20,7 @@
 --;
 
 -- Update the description to indicate this only works with KVM + Ceph 
--- (not implemented properly atm for KVM+NFS/local, and it accidentaly works with XS + NFS. Not applicable for VMware)
+-- (not implemented properly atm for KVM+NFS/local, and it accidentally works with XS + NFS. Not applicable for VMware)
 UPDATE `cloud`.`configuration` SET `description`='Indicates whether to always backup primary storage snapshot to secondary storage. Keeping snapshots only on Primary storage is applicable for KVM + Ceph only.' WHERE  `name`='snapshot.backup.to.secondary';
 
 -- KVM: enable storage data motion on KVM hypervisor_capabilities
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41500to41510-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41500to41510-cleanup.sql
index 0939030..9866ed0 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41500to41510-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41500to41510-cleanup.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema upgrade cleanup from 4.15.0.0 to 4.15.1.0
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql b/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql
index 97a4a3e..8b9e000 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql
@@ -35,4 +35,3 @@
 -- Add support for VMware 7.0.1.0
 INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, max_hosts_per_cluster, storage_motion_supported, vm_snapshot_enabled) values (UUID(), 'VMware', '7.0.1.0', 1024, 0, 59, 64, 1, 1);
 INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) SELECT UUID(),'VMware', '7.0.1.0', guest_os_name, guest_os_id, utc_timestamp(), 0  FROM `cloud`.`guest_os_hypervisor` WHERE hypervisor_type='VMware' AND hypervisor_version='7.0';
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41510to41520-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41510to41520-cleanup.sql
index e36ac30..fad2403 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41510to41520-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41510to41520-cleanup.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema upgrade cleanup from 4.15.1.0 to 4.15.2.0
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41600to41610-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41600to41610-cleanup.sql
index 9db01dd..9993611 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41600to41610-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41600to41610-cleanup.sql
@@ -17,4 +17,4 @@
 
 --;
 -- Schema upgrade cleanup from 4.16.0.0 to 4.16.1.0
---;
\ No newline at end of file
+--;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700-cleanup.sql
index 667168b..3310fe4 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700-cleanup.sql
@@ -17,4 +17,4 @@
 
 --;
 -- Schema upgrade cleanup from 4.16.1.0 to 4.17.0.0
---;
\ No newline at end of file
+--;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql
index c259c14..8417ec2 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql
@@ -968,4 +968,4 @@
 ;END;
 
 CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (2, 'Debian GNU/Linux 11 (64-bit)', 'XenServer', '8.2.1', 'Debian Bullseye 11');
-CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (2, 'Debian GNU/Linux 11 (32-bit)', 'XenServer', '8.2.1', 'Debian Bullseye 11');
\ No newline at end of file
+CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (2, 'Debian GNU/Linux 11 (32-bit)', 'XenServer', '8.2.1', 'Debian Bullseye 11');
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41700to41710-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41700to41710-cleanup.sql
index a6bdbfc..0426797 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41700to41710-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41700to41710-cleanup.sql
@@ -17,4 +17,4 @@
 
 --;
 -- Schema upgrade cleanup from 4.17.0.0 to 4.17.1.0
---;
\ No newline at end of file
+--;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41700to41710.sql b/engine/schema/src/main/resources/META-INF/db/schema-41700to41710.sql
index 7b8ed8f..2ccd4a8 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41700to41710.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41700to41710.sql
@@ -128,4 +128,4 @@
             and async_job.job_status = 0;
 
 -- PR #6080 Change column `value` size from 255 to 4096 characters, matching the API "updateConfiguration" "value" size
-ALTER TABLE `cloud`.`account_details` MODIFY `value` VARCHAR(4096) NOT NULL;
\ No newline at end of file
+ALTER TABLE `cloud`.`account_details` MODIFY `value` VARCHAR(4096) NOT NULL;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql
index 7941424..c51d5a4 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql
@@ -402,7 +402,7 @@
 ALTER TABLE `cloud`.`load_balancing_rules`
 ADD cidr_list VARCHAR(4096);
 
--- savely add resources in parallel
+-- safely add resources in parallel
 -- PR#5984 Create table to persist VM stats.
 DROP TABLE IF EXISTS `cloud`.`resource_reservation`;
 CREATE TABLE `cloud`.`resource_reservation` (
@@ -1570,4 +1570,4 @@
 WHERE
   usage_type = 24 AND usage_display like '% io write';
 
-CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os', 'display', 'tinyint(1) DEFAULT ''1'' COMMENT ''should this guest_os be shown to the end user'' ');
\ No newline at end of file
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os', 'display', 'tinyint(1) DEFAULT ''1'' COMMENT ''should this guest_os be shown to the end user'' ');
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41810to41900-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900-cleanup.sql
new file mode 100644
index 0000000..ff0e780
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900-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.18.1.0 to 4.19.0.0
+--;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql
new file mode 100644
index 0000000..a6f45c2
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql
@@ -0,0 +1,325 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+--;
+-- Schema upgrade from 4.18.1.0 to 4.19.0.0
+--;
+
+ALTER TABLE `cloud`.`mshost` MODIFY COLUMN `state` varchar(25);
+
+UPDATE `cloud`.`network_offerings` SET conserve_mode=1 WHERE name='DefaultIsolatedNetworkOfferingForVpcNetworks';
+
+-- Invalidate existing console_session records
+UPDATE `cloud`.`console_session` SET removed=now();
+-- Modify acquired column in console_session to datetime type
+ALTER TABLE `cloud`.`console_session` DROP `acquired`, ADD `acquired` datetime COMMENT 'When the session was acquired' AFTER `host_id`;
+
+-- IP quarantine PR#7378
+CREATE TABLE IF NOT EXISTS `cloud`.`quarantined_ips` (
+  `id` bigint(20) unsigned NOT NULL auto_increment,
+  `uuid` varchar(255) UNIQUE,
+  `public_ip_address_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the quarantined public IP address, foreign key to `user_ip_address` table',
+  `previous_owner_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the previous owner of the public IP address, foreign key to `account` table',
+  `created` datetime NOT NULL,
+  `removed` datetime DEFAULT NULL,
+  `end_date` datetime NOT NULL,
+  `removal_reason` VARCHAR(255) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_quarantined_ips__public_ip_address_id` FOREIGN KEY(`public_ip_address_id`) REFERENCES `cloud`.`user_ip_address`(`id`),
+  CONSTRAINT `fk_quarantined_ips__previous_owner_id` FOREIGN KEY(`previous_owner_id`) REFERENCES `cloud`.`account`(`id`)
+);
+
+-- create_public_parameter_on_roles. #6960
+ALTER TABLE `cloud`.`roles` ADD COLUMN `public_role` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Indicates whether the role will be visible to all users (public) or only to root admins (private). If this parameter is not specified during the creation of the role its value will be defaulted to true (public).';
+
+-- Create heuristic table for dynamic allocating resources to the secondary storage
+CREATE TABLE IF NOT EXISTS `cloud`.`heuristics` (
+    `id` bigint(20) unsigned NOT NULL auto_increment,
+    `uuid` varchar(255) UNIQUE NOT NULL,
+    `name` text NOT NULL,
+    `description` text DEFAULT NULL,
+    `zone_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the zone to apply the heuristic, foreign key to `data_center` table',
+    `type` varchar(255) NOT NULL,
+    `heuristic_rule` text NOT NULL COMMENT 'JS script that defines to which secondary storage the resource will be allocated.',
+    `created` datetime NOT NULL,
+    `removed` datetime DEFAULT NULL,
+    PRIMARY KEY (`id`),
+    CONSTRAINT `fk_heuristics__zone_id` FOREIGN KEY(`zone_id`) REFERENCES `cloud`.`data_center`(`id`)
+);
+
+-- Add tables for VM Scheduler
+DROP TABLE IF EXISTS `cloud`.`vm_schedule`;
+CREATE TABLE `cloud`.`vm_schedule` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `vm_id` bigint unsigned NOT NULL,
+  `uuid` varchar(40) NOT NULL COMMENT 'schedule uuid',
+  `description` varchar(1024) COMMENT 'description of the vm schedule',
+  `schedule` varchar(255) NOT NULL COMMENT 'schedule frequency in cron format',
+  `timezone` varchar(100) NOT NULL COMMENT 'the timezone in which the schedule time is specified',
+  `action` varchar(20) NOT NULL COMMENT 'action to perform',
+  `enabled` int(1) NOT NULL COMMENT 'Enabled or disabled',
+  `start_date` datetime NOT NULL COMMENT 'start time for this schedule',
+  `end_date` datetime COMMENT 'end time for this schedule',
+  `created` datetime NOT NULL COMMENT 'date created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY (`id`),
+  INDEX `i_vm_schedule__vm_id`(`vm_id`),
+  INDEX `i_vm_schedule__enabled_end_date`(`enabled`, `end_date`),
+  CONSTRAINT `fk_vm_schedule__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `cloud`.`vm_scheduled_job`;
+CREATE TABLE `cloud`.`vm_scheduled_job` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `vm_id` bigint unsigned NOT NULL,
+  `vm_schedule_id` bigint unsigned NOT NULL,
+  `uuid` varchar(40) NOT NULL COMMENT 'scheduled job uuid',
+  `action` varchar(20) NOT NULL COMMENT 'action to perform',
+  `scheduled_timestamp` datetime NOT NULL COMMENT 'Time at which the action is taken',
+  `async_job_id` bigint unsigned DEFAULT NULL COMMENT 'If this schedule is being executed, it is the id of the create aysnc_job. Before that it is null',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY (`vm_schedule_id`, `scheduled_timestamp`),
+  INDEX `i_vm_scheduled_job__scheduled_timestamp`(`scheduled_timestamp`),
+  INDEX `i_vm_scheduled_job__vm_id`(`vm_id`),
+  CONSTRAINT `fk_vm_scheduled_job__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE,
+  CONSTRAINT `fk_vm_scheduled_job__vm_schedule_id` FOREIGN KEY (`vm_schedule_id`) REFERENCES `vm_schedule`(`id`) ON DELETE CASCADE
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Add support for different cluster types for kubernetes
+ALTER TABLE `cloud`.`kubernetes_cluster` ADD COLUMN `cluster_type` varchar(64) DEFAULT 'CloudManaged' COMMENT 'type of cluster';
+ALTER TABLE `cloud`.`kubernetes_cluster` MODIFY COLUMN `kubernetes_version_id` bigint unsigned NULL COMMENT 'the ID of the Kubernetes version of this Kubernetes cluster';
+
+-- Add indexes for data store browser
+ALTER TABLE `cloud`.`template_spool_ref` ADD INDEX `i_template_spool_ref__install_path`(`install_path`);
+ALTER TABLE `cloud`.`volumes` ADD INDEX `i_volumes__path`(`path`);
+ALTER TABLE `cloud`.`snapshot_store_ref` ADD INDEX `i_snapshot_store_ref__install_path`(`install_path`);
+ALTER TABLE `cloud`.`template_store_ref` ADD INDEX `i_template_store_ref__install_path`(`install_path`);
+
+-- Add table for image store object download
+DROP TABLE IF EXISTS `cloud`.`image_store_object_download`;
+CREATE TABLE `cloud`.`image_store_object_download` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `store_id` bigint unsigned NOT NULL COMMENT 'image store id',
+  `path` varchar(255) NOT NULL COMMENT 'path on store',
+  `download_url` varchar(255) NOT NULL COMMENT 'download url',
+  `created` datetime COMMENT 'date created',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY (`store_id`, `path`),
+  INDEX `i_image_store_object_download__created`(`created`),
+  CONSTRAINT `fk_image_store_object_download__store_id` FOREIGN KEY (`store_id`) REFERENCES `image_store`(`id`) ON DELETE CASCADE
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Set removed state for all removed accounts
+UPDATE `cloud`.`account` SET state='removed' WHERE `removed` IS NOT NULL;
+
+
+-- New tables for VNF
+CREATE TABLE IF NOT EXISTS `cloud`.`vnf_template_nics` (
+    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+    `template_id` bigint unsigned NOT NULL COMMENT 'id of the VNF template',
+    `device_id` bigint unsigned NOT NULL COMMENT 'Device id of the NIC when plugged into the VNF appliances',
+    `device_name` varchar(1024) NOT NULL COMMENT 'Name of the NIC',
+    `required` tinyint NOT NULL DEFAULT '1' COMMENT 'True if the NIC is required. False if optional',
+    `management` tinyint NOT NULL DEFAULT '1' COMMENT 'True if the NIC is a management interface',
+    `description` varchar(1024) COMMENT 'Description of the NIC',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_template_id_device_id` (`template_id`, `device_id`),
+    KEY `fk_vnf_template_nics__template_id` (`template_id`),
+    CONSTRAINT `fk_vnf_template_nics__template_id` FOREIGN KEY (`template_id`) REFERENCES `vm_template` (`id`) ON DELETE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE IF NOT EXISTS `cloud`.`vnf_template_details` (
+    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+    `template_id` bigint unsigned NOT NULL COMMENT 'id of the VNF template',
+    `name` varchar(255) NOT NULL,
+    `value` varchar(1024) NOT NULL,
+    `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user',
+    PRIMARY KEY (`id`),
+    KEY `fk_vnf_template_details__template_id` (`template_id`),
+    CONSTRAINT `fk_vnf_template_details__template_id` FOREIGN KEY (`template_id`) REFERENCES `vm_template` (`id`) ON DELETE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- Add tables for Cluster DRS
+DROP TABLE IF EXISTS `cloud`.`cluster_drs_plan`;
+CREATE TABLE `cloud`.`cluster_drs_plan` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `cluster_id` bigint unsigned NOT NULL,
+  `event_id` bigint unsigned NOT NULL,
+  `uuid` varchar(40) NOT NULL COMMENT 'schedule uuid',
+  `type` varchar(20) NOT NULL COMMENT 'type of plan',
+  `status` varchar(20) NOT NULL COMMENT 'status of plan',
+  `created` datetime NOT NULL COMMENT 'date created',
+  PRIMARY KEY (`id`),
+  INDEX `i_cluster_drs_plan__cluster_id_status`(`cluster_id`, `status`),
+  INDEX `i_cluster_drs_plan__status`(`status`),
+  INDEX `i_cluster_drs_plan__created`(`created`),
+  CONSTRAINT `fk_cluster_drs_plan__cluster_id` FOREIGN KEY (`cluster_id`) REFERENCES `cluster`(`id`) ON DELETE CASCADE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `cloud`.`cluster_drs_plan_migration`;
+CREATE TABLE `cloud`.`cluster_drs_plan_migration` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `plan_id` bigint unsigned NOT NULL,
+  `vm_id` bigint unsigned NOT NULL,
+  `src_host_id` bigint unsigned NOT NULL,
+  `dest_host_id` bigint unsigned NOT NULL,
+  `job_id` bigint unsigned NULL,
+  `status` varchar(20) NULL COMMENT 'status of async job',
+  PRIMARY KEY (`id`),
+  INDEX `i_cluster_drs_plan_migration__plan_id_status`(`plan_id`, `status`),
+  CONSTRAINT `fk_cluster_drs_plan_migration__plan_id` FOREIGN KEY (`plan_id`) REFERENCES `cluster_drs_plan`(`id`) ON DELETE CASCADE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8;
+
+INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('DRS', 'drs', 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Miscellaneous'));
+
+UPDATE `cloud`.`configuration`
+    SET subgroup_id = (SELECT id FROM `cloud`.`configuration_subgroup` WHERE name = 'DRS')
+    WHERE name IN ('drs.automatic.enable', 'drs.algorithm', 'drs.automatic.interval', 'drs.max.migrations', 'drs.imbalance', 'drs.metric', 'drs.plan.expire.interval');
+
+-- Add table for snapshot zone reference
+CREATE TABLE  `cloud`.`snapshot_zone_ref` (
+  `id` bigint unsigned NOT NULL auto_increment,
+  `zone_id` bigint unsigned NOT NULL,
+  `snapshot_id` bigint unsigned NOT NULL,
+  `created` DATETIME NOT NULL,
+  `last_updated` DATETIME,
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY  (`id`),
+  CONSTRAINT `fk_snapshot_zone_ref__zone_id` FOREIGN KEY `fk_snapshot_zone_ref__zone_id` (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE,
+  INDEX `i_snapshot_zone_ref__zone_id`(`zone_id`),
+  CONSTRAINT `fk_snapshot_zone_ref__snapshot_id` FOREIGN KEY `fk_snapshot_zone_ref__snapshot_id` (`snapshot_id`) REFERENCES `snapshots` (`id`) ON DELETE CASCADE,
+  INDEX `i_snapshot_zone_ref__snapshot_id`(`snapshot_id`),
+  INDEX `i_snapshot_zone_ref__removed`(`removed`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
+
+-- Alter snapshot_store_ref table to add download related fields
+ALTER TABLE `cloud`.`snapshot_store_ref`
+    ADD COLUMN `download_state` varchar(255) DEFAULT NULL COMMENT 'the state of the snapshot download' AFTER `volume_id`,
+    ADD COLUMN `download_pct` int unsigned DEFAULT NULL COMMENT 'the percentage of the snapshot download completed' AFTER `download_state`,
+    ADD COLUMN `error_str` varchar(255) DEFAULT NULL COMMENT 'the error message when the snapshot download occurs' AFTER `download_pct`,
+    ADD COLUMN `local_path` varchar(255) DEFAULT NULL COMMENT 'the path of the snapshot download' AFTER `error_str`,
+    ADD COLUMN `display` tinyint(1) unsigned NOT NULL DEFAULT 1  COMMENT '1 implies store reference is available for listing' AFTER `error_str`;
+
+UPDATE `cloud`.`configuration` SET
+    `options` = concat(`options`, ',OAUTH2'),
+    `default_value` = concat(`default_value`, ',OAUTH2'),
+    `value` = concat(`value`, ',OAUTH2')
+WHERE `name` = 'user.authenticators.order' ;
+
+UPDATE `cloud`.`configuration` SET
+    `options` = concat(`options`, ',OAUTH2Auth'),
+    `default_value` = concat(`default_value`, ',OAUTH2Auth'),
+    `value` = concat(`value`, ',OAUTH2Auth')
+where `name` = 'pluggableApi.authenticators.order' ;
+
+-- Create table for OAuth provider details
+DROP TABLE IF EXISTS `cloud`.`oauth_provider`;
+CREATE TABLE `cloud`.`oauth_provider` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40) NOT NULL COMMENT 'unique identifier',
+  `description` varchar(1024) COMMENT 'description of the provider',
+  `provider` varchar(40) NOT NULL COMMENT 'name of the provider',
+  `client_id` varchar(255) NOT NULL COMMENT 'client id which is configured in the provider',
+  `secret_key` varchar(255) NOT NULL COMMENT 'secret key which is configured in the provider',
+  `redirect_uri` varchar(255) NOT NULL COMMENT 'redirect uri which is configured in the provider',
+  `enabled` int(1) NOT NULL DEFAULT 1 COMMENT 'Enabled or disabled',
+  `created` datetime NOT NULL COMMENT 'date created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY (`id`)
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Flexible tags
+ALTER TABLE `cloud`.`storage_pool_tags` ADD COLUMN is_tag_a_rule int(1) UNSIGNED not null DEFAULT 0;
+
+ALTER TABLE `cloud`.`storage_pool_tags` MODIFY tag text NOT NULL;
+
+ALTER TABLE `cloud`.`host_tags` ADD COLUMN is_tag_a_rule int(1) UNSIGNED not null DEFAULT 0;
+
+ALTER TABLE `cloud`.`host_tags` MODIFY tag text NOT NULL;
+
+DROP TABLE IF EXISTS `cloud`.`object_store`;
+CREATE TABLE `cloud`.`object_store` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `name` varchar(255) NOT NULL COMMENT 'name of object store',
+  `object_provider_name` varchar(255) NOT NULL COMMENT 'id of object_store_provider',
+  `url` varchar(255) NOT NULL COMMENT 'url of the object store',
+  `uuid` varchar(255) COMMENT 'uuid of object store',
+  `created` datetime COMMENT 'date the object store first signed on',
+  `removed` datetime COMMENT 'date removed if not null',
+  `total_size` bigint unsigned COMMENT 'storage total size statistics',
+  `used_bytes` bigint unsigned COMMENT 'storage available bytes statistics',
+  PRIMARY KEY(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `cloud`.`object_store_details`;
+CREATE TABLE `cloud`.`object_store_details` (
+  `id` bigint unsigned UNIQUE NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `store_id` bigint unsigned NOT NULL COMMENT 'store 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',
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_object_store_details__store_id` FOREIGN KEY `fk_object_store__store_id`(`store_id`) REFERENCES `object_store`(`id`) ON DELETE CASCADE,
+  INDEX `i_object_store__name__value`(`name`(128), `value`(128))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `cloud`.`bucket`;
+CREATE TABLE `cloud`.`bucket` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `name` varchar(255) NOT NULL COMMENT 'name of bucket',
+  `object_store_id` varchar(255) NOT NULL COMMENT 'id of object_store',
+  `state` varchar(255) NOT NULL COMMENT 'state of the bucket',
+  `uuid` varchar(255) COMMENT 'uuid of bucket',
+  `domain_id` bigint unsigned NOT NULL COMMENT 'domain the bucket belongs to',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this bucket',
+  `size` bigint unsigned COMMENT 'total size of bucket objects',
+  `quota` bigint unsigned COMMENT 'Allocated bucket quota in GB',
+  `versioning` boolean COMMENT 'versioning enable/disable',
+  `encryption` boolean COMMENT 'encryption enable/disbale',
+  `object_lock` boolean COMMENT 'Lock objects in bucket',
+  `policy` varchar(255) COMMENT 'Bucket Access Policy',
+  `access_key` varchar(255) COMMENT 'Bucket Access Key',
+  `secret_key` varchar(255) COMMENT 'Bucket Secret Key',
+  `bucket_url` varchar(255) COMMENT 'URL to access bucket',
+  `created` datetime COMMENT 'date the bucket was created',
+  `removed` datetime COMMENT 'date removed if not null',
+  PRIMARY KEY(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `cloud`.`bucket_statistics`;
+CREATE TABLE `cloud`.`bucket_statistics` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this bucket',
+  `bucket_id` bigint unsigned NOT NULL COMMENT 'id of this bucket',
+  `size` bigint unsigned COMMENT 'total size of bucket objects',
+   PRIMARY KEY(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `cloud_usage`.`bucket_statistics`;
+CREATE TABLE `cloud_usage`.`bucket_statistics` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `account_id` bigint unsigned NOT NULL COMMENT 'owner of this bucket',
+  `bucket_id` bigint unsigned NOT NULL COMMENT 'id of this bucket',
+  `size` bigint unsigned COMMENT 'total size of bucket objects',
+   PRIMARY KEY(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Add remover account ID to quarantined IPs table.
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.quarantined_ips', 'remover_account_id', 'bigint(20) unsigned DEFAULT NULL COMMENT "ID of the account that removed the IP from quarantine, foreign key to `account` table"');
+
+-- Explicitly add support for VMware 8.0b (8.0.0.2), 8.0c (8.0.0.3)
+INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, max_hosts_per_cluster, storage_motion_supported, vm_snapshot_enabled) values (UUID(), 'VMware', '8.0.0.2', 1024, 0, 59, 64, 1, 1);
+INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, max_hosts_per_cluster, storage_motion_supported, vm_snapshot_enabled) values (UUID(), 'VMware', '8.0.0.3', 1024, 0, 59, 64, 1, 1);
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-cleanup.sql
new file mode 100644
index 0000000..b580d42
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-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.19.0.0 to 4.19.1.0
+--;
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql
new file mode 100644
index 0000000..e704d61
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910.sql
@@ -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.
+
+--;
+-- Schema upgrade from 4.19.0.0 to 4.19.1.0
+--;
+
+-- Updates the populated Quota tariff's types VM_DISK_BYTES_READ, VM_DISK_BYTES_WRITE, VM_DISK_IO_READ and VM_DISK_IO_WRITE to the correct unit.
+
+UPDATE cloud_usage.quota_tariff
+SET usage_unit = 'Bytes', updated_on = NOW()
+WHERE effective_on = '2010-05-04 00:00:00'
+AND name IN ('VM_DISK_BYTES_READ', 'VM_DISK_BYTES_WRITE');
+
+UPDATE cloud_usage.quota_tariff
+SET usage_unit = 'IOPS', updated_on = NOW()
+WHERE effective_on = '2010-05-04 00:00:00'
+AND name IN ('VM_DISK_IO_READ', 'VM_DISK_IO_WRITE');
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-420to421.sql b/engine/schema/src/main/resources/META-INF/db/schema-420to421.sql
index bf1acc1..b99af28 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-420to421.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-420to421.sql
@@ -238,7 +238,7 @@
 
 
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'baremetal.ipmi.lan.interface', 'default', 'option specified in -I option of impitool. candidates are: open/bmc/lipmi/lan/lanplus/free/imb, see ipmitool man page for details. default value "default" means using default option of ipmitool');
-INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'baremetal.ipmi.fail.retry', 'default', "ipmi interface will be temporary out of order after power opertions(e.g. cycle, on), it leads following commands fail immediately. The value specifies retry times before accounting it as real failure");
+INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'baremetal.ipmi.fail.retry', 'default', "ipmi interface will be temporary out of order after power operations(e.g. cycle, on), it leads following commands fail immediately. The value specifies retry times before accounting it as real failure");
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vmware.hung.wokervm.timeout', '7200', 'Worker VM timeout in seconds');
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Alert", 'DEFAULT', 'management-server', "alert.smtp.connectiontimeout", "30000", "Socket connection timeout value in milliseconds. -1 for infinite timeout.");
 INSERT IGNORE INTO `cloud`.`configuration` VALUES ("Alert", 'DEFAULT', 'management-server', "alert.smtp.timeout", "30000", "Socket I/O timeout value in milliseconds. -1 for infinite timeout.");
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-421to430-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-421to430-cleanup.sql
index 4a58e65..ce3b757 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-421to430-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-421to430-cleanup.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema cleanup from 4.2.0 to 4.3.0;
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-441to442.sql b/engine/schema/src/main/resources/META-INF/db/schema-441to442.sql
index 3b1618c..d90c83e 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-441to442.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-441to442.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema upgrade from 4.4.1 to 4.4.2;
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-442to450-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-442to450-cleanup.sql
index 33ad921..a8ea565 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-442to450-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-442to450-cleanup.sql
@@ -30,4 +30,4 @@
 
 UPDATE `cloud`.`configuration`
 SET value = 'XenServer'
-Where value = 'Xen';
\ No newline at end of file
+Where value = 'Xen';
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-442to450.sql b/engine/schema/src/main/resources/META-INF/db/schema-442to450.sql
index 06ee70b..90a52bd 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-442to450.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-442to450.sql
@@ -448,7 +448,7 @@
 
 UPDATE configuration SET value='KVM,XenServer,VMware,BareMetal,Ovm,LXC,Hyperv' WHERE name='hypervisor.list';
 UPDATE `cloud`.`configuration` SET description="If set to true, will set guest VM's name as it appears on the hypervisor, to its hostname. The flag is supported for VMware hypervisor only" WHERE name='vm.instancename.flag';
-INSERT IGNORE INTO `cloud`.`configuration`(category, instance, component, name, value, description, default_value) VALUES ('Advanced', 'DEFAULT', 'management-server', 'implicit.host.tags', 'GPU', 'Tag hosts at the time of host disovery based on the host properties/capabilities ', 'GPU');
+INSERT IGNORE INTO `cloud`.`configuration`(category, instance, component, name, value, description, default_value) VALUES ('Advanced', 'DEFAULT', 'management-server', 'implicit.host.tags', 'GPU', 'Tag hosts at the time of host discovery based on the host properties/capabilities ', 'GPU');
 
 DROP VIEW IF EXISTS `cloud`.`domain_router_view`;
 CREATE VIEW `cloud`.`domain_router_view` AS
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-452to460-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-452to460-cleanup.sql
index db5ce11..24083f9 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-452to460-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-452to460-cleanup.sql
@@ -20,4 +20,3 @@
 --
 
 DELETE FROM `cloud`.`configuration` where name='router.reboot.when.outofband.migrated';
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-452to460.sql b/engine/schema/src/main/resources/META-INF/db/schema-452to460.sql
index 1046a00..bb291fe 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-452to460.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-452to460.sql
@@ -419,5 +419,3 @@
 INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'KVM', 'default', 'CentOS 7', 246, utc_timestamp(), 0);
 
 UPDATE  `cloud`.`hypervisor_capabilities` SET  `max_data_volumes_limit` =  '32' WHERE  `hypervisor_capabilities`.`hypervisor_type` =  'KVM';
-
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-461to470.sql b/engine/schema/src/main/resources/META-INF/db/schema-461to470.sql
index 7c70199..238acb5 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-461to470.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-461to470.sql
@@ -250,4 +250,3 @@
 INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` values (25,UUID(),'VMware','6.0',128,0,13,32,1,1);
 INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '5.5', 'rhel7_64Guest', 245, utc_timestamp(), 0);
 INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) SELECT UUID(),'VMware', '6.0', guest_os_name, guest_os_id, utc_timestamp(), 0  FROM `cloud`.`guest_os_hypervisor` WHERE hypervisor_type='VMware' AND hypervisor_version='5.5' AND (guest_os_id NOT IN (1,2,3,4,62,63,64,65,156,157,221,222) AND guest_os_id NOT BETWEEN 121 AND 130);
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-480to481.sql b/engine/schema/src/main/resources/META-INF/db/schema-480to481.sql
index 4c8637b..3b2d40b 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-480to481.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-480to481.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema upgrade from 4.8.0 to 4.8.1;
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-481to490-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-481to490-cleanup.sql
index 0b426dc..1868a09 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-481to490-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-481to490-cleanup.sql
@@ -268,4 +268,3 @@
         `cloud`.`async_job` ON async_job.instance_id = user.id
             and async_job.instance_type = 'User'
             and async_job.job_status = 0;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-490to4910-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-490to4910-cleanup.sql
index 657713b..b0c4daf 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-490to4910-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-490to4910-cleanup.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema cleanup from 4.9.0 to 4.9.1.0;
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-4910to4920-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-4910to4920-cleanup.sql
index 963fda4..d4ffed5 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-4910to4920-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-4910to4920-cleanup.sql
@@ -18,4 +18,3 @@
 --;
 -- Schema cleanup from 4.9.1.0 to 4.9.2.0;
 --;
-
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_netstats_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_netstats_view.sql
new file mode 100644
index 0000000..11193c4
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_netstats_view.sql
@@ -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.
+
+-- cloud.account_netstats_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`account_netstats_view`;
+
+CREATE VIEW `cloud`.`account_netstats_view` AS
+select
+    `user_statistics`.`account_id` AS `account_id`,
+    (sum(`user_statistics`.`net_bytes_received`) + sum(`user_statistics`.`current_bytes_received`)) AS `bytesReceived`,
+    (sum(`user_statistics`.`net_bytes_sent`) + sum(`user_statistics`.`current_bytes_sent`)) AS `bytesSent`
+from
+    `user_statistics`
+group by
+    `user_statistics`.`account_id`;
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
new file mode 100644
index 0000000..27d70b4
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql
@@ -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.
+
+-- cloud.account_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`account_view`;
+
+CREATE VIEW `cloud`.`account_view` AS
+select
+    `account`.`id` AS `id`,
+    `account`.`uuid` AS `uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `type`,
+    `account`.`role_id` AS `role_id`,
+    `account`.`state` AS `state`,
+    `account`.`created` AS `created`,
+    `account`.`removed` AS `removed`,
+    `account`.`cleanup_needed` AS `cleanup_needed`,
+    `account`.`network_domain` AS `network_domain`,
+    `account`.`default` AS `default`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `account_netstats_view`.`bytesReceived` AS `bytesReceived`,
+    `account_netstats_view`.`bytesSent` AS `bytesSent`,
+    `vmlimit`.`max` AS `vmLimit`,
+    `vmcount`.`count` AS `vmTotal`,
+    `runningvm`.`vmcount` AS `runningVms`,
+    `stoppedvm`.`vmcount` AS `stoppedVms`,
+    `iplimit`.`max` AS `ipLimit`,
+    `ipcount`.`count` AS `ipTotal`,
+    `free_ip_view`.`free_ip` AS `ipFree`,
+    `volumelimit`.`max` AS `volumeLimit`,
+    `volumecount`.`count` AS `volumeTotal`,
+    `snapshotlimit`.`max` AS `snapshotLimit`,
+    `snapshotcount`.`count` AS `snapshotTotal`,
+    `templatelimit`.`max` AS `templateLimit`,
+    `templatecount`.`count` AS `templateTotal`,
+    `vpclimit`.`max` AS `vpcLimit`,
+    `vpccount`.`count` AS `vpcTotal`,
+    `projectlimit`.`max` AS `projectLimit`,
+    `projectcount`.`count` AS `projectTotal`,
+    `networklimit`.`max` AS `networkLimit`,
+    `networkcount`.`count` AS `networkTotal`,
+    `cpulimit`.`max` AS `cpuLimit`,
+    `cpucount`.`count` AS `cpuTotal`,
+    `memorylimit`.`max` AS `memoryLimit`,
+    `memorycount`.`count` AS `memoryTotal`,
+    `primary_storage_limit`.`max` AS `primaryStorageLimit`,
+    `primary_storage_count`.`count` AS `primaryStorageTotal`,
+    `secondary_storage_limit`.`max` AS `secondaryStorageLimit`,
+    `secondary_storage_count`.`count` AS `secondaryStorageTotal`,
+    `async_job`.`id` AS `job_id`,
+    `async_job`.`uuid` AS `job_uuid`,
+    `async_job`.`job_status` AS `job_status`,
+    `async_job`.`account_id` AS `job_account_id`
+from
+    (`free_ip_view`
+join ((((((((((((((((((((((((((((((`account`
+join `domain` on
+    ((`account`.`domain_id` = `domain`.`id`)))
+left join `data_center` on
+    ((`account`.`default_zone_id` = `data_center`.`id`)))
+left join `account_netstats_view` on
+    ((`account`.`id` = `account_netstats_view`.`account_id`)))
+left join `resource_limit` `vmlimit` on
+    (((`account`.`id` = `vmlimit`.`account_id`)
+        and (`vmlimit`.`type` = 'user_vm'))))
+left join `resource_count` `vmcount` on
+    (((`account`.`id` = `vmcount`.`account_id`)
+        and (`vmcount`.`type` = 'user_vm'))))
+left join `account_vmstats_view` `runningvm` on
+    (((`account`.`id` = `runningvm`.`account_id`)
+        and (`runningvm`.`state` = 'Running'))))
+left join `account_vmstats_view` `stoppedvm` on
+    (((`account`.`id` = `stoppedvm`.`account_id`)
+        and (`stoppedvm`.`state` = 'Stopped'))))
+left join `resource_limit` `iplimit` on
+    (((`account`.`id` = `iplimit`.`account_id`)
+        and (`iplimit`.`type` = 'public_ip'))))
+left join `resource_count` `ipcount` on
+    (((`account`.`id` = `ipcount`.`account_id`)
+        and (`ipcount`.`type` = 'public_ip'))))
+left join `resource_limit` `volumelimit` on
+    (((`account`.`id` = `volumelimit`.`account_id`)
+        and (`volumelimit`.`type` = 'volume'))))
+left join `resource_count` `volumecount` on
+    (((`account`.`id` = `volumecount`.`account_id`)
+        and (`volumecount`.`type` = 'volume'))))
+left join `resource_limit` `snapshotlimit` on
+    (((`account`.`id` = `snapshotlimit`.`account_id`)
+        and (`snapshotlimit`.`type` = 'snapshot'))))
+left join `resource_count` `snapshotcount` on
+    (((`account`.`id` = `snapshotcount`.`account_id`)
+        and (`snapshotcount`.`type` = 'snapshot'))))
+left join `resource_limit` `templatelimit` on
+    (((`account`.`id` = `templatelimit`.`account_id`)
+        and (`templatelimit`.`type` = 'template'))))
+left join `resource_count` `templatecount` on
+    (((`account`.`id` = `templatecount`.`account_id`)
+        and (`templatecount`.`type` = 'template'))))
+left join `resource_limit` `vpclimit` on
+    (((`account`.`id` = `vpclimit`.`account_id`)
+        and (`vpclimit`.`type` = 'vpc'))))
+left join `resource_count` `vpccount` on
+    (((`account`.`id` = `vpccount`.`account_id`)
+        and (`vpccount`.`type` = 'vpc'))))
+left join `resource_limit` `projectlimit` on
+    (((`account`.`id` = `projectlimit`.`account_id`)
+        and (`projectlimit`.`type` = 'project'))))
+left join `resource_count` `projectcount` on
+    (((`account`.`id` = `projectcount`.`account_id`)
+        and (`projectcount`.`type` = 'project'))))
+left join `resource_limit` `networklimit` on
+    (((`account`.`id` = `networklimit`.`account_id`)
+        and (`networklimit`.`type` = 'network'))))
+left join `resource_count` `networkcount` on
+    (((`account`.`id` = `networkcount`.`account_id`)
+        and (`networkcount`.`type` = 'network'))))
+left join `resource_limit` `cpulimit` on
+    (((`account`.`id` = `cpulimit`.`account_id`)
+        and (`cpulimit`.`type` = 'cpu'))))
+left join `resource_count` `cpucount` on
+    (((`account`.`id` = `cpucount`.`account_id`)
+        and (`cpucount`.`type` = 'cpu'))))
+left join `resource_limit` `memorylimit` on
+    (((`account`.`id` = `memorylimit`.`account_id`)
+        and (`memorylimit`.`type` = 'memory'))))
+left join `resource_count` `memorycount` on
+    (((`account`.`id` = `memorycount`.`account_id`)
+        and (`memorycount`.`type` = 'memory'))))
+left join `resource_limit` `primary_storage_limit` on
+    (((`account`.`id` = `primary_storage_limit`.`account_id`)
+        and (`primary_storage_limit`.`type` = 'primary_storage'))))
+left join `resource_count` `primary_storage_count` on
+    (((`account`.`id` = `primary_storage_count`.`account_id`)
+        and (`primary_storage_count`.`type` = 'primary_storage'))))
+left join `resource_limit` `secondary_storage_limit` on
+    (((`account`.`id` = `secondary_storage_limit`.`account_id`)
+        and (`secondary_storage_limit`.`type` = 'secondary_storage'))))
+left join `resource_count` `secondary_storage_count` on
+    (((`account`.`id` = `secondary_storage_count`.`account_id`)
+        and (`secondary_storage_count`.`type` = 'secondary_storage'))))
+left join `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.account_vmstats_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_vmstats_view.sql
new file mode 100644
index 0000000..df6a216
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_vmstats_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.
+
+-- cloud.account_vmstats_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`account_vmstats_view`;
+
+CREATE VIEW `cloud`.`account_vmstats_view` AS
+select
+    `vm_instance`.`account_id` AS `account_id`,
+    `vm_instance`.`state` AS `state`,
+    count(0) AS `vmcount`
+from
+    `vm_instance`
+where
+    ((`vm_instance`.`vm_type` = 'User')
+        and (`vm_instance`.`removed` is null))
+group by
+    `vm_instance`.`account_id`,
+    `vm_instance`.`state`;
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.affinity_group_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.affinity_group_view.sql
new file mode 100644
index 0000000..90a398e
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.affinity_group_view.sql
@@ -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.
+
+-- cloud.affinity_group_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`affinity_group_view`;
+
+CREATE VIEW `cloud`.`affinity_group_view` AS
+select
+    `affinity_group`.`id` AS `id`,
+    `affinity_group`.`name` AS `name`,
+    `affinity_group`.`type` AS `type`,
+    `affinity_group`.`description` AS `description`,
+    `affinity_group`.`uuid` AS `uuid`,
+    `affinity_group`.`acl_type` AS `acl_type`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `vm_instance`.`id` AS `vm_id`,
+    `vm_instance`.`uuid` AS `vm_uuid`,
+    `vm_instance`.`name` AS `vm_name`,
+    `vm_instance`.`state` AS `vm_state`,
+    `user_vm`.`display_name` AS `vm_display_name`
+from
+    ((((((`affinity_group`
+join `account` on
+    ((`affinity_group`.`account_id` = `account`.`id`)))
+join `domain` on
+    ((`affinity_group`.`domain_id` = `domain`.`id`)))
+left join `projects` on
+    ((`projects`.`project_account_id` = `account`.`id`)))
+left join `affinity_group_vm_map` on
+    ((`affinity_group`.`id` = `affinity_group_vm_map`.`affinity_group_id`)))
+left join `vm_instance` on
+    ((`vm_instance`.`id` = `affinity_group_vm_map`.`instance_id`)))
+left join `user_vm` on
+    ((`user_vm`.`id` = `vm_instance`.`id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.async_job_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.async_job_view.sql
new file mode 100644
index 0000000..8e941a0
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.async_job_view.sql
@@ -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.
+
+-- VIEW `cloud`.`async_job_view`;
+
+DROP VIEW IF EXISTS `cloud`.`async_job_view`;
+
+CREATE VIEW `cloud`.`async_job_view` AS
+select
+    account.id account_id,
+    account.uuid account_uuid,
+    account.account_name account_name,
+    account.type account_type,
+    domain.id domain_id,
+    domain.uuid domain_uuid,
+    domain.name domain_name,
+    domain.path domain_path,
+    user.id user_id,
+    user.uuid user_uuid,
+    async_job.id,
+    async_job.uuid,
+    async_job.job_cmd,
+    async_job.job_status,
+    async_job.job_process_status,
+    async_job.job_result_code,
+    async_job.job_result,
+    async_job.created,
+    async_job.removed,
+    async_job.instance_type,
+    async_job.instance_id,
+    async_job.job_executing_msid,
+    CASE
+        WHEN async_job.instance_type = 'Volume' THEN volumes.uuid
+        WHEN
+                    async_job.instance_type = 'Template'
+                or async_job.instance_type = 'Iso'
+            THEN
+            vm_template.uuid
+        WHEN
+                    async_job.instance_type = 'VirtualMachine'
+                or async_job.instance_type = 'ConsoleProxy'
+                or async_job.instance_type = 'SystemVm'
+                or async_job.instance_type = 'DomainRouter'
+            THEN
+            vm_instance.uuid
+        WHEN async_job.instance_type = 'Snapshot' THEN snapshots.uuid
+        WHEN async_job.instance_type = 'Host' THEN host.uuid
+        WHEN async_job.instance_type = 'StoragePool' THEN storage_pool.uuid
+        WHEN async_job.instance_type = 'IpAddress' THEN user_ip_address.uuid
+        WHEN async_job.instance_type = 'SecurityGroup' THEN security_group.uuid
+        WHEN async_job.instance_type = 'PhysicalNetwork' THEN physical_network.uuid
+        WHEN async_job.instance_type = 'TrafficType' THEN physical_network_traffic_types.uuid
+        WHEN async_job.instance_type = 'PhysicalNetworkServiceProvider' THEN physical_network_service_providers.uuid
+        WHEN async_job.instance_type = 'FirewallRule' THEN firewall_rules.uuid
+        WHEN async_job.instance_type = 'Account' THEN acct.uuid
+        WHEN async_job.instance_type = 'User' THEN us.uuid
+        WHEN async_job.instance_type = 'StaticRoute' THEN static_routes.uuid
+        WHEN async_job.instance_type = 'PrivateGateway' THEN vpc_gateways.uuid
+        WHEN async_job.instance_type = 'Counter' THEN counter.uuid
+        WHEN async_job.instance_type = 'Condition' THEN conditions.uuid
+        WHEN async_job.instance_type = 'AutoScalePolicy' THEN autoscale_policies.uuid
+        WHEN async_job.instance_type = 'AutoScaleVmProfile' THEN autoscale_vmprofiles.uuid
+        WHEN async_job.instance_type = 'AutoScaleVmGroup' THEN autoscale_vmgroups.uuid
+        ELSE null
+        END instance_uuid
+from
+    `cloud`.`async_job`
+        left join
+    `cloud`.`account` ON async_job.account_id = account.id
+        left join
+    `cloud`.`domain` ON domain.id = account.domain_id
+        left join
+    `cloud`.`user` ON async_job.user_id = user.id
+        left join
+    `cloud`.`volumes` ON async_job.instance_id = volumes.id
+        left join
+    `cloud`.`vm_template` ON async_job.instance_id = vm_template.id
+        left join
+    `cloud`.`vm_instance` ON async_job.instance_id = vm_instance.id
+        left join
+    `cloud`.`snapshots` ON async_job.instance_id = snapshots.id
+        left join
+    `cloud`.`host` ON async_job.instance_id = host.id
+        left join
+    `cloud`.`storage_pool` ON async_job.instance_id = storage_pool.id
+        left join
+    `cloud`.`user_ip_address` ON async_job.instance_id = user_ip_address.id
+        left join
+    `cloud`.`security_group` ON async_job.instance_id = security_group.id
+        left join
+    `cloud`.`physical_network` ON async_job.instance_id = physical_network.id
+        left join
+    `cloud`.`physical_network_traffic_types` ON async_job.instance_id = physical_network_traffic_types.id
+        left join
+    `cloud`.`physical_network_service_providers` ON async_job.instance_id = physical_network_service_providers.id
+        left join
+    `cloud`.`firewall_rules` ON async_job.instance_id = firewall_rules.id
+        left join
+    `cloud`.`account` acct ON async_job.instance_id = acct.id
+        left join
+    `cloud`.`user` us ON async_job.instance_id = us.id
+        left join
+    `cloud`.`static_routes` ON async_job.instance_id = static_routes.id
+        left join
+    `cloud`.`vpc_gateways` ON async_job.instance_id = vpc_gateways.id
+        left join
+    `cloud`.`counter` ON async_job.instance_id = counter.id
+        left join
+    `cloud`.`conditions` ON async_job.instance_id = conditions.id
+        left join
+    `cloud`.`autoscale_policies` ON async_job.instance_id = autoscale_policies.id
+        left join
+    `cloud`.`autoscale_vmprofiles` ON async_job.instance_id = autoscale_vmprofiles.id
+        left join
+    `cloud`.`autoscale_vmgroups` ON async_job.instance_id = autoscale_vmgroups.id;
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
new file mode 100644
index 0000000..c34df4f
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.data_center_view.sql
@@ -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.
+
+-- VIEW `cloud`.`data_center_view`;
+
+DROP VIEW IF EXISTS `cloud`.`data_center_view`;
+
+CREATE VIEW `cloud`.`data_center_view` AS
+select
+    data_center.id,
+    data_center.uuid,
+    data_center.name,
+    data_center.is_security_group_enabled,
+    data_center.is_local_storage_enabled,
+    data_center.description,
+    data_center.dns1,
+    data_center.dns2,
+    data_center.ip6_dns1,
+    data_center.ip6_dns2,
+    data_center.internal_dns1,
+    data_center.internal_dns2,
+    data_center.guest_network_cidr,
+    data_center.domain,
+    data_center.networktype,
+    data_center.allocation_state,
+    data_center.zone_token,
+    data_center.dhcp_provider,
+    data_center.type,
+    data_center.removed,
+    data_center.sort_key,
+    domain.id domain_id,
+    domain.uuid domain_uuid,
+    domain.name domain_name,
+    domain.path domain_path,
+    dedicated_resources.affinity_group_id,
+    dedicated_resources.account_id,
+    affinity_group.uuid affinity_group_uuid
+from
+    `cloud`.`data_center`
+        left join
+    `cloud`.`domain` ON data_center.domain_id = domain.id
+        left join
+    `cloud`.`dedicated_resources` ON data_center.id = dedicated_resources.data_center_id
+        left join
+    `cloud`.`affinity_group` ON dedicated_resources.affinity_group_id = affinity_group.id;
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql
new file mode 100644
index 0000000..dffaec5
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql
@@ -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.
+
+-- VIEW `cloud`.`disk_offering_view`;
+
+DROP VIEW IF EXISTS `cloud`.`disk_offering_view`;
+
+CREATE VIEW `cloud`.`disk_offering_view` AS
+SELECT
+    `disk_offering`.`id` AS `id`,
+    `disk_offering`.`uuid` AS `uuid`,
+    `disk_offering`.`name` AS `name`,
+    `disk_offering`.`display_text` AS `display_text`,
+    `disk_offering`.`provisioning_type` AS `provisioning_type`,
+    `disk_offering`.`disk_size` AS `disk_size`,
+    `disk_offering`.`min_iops` AS `min_iops`,
+    `disk_offering`.`max_iops` AS `max_iops`,
+    `disk_offering`.`created` AS `created`,
+    `disk_offering`.`tags` AS `tags`,
+    `disk_offering`.`customized` AS `customized`,
+    `disk_offering`.`customized_iops` AS `customized_iops`,
+    `disk_offering`.`removed` AS `removed`,
+    `disk_offering`.`use_local_storage` AS `use_local_storage`,
+    `disk_offering`.`hv_ss_reserve` AS `hv_ss_reserve`,
+    `disk_offering`.`bytes_read_rate` AS `bytes_read_rate`,
+    `disk_offering`.`bytes_read_rate_max` AS `bytes_read_rate_max`,
+    `disk_offering`.`bytes_read_rate_max_length` AS `bytes_read_rate_max_length`,
+    `disk_offering`.`bytes_write_rate` AS `bytes_write_rate`,
+    `disk_offering`.`bytes_write_rate_max` AS `bytes_write_rate_max`,
+    `disk_offering`.`bytes_write_rate_max_length` AS `bytes_write_rate_max_length`,
+    `disk_offering`.`iops_read_rate` AS `iops_read_rate`,
+    `disk_offering`.`iops_read_rate_max` AS `iops_read_rate_max`,
+    `disk_offering`.`iops_read_rate_max_length` AS `iops_read_rate_max_length`,
+    `disk_offering`.`iops_write_rate` AS `iops_write_rate`,
+    `disk_offering`.`iops_write_rate_max` AS `iops_write_rate_max`,
+    `disk_offering`.`iops_write_rate_max_length` AS `iops_write_rate_max_length`,
+    `disk_offering`.`cache_mode` AS `cache_mode`,
+    `disk_offering`.`sort_key` AS `sort_key`,
+    `disk_offering`.`compute_only` AS `compute_only`,
+    `disk_offering`.`display_offering` AS `display_offering`,
+    `disk_offering`.`state` AS `state`,
+    `disk_offering`.`disk_size_strictness` AS `disk_size_strictness`,
+    `vsphere_storage_policy`.`value` AS `vsphere_storage_policy`,
+    `disk_offering`.`encrypt` AS `encrypt`,
+    GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id,
+    GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid,
+    GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name,
+    GROUP_CONCAT(DISTINCT(domain.path)) AS domain_path,
+    GROUP_CONCAT(DISTINCT(zone.id)) AS zone_id,
+    GROUP_CONCAT(DISTINCT(zone.uuid)) AS zone_uuid,
+    GROUP_CONCAT(DISTINCT(zone.name)) AS zone_name
+FROM
+    `cloud`.`disk_offering`
+        LEFT JOIN
+    `cloud`.`disk_offering_details` AS `domain_details` ON `domain_details`.`offering_id` = `disk_offering`.`id` AND `domain_details`.`name`='domainid'
+        LEFT JOIN
+    `cloud`.`domain` AS `domain` ON FIND_IN_SET(`domain`.`id`, `domain_details`.`value`)
+        LEFT JOIN
+    `cloud`.`disk_offering_details` AS `zone_details` ON `zone_details`.`offering_id` = `disk_offering`.`id` AND `zone_details`.`name`='zoneid'
+        LEFT JOIN
+    `cloud`.`data_center` AS `zone` ON FIND_IN_SET(`zone`.`id`, `zone_details`.`value`)
+        LEFT JOIN
+    `cloud`.`disk_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`offering_id` = `disk_offering`.`id`
+        AND `vsphere_storage_policy`.`name` = 'storagepolicy'
+GROUP BY
+    `disk_offering`.`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
new file mode 100644
index 0000000..70394e8
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql
@@ -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.
+
+-- VIEW `cloud`.`domain_router_view`;
+
+DROP VIEW IF EXISTS `cloud`.`domain_router_view`;
+
+CREATE VIEW `cloud`.`domain_router_view` AS
+select
+    vm_instance.id id,
+    vm_instance.name name,
+    account.id account_id,
+    account.uuid account_uuid,
+    account.account_name account_name,
+    account.type account_type,
+    domain.id domain_id,
+    domain.uuid domain_uuid,
+    domain.name domain_name,
+    domain.path domain_path,
+    projects.id project_id,
+    projects.uuid project_uuid,
+    projects.name project_name,
+    vm_instance.uuid uuid,
+    vm_instance.created created,
+    vm_instance.state state,
+    vm_instance.removed removed,
+    vm_instance.pod_id pod_id,
+    vm_instance.instance_name instance_name,
+    host_pod_ref.uuid pod_uuid,
+    data_center.id data_center_id,
+    data_center.uuid data_center_uuid,
+    data_center.name data_center_name,
+    data_center.networktype data_center_network_type,
+    data_center.dns1 dns1,
+    data_center.dns2 dns2,
+    data_center.ip6_dns1 ip6_dns1,
+    data_center.ip6_dns2 ip6_dns2,
+    host.id host_id,
+    host.uuid host_uuid,
+    host.name host_name,
+    host.hypervisor_type,
+    host.cluster_id cluster_id,
+    host.status host_status,
+    host.resource_state host_resource_state,
+    vm_template.id template_id,
+    vm_template.uuid template_uuid,
+    service_offering.id service_offering_id,
+    service_offering.uuid service_offering_uuid,
+    service_offering.name service_offering_name,
+    nics.id nic_id,
+    nics.uuid nic_uuid,
+    nics.network_id network_id,
+    nics.ip4_address ip_address,
+    nics.ip6_address ip6_address,
+    nics.ip6_gateway ip6_gateway,
+    nics.ip6_cidr ip6_cidr,
+    nics.default_nic is_default_nic,
+    nics.gateway gateway,
+    nics.netmask netmask,
+    nics.mac_address mac_address,
+    nics.broadcast_uri broadcast_uri,
+    nics.isolation_uri isolation_uri,
+    nics.mtu mtu,
+    vpc.id vpc_id,
+    vpc.uuid vpc_uuid,
+    vpc.name vpc_name,
+    networks.uuid network_uuid,
+    networks.name network_name,
+    networks.network_domain network_domain,
+    networks.traffic_type traffic_type,
+    networks.guest_type guest_type,
+    async_job.id job_id,
+    async_job.uuid job_uuid,
+    async_job.job_status job_status,
+    async_job.account_id job_account_id,
+    domain_router.template_version template_version,
+    domain_router.scripts_version scripts_version,
+    domain_router.is_redundant_router is_redundant_router,
+    domain_router.redundant_state redundant_state,
+    domain_router.stop_pending stop_pending,
+    domain_router.role role,
+    domain_router.software_version software_version
+from
+    `cloud`.`domain_router`
+        inner join
+    `cloud`.`vm_instance` ON vm_instance.id = domain_router.id
+        inner join
+    `cloud`.`account` ON vm_instance.account_id = account.id
+        inner join
+    `cloud`.`domain` ON vm_instance.domain_id = domain.id
+        left join
+    `cloud`.`host_pod_ref` ON vm_instance.pod_id = host_pod_ref.id
+        left join
+    `cloud`.`projects` ON projects.project_account_id = account.id
+        left join
+    `cloud`.`data_center` ON vm_instance.data_center_id = data_center.id
+        left join
+    `cloud`.`host` ON vm_instance.host_id = host.id
+        left join
+    `cloud`.`vm_template` ON vm_instance.vm_template_id = vm_template.id
+        left join
+    `cloud`.`service_offering` ON vm_instance.service_offering_id = service_offering.id
+        left join
+    `cloud`.`nics` ON vm_instance.id = nics.instance_id and nics.removed is null
+        left join
+    `cloud`.`networks` ON nics.network_id = networks.id
+        left join
+    `cloud`.`vpc` ON domain_router.vpc_id = vpc.id and vpc.removed is null
+        left join
+    `cloud`.`async_job` ON async_job.instance_id = vm_instance.id
+        and async_job.instance_type = 'DomainRouter'
+        and async_job.job_status = 0;
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
new file mode 100644
index 0000000..2d8a9b5
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_view.sql
@@ -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.
+
+-- VIEW `cloud`.`domain_view`;
+
+DROP VIEW IF EXISTS `cloud`.`domain_view`;
+
+CREATE VIEW `cloud`.`domain_view` AS
+select
+    `domain`.`id` AS `id`,
+    `domain`.`parent` AS `parent`,
+    `domain`.`name` AS `name`,
+    `domain`.`uuid` AS `uuid`,
+    `domain`.`owner` AS `owner`,
+    `domain`.`path` AS `path`,
+    `domain`.`level` AS `level`,
+    `domain`.`child_count` AS `child_count`,
+    `domain`.`next_child_seq` AS `next_child_seq`,
+    `domain`.`created` AS `created`,
+    `domain`.`removed` AS `removed`,
+    `domain`.`state` AS `state`,
+    `domain`.`network_domain` AS `network_domain`,
+    `domain`.`type` AS `type`,
+    `vmlimit`.`max` AS `vmLimit`,
+    `vmcount`.`count` AS `vmTotal`,
+    `iplimit`.`max` AS `ipLimit`,
+    `ipcount`.`count` AS `ipTotal`,
+    `volumelimit`.`max` AS `volumeLimit`,
+    `volumecount`.`count` AS `volumeTotal`,
+    `snapshotlimit`.`max` AS `snapshotLimit`,
+    `snapshotcount`.`count` AS `snapshotTotal`,
+    `templatelimit`.`max` AS `templateLimit`,
+    `templatecount`.`count` AS `templateTotal`,
+    `vpclimit`.`max` AS `vpcLimit`,
+    `vpccount`.`count` AS `vpcTotal`,
+    `projectlimit`.`max` AS `projectLimit`,
+    `projectcount`.`count` AS `projectTotal`,
+    `networklimit`.`max` AS `networkLimit`,
+    `networkcount`.`count` AS `networkTotal`,
+    `cpulimit`.`max` AS `cpuLimit`,
+    `cpucount`.`count` AS `cpuTotal`,
+    `memorylimit`.`max` AS `memoryLimit`,
+    `memorycount`.`count` AS `memoryTotal`,
+    `primary_storage_limit`.`max` AS `primaryStorageLimit`,
+    `primary_storage_count`.`count` AS `primaryStorageTotal`,
+    `secondary_storage_limit`.`max` AS `secondaryStorageLimit`,
+    `secondary_storage_count`.`count` AS `secondaryStorageTotal`
+from
+    `cloud`.`domain`
+        left join
+    `cloud`.`resource_limit` vmlimit ON domain.id = vmlimit.domain_id
+        and vmlimit.type = 'user_vm'
+        left join
+    `cloud`.`resource_count` vmcount ON domain.id = vmcount.domain_id
+        and vmcount.type = 'user_vm'
+        left join
+    `cloud`.`resource_limit` iplimit ON domain.id = iplimit.domain_id
+        and iplimit.type = 'public_ip'
+        left join
+    `cloud`.`resource_count` ipcount ON domain.id = ipcount.domain_id
+        and ipcount.type = 'public_ip'
+        left join
+    `cloud`.`resource_limit` volumelimit ON domain.id = volumelimit.domain_id
+        and volumelimit.type = 'volume'
+        left join
+    `cloud`.`resource_count` volumecount ON domain.id = volumecount.domain_id
+        and volumecount.type = 'volume'
+        left join
+    `cloud`.`resource_limit` snapshotlimit ON domain.id = snapshotlimit.domain_id
+        and snapshotlimit.type = 'snapshot'
+        left join
+    `cloud`.`resource_count` snapshotcount ON domain.id = snapshotcount.domain_id
+        and snapshotcount.type = 'snapshot'
+        left join
+    `cloud`.`resource_limit` templatelimit ON domain.id = templatelimit.domain_id
+        and templatelimit.type = 'template'
+        left join
+    `cloud`.`resource_count` templatecount ON domain.id = templatecount.domain_id
+        and templatecount.type = 'template'
+        left join
+    `cloud`.`resource_limit` vpclimit ON domain.id = vpclimit.domain_id
+        and vpclimit.type = 'vpc'
+        left join
+    `cloud`.`resource_count` vpccount ON domain.id = vpccount.domain_id
+        and vpccount.type = 'vpc'
+        left join
+    `cloud`.`resource_limit` projectlimit ON domain.id = projectlimit.domain_id
+        and projectlimit.type = 'project'
+        left join
+    `cloud`.`resource_count` projectcount ON domain.id = projectcount.domain_id
+        and projectcount.type = 'project'
+        left join
+    `cloud`.`resource_limit` networklimit ON domain.id = networklimit.domain_id
+        and networklimit.type = 'network'
+        left join
+    `cloud`.`resource_count` networkcount ON domain.id = networkcount.domain_id
+        and networkcount.type = 'network'
+        left join
+    `cloud`.`resource_limit` cpulimit ON domain.id = cpulimit.domain_id
+        and cpulimit.type = 'cpu'
+        left join
+    `cloud`.`resource_count` cpucount ON domain.id = cpucount.domain_id
+        and cpucount.type = 'cpu'
+        left join
+    `cloud`.`resource_limit` memorylimit ON domain.id = memorylimit.domain_id
+        and memorylimit.type = 'memory'
+        left join
+    `cloud`.`resource_count` memorycount ON domain.id = memorycount.domain_id
+        and memorycount.type = 'memory'
+        left join
+    `cloud`.`resource_limit` primary_storage_limit ON domain.id = primary_storage_limit.domain_id
+        and primary_storage_limit.type = 'primary_storage'
+        left join
+    `cloud`.`resource_count` primary_storage_count ON domain.id = primary_storage_count.domain_id
+        and primary_storage_count.type = 'primary_storage'
+        left join
+    `cloud`.`resource_limit` secondary_storage_limit ON domain.id = secondary_storage_limit.domain_id
+        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';
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.event_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.event_view.sql
new file mode 100644
index 0000000..0a15ae4
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.event_view.sql
@@ -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.
+
+-- cloud.event_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`event_view`;
+
+CREATE VIEW `cloud`.`event_view` AS
+select
+    `event`.`id` AS `id`,
+    `event`.`uuid` AS `uuid`,
+    `event`.`type` AS `type`,
+    `event`.`state` AS `state`,
+    `event`.`description` AS `description`,
+    `event`.`resource_id` AS `resource_id`,
+    `event`.`resource_type` AS `resource_type`,
+    `event`.`created` AS `created`,
+    `event`.`level` AS `level`,
+    `event`.`parameters` AS `parameters`,
+    `event`.`start_id` AS `start_id`,
+    `eve`.`uuid` AS `start_uuid`,
+    `event`.`user_id` AS `user_id`,
+    `event`.`archived` AS `archived`,
+    `event`.`display` AS `display`,
+    `user`.`username` AS `user_name`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`
+from
+    (((((`event`
+join `account` on
+    ((`event`.`account_id` = `account`.`id`)))
+join `domain` on
+    ((`event`.`domain_id` = `domain`.`id`)))
+join `user` on
+    ((`event`.`user_id` = `user`.`id`)))
+left join `projects` on
+    ((`projects`.`project_account_id` = `event`.`account_id`)))
+left join `event` `eve` on
+    ((`event`.`start_id` = `eve`.`id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.free_ip_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.free_ip_view.sql
new file mode 100644
index 0000000..29c22f3
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.free_ip_view.sql
@@ -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.
+
+-- cloud.free_ip_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`free_ip_view`;
+
+CREATE VIEW `cloud`.`free_ip_view` AS
+select
+    count(`user_ip_address`.`id`) AS `free_ip`
+from
+    (`user_ip_address`
+join `vlan` on
+    (((`vlan`.`id` = `user_ip_address`.`vlan_db_id`)
+        and (`vlan`.`vlan_type` = 'VirtualNetwork'))))
+where
+    (`user_ip_address`.`state` = 'Free');
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
new file mode 100644
index 0000000..5c6d4fd
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.host_view.sql
@@ -0,0 +1,111 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT 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`.`host_view`;
+
+DROP VIEW IF EXISTS `cloud`.`host_view`;
+
+CREATE VIEW `cloud`.`host_view` AS
+SELECT
+    host.id,
+    host.uuid,
+    host.name,
+    host.status,
+    host.disconnected,
+    host.type,
+    host.private_ip_address,
+    host.version,
+    host.hypervisor_type,
+    host.hypervisor_version,
+    host.capabilities,
+    host.last_ping,
+    host.created,
+    host.removed,
+    host.resource_state,
+    host.mgmt_server_id,
+    host.cpu_sockets,
+    host.cpus,
+    host.speed,
+    host.ram,
+    cluster.id cluster_id,
+    cluster.uuid cluster_uuid,
+    cluster.name cluster_name,
+    cluster.cluster_type,
+    data_center.id data_center_id,
+    data_center.uuid data_center_uuid,
+    data_center.name data_center_name,
+    data_center.networktype data_center_type,
+    host_pod_ref.id pod_id,
+    host_pod_ref.uuid pod_uuid,
+    host_pod_ref.name pod_name,
+    GROUP_CONCAT(DISTINCT(host_tags.tag)) AS tag,
+    `host_tags`.`is_tag_a_rule` AS `is_tag_a_rule`,
+    guest_os_category.id guest_os_category_id,
+    guest_os_category.uuid guest_os_category_uuid,
+    guest_os_category.name guest_os_category_name,
+    mem_caps.used_capacity memory_used_capacity,
+    mem_caps.reserved_capacity memory_reserved_capacity,
+    cpu_caps.used_capacity cpu_used_capacity,
+    cpu_caps.reserved_capacity cpu_reserved_capacity,
+    async_job.id job_id,
+    async_job.uuid job_uuid,
+    async_job.job_status job_status,
+    async_job.account_id job_account_id,
+    oobm.enabled AS `oobm_enabled`,
+    oobm.power_state AS `oobm_power_state`,
+    ha_config.enabled AS `ha_enabled`,
+    ha_config.ha_state AS `ha_state`,
+    ha_config.provider AS `ha_provider`,
+    `last_annotation_view`.`annotation` AS `annotation`,
+    `last_annotation_view`.`created` AS `last_annotated`,
+    `user`.`username` AS `username`
+FROM
+    `cloud`.`host`
+        LEFT JOIN
+    `cloud`.`cluster` ON host.cluster_id = cluster.id
+        LEFT JOIN
+    `cloud`.`data_center` ON host.data_center_id = data_center.id
+        LEFT JOIN
+    `cloud`.`host_pod_ref` ON host.pod_id = host_pod_ref.id
+        LEFT JOIN
+    `cloud`.`host_details` ON host.id = host_details.host_id
+        AND host_details.name = 'guest.os.category.id'
+        LEFT JOIN
+    `cloud`.`guest_os_category` ON guest_os_category.id = CONVERT ( host_details.value, UNSIGNED )
+        LEFT JOIN
+    `cloud`.`host_tags` ON host_tags.host_id = host.id
+        LEFT JOIN
+    `cloud`.`op_host_capacity` mem_caps ON host.id = mem_caps.host_id
+        AND mem_caps.capacity_type = 0
+        LEFT JOIN
+    `cloud`.`op_host_capacity` cpu_caps ON host.id = cpu_caps.host_id
+        AND cpu_caps.capacity_type = 1
+        LEFT JOIN
+    `cloud`.`async_job` ON async_job.instance_id = host.id
+        AND async_job.instance_type = 'Host'
+        AND async_job.job_status = 0
+        LEFT JOIN
+    `cloud`.`oobm` ON oobm.host_id = host.id
+        left join
+    `cloud`.`ha_config` ON ha_config.resource_id=host.id
+        and ha_config.resource_type='Host'
+        LEFT JOIN
+    `cloud`.`last_annotation_view` ON `last_annotation_view`.`entity_uuid` = `host`.`uuid`
+        LEFT JOIN
+    `cloud`.`user` ON `user`.`uuid` = `last_annotation_view`.`user_uuid`
+GROUP BY
+    `host`.`id`;
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.image_store_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.image_store_view.sql
new file mode 100644
index 0000000..88d6830
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.image_store_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.
+
+-- cloud.image_store_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`image_store_view`;
+
+CREATE VIEW `cloud`.`image_store_view` AS
+select
+    `image_store`.`id` AS `id`,
+    `image_store`.`uuid` AS `uuid`,
+    `image_store`.`name` AS `name`,
+    `image_store`.`image_provider_name` AS `image_provider_name`,
+    `image_store`.`protocol` AS `protocol`,
+    `image_store`.`url` AS `url`,
+    `image_store`.`scope` AS `scope`,
+    `image_store`.`role` AS `role`,
+    `image_store`.`readonly` AS `readonly`,
+    `image_store`.`removed` AS `removed`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `image_store_details`.`name` AS `detail_name`,
+    `image_store_details`.`value` AS `detail_value`
+from
+    ((`image_store`
+left join `data_center` on
+    ((`image_store`.`data_center_id` = `data_center`.`id`)))
+left join `image_store_details` on
+    ((`image_store_details`.`store_id` = `image_store`.`id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.instance_group_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.instance_group_view.sql
new file mode 100644
index 0000000..8bdc818
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.instance_group_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.instance_group_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`instance_group_view`;
+
+CREATE VIEW `cloud`.`instance_group_view` AS
+select
+    `instance_group`.`id` AS `id`,
+    `instance_group`.`uuid` AS `uuid`,
+    `instance_group`.`name` AS `name`,
+    `instance_group`.`removed` AS `removed`,
+    `instance_group`.`created` AS `created`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`
+from
+    (((`instance_group`
+join `account` on
+    ((`instance_group`.`account_id` = `account`.`id`)))
+join `domain` on
+    ((`account`.`domain_id` = `domain`.`id`)))
+left join `projects` on
+    ((`projects`.`project_account_id` = `instance_group`.`account_id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.last_annotation_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.last_annotation_view.sql
new file mode 100644
index 0000000..f317fba
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.last_annotation_view.sql
@@ -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.
+
+-- cloud.last_annotation_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`last_annotation_view`;
+
+CREATE VIEW `cloud`.`last_annotation_view` AS
+select
+    `annotations`.`uuid` AS `uuid`,
+    `annotations`.`annotation` AS `annotation`,
+    `annotations`.`entity_uuid` AS `entity_uuid`,
+    `annotations`.`entity_type` AS `entity_type`,
+    `annotations`.`user_uuid` AS `user_uuid`,
+    `annotations`.`created` AS `created`,
+    `annotations`.`removed` AS `removed`
+from
+    `annotations`
+where
+    `annotations`.`created` in (
+    select
+        max(`annotations`.`created`)
+    from
+        `annotations`
+    where
+        (`annotations`.`removed` is null)
+    group by
+        `annotations`.`entity_uuid`);
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.mshost_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.mshost_view.sql
new file mode 100644
index 0000000..9b68f17
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.mshost_view.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.
+
+-- cloud.mshost_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`mshost_view`;
+
+CREATE VIEW `cloud`.`mshost_view` AS
+select
+    `mshost`.`id` AS `id`,
+    `mshost`.`msid` AS `msid`,
+    `mshost`.`runid` AS `runid`,
+    `mshost`.`name` AS `name`,
+    `mshost`.`uuid` AS `uuid`,
+    `mshost`.`state` AS `state`,
+    `mshost`.`version` AS `version`,
+    `mshost`.`service_ip` AS `service_ip`,
+    `mshost`.`service_port` AS `service_port`,
+    `mshost`.`last_update` AS `last_update`,
+    `mshost`.`removed` AS `removed`,
+    `mshost`.`alert_count` AS `alert_count`,
+    `mshost_status`.`last_jvm_start` AS `last_jvm_start`,
+    `mshost_status`.`last_jvm_stop` AS `last_jvm_stop`,
+    `mshost_status`.`last_system_boot` AS `last_system_boot`,
+    `mshost_status`.`os_distribution` AS `os_distribution`,
+    `mshost_status`.`java_name` AS `java_name`,
+    `mshost_status`.`java_version` AS `java_version`
+from
+    (`mshost`
+left join `mshost_status` on
+    ((`mshost`.`uuid` = `mshost_status`.`ms_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
new file mode 100644
index 0000000..8ba291e
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql
@@ -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.
+
+-- VIEW `cloud`.`network_offering_view`;
+
+DROP VIEW IF EXISTS `cloud`.`network_offering_view`;
+
+CREATE VIEW `cloud`.`network_offering_view` AS
+SELECT
+    `network_offerings`.`id` AS `id`,
+    `network_offerings`.`uuid` AS `uuid`,
+    `network_offerings`.`name` AS `name`,
+    `network_offerings`.`unique_name` AS `unique_name`,
+    `network_offerings`.`display_text` AS `display_text`,
+    `network_offerings`.`nw_rate` AS `nw_rate`,
+    `network_offerings`.`mc_rate` AS `mc_rate`,
+    `network_offerings`.`traffic_type` AS `traffic_type`,
+    `network_offerings`.`tags` AS `tags`,
+    `network_offerings`.`system_only` AS `system_only`,
+    `network_offerings`.`specify_vlan` AS `specify_vlan`,
+    `network_offerings`.`service_offering_id` AS `service_offering_id`,
+    `network_offerings`.`conserve_mode` AS `conserve_mode`,
+    `network_offerings`.`created` AS `created`,
+    `network_offerings`.`removed` AS `removed`,
+    `network_offerings`.`default` AS `default`,
+    `network_offerings`.`availability` AS `availability`,
+    `network_offerings`.`dedicated_lb_service` AS `dedicated_lb_service`,
+    `network_offerings`.`shared_source_nat_service` AS `shared_source_nat_service`,
+    `network_offerings`.`sort_key` AS `sort_key`,
+    `network_offerings`.`redundant_router_service` AS `redundant_router_service`,
+    `network_offerings`.`state` AS `state`,
+    `network_offerings`.`guest_type` AS `guest_type`,
+    `network_offerings`.`elastic_ip_service` AS `elastic_ip_service`,
+    `network_offerings`.`eip_associate_public_ip` AS `eip_associate_public_ip`,
+    `network_offerings`.`elastic_lb_service` AS `elastic_lb_service`,
+    `network_offerings`.`specify_ip_ranges` AS `specify_ip_ranges`,
+    `network_offerings`.`inline` AS `inline`,
+    `network_offerings`.`is_persistent` AS `is_persistent`,
+    `network_offerings`.`internal_lb` AS `internal_lb`,
+    `network_offerings`.`public_lb` AS `public_lb`,
+    `network_offerings`.`egress_default_policy` AS `egress_default_policy`,
+    `network_offerings`.`concurrent_connections` AS `concurrent_connections`,
+    `network_offerings`.`keep_alive_enabled` AS `keep_alive_enabled`,
+    `network_offerings`.`supports_streched_l2` AS `supports_streched_l2`,
+    `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`.`service_package_id` AS `service_package_id`,
+    GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id,
+    GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid,
+    GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name,
+    GROUP_CONCAT(DISTINCT(domain.path)) AS domain_path,
+    GROUP_CONCAT(DISTINCT(zone.id)) AS zone_id,
+    GROUP_CONCAT(DISTINCT(zone.uuid)) AS zone_uuid,
+    GROUP_CONCAT(DISTINCT(zone.name)) AS zone_name,
+    `offering_details`.value AS internet_protocol
+FROM
+    `cloud`.`network_offerings`
+        LEFT JOIN
+    `cloud`.`network_offering_details` AS `domain_details` ON `domain_details`.`network_offering_id` = `network_offerings`.`id` AND `domain_details`.`name`='domainid'
+        LEFT JOIN
+    `cloud`.`domain` AS `domain` ON FIND_IN_SET(`domain`.`id`, `domain_details`.`value`)
+        LEFT JOIN
+    `cloud`.`network_offering_details` AS `zone_details` ON `zone_details`.`network_offering_id` = `network_offerings`.`id` AND `zone_details`.`name`='zoneid'
+        LEFT JOIN
+    `cloud`.`data_center` AS `zone` ON FIND_IN_SET(`zone`.`id`, `zone_details`.`value`)
+        LEFT JOIN
+    `cloud`.`network_offering_details` AS `offering_details` ON `offering_details`.`network_offering_id` = `network_offerings`.`id` AND `offering_details`.`name`='internetProtocol'
+GROUP BY
+    `network_offerings`.`id`;
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.project_account_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.project_account_view.sql
new file mode 100644
index 0000000..c896189
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.project_account_view.sql
@@ -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.
+
+-- cloud.project_account_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`project_account_view`;
+
+CREATE VIEW `cloud`.`project_account_view` AS
+select
+    `project_account`.`id` AS `id`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `user`.`id` AS `user_id`,
+    `user`.`uuid` AS `user_uuid`,
+    `user`.`username` AS `user_name`,
+    `project_account`.`account_role` AS `account_role`,
+    `project_role`.`id` AS `project_role_id`,
+    `project_role`.`uuid` AS `project_role_uuid`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`
+from
+    (((((`project_account`
+join `account` on
+    ((`project_account`.`account_id` = `account`.`id`)))
+join `domain` on
+    ((`account`.`domain_id` = `domain`.`id`)))
+join `projects` on
+    ((`projects`.`id` = `project_account`.`project_id`)))
+left join `project_role` on
+    ((`project_account`.`project_role_id` = `project_role`.`id`)))
+left join `user` on
+    ((`project_account`.`user_id` = `user`.`id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.project_invitation_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.project_invitation_view.sql
new file mode 100644
index 0000000..fae35b9
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.project_invitation_view.sql
@@ -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.
+
+-- cloud.project_invitation_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`project_invitation_view`;
+
+CREATE VIEW `cloud`.`project_invitation_view` AS
+select
+    `project_invitations`.`id` AS `id`,
+    `project_invitations`.`uuid` AS `uuid`,
+    `project_invitations`.`email` AS `email`,
+    `project_invitations`.`created` AS `created`,
+    `project_invitations`.`state` AS `state`,
+    `project_invitations`.`project_role_id` AS `project_role_id`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `user`.`id` AS `user_id`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`
+from
+    ((((`project_invitations`
+left join `account` on
+    ((`project_invitations`.`account_id` = `account`.`id`)))
+left join `domain` on
+    ((`project_invitations`.`domain_id` = `domain`.`id`)))
+left join `projects` on
+    ((`projects`.`id` = `project_invitations`.`project_id`)))
+left join `user` on
+    ((`project_invitations`.`user_id` = `user`.`id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.project_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.project_view.sql
new file mode 100644
index 0000000..31461b1
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.project_view.sql
@@ -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.
+
+-- cloud.project_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`project_view`;
+
+CREATE VIEW `cloud`.`project_view` AS
+select
+    `projects`.`id` AS `id`,
+    `projects`.`uuid` AS `uuid`,
+    `projects`.`name` AS `name`,
+    `projects`.`display_text` AS `display_text`,
+    `projects`.`state` AS `state`,
+    `projects`.`removed` AS `removed`,
+    `projects`.`created` AS `created`,
+    `projects`.`project_account_id` AS `project_account_id`,
+    `account`.`account_name` AS `owner`,
+    `pacct`.`account_id` AS `account_id`,
+    `pacct`.`user_id` AS `user_id`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`
+from
+    ((((`projects`
+join `domain` on
+    ((`projects`.`domain_id` = `domain`.`id`)))
+join `project_account` on
+    (((`projects`.`id` = `project_account`.`project_id`)
+        and (`project_account`.`account_role` = 'Admin'))))
+join `account` on
+    ((`account`.`id` = `project_account`.`account_id`)))
+left join `project_account` `pacct` on
+    ((`projects`.`id` = `pacct`.`project_id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.resource_tag_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.resource_tag_view.sql
new file mode 100644
index 0000000..3d77d49
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.resource_tag_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.
+
+-- cloud.resource_tag_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`resource_tag_view`;
+
+CREATE VIEW `cloud`.`resource_tag_view` AS
+select
+    `resource_tags`.`id` AS `id`,
+    `resource_tags`.`uuid` AS `uuid`,
+    `resource_tags`.`key` AS `key`,
+    `resource_tags`.`value` AS `value`,
+    `resource_tags`.`resource_id` AS `resource_id`,
+    `resource_tags`.`resource_uuid` AS `resource_uuid`,
+    `resource_tags`.`resource_type` AS `resource_type`,
+    `resource_tags`.`customer` AS `customer`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`
+from
+    (((`resource_tags`
+join `account` on
+    ((`resource_tags`.`account_id` = `account`.`id`)))
+join `domain` on
+    ((`resource_tags`.`domain_id` = `domain`.`id`)))
+left join `projects` on
+    ((`projects`.`project_account_id` = `resource_tags`.`account_id`)));
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.security_group_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.security_group_view.sql
new file mode 100644
index 0000000..3cae860
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.security_group_view.sql
@@ -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.
+
+-- cloud.security_group_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`security_group_view`;
+
+CREATE VIEW `cloud`.`security_group_view` AS
+select
+    `security_group`.`id` AS `id`,
+    `security_group`.`name` AS `name`,
+    `security_group`.`description` AS `description`,
+    `security_group`.`uuid` AS `uuid`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `security_group_rule`.`id` AS `rule_id`,
+    `security_group_rule`.`uuid` AS `rule_uuid`,
+    `security_group_rule`.`type` AS `rule_type`,
+    `security_group_rule`.`start_port` AS `rule_start_port`,
+    `security_group_rule`.`end_port` AS `rule_end_port`,
+    `security_group_rule`.`protocol` AS `rule_protocol`,
+    `security_group_rule`.`allowed_network_id` AS `rule_allowed_network_id`,
+    `security_group_rule`.`allowed_ip_cidr` AS `rule_allowed_ip_cidr`,
+    `security_group_rule`.`create_status` AS `rule_create_status`,
+    `resource_tags`.`id` AS `tag_id`,
+    `resource_tags`.`uuid` AS `tag_uuid`,
+    `resource_tags`.`key` AS `tag_key`,
+    `resource_tags`.`value` AS `tag_value`,
+    `resource_tags`.`domain_id` AS `tag_domain_id`,
+    `resource_tags`.`account_id` AS `tag_account_id`,
+    `resource_tags`.`resource_id` AS `tag_resource_id`,
+    `resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
+    `resource_tags`.`resource_type` AS `tag_resource_type`,
+    `resource_tags`.`customer` AS `tag_customer`,
+    `async_job`.`id` AS `job_id`,
+    `async_job`.`uuid` AS `job_uuid`,
+    `async_job`.`job_status` AS `job_status`,
+    `async_job`.`account_id` AS `job_account_id`
+from
+    ((((((`security_group`
+left join `security_group_rule` on
+    ((`security_group`.`id` = `security_group_rule`.`security_group_id`)))
+join `account` on
+    ((`security_group`.`account_id` = `account`.`id`)))
+join `domain` on
+    ((`security_group`.`domain_id` = `domain`.`id`)))
+left join `projects` on
+    ((`projects`.`project_account_id` = `security_group`.`account_id`)))
+left join `resource_tags` on
+    (((`resource_tags`.`resource_id` = `security_group`.`id`)
+        and (`resource_tags`.`resource_type` = 'SecurityGroup'))))
+left join `async_job` on
+    (((`async_job`.`instance_id` = `security_group`.`id`)
+        and (`async_job`.`instance_type` = 'SecurityGroup')
+            and (`async_job`.`job_status` = 0))));
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
new file mode 100644
index 0000000..c894429
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql
@@ -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.
+
+-- VIEW `cloud`.`service_offering_view`;
+
+DROP VIEW IF EXISTS `cloud`.`service_offering_view`;
+
+CREATE VIEW `cloud`.`service_offering_view` AS
+SELECT
+    `service_offering`.`id` AS `id`,
+    `service_offering`.`uuid` AS `uuid`,
+    `service_offering`.`name` AS `name`,
+    `service_offering`.`state` AS `state`,
+    `service_offering`.`display_text` AS `display_text`,
+    `disk_offering`.`provisioning_type` AS `provisioning_type`,
+    `service_offering`.`created` AS `created`,
+    `disk_offering`.`tags` AS `tags`,
+    `service_offering`.`removed` AS `removed`,
+    `disk_offering`.`use_local_storage` AS `use_local_storage`,
+    `service_offering`.`system_use` AS `system_use`,
+    `disk_offering`.`id` AS `disk_offering_id`,
+    `disk_offering`.`name` AS `disk_offering_name`,
+    `disk_offering`.`uuid` AS `disk_offering_uuid`,
+    `disk_offering`.`display_text` AS `disk_offering_display_text`,
+    `disk_offering`.`customized_iops` AS `customized_iops`,
+    `disk_offering`.`min_iops` AS `min_iops`,
+    `disk_offering`.`max_iops` AS `max_iops`,
+    `disk_offering`.`hv_ss_reserve` AS `hv_ss_reserve`,
+    `disk_offering`.`bytes_read_rate` AS `bytes_read_rate`,
+    `disk_offering`.`bytes_read_rate_max` AS `bytes_read_rate_max`,
+    `disk_offering`.`bytes_read_rate_max_length` AS `bytes_read_rate_max_length`,
+    `disk_offering`.`bytes_write_rate` AS `bytes_write_rate`,
+    `disk_offering`.`bytes_write_rate_max` AS `bytes_write_rate_max`,
+    `disk_offering`.`bytes_write_rate_max_length` AS `bytes_write_rate_max_length`,
+    `disk_offering`.`iops_read_rate` AS `iops_read_rate`,
+    `disk_offering`.`iops_read_rate_max` AS `iops_read_rate_max`,
+    `disk_offering`.`iops_read_rate_max_length` AS `iops_read_rate_max_length`,
+    `disk_offering`.`iops_write_rate` AS `iops_write_rate`,
+    `disk_offering`.`iops_write_rate_max` AS `iops_write_rate_max`,
+    `disk_offering`.`iops_write_rate_max_length` AS `iops_write_rate_max_length`,
+    `disk_offering`.`cache_mode` AS `cache_mode`,
+    `disk_offering`.`disk_size` AS `root_disk_size`,
+    `disk_offering`.`encrypt` AS `encrypt_root`,
+    `service_offering`.`cpu` AS `cpu`,
+    `service_offering`.`speed` AS `speed`,
+    `service_offering`.`ram_size` AS `ram_size`,
+    `service_offering`.`nw_rate` AS `nw_rate`,
+    `service_offering`.`mc_rate` AS `mc_rate`,
+    `service_offering`.`ha_enabled` AS `ha_enabled`,
+    `service_offering`.`limit_cpu_use` AS `limit_cpu_use`,
+    `service_offering`.`host_tag` AS `host_tag`,
+    `service_offering`.`default_use` AS `default_use`,
+    `service_offering`.`vm_type` AS `vm_type`,
+    `service_offering`.`sort_key` AS `sort_key`,
+    `service_offering`.`is_volatile` AS `is_volatile`,
+    `service_offering`.`deployment_planner` AS `deployment_planner`,
+    `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`,
+    GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id,
+    GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid,
+    GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name,
+    GROUP_CONCAT(DISTINCT(domain.path)) AS domain_path,
+    GROUP_CONCAT(DISTINCT(zone.id)) AS zone_id,
+    GROUP_CONCAT(DISTINCT(zone.uuid)) AS zone_uuid,
+    GROUP_CONCAT(DISTINCT(zone.name)) AS zone_name,
+    IFNULL(`min_compute_details`.`value`, `cpu`) AS min_cpu,
+    IFNULL(`max_compute_details`.`value`, `cpu`) AS max_cpu,
+    IFNULL(`min_memory_details`.`value`, `ram_size`) AS min_memory,
+    IFNULL(`max_memory_details`.`value`, `ram_size`) AS max_memory
+FROM
+    `cloud`.`service_offering`
+        INNER JOIN
+    `cloud`.`disk_offering` ON service_offering.disk_offering_id = disk_offering.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`)
+        LEFT JOIN
+    `cloud`.`service_offering_details` AS `zone_details` ON `zone_details`.`service_offering_id` = `service_offering`.`id` AND `zone_details`.`name`='zoneid'
+        LEFT JOIN
+    `cloud`.`data_center` AS `zone` ON FIND_IN_SET(`zone`.`id`, `zone_details`.`value`)
+        LEFT JOIN
+    `cloud`.`service_offering_details` AS `min_compute_details` ON `min_compute_details`.`service_offering_id` = `service_offering`.`id`
+        AND `min_compute_details`.`name` = 'mincpunumber'
+        LEFT JOIN
+    `cloud`.`service_offering_details` AS `max_compute_details` ON `max_compute_details`.`service_offering_id` = `service_offering`.`id`
+        AND `max_compute_details`.`name` = 'maxcpunumber'
+        LEFT JOIN
+    `cloud`.`service_offering_details` AS `min_memory_details` ON `min_memory_details`.`service_offering_id` = `service_offering`.`id`
+        AND `min_memory_details`.`name` = 'minmemory'
+        LEFT JOIN
+    `cloud`.`service_offering_details` AS `max_memory_details` ON `max_memory_details`.`service_offering_id` = `service_offering`.`id`
+        AND `max_memory_details`.`name` = 'maxmemory'
+        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'
+GROUP BY
+    `service_offering`.`id`;
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql
new file mode 100644
index 0000000..c6b8d6b
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql
@@ -0,0 +1,107 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT 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`.`snapshot_view`;
+
+DROP VIEW IF EXISTS `cloud`.`snapshot_view`;
+
+CREATE VIEW `cloud`.`snapshot_view` AS
+SELECT
+    `snapshots`.`id` AS `id`,
+    `snapshots`.`uuid` AS `uuid`,
+    `snapshots`.`name` AS `name`,
+    `snapshots`.`status` AS `status`,
+    `snapshots`.`disk_offering_id` AS `disk_offering_id`,
+    `snapshots`.`snapshot_type` AS `snapshot_type`,
+    `snapshots`.`type_description` AS `type_description`,
+    `snapshots`.`size` AS `size`,
+    `snapshots`.`created` AS `created`,
+    `snapshots`.`removed` AS `removed`,
+    `snapshots`.`location_type` AS `location_type`,
+    `snapshots`.`hypervisor_type` AS `hypervisor_type`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `volumes`.`id` AS `volume_id`,
+    `volumes`.`uuid` AS `volume_uuid`,
+    `volumes`.`name` AS `volume_name`,
+    `volumes`.`volume_type` AS `volume_type`,
+    `volumes`.`size` AS `volume_size`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `snapshot_store_ref`.`store_id` AS `store_id`,
+    IFNULL(`image_store`.`uuid`, `storage_pool`.`uuid`) AS `store_uuid`,
+    IFNULL(`image_store`.`name`, `storage_pool`.`name`) AS `store_name`,
+    `snapshot_store_ref`.`store_role` AS `store_role`,
+    `snapshot_store_ref`.`state` AS `store_state`,
+    `snapshot_store_ref`.`download_state` AS `download_state`,
+    `snapshot_store_ref`.`download_pct` AS `download_pct`,
+    `snapshot_store_ref`.`error_str` AS `error_str`,
+    `snapshot_store_ref`.`size` AS `store_size`,
+    `snapshot_store_ref`.`created` AS `created_on_store`,
+    `resource_tags`.`id` AS `tag_id`,
+    `resource_tags`.`uuid` AS `tag_uuid`,
+    `resource_tags`.`key` AS `tag_key`,
+    `resource_tags`.`value` AS `tag_value`,
+    `resource_tags`.`domain_id` AS `tag_domain_id`,
+    `domain`.`uuid` AS `tag_domain_uuid`,
+    `domain`.`name` AS `tag_domain_name`,
+    `resource_tags`.`account_id` AS `tag_account_id`,
+    `account`.`account_name` AS `tag_account_name`,
+    `resource_tags`.`resource_id` AS `tag_resource_id`,
+    `resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
+    `resource_tags`.`resource_type` AS `tag_resource_type`,
+    `resource_tags`.`customer` AS `tag_customer`,
+    CONCAT(`snapshots`.`id`,
+           '_',
+           IFNULL(`snapshot_store_ref`.`store_role`, 'UNKNOWN'),
+           '_',
+           IFNULL(`snapshot_store_ref`.`store_id`, 0)) AS `snapshot_store_pair`
+FROM
+    ((((((((((`snapshots`
+        JOIN `account` ON ((`account`.`id` = `snapshots`.`account_id`)))
+        JOIN `domain` ON ((`domain`.`id` = `account`.`domain_id`)))
+        LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`)))
+        LEFT JOIN `volumes` ON ((`volumes`.`id` = `snapshots`.`volume_id`)))
+        LEFT JOIN `snapshot_store_ref` ON (((`snapshot_store_ref`.`snapshot_id` = `snapshots`.`id`)
+        AND (`snapshot_store_ref`.`state` != 'Destroyed')
+        AND (`snapshot_store_ref`.`display` = 1))))
+        LEFT JOIN `image_store` ON ((ISNULL(`image_store`.`removed`)
+        AND (`snapshot_store_ref`.`store_role` = 'Image')
+        AND (`snapshot_store_ref`.`store_id` IS NOT NULL)
+        AND (`image_store`.`id` = `snapshot_store_ref`.`store_id`))))
+        LEFT JOIN `storage_pool` ON ((ISNULL(`storage_pool`.`removed`)
+        AND (`snapshot_store_ref`.`store_role` = 'Primary')
+        AND (`snapshot_store_ref`.`store_id` IS NOT NULL)
+        AND (`storage_pool`.`id` = `snapshot_store_ref`.`store_id`))))
+        LEFT JOIN `snapshot_zone_ref` ON (((`snapshot_zone_ref`.`snapshot_id` = `snapshots`.`id`)
+        AND ISNULL(`snapshot_store_ref`.`store_id`)
+        AND ISNULL(`snapshot_zone_ref`.`removed`))))
+        LEFT JOIN `data_center` ON (((`image_store`.`data_center_id` = `data_center`.`id`)
+        OR (`storage_pool`.`data_center_id` = `data_center`.`id`)
+        OR (`snapshot_zone_ref`.`zone_id` = `data_center`.`id`))))
+        LEFT JOIN `resource_tags` ON ((`resource_tags`.`resource_id` = `snapshots`.`id`)
+        AND (`resource_tags`.`resource_type` = 'Snapshot')));
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
new file mode 100644
index 0000000..e6cc945
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql
@@ -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.
+
+-- VIEW `cloud`.`storage_pool_view`;
+
+DROP VIEW IF EXISTS `cloud`.`storage_pool_view`;
+
+CREATE VIEW `cloud`.`storage_pool_view` AS
+SELECT
+    `storage_pool`.`id` AS `id`,
+    `storage_pool`.`uuid` AS `uuid`,
+    `storage_pool`.`name` AS `name`,
+    `storage_pool`.`status` AS `status`,
+    `storage_pool`.`path` AS `path`,
+    `storage_pool`.`pool_type` AS `pool_type`,
+    `storage_pool`.`host_address` AS `host_address`,
+    `storage_pool`.`created` AS `created`,
+    `storage_pool`.`removed` AS `removed`,
+    `storage_pool`.`capacity_bytes` AS `capacity_bytes`,
+    `storage_pool`.`capacity_iops` AS `capacity_iops`,
+    `storage_pool`.`scope` AS `scope`,
+    `storage_pool`.`hypervisor` AS `hypervisor`,
+    `storage_pool`.`storage_provider_name` AS `storage_provider_name`,
+    `storage_pool`.`parent` AS `parent`,
+    `cluster`.`id` AS `cluster_id`,
+    `cluster`.`uuid` AS `cluster_uuid`,
+    `cluster`.`name` AS `cluster_name`,
+    `cluster`.`cluster_type` AS `cluster_type`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `data_center`.`networktype` AS `data_center_type`,
+    `host_pod_ref`.`id` AS `pod_id`,
+    `host_pod_ref`.`uuid` AS `pod_uuid`,
+    `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`,
+    `op_host_capacity`.`used_capacity` AS `disk_used_capacity`,
+    `op_host_capacity`.`reserved_capacity` AS `disk_reserved_capacity`,
+    `async_job`.`id` AS `job_id`,
+    `async_job`.`uuid` AS `job_uuid`,
+    `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))));
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
new file mode 100644
index 0000000..40b416b
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.template_view.sql
@@ -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.
+
+-- VIEW `cloud`.`template_view`;
+
+DROP VIEW IF EXISTS `cloud`.`template_view`;
+
+CREATE VIEW `cloud`.`template_view` AS
+SELECT
+    `vm_template`.`id` AS `id`,
+    `vm_template`.`uuid` AS `uuid`,
+    `vm_template`.`unique_name` AS `unique_name`,
+    `vm_template`.`name` AS `name`,
+    `vm_template`.`public` AS `public`,
+    `vm_template`.`featured` AS `featured`,
+    `vm_template`.`type` AS `type`,
+    `vm_template`.`hvm` AS `hvm`,
+    `vm_template`.`bits` AS `bits`,
+    `vm_template`.`url` AS `url`,
+    `vm_template`.`format` AS `format`,
+    `vm_template`.`created` AS `created`,
+    `vm_template`.`checksum` AS `checksum`,
+    `vm_template`.`display_text` AS `display_text`,
+    `vm_template`.`enable_password` AS `enable_password`,
+    `vm_template`.`dynamically_scalable` AS `dynamically_scalable`,
+    `vm_template`.`state` AS `template_state`,
+    `vm_template`.`guest_os_id` AS `guest_os_id`,
+    `guest_os`.`uuid` AS `guest_os_uuid`,
+    `guest_os`.`display_name` AS `guest_os_name`,
+    `vm_template`.`bootable` AS `bootable`,
+    `vm_template`.`prepopulate` AS `prepopulate`,
+    `vm_template`.`cross_zones` AS `cross_zones`,
+    `vm_template`.`hypervisor_type` AS `hypervisor_type`,
+    `vm_template`.`extractable` AS `extractable`,
+    `vm_template`.`template_tag` AS `template_tag`,
+    `vm_template`.`sort_key` AS `sort_key`,
+    `vm_template`.`removed` AS `removed`,
+    `vm_template`.`enable_sshkey` AS `enable_sshkey`,
+    `parent_template`.`id` AS `parent_template_id`,
+    `parent_template`.`uuid` AS `parent_template_uuid`,
+    `source_template`.`id` AS `source_template_id`,
+    `source_template`.`uuid` AS `source_template_uuid`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `launch_permission`.`account_id` AS `lp_account_id`,
+    `template_store_ref`.`store_id` AS `store_id`,
+    `image_store`.`scope` AS `store_scope`,
+    `template_store_ref`.`state` AS `state`,
+    `template_store_ref`.`download_state` AS `download_state`,
+    `template_store_ref`.`download_pct` AS `download_pct`,
+    `template_store_ref`.`error_str` AS `error_str`,
+    `template_store_ref`.`size` AS `size`,
+    `template_store_ref`.physical_size AS `physical_size`,
+    `template_store_ref`.`destroyed` AS `destroyed`,
+    `template_store_ref`.`created` AS `created_on_store`,
+    `vm_template_details`.`name` AS `detail_name`,
+    `vm_template_details`.`value` AS `detail_value`,
+    `resource_tags`.`id` AS `tag_id`,
+    `resource_tags`.`uuid` AS `tag_uuid`,
+    `resource_tags`.`key` AS `tag_key`,
+    `resource_tags`.`value` AS `tag_value`,
+    `resource_tags`.`domain_id` AS `tag_domain_id`,
+    `domain`.`uuid` AS `tag_domain_uuid`,
+    `domain`.`name` AS `tag_domain_name`,
+    `resource_tags`.`account_id` AS `tag_account_id`,
+    `account`.`account_name` AS `tag_account_name`,
+    `resource_tags`.`resource_id` AS `tag_resource_id`,
+    `resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
+    `resource_tags`.`resource_type` AS `tag_resource_type`,
+    `resource_tags`.`customer` AS `tag_customer`,
+    CONCAT(`vm_template`.`id`,
+           '_',
+           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`,
+    `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`
+FROM
+    (((((((((((((`vm_template`
+        JOIN `guest_os` ON ((`guest_os`.`id` = `vm_template`.`guest_os_id`)))
+        JOIN `account` ON ((`account`.`id` = `vm_template`.`account_id`)))
+        JOIN `domain` ON ((`domain`.`id` = `account`.`domain_id`)))
+        LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`)))
+        LEFT JOIN `vm_template_details` ON ((`vm_template_details`.`template_id` = `vm_template`.`id`)))
+        LEFT JOIN `vm_template` `source_template` ON ((`source_template`.`id` = `vm_template`.`source_template_id`)))
+        LEFT JOIN `template_store_ref` ON (((`template_store_ref`.`template_id` = `vm_template`.`id`)
+            AND (`template_store_ref`.`store_role` = 'Image')
+            AND (`template_store_ref`.`destroyed` = 0))))
+        LEFT JOIN `vm_template` `parent_template` ON ((`parent_template`.`id` = `vm_template`.`parent_template_id`)))
+        LEFT JOIN `image_store` ON ((ISNULL(`image_store`.`removed`)
+            AND (`template_store_ref`.`store_id` IS NOT NULL)
+            AND (`image_store`.`id` = `template_store_ref`.`store_id`))))
+        LEFT JOIN `template_zone_ref` ON (((`template_zone_ref`.`template_id` = `vm_template`.`id`)
+            AND ISNULL(`template_store_ref`.`store_id`)
+            AND ISNULL(`template_zone_ref`.`removed`))))
+        LEFT JOIN `data_center` ON (((`image_store`.`data_center_id` = `data_center`.`id`)
+            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 `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
new file mode 100644
index 0000000..7eedc03
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql
@@ -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.
+
+-- VIEW `cloud`.`user_view`;
+
+DROP VIEW IF EXISTS `cloud`.`user_view`;
+
+CREATE VIEW `cloud`.`user_view` AS
+select
+    user.id,
+    user.uuid,
+    user.username,
+    user.password,
+    user.firstname,
+    user.lastname,
+    user.email,
+    user.state,
+    user.api_key,
+    user.secret_key,
+    user.created,
+    user.removed,
+    user.timezone,
+    user.registration_token,
+    user.is_registered,
+    user.incorrect_login_attempts,
+    user.source,
+    user.default,
+    account.id account_id,
+    account.uuid account_uuid,
+    account.account_name account_name,
+    account.type account_type,
+    account.role_id account_role_id,
+    domain.id domain_id,
+    domain.uuid domain_uuid,
+    domain.name domain_name,
+    domain.path domain_path,
+    async_job.id job_id,
+    async_job.uuid job_uuid,
+    async_job.job_status job_status,
+    async_job.account_id job_account_id,
+    user.is_user_2fa_enabled is_user_2fa_enabled
+from
+    `cloud`.`user`
+        inner join
+    `cloud`.`account` ON user.account_id = account.id
+        inner join
+    `cloud`.`domain` ON account.domain_id = domain.id
+        left join
+    `cloud`.`async_job` ON async_job.instance_id = user.id
+        and async_job.instance_type = 'User'
+        and async_job.job_status = 0;
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
new file mode 100644
index 0000000..7a057dc
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql
@@ -0,0 +1,215 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT 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`.`user_vm_view`;
+
+DROP VIEW IF EXISTS `cloud`.`user_vm_view`;
+
+CREATE VIEW `user_vm_view` AS
+SELECT
+    `vm_instance`.`id` AS `id`,
+    `vm_instance`.`name` AS `name`,
+    `user_vm`.`display_name` AS `display_name`,
+    `user_vm`.`user_data` AS `user_data`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `instance_group`.`id` AS `instance_group_id`,
+    `instance_group`.`uuid` AS `instance_group_uuid`,
+    `instance_group`.`name` AS `instance_group_name`,
+    `vm_instance`.`uuid` AS `uuid`,
+    `vm_instance`.`user_id` AS `user_id`,
+    `vm_instance`.`last_host_id` AS `last_host_id`,
+    `vm_instance`.`vm_type` AS `type`,
+    `vm_instance`.`limit_cpu_use` AS `limit_cpu_use`,
+    `vm_instance`.`created` AS `created`,
+    `vm_instance`.`state` AS `state`,
+    `vm_instance`.`update_time` AS `update_time`,
+    `vm_instance`.`removed` AS `removed`,
+    `vm_instance`.`ha_enabled` AS `ha_enabled`,
+    `vm_instance`.`hypervisor_type` AS `hypervisor_type`,
+    `vm_instance`.`instance_name` AS `instance_name`,
+    `vm_instance`.`guest_os_id` AS `guest_os_id`,
+    `vm_instance`.`display_vm` AS `display_vm`,
+    `guest_os`.`uuid` AS `guest_os_uuid`,
+    `vm_instance`.`pod_id` AS `pod_id`,
+    `host_pod_ref`.`uuid` AS `pod_uuid`,
+    `vm_instance`.`private_ip_address` AS `private_ip_address`,
+    `vm_instance`.`private_mac_address` AS `private_mac_address`,
+    `vm_instance`.`vm_type` AS `vm_type`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `data_center`.`is_security_group_enabled` AS `security_group_enabled`,
+    `data_center`.`networktype` AS `data_center_network_type`,
+    `host`.`id` AS `host_id`,
+    `host`.`uuid` AS `host_uuid`,
+    `host`.`name` AS `host_name`,
+    `host`.`cluster_id` AS `cluster_id`,
+    `host`.`status` AS `host_status`,
+    `host`.`resource_state` AS `host_resource_state`,
+    `vm_template`.`id` AS `template_id`,
+    `vm_template`.`uuid` AS `template_uuid`,
+    `vm_template`.`name` AS `template_name`,
+    `vm_template`.`type` AS `template_type`,
+    `vm_template`.`display_text` AS `template_display_text`,
+    `vm_template`.`enable_password` AS `password_enabled`,
+    `iso`.`id` AS `iso_id`,
+    `iso`.`uuid` AS `iso_uuid`,
+    `iso`.`name` AS `iso_name`,
+    `iso`.`display_text` AS `iso_display_text`,
+    `service_offering`.`id` AS `service_offering_id`,
+    `service_offering`.`uuid` AS `service_offering_uuid`,
+    `disk_offering`.`uuid` AS `disk_offering_uuid`,
+    `disk_offering`.`id` AS `disk_offering_id`,
+    (CASE
+         WHEN ISNULL(`service_offering`.`cpu`) THEN `custom_cpu`.`value`
+         ELSE `service_offering`.`cpu`
+        END) AS `cpu`,
+    (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`,
+    `backup_offering`.`uuid` AS `backup_offering_uuid`,
+    `backup_offering`.`id` AS `backup_offering_id`,
+    `service_offering`.`name` AS `service_offering_name`,
+    `disk_offering`.`name` AS `disk_offering_name`,
+    `backup_offering`.`name` AS `backup_offering_name`,
+    `storage_pool`.`id` AS `pool_id`,
+    `storage_pool`.`uuid` AS `pool_uuid`,
+    `storage_pool`.`pool_type` AS `pool_type`,
+    `volumes`.`id` AS `volume_id`,
+    `volumes`.`uuid` AS `volume_uuid`,
+    `volumes`.`device_id` AS `volume_device_id`,
+    `volumes`.`volume_type` AS `volume_type`,
+    `security_group`.`id` AS `security_group_id`,
+    `security_group`.`uuid` AS `security_group_uuid`,
+    `security_group`.`name` AS `security_group_name`,
+    `security_group`.`description` AS `security_group_description`,
+    `nics`.`id` AS `nic_id`,
+    `nics`.`uuid` AS `nic_uuid`,
+    `nics`.`device_id` AS `nic_device_id`,
+    `nics`.`network_id` AS `network_id`,
+    `nics`.`ip4_address` AS `ip_address`,
+    `nics`.`ip6_address` AS `ip6_address`,
+    `nics`.`ip6_gateway` AS `ip6_gateway`,
+    `nics`.`ip6_cidr` AS `ip6_cidr`,
+    `nics`.`default_nic` AS `is_default_nic`,
+    `nics`.`gateway` AS `gateway`,
+    `nics`.`netmask` AS `netmask`,
+    `nics`.`mac_address` AS `mac_address`,
+    `nics`.`broadcast_uri` AS `broadcast_uri`,
+    `nics`.`isolation_uri` AS `isolation_uri`,
+    `vpc`.`id` AS `vpc_id`,
+    `vpc`.`uuid` AS `vpc_uuid`,
+    `networks`.`uuid` AS `network_uuid`,
+    `networks`.`name` AS `network_name`,
+    `networks`.`traffic_type` AS `traffic_type`,
+    `networks`.`guest_type` AS `guest_type`,
+    `user_ip_address`.`id` AS `public_ip_id`,
+    `user_ip_address`.`uuid` AS `public_ip_uuid`,
+    `user_ip_address`.`public_ip_address` AS `public_ip_address`,
+    `ssh_details`.`value` AS `keypair_names`,
+    `resource_tags`.`id` AS `tag_id`,
+    `resource_tags`.`uuid` AS `tag_uuid`,
+    `resource_tags`.`key` AS `tag_key`,
+    `resource_tags`.`value` AS `tag_value`,
+    `resource_tags`.`domain_id` AS `tag_domain_id`,
+    `domain`.`uuid` AS `tag_domain_uuid`,
+    `domain`.`name` AS `tag_domain_name`,
+    `resource_tags`.`account_id` AS `tag_account_id`,
+    `account`.`account_name` AS `tag_account_name`,
+    `resource_tags`.`resource_id` AS `tag_resource_id`,
+    `resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
+    `resource_tags`.`resource_type` AS `tag_resource_type`,
+    `resource_tags`.`customer` AS `tag_customer`,
+    `async_job`.`id` AS `job_id`,
+    `async_job`.`uuid` AS `job_uuid`,
+    `async_job`.`job_status` AS `job_status`,
+    `async_job`.`account_id` AS `job_account_id`,
+    `affinity_group`.`id` AS `affinity_group_id`,
+    `affinity_group`.`uuid` AS `affinity_group_uuid`,
+    `affinity_group`.`name` AS `affinity_group_name`,
+    `affinity_group`.`description` AS `affinity_group_description`,
+    `autoscale_vmgroups`.`id` AS `autoscale_vmgroup_id`,
+    `autoscale_vmgroups`.`uuid` AS `autoscale_vmgroup_uuid`,
+    `autoscale_vmgroups`.`name` AS `autoscale_vmgroup_name`,
+    `vm_instance`.`dynamically_scalable` AS `dynamically_scalable`,
+    `user_data`.`id` AS `user_data_id`,
+    `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`
+FROM
+    (((((((((((((((((((((((((((((((((((`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`)))
+        JOIN `domain` ON ((`vm_instance`.`domain_id` = `domain`.`id`)))
+        LEFT JOIN `guest_os` ON ((`vm_instance`.`guest_os_id` = `guest_os`.`id`)))
+        LEFT JOIN `host_pod_ref` ON ((`vm_instance`.`pod_id` = `host_pod_ref`.`id`)))
+        LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`)))
+        LEFT JOIN `instance_group_vm_map` ON ((`vm_instance`.`id` = `instance_group_vm_map`.`instance_id`)))
+        LEFT JOIN `instance_group` ON ((`instance_group_vm_map`.`group_id` = `instance_group`.`id`)))
+        LEFT JOIN `data_center` ON ((`vm_instance`.`data_center_id` = `data_center`.`id`)))
+        LEFT JOIN `host` ON ((`vm_instance`.`host_id` = `host`.`id`)))
+        LEFT JOIN `vm_template` ON ((`vm_instance`.`vm_template_id` = `vm_template`.`id`)))
+        LEFT JOIN `vm_template` `iso` ON ((`iso`.`id` = `user_vm`.`iso_id`)))
+        LEFT JOIN `volumes` ON ((`vm_instance`.`id` = `volumes`.`instance_id`)))
+        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 `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`)))
+        LEFT JOIN `security_group` ON ((`security_group_vm_map`.`security_group_id` = `security_group`.`id`)))
+        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 `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` ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`)))
+        LEFT JOIN `user_vm_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'))))
+        LEFT JOIN `async_job` ON (((`async_job`.`instance_id` = `vm_instance`.`id`)
+        AND (`async_job`.`instance_type` = 'VirtualMachine')
+        AND (`async_job`.`job_status` = 0))))
+        LEFT JOIN `affinity_group_vm_map` ON ((`vm_instance`.`id` = `affinity_group_vm_map`.`instance_id`)))
+        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`)
+        AND (`custom_cpu`.`name` = 'CpuNumber'))))
+        LEFT JOIN `user_vm_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'))));
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
new file mode 100644
index 0000000..fd21fff
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql
@@ -0,0 +1,156 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT 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`.`volume_view`;
+
+DROP VIEW IF EXISTS `cloud`.`volume_view`;
+
+CREATE VIEW `cloud`.`volume_view` AS
+SELECT
+    `volumes`.`id` AS `id`,
+    `volumes`.`uuid` AS `uuid`,
+    `volumes`.`name` AS `name`,
+    `volumes`.`device_id` AS `device_id`,
+    `volumes`.`volume_type` AS `volume_type`,
+    `volumes`.`provisioning_type` AS `provisioning_type`,
+    `volumes`.`size` AS `size`,
+    `volumes`.`min_iops` AS `min_iops`,
+    `volumes`.`max_iops` AS `max_iops`,
+    `volumes`.`created` AS `created`,
+    `volumes`.`state` AS `state`,
+    `volumes`.`attached` AS `attached`,
+    `volumes`.`removed` AS `removed`,
+    `volumes`.`display_volume` AS `display_volume`,
+    `volumes`.`format` AS `format`,
+    `volumes`.`path` AS `path`,
+    `volumes`.`chain_info` AS `chain_info`,
+    `volumes`.`external_uuid` AS `external_uuid`,
+    `account`.`id` AS `account_id`,
+    `account`.`uuid` AS `account_uuid`,
+    `account`.`account_name` AS `account_name`,
+    `account`.`type` AS `account_type`,
+    `domain`.`id` AS `domain_id`,
+    `domain`.`uuid` AS `domain_uuid`,
+    `domain`.`name` AS `domain_name`,
+    `domain`.`path` AS `domain_path`,
+    `projects`.`id` AS `project_id`,
+    `projects`.`uuid` AS `project_uuid`,
+    `projects`.`name` AS `project_name`,
+    `data_center`.`id` AS `data_center_id`,
+    `data_center`.`uuid` AS `data_center_uuid`,
+    `data_center`.`name` AS `data_center_name`,
+    `data_center`.`networktype` AS `data_center_type`,
+    `vm_instance`.`id` AS `vm_id`,
+    `vm_instance`.`uuid` AS `vm_uuid`,
+    `vm_instance`.`name` AS `vm_name`,
+    `vm_instance`.`state` AS `vm_state`,
+    `vm_instance`.`vm_type` AS `vm_type`,
+    `user_vm`.`display_name` AS `vm_display_name`,
+    `volume_store_ref`.`size` AS `volume_store_size`,
+    `volume_store_ref`.`download_pct` AS `download_pct`,
+    `volume_store_ref`.`download_state` AS `download_state`,
+    `volume_store_ref`.`error_str` AS `error_str`,
+    `volume_store_ref`.`created` AS `created_on_store`,
+    `disk_offering`.`id` AS `disk_offering_id`,
+    `disk_offering`.`uuid` AS `disk_offering_uuid`,
+    `disk_offering`.`name` AS `disk_offering_name`,
+    `disk_offering`.`display_text` AS `disk_offering_display_text`,
+    `disk_offering`.`use_local_storage` AS `use_local_storage`,
+    `service_offering`.`system_use` AS `system_use`,
+    `disk_offering`.`bytes_read_rate` AS `bytes_read_rate`,
+    `disk_offering`.`bytes_write_rate` AS `bytes_write_rate`,
+    `disk_offering`.`iops_read_rate` AS `iops_read_rate`,
+    `disk_offering`.`iops_write_rate` AS `iops_write_rate`,
+    `disk_offering`.`cache_mode` AS `cache_mode`,
+    `storage_pool`.`id` AS `pool_id`,
+    `storage_pool`.`uuid` AS `pool_uuid`,
+    `storage_pool`.`name` AS `pool_name`,
+    `cluster`.`id` AS `cluster_id`,
+    `cluster`.`name` AS `cluster_name`,
+    `cluster`.`uuid` AS `cluster_uuid`,
+    `cluster`.`hypervisor_type` AS `hypervisor_type`,
+    `vm_template`.`id` AS `template_id`,
+    `vm_template`.`uuid` AS `template_uuid`,
+    `vm_template`.`extractable` AS `extractable`,
+    `vm_template`.`type` AS `template_type`,
+    `vm_template`.`name` AS `template_name`,
+    `vm_template`.`display_text` AS `template_display_text`,
+    `iso`.`id` AS `iso_id`,
+    `iso`.`uuid` AS `iso_uuid`,
+    `iso`.`name` AS `iso_name`,
+    `iso`.`display_text` AS `iso_display_text`,
+    `resource_tags`.`id` AS `tag_id`,
+    `resource_tags`.`uuid` AS `tag_uuid`,
+    `resource_tags`.`key` AS `tag_key`,
+    `resource_tags`.`value` AS `tag_value`,
+    `resource_tags`.`domain_id` AS `tag_domain_id`,
+    `resource_tags`.`account_id` AS `tag_account_id`,
+    `resource_tags`.`resource_id` AS `tag_resource_id`,
+    `resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
+    `resource_tags`.`resource_type` AS `tag_resource_type`,
+    `resource_tags`.`customer` AS `tag_customer`,
+    `async_job`.`id` AS `job_id`,
+    `async_job`.`uuid` AS `job_uuid`,
+    `async_job`.`job_status` AS `job_status`,
+    `async_job`.`account_id` AS `job_account_id`,
+    `host_pod_ref`.`id` AS `pod_id`,
+    `host_pod_ref`.`uuid` AS `pod_uuid`,
+    `host_pod_ref`.`name` AS `pod_name`,
+    `resource_tag_account`.`account_name` AS `tag_account_name`,
+    `resource_tag_domain`.`uuid` AS `tag_domain_uuid`,
+    `resource_tag_domain`.`name` AS `tag_domain_name`
+FROM
+    ((((((((((((((((((`volumes`
+JOIN `account`ON
+    ((`volumes`.`account_id` = `account`.`id`)))
+JOIN `domain`ON
+    ((`volumes`.`domain_id` = `domain`.`id`)))
+LEFT JOIN `projects`ON
+    ((`projects`.`project_account_id` = `account`.`id`)))
+LEFT JOIN `data_center`ON
+    ((`volumes`.`data_center_id` = `data_center`.`id`)))
+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
+    ((`volumes`.`id` = `volume_store_ref`.`volume_id`)))
+LEFT JOIN `service_offering`ON
+    ((`vm_instance`.`service_offering_id` = `service_offering`.`id`)))
+LEFT JOIN `disk_offering`ON
+    ((`volumes`.`disk_offering_id` = `disk_offering`.`id`)))
+LEFT JOIN `storage_pool`ON
+    ((`volumes`.`pool_id` = `storage_pool`.`id`)))
+LEFT JOIN `host_pod_ref`ON
+    ((`storage_pool`.`pod_id` = `host_pod_ref`.`id`)))
+LEFT JOIN `cluster`ON
+    ((`storage_pool`.`cluster_id` = `cluster`.`id`)))
+LEFT JOIN `vm_template`ON
+    ((`volumes`.`template_id` = `vm_template`.`id`)))
+LEFT JOIN `vm_template` `iso`ON
+    ((`iso`.`id` = `volumes`.`iso_id`)))
+LEFT JOIN `resource_tags`ON
+    (((`resource_tags`.`resource_id` = `volumes`.`id`)
+        and (`resource_tags`.`resource_type` = 'Volume'))))
+LEFT JOIN `async_job`ON
+    (((`async_job`.`instance_id` = `volumes`.`id`)
+        and (`async_job`.`instance_type` = 'Volume')
+            and (`async_job`.`job_status` = 0))))
+LEFT JOIN `account` `resource_tag_account`ON
+    ((`resource_tag_account`.`id` = `resource_tags`.`account_id`)))
+LEFT JOIN `domain` `resource_tag_domain`ON
+    ((`resource_tag_domain`.`id` = `resource_tags`.`domain_id`)));
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
new file mode 100644
index 0000000..cb762a5
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
@@ -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.
+
+-- cloud.vpc_offering_view source
+
+
+DROP VIEW IF EXISTS `cloud`.`vpc_offering_view`;
+
+CREATE VIEW `cloud`.`vpc_offering_view` AS
+select
+    `vpc_offerings`.`id` AS `id`,
+    `vpc_offerings`.`uuid` AS `uuid`,
+    `vpc_offerings`.`name` AS `name`,
+    `vpc_offerings`.`unique_name` AS `unique_name`,
+    `vpc_offerings`.`display_text` AS `display_text`,
+    `vpc_offerings`.`state` AS `state`,
+    `vpc_offerings`.`default` AS `default`,
+    `vpc_offerings`.`created` AS `created`,
+    `vpc_offerings`.`removed` AS `removed`,
+    `vpc_offerings`.`service_offering_id` AS `service_offering_id`,
+    `vpc_offerings`.`supports_distributed_router` AS `supports_distributed_router`,
+    `vpc_offerings`.`supports_region_level_vpc` AS `supports_region_level_vpc`,
+    `vpc_offerings`.`redundant_router_service` AS `redundant_router_service`,
+    `vpc_offerings`.`sort_key` AS `sort_key`,
+    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`,
+    group_concat(distinct `domain`.`path` separator ',') AS `domain_path`,
+    group_concat(distinct `zone`.`id` separator ',') AS `zone_id`,
+    group_concat(distinct `zone`.`uuid` separator ',') AS `zone_uuid`,
+    group_concat(distinct `zone`.`name` separator ',') AS `zone_name`,
+    `offering_details`.`value` AS `internet_protocol`
+from
+    (((((`vpc_offerings`
+left join `vpc_offering_details` `domain_details` on
+    (((`domain_details`.`offering_id` = `vpc_offerings`.`id`)
+        and (`domain_details`.`name` = 'domainid'))))
+left join `domain` on
+    ((0 <> find_in_set(`domain`.`id`, `domain_details`.`value`))))
+left join `vpc_offering_details` `zone_details` on
+    (((`zone_details`.`offering_id` = `vpc_offerings`.`id`)
+        and (`zone_details`.`name` = 'zoneid'))))
+left join `data_center` `zone` on
+    ((0 <> find_in_set(`zone`.`id`, `zone_details`.`value`))))
+left join `vpc_offering_details` `offering_details` on
+    (((`offering_details`.`offering_id` = `vpc_offerings`.`id`)
+        and (`offering_details`.`name` = 'internetprotocol'))))
+group by
+    `vpc_offerings`.`id`;
diff --git a/engine/schema/src/test/java/com/cloud/host/HostVOTest.java b/engine/schema/src/test/java/com/cloud/host/HostVOTest.java
index 9ab010d..76bc527 100755
--- a/engine/schema/src/test/java/com/cloud/host/HostVOTest.java
+++ b/engine/schema/src/test/java/com/cloud/host/HostVOTest.java
@@ -1,61 +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 com.cloud.host;
-
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.vm.VirtualMachine;
-import java.util.Arrays;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import org.junit.Test;
-import org.junit.Before;
-
-public class HostVOTest {
-    HostVO host;
-    ServiceOfferingVO offering;
-
-    @Before
-    public void setUp() throws Exception {
-        host = new HostVO();
-        offering = new ServiceOfferingVO("TestSO", 0, 0, 0, 0, 0,
-                false, "TestSO", false,VirtualMachine.Type.User,false);
-    }
-
-    @Test
-    public void testNoSO() {
-        assertFalse(host.checkHostServiceOfferingTags(null));
-    }
-
-    @Test
-    public void testNoTag() {
-        assertTrue(host.checkHostServiceOfferingTags(offering));
-    }
-
-    @Test
-    public void testRightTag() {
-        host.setHostTags(Arrays.asList("tag1","tag2"));
-        offering.setHostTag("tag2,tag1");
-        assertTrue(host.checkHostServiceOfferingTags(offering));
-    }
-
-    @Test
-    public void testWrongTag() {
-        host.setHostTags(Arrays.asList("tag1","tag2"));
-        offering.setHostTag("tag2,tag4");
-        assertFalse(host.checkHostServiceOfferingTags(offering));
-    }
-}
+// Licensed to the Apache Software Foundation (ASF) under one

+// or more contributor license agreements.  See the NOTICE file

+// distributed with this work for additional information

+// regarding copyright ownership.  The ASF licenses this file

+// to you under the Apache License, Version 2.0 (the

+// "License"); you may not use this file except in compliance

+// with the License.  You may obtain a copy of the License at

+//

+//   http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing,

+// software distributed under the License is distributed on an

+// "AS IS" BASIS, WITHOUT WARRANTIES 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.host;

+

+import com.cloud.service.ServiceOfferingVO;

+import com.cloud.vm.VirtualMachine;

+import java.util.Arrays;

+import java.util.List;

+

+import static org.junit.Assert.assertFalse;

+import static org.junit.Assert.assertTrue;

+import org.junit.Test;

+import org.junit.Before;

+

+public class HostVOTest {

+    HostVO host;

+    ServiceOfferingVO offering;

+

+    @Before

+    public void setUp() throws Exception {

+        host = new HostVO();

+        offering = new ServiceOfferingVO("TestSO", 0, 0, 0, 0, 0,

+                false, "TestSO", false,VirtualMachine.Type.User,false);

+    }

+

+    @Test

+    public void testNoSO() {

+        assertFalse(host.checkHostServiceOfferingTags(null));

+    }

+

+    @Test

+    public void testNoTag() {

+        assertTrue(host.checkHostServiceOfferingTags(offering));

+    }

+

+    @Test

+    public void testRightTag() {

+        host.setHostTags(Arrays.asList("tag1","tag2"), false);

+        offering.setHostTag("tag2,tag1");

+        assertTrue(host.checkHostServiceOfferingTags(offering));

+    }

+

+    @Test

+    public void testWrongTag() {

+        host.setHostTags(Arrays.asList("tag1","tag2"), false);

+        offering.setHostTag("tag2,tag4");

+        assertFalse(host.checkHostServiceOfferingTags(offering));

+    }

+

+    @Test

+    public void checkHostServiceOfferingTagsTestRuleTagWithServiceTagThatMatches() {

+        host.setHostTags(List.of("tags[0] == 'A'"), true);

+        offering.setHostTag("A");

+        assertTrue(host.checkHostServiceOfferingTags(offering));

+    }

+

+    @Test

+    public void checkHostServiceOfferingTagsTestRuleTagWithServiceTagThatDoesNotMatch() {

+        host.setHostTags(List.of("tags[0] == 'A'"), true);

+        offering.setHostTag("B");

+        assertFalse(host.checkHostServiceOfferingTags(offering));

+    }

+

+    @Test

+    public void checkHostServiceOfferingTagsTestRuleTagWithNullServiceTag() {

+        host.setHostTags(List.of("tags[0] == 'A'"), true);

+        offering.setHostTag(null);

+        assertFalse(host.checkHostServiceOfferingTags(offering));

+    }

+}

diff --git a/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java b/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
index 7e9658e..6813a20 100755
--- a/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
+++ b/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
@@ -17,6 +17,7 @@
 package com.cloud.network.as;
 
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -42,14 +43,14 @@
     public void testCounterParamsForUpdate() {
         AutoScaleVmProfileVO profile = new AutoScaleVmProfileVO();
 
-        Map<String, HashMap<String, String>> counterParamList = new HashMap<>();
-        counterParamList.put("0", new HashMap<>() {{ put("name", "snmpcommunity"); put("value", "public"); }});
-        counterParamList.put("1", new HashMap<>() {{ put("name", "snmpport"); put("value", "161"); }});
+        Map<String, LinkedHashMap<String, String>> counterParamList = new LinkedHashMap<>();
+        counterParamList.put("0", new LinkedHashMap<>() {{ put("name", "snmpcommunity"); put("value", "public"); }});
+        counterParamList.put("1", new LinkedHashMap<>() {{ put("name", "snmpport"); put("value", "161"); }});
 
         profile.setCounterParamsForUpdate(counterParamList);
         Assert.assertEquals("snmpcommunity=public&snmpport=161", profile.getCounterParamsString());
+        List<Pair<String, String>> counterParams = profile.getCounterParams();
 
-        List<Pair<String, String>>  counterParams = profile.getCounterParams();
         Assert.assertEquals(2, counterParams.size());
         Assert.assertEquals("snmpcommunity", counterParams.get(0).first());
         Assert.assertEquals("public", counterParams.get(0).second());
@@ -69,10 +70,17 @@
 
         List<Pair<String, String>> otherDeployParamsList = profile.getOtherDeployParamsList();
         Assert.assertEquals(2, otherDeployParamsList.size());
-        Assert.assertEquals("serviceofferingid", otherDeployParamsList.get(0).first());
-        Assert.assertEquals("a7fb50f6-01d9-11ed-8bc1-77f8f0228926", otherDeployParamsList.get(0).second());
-        Assert.assertEquals("rootdisksize", otherDeployParamsList.get(1).first());
-        Assert.assertEquals("10", otherDeployParamsList.get(1).second());
+        Assert.assertTrue(containsPair(otherDeployParamsList, "serviceofferingid", "a7fb50f6-01d9-11ed-8bc1-77f8f0228926"));
+        Assert.assertTrue(containsPair(otherDeployParamsList, "rootdisksize", "10"));
+    }
+
+    private boolean containsPair(List<Pair<String, String>> list, String key, String value) {
+        for (Pair<String, String> pair : list) {
+            if (key.equals(pair.first()) && value.equals(pair.second())) {
+                return true;
+            }
+        }
+        return false;
     }
 
     @Test
diff --git a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImplTest.java
index bcfcdf9..5b3f5eb 100644
--- a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImplTest.java
@@ -31,12 +31,11 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class AutoScaleVmGroupDaoImplTest {
 
     @Mock
@@ -52,7 +51,7 @@
     AutoScaleVmGroupVO autoScaleVmGroupVOMock;
 
     @Spy
-    AutoScaleVmGroupDaoImpl AutoScaleVmGroupDaoImplSpy = PowerMockito.spy(new AutoScaleVmGroupDaoImpl());
+    AutoScaleVmGroupDaoImpl AutoScaleVmGroupDaoImplSpy;
 
     @Before
     public void setUp() {
@@ -63,7 +62,7 @@
     @Test
     public void testListByLoadBalancer() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(listAutoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(listAutoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
 
         long loadBalancerId = 100L;
 
@@ -77,7 +76,7 @@
     @Test
     public void testListByProfile() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(listAutoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(listAutoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
 
         long profileId = 101L;
 
@@ -91,7 +90,7 @@
     @Test
     public void testListByAccount() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(listAutoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(listAutoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
 
         long accountId = 102L;
 
@@ -105,7 +104,7 @@
     @Test
     public void testUpdateState1() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(null).when(AutoScaleVmGroupDaoImplSpy, "findOneBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(null).when(AutoScaleVmGroupDaoImplSpy).findOneBy(Mockito.any(SearchCriteria.class));
 
         long groupId = 10L;
         AutoScaleVmGroup.State oldState = AutoScaleVmGroup.State.ENABLED;
@@ -118,9 +117,9 @@
     @Test
     public void testUpdateState2() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(autoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy, "findOneBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(autoScaleVmGroupVOMock).when(AutoScaleVmGroupDaoImplSpy).findOneBy(Mockito.any(SearchCriteria.class));
         Mockito.doNothing().when(autoScaleVmGroupVOMock).setState(Mockito.any(AutoScaleVmGroup.State.class));
-        PowerMockito.doReturn(true).when(AutoScaleVmGroupDaoImplSpy).update(Mockito.anyLong(), Mockito.any(AutoScaleVmGroupVO.class));
+        Mockito.doReturn(true).when(AutoScaleVmGroupDaoImplSpy).update(Mockito.anyLong(), Mockito.any(AutoScaleVmGroupVO.class));
 
         long groupId = 10L;
         AutoScaleVmGroup.State oldState = AutoScaleVmGroup.State.ENABLED;
diff --git a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImplTest.java
index 965abac..604e902 100644
--- a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImplTest.java
@@ -28,15 +28,16 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Date;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class AutoScaleVmGroupStatisticsDaoImplTest {
 
     @Mock
@@ -48,7 +49,9 @@
     @Mock
     List<AutoScaleVmGroupStatisticsVO> listAutoScaleVmGroupStatisticsVOMock;
 
-    AutoScaleVmGroupStatisticsDaoImpl AutoScaleVmGroupStatisticsDaoImplSpy = PowerMockito.spy(new AutoScaleVmGroupStatisticsDaoImpl());
+    @Spy
+    @InjectMocks
+    AutoScaleVmGroupStatisticsDaoImpl AutoScaleVmGroupStatisticsDaoImplSpy;
 
     long groupId = 4L;
     long policyId = 5L;
@@ -60,20 +63,20 @@
     @Before
     public void setUp() throws Exception {
         AutoScaleVmGroupStatisticsDaoImplSpy.groupAndCounterSearch = searchBuilderAutoScaleVmGroupStatisticsVOMock;
-        PowerMockito.doReturn(searchBuilderAutoScaleVmGroupStatisticsVOMock).when(AutoScaleVmGroupStatisticsDaoImplSpy).createSearchBuilder();
+        Mockito.doReturn(searchBuilderAutoScaleVmGroupStatisticsVOMock).when(AutoScaleVmGroupStatisticsDaoImplSpy).createSearchBuilder();
         Mockito.doReturn(searchCriteriaAutoScaleVmGroupStatisticsVOMock).when(searchBuilderAutoScaleVmGroupStatisticsVOMock).create();
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupStatisticsVOMock).setParameters(Mockito.anyString(), Mockito.any());
 
-        PowerMockito.doReturn(listAutoScaleVmGroupStatisticsVOMock).when(AutoScaleVmGroupStatisticsDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(listAutoScaleVmGroupStatisticsVOMock).when(AutoScaleVmGroupStatisticsDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
 
-        PowerMockito.doReturn(autoScaleVmGroupStatisticsVO).when(AutoScaleVmGroupStatisticsDaoImplSpy).createForUpdate();
-        PowerMockito.doReturn(1).when(AutoScaleVmGroupStatisticsDaoImplSpy).update(Mockito.any(AutoScaleVmGroupStatisticsVO.class), Mockito.any(SearchCriteria.class));
-        PowerMockito.doReturn(autoScaleVmGroupStatisticsVO).when(AutoScaleVmGroupStatisticsDaoImplSpy).persist(Mockito.any(AutoScaleVmGroupStatisticsVO.class));
+        Mockito.doReturn(autoScaleVmGroupStatisticsVO).when(AutoScaleVmGroupStatisticsDaoImplSpy).createForUpdate();
+        Mockito.doReturn(1).when(AutoScaleVmGroupStatisticsDaoImplSpy).update(Mockito.any(AutoScaleVmGroupStatisticsVO.class), Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(autoScaleVmGroupStatisticsVO).when(AutoScaleVmGroupStatisticsDaoImplSpy).persist(Mockito.any(AutoScaleVmGroupStatisticsVO.class));
     }
 
     @Test
     public void testRemoveByGroupId1() {
-        PowerMockito.doReturn(2).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(2).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
         boolean result = AutoScaleVmGroupStatisticsDaoImplSpy.removeByGroupId(groupId);
         Assert.assertTrue(result);
         Mockito.verify(searchCriteriaAutoScaleVmGroupStatisticsVOMock).setParameters("vmGroupId", groupId);
@@ -81,7 +84,7 @@
 
     @Test
     public void testRemoveByGroupId2() {
-        PowerMockito.doReturn(-1).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(-1).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
         boolean result = AutoScaleVmGroupStatisticsDaoImplSpy.removeByGroupId(groupId);
         Assert.assertFalse(result);
         Mockito.verify(searchCriteriaAutoScaleVmGroupStatisticsVOMock).setParameters("vmGroupId", groupId);
@@ -90,7 +93,7 @@
 
     @Test
     public void testRemoveByGroupId3() {
-        PowerMockito.doReturn(-1).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(-1).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
         boolean result = AutoScaleVmGroupStatisticsDaoImplSpy.removeByGroupId(groupId, date);
         Assert.assertFalse(result);
         Mockito.verify(searchCriteriaAutoScaleVmGroupStatisticsVOMock).setParameters("vmGroupId", groupId);
@@ -100,7 +103,7 @@
 
     @Test
     public void testRemoveByGroupAndPolicy1() {
-        PowerMockito.doReturn(2).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(2).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
 
         boolean result = AutoScaleVmGroupStatisticsDaoImplSpy.removeByGroupAndPolicy(groupId, policyId, null);
 
@@ -113,7 +116,7 @@
 
     @Test
     public void testRemoveByGroupAndPolicy2() {
-        PowerMockito.doReturn(-1).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(-1).when(AutoScaleVmGroupStatisticsDaoImplSpy).expunge(Mockito.any(SearchCriteria.class));
 
         boolean result = AutoScaleVmGroupStatisticsDaoImplSpy.removeByGroupAndPolicy(groupId, policyId, date);
 
diff --git a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java
index 2dde100..e13ad42 100644
--- a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java
@@ -32,13 +32,12 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class AutoScaleVmGroupVmMapDaoImplTest {
 
     @Mock
@@ -57,7 +56,7 @@
     SearchCriteria<Integer> searchCriteriaCountAvailableVmsByGroup;
 
     @Spy
-    AutoScaleVmGroupVmMapDaoImpl AutoScaleVmGroupVmMapDaoImplSpy = PowerMockito.spy(new AutoScaleVmGroupVmMapDaoImpl());
+    AutoScaleVmGroupVmMapDaoImpl AutoScaleVmGroupVmMapDaoImplSpy;
 
     @Before
     public void setUp() {
@@ -69,9 +68,7 @@
 
     @Test
     public void testCountAvailableVmsByGroup() throws Exception {
-        Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setJoinParameters(Mockito.anyString(), Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(Arrays.asList(5)).when(AutoScaleVmGroupVmMapDaoImplSpy).customSearch(Mockito.any(SearchCriteria.class), Mockito.eq(null));
+        Mockito.doReturn(Arrays.asList(5)).when(AutoScaleVmGroupVmMapDaoImplSpy).customSearch(Mockito.any(SearchCriteria.class), Mockito.eq(null));
 
         long groupId = 4L;
 
@@ -86,7 +83,7 @@
     @Test
     public void testCountByGroup() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(6).when(AutoScaleVmGroupVmMapDaoImplSpy, "getCountIncludingRemoved", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(6).when(AutoScaleVmGroupVmMapDaoImplSpy).getCountIncludingRemoved(Mockito.any(SearchCriteria.class));
 
         long groupId = 4L;
 
@@ -100,7 +97,7 @@
     @Test
     public void testListByGroup() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(listAutoScaleVmGroupVmMapVOMock).when(AutoScaleVmGroupVmMapDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(listAutoScaleVmGroupVmMapVOMock).when(AutoScaleVmGroupVmMapDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
 
         long groupId = 4L;
 
@@ -114,7 +111,7 @@
     @Test
     public void testListByVm() throws Exception {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(listAutoScaleVmGroupVmMapVOMock).when(AutoScaleVmGroupVmMapDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(listAutoScaleVmGroupVmMapVOMock).when(AutoScaleVmGroupVmMapDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
 
         long vmId = 100L;
 
@@ -128,7 +125,7 @@
     @Test
     public void testRemoveByVm() {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(2).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(2).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
 
         long vmId = 3L;
 
@@ -143,7 +140,7 @@
     @Test
     public void testRemoveByGroup() {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(2).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(2).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
 
         long groupId = 4L;
 
@@ -158,7 +155,7 @@
     @Test
     public void testRemoveByGroupAndVm() {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(2).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(2).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
 
         long vmId = 3L;
         long groupId = 4L;
@@ -175,7 +172,7 @@
     @Test
     public void testRemoveByVmFailed() {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(-1).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(-1).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
 
         long vmId = 3L;
 
@@ -190,7 +187,7 @@
     @Test
     public void testRemoveByGroupFailed() {
         Mockito.doNothing().when(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(-1).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
+        Mockito.doReturn(-1).when(AutoScaleVmGroupVmMapDaoImplSpy).remove(Mockito.any(SearchCriteria.class));
 
         long groupId = 4L;
 
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 e773ff9..ab5f435 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,17 +22,18 @@
 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;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+
 
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class NetworkDaoImplTest {
 
     @Mock
@@ -46,21 +47,25 @@
 
     @Test
     public void listByPhysicalNetworkTrafficTypeTestSetParametersValidation() throws Exception {
-        NetworkDaoImpl networkDaoImplSpy = PowerMockito.spy(new NetworkDaoImpl());
+        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));
 
-        networkDaoImplSpy.AllFieldsSearch = searchBuilderNetworkVoMock;
-        Mockito.doReturn(searchCriteriaNetworkVoMock).when(searchBuilderNetworkVoMock).create();
-        Mockito.doNothing().when(searchCriteriaNetworkVoMock).setParameters(Mockito.anyString(), Mockito.any());
-        PowerMockito.doReturn(listNetworkVoMock).when(networkDaoImplSpy, "listBy", Mockito.any(SearchCriteria.class));
+            long expectedPhysicalNetwork = 2513l;
 
-        long expectedPhysicalNetwork = 2513l;
+            for (Networks.TrafficType trafficType : Networks.TrafficType.values()) {
+                List<NetworkVO> result = networkDaoImplSpy.listByPhysicalNetworkTrafficType(expectedPhysicalNetwork, trafficType);
+                Assert.assertEquals(listNetworkVoMock, result);
+                Mockito.verify(searchCriteriaNetworkVoMock).setParameters("trafficType", trafficType);
+            }
 
-        for (Networks.TrafficType trafficType : Networks.TrafficType.values()) {
-            List<NetworkVO> 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("physicalNetwork", expectedPhysicalNetwork);
+        } finally {
+            txn.close();
         }
-
-        Mockito.verify(searchCriteriaNetworkVoMock, Mockito.times(Networks.TrafficType.values().length)).setParameters("physicalNetwork", expectedPhysicalNetwork);
     }
 }
diff --git a/engine/schema/src/test/java/com/cloud/storage/VnfTemplateDetailVOTest.java b/engine/schema/src/test/java/com/cloud/storage/VnfTemplateDetailVOTest.java
new file mode 100755
index 0000000..99edbc4
--- /dev/null
+++ b/engine/schema/src/test/java/com/cloud/storage/VnfTemplateDetailVOTest.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.storage;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VnfTemplateDetailVOTest {
+
+    static long templateId  = 100L;
+    static String name = "key1";
+    static String value = "value1";
+    static boolean display = true;
+
+    @Test
+    public void testVnfTemplateNicVOProperties() {
+        VnfTemplateDetailVO detailVO = new VnfTemplateDetailVO(templateId, name, value, display);
+
+        Assert.assertEquals(templateId, detailVO.getResourceId());
+        Assert.assertEquals(name, detailVO.getName());
+        Assert.assertEquals(value, detailVO.getValue());
+        Assert.assertEquals(display, detailVO.isDisplay());
+    }
+}
diff --git a/engine/schema/src/test/java/com/cloud/storage/VnfTemplateNicVOTest.java b/engine/schema/src/test/java/com/cloud/storage/VnfTemplateNicVOTest.java
new file mode 100755
index 0000000..96ebd4e
--- /dev/null
+++ b/engine/schema/src/test/java/com/cloud/storage/VnfTemplateNicVOTest.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.storage;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VnfTemplateNicVOTest {
+
+    static long templateId  = 100L;
+    static long deviceId = 0L;
+    static String deviceName = "eth0";
+    static boolean required = true;
+    static boolean management = false;
+    static String description = "description of vnf nic";
+
+
+    @Test
+    public void testVnfTemplateNicVOProperties() {
+        VnfTemplateNicVO nicVO = new VnfTemplateNicVO(templateId, deviceId, deviceName, required, management, description);
+
+        Assert.assertEquals(templateId, nicVO.getTemplateId());
+        Assert.assertEquals(deviceId, nicVO.getDeviceId());
+        Assert.assertEquals(deviceName, nicVO.getDeviceName());
+        Assert.assertEquals(required, nicVO.isRequired());
+        Assert.assertEquals(management, nicVO.isManagement());
+        Assert.assertEquals(description, nicVO.getDescription());
+
+        String expected = String.format("Template {\"deviceId\":%d,\"id\":0,\"required\":%s,\"templateId\":%d}", deviceId, required, templateId);
+        Assert.assertEquals(expected, nicVO.toString());
+    }
+}
diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/StoragePoolTagsDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/StoragePoolTagsDaoImplTest.java
index dcbd665..9277bf9 100755
--- a/engine/schema/src/test/java/com/cloud/storage/dao/StoragePoolTagsDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/storage/dao/StoragePoolTagsDaoImplTest.java
@@ -25,7 +25,7 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.storage.StoragePoolTagVO;
 import com.cloud.utils.db.Filter;
@@ -43,7 +43,7 @@
 
 import junit.framework.TestCase;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class StoragePoolTagsDaoImplTest extends TestCase {
 
     @Mock
diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/VnfTemplateNicDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/VnfTemplateNicDaoImplTest.java
new file mode 100644
index 0000000..6a574fc
--- /dev/null
+++ b/engine/schema/src/test/java/com/cloud/storage/dao/VnfTemplateNicDaoImplTest.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 com.cloud.storage.dao;
+
+import com.cloud.storage.VnfTemplateNicVO;
+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.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VnfTemplateNicDaoImplTest {
+
+    @Mock
+    SearchBuilder<VnfTemplateNicVO> searchBuilderVnfTemplateNicVOMock;
+
+    @Mock
+    SearchCriteria<VnfTemplateNicVO> searchCriteriaVnfTemplateNicVOMock;
+
+    @Mock
+    List<VnfTemplateNicVO> listVnfTemplateNicVOMock;
+
+    @Mock
+    private TransactionLegacy transactionMock;
+
+    @Spy
+    VnfTemplateNicDaoImpl vnfTemplateNicDaoImplSpy;
+
+    @Before
+    public void setUp() {
+        vnfTemplateNicDaoImplSpy.TemplateSearch = searchBuilderVnfTemplateNicVOMock;
+        Mockito.doReturn(searchCriteriaVnfTemplateNicVOMock).when(searchBuilderVnfTemplateNicVOMock).create();
+        Mockito.doNothing().when(searchCriteriaVnfTemplateNicVOMock).setParameters(Mockito.anyString(), Mockito.any());
+    }
+
+    @Test
+    public void testListByTemplateId() {
+        Mockito.doReturn(listVnfTemplateNicVOMock).when(vnfTemplateNicDaoImplSpy).listBy(Mockito.any(SearchCriteria.class));
+        long templateId = 100L;
+
+        List<VnfTemplateNicVO> result = vnfTemplateNicDaoImplSpy.listByTemplateId(templateId);
+
+        Assert.assertEquals(listVnfTemplateNicVOMock, result);
+        Mockito.verify(searchCriteriaVnfTemplateNicVOMock).setParameters("templateId", templateId);
+    }
+
+    @Test
+    public void testDeleteByTemplateId() {
+        Mockito.doReturn(0).when(vnfTemplateNicDaoImplSpy).remove(searchCriteriaVnfTemplateNicVOMock);
+        long templateId = 100L;
+
+        try (MockedStatic<TransactionLegacy> ignore = Mockito.mockStatic(TransactionLegacy.class)) {
+            Mockito.when(TransactionLegacy.currentTxn()).thenReturn(transactionMock);
+            Mockito.doNothing().when(transactionMock).start();
+            Mockito.doReturn(true).when(transactionMock).commit();
+
+            vnfTemplateNicDaoImplSpy.deleteByTemplateId(templateId);
+
+            Mockito.verify(transactionMock, Mockito.times(1)).start();
+            Mockito.verify(vnfTemplateNicDaoImplSpy, Mockito.times(1)).remove(searchCriteriaVnfTemplateNicVOMock);
+            Mockito.verify(transactionMock, Mockito.times(1)).commit();
+        }
+    }
+}
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
new file mode 100644
index 0000000..7968ee4
--- /dev/null
+++ b/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.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 static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.utils.db.TransactionLegacy;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VolumeDaoImplTest {
+    @Mock
+    private PreparedStatement preparedStatementMock;
+
+    @Mock
+    private TransactionLegacy transactionMock;
+
+    private static MockedStatic<TransactionLegacy> mockedTransactionLegacy;
+
+    private final VolumeDaoImpl volumeDao = new VolumeDaoImpl();
+
+    @BeforeClass
+    public static void init() {
+        mockedTransactionLegacy = Mockito.mockStatic(TransactionLegacy.class);
+    }
+
+    @AfterClass
+    public static void close() {
+        mockedTransactionLegacy.close();
+    }
+
+    @Test
+    public void testListPoolIdsByVolumeCount_with_cluster_details() throws SQLException {
+        final String ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_QUERY_WITH_CLUSTER =
+                "SELECT pool.id, SUM(IF(vol.state='Ready' AND vol.account_id = ?, 1, 0)) FROM `cloud`.`storage_pool` pool LEFT JOIN `cloud`.`volumes` vol ON pool.id = vol.pool_id WHERE pool.data_center_id = ?  AND pool.pod_id = ? AND pool.cluster_id = ? GROUP BY pool.id ORDER BY 2 ASC ";
+        final long dcId = 1, accountId = 1;
+        final Long podId = 1L, clusterId = 1L;
+
+        when(TransactionLegacy.currentTxn()).thenReturn(transactionMock);
+        when(transactionMock.prepareAutoCloseStatement(startsWith(ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_QUERY_WITH_CLUSTER))).thenReturn(preparedStatementMock);
+        ResultSet rs = Mockito.mock(ResultSet.class);
+        when(preparedStatementMock.executeQuery()).thenReturn(rs, rs);
+
+        volumeDao.listPoolIdsByVolumeCount(dcId, podId, clusterId, accountId);
+
+        verify(transactionMock, times(1)).prepareAutoCloseStatement(ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_QUERY_WITH_CLUSTER);
+        verify(preparedStatementMock, times(1)).setLong(1, accountId);
+        verify(preparedStatementMock, times(1)).setLong(2, dcId);
+        verify(preparedStatementMock, times(1)).setLong(3, podId);
+        verify(preparedStatementMock, times(1)).setLong(4, clusterId);
+        verify(preparedStatementMock, times(4)).setLong(anyInt(), anyLong());
+        verify(preparedStatementMock, times(1)).executeQuery();
+    }
+
+    @Test
+    public void testListPoolIdsByVolumeCount_without_cluster_details() throws SQLException {
+        final String ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_QUERY_WITHOUT_CLUSTER =
+                "SELECT pool.id, SUM(IF(vol.state='Ready' AND vol.account_id = ?, 1, 0)) FROM `cloud`.`storage_pool` pool LEFT JOIN `cloud`.`volumes` vol ON pool.id = vol.pool_id WHERE pool.data_center_id = ?  GROUP BY pool.id ORDER BY 2 ASC ";
+        final long dcId = 1, accountId = 1;
+
+        when(TransactionLegacy.currentTxn()).thenReturn(transactionMock);
+        when(transactionMock.prepareAutoCloseStatement(startsWith(ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_QUERY_WITHOUT_CLUSTER))).thenReturn(preparedStatementMock);
+        ResultSet rs = Mockito.mock(ResultSet.class);
+        when(preparedStatementMock.executeQuery()).thenReturn(rs, rs);
+
+        volumeDao.listPoolIdsByVolumeCount(dcId, null, null, accountId);
+
+        verify(transactionMock, times(1)).prepareAutoCloseStatement(ORDER_POOLS_NUMBER_OF_VOLUMES_FOR_ACCOUNT_QUERY_WITHOUT_CLUSTER);
+        verify(preparedStatementMock, times(1)).setLong(1, accountId);
+        verify(preparedStatementMock, times(1)).setLong(2, dcId);
+        verify(preparedStatementMock, times(2)).setLong(anyInt(), anyLong());
+        verify(preparedStatementMock, times(1)).executeQuery();
+    }
+}
diff --git a/engine/schema/src/test/java/com/cloud/upgrade/GuestOsMapperTest.java b/engine/schema/src/test/java/com/cloud/upgrade/GuestOsMapperTest.java
index fa6c693..94180c9 100644
--- a/engine/schema/src/test/java/com/cloud/upgrade/GuestOsMapperTest.java
+++ b/engine/schema/src/test/java/com/cloud/upgrade/GuestOsMapperTest.java
@@ -20,6 +20,7 @@
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.storage.GuestOSHypervisorVO;
 import com.cloud.storage.dao.GuestOSHypervisorDao;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,12 +30,12 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class GuestOsMapperTest {
 
     @Spy
@@ -44,9 +45,16 @@
     @Mock
     GuestOSHypervisorDao guestOSHypervisorDao;
 
+    private AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -75,8 +83,6 @@
         guestOSHypervisorVOS.add(guestOSHypervisorVO);
         Mockito.when(guestOSHypervisorDao.listByHypervisorTypeAndVersion(Mockito.anyString(), Mockito.anyString())).thenReturn(guestOSHypervisorVOS);
         Mockito.when(guestOSHypervisorVO.getGuestOsName()).thenReturn("centos");
-        GuestOSHypervisorVO guestOsMapping = Mockito.mock(GuestOSHypervisorVO.class);
-        Mockito.when(guestOSHypervisorDao.persist(guestOsMapping)).thenReturn(guestOsMapping);
 
         boolean result = guestOsMapper.copyGuestOSHypervisorMappings(Hypervisor.HypervisorType.XenServer, "6.0", "7.0");
         Assert.assertTrue(result);
diff --git a/engine/schema/src/test/java/com/cloud/upgrade/dao/DatabaseAccessObjectTest.java b/engine/schema/src/test/java/com/cloud/upgrade/dao/DatabaseAccessObjectTest.java
index 7e78c3e..bd05fbe 100644
--- a/engine/schema/src/test/java/com/cloud/upgrade/dao/DatabaseAccessObjectTest.java
+++ b/engine/schema/src/test/java/com/cloud/upgrade/dao/DatabaseAccessObjectTest.java
@@ -16,11 +16,12 @@
 // under the License.
 package com.cloud.upgrade.dao;
 
-import static org.mockito.Matchers.startsWith;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.contains;
-import static org.mockito.Matchers.eq;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,8 +38,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class DatabaseAccessObjectTest {
@@ -59,7 +60,8 @@
 
     @Before
     public void setup() {
-        Whitebox.setInternalState(dao.getClass(), "s_logger", loggerMock);
+        ReflectionTestUtils.setField(dao, "s_logger", loggerMock);
+
     }
 
     @Test
@@ -91,8 +93,8 @@
 
     @Test
     public void generateIndexNameTest() {
-        String indexName = dao.generateIndexName("mytable","mycolumn");
-        Assert.assertEquals( "i_mytable__mycolumn", indexName);
+        String indexName = dao.generateIndexName("mytable","mycolumn1", "mycolumn2");
+        Assert.assertEquals( "i_mytable__mycolumn1__mycolumn2", indexName);
     }
 
     @Test
@@ -134,10 +136,11 @@
 
         Connection conn = connectionMock;
         String tableName = "mytable";
-        String columnName = "mycolumn";
+        String columnName1 = "mycolumn1";
+        String columnName2 = "mycolumn2";
         String indexName = "myindex";
 
-        dao.createIndex(conn, tableName, columnName, indexName);
+        dao.createIndex(conn, tableName, indexName, columnName1, columnName2);
         verify(connectionMock, times(1)).prepareStatement(anyString());
         verify(preparedStatementMock, times(1)).execute();
         verify(preparedStatementMock, times(1)).close();
diff --git a/engine/schema/src/test/java/com/cloud/upgrade/dao/DbUpgradeUtilsTest.java b/engine/schema/src/test/java/com/cloud/upgrade/dao/DbUpgradeUtilsTest.java
index d248cfb..1b77540 100644
--- a/engine/schema/src/test/java/com/cloud/upgrade/dao/DbUpgradeUtilsTest.java
+++ b/engine/schema/src/test/java/com/cloud/upgrade/dao/DbUpgradeUtilsTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package com.cloud.upgrade.dao;
 
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.when;
 
 import java.sql.Connection;
 import java.util.ArrayList;
@@ -31,10 +31,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DbUpgradeUtilsTest {
 
     @Mock
@@ -45,7 +45,7 @@
 
     @Before
     public void setupClass() {
-        Whitebox.setInternalState(DbUpgradeUtils.class, "dao", daoMock);
+        ReflectionTestUtils.setField(DbUpgradeUtils.class, "dao", daoMock);
     }
 
     @Test
diff --git a/engine/schema/src/test/java/com/cloud/usage/dao/UsageStorageDaoImplTest.java b/engine/schema/src/test/java/com/cloud/usage/dao/UsageStorageDaoImplTest.java
index 2a4a58e..c3ad42a 100644
--- a/engine/schema/src/test/java/com/cloud/usage/dao/UsageStorageDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/usage/dao/UsageStorageDaoImplTest.java
@@ -32,15 +32,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(TransactionLegacy.class)
-@PowerMockIgnore("javax.management.*")
+@RunWith(MockitoJUnitRunner.class)
 public class UsageStorageDaoImplTest {
 
     @Mock
@@ -63,26 +59,26 @@
         String UPDATE_DELETED = "UPDATE usage_storage SET deleted = ? WHERE account_id = ? AND entity_id = ? AND storage_type = ? AND zone_id = ? and deleted IS NULL";
         Date deleted = new Date();
 
-        PowerMockito.mockStatic(TransactionLegacy.class);
-        Mockito.when(TransactionLegacy.open(TransactionLegacy.USAGE_DB)).thenReturn(transactionMock);
+        try (MockedStatic<TransactionLegacy> ignore = Mockito.mockStatic(TransactionLegacy.class)) {
+            Mockito.when(TransactionLegacy.open(TransactionLegacy.USAGE_DB)).thenReturn(transactionMock);
 
-        when(transactionMock.prepareStatement(contains(UPDATE_DELETED))).thenReturn(preparedStatementMock);
-        when(userStorageVOMock.getAccountId()).thenReturn(accountId);
-        when(userStorageVOMock.getEntityId()).thenReturn(id);
-        when(userStorageVOMock.getStorageType()).thenReturn(storageType);
-        when(userStorageVOMock.getZoneId()).thenReturn(zoneId);
-        when(userStorageVOMock.getDeleted()).thenReturn(deleted);
+            when(transactionMock.prepareStatement(contains(UPDATE_DELETED))).thenReturn(preparedStatementMock);
+            when(userStorageVOMock.getAccountId()).thenReturn(accountId);
+            when(userStorageVOMock.getEntityId()).thenReturn(id);
+            when(userStorageVOMock.getStorageType()).thenReturn(storageType);
+            when(userStorageVOMock.getZoneId()).thenReturn(zoneId);
+            when(userStorageVOMock.getDeleted()).thenReturn(deleted);
 
 
+            usageDao.update(userStorageVOMock);
 
-        usageDao.update(userStorageVOMock);
-
-        verify(transactionMock, times(1)).prepareStatement(UPDATE_DELETED);
-        verify(preparedStatementMock, times(1)).setString(1, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), deleted));
-        verify(preparedStatementMock, times(1)).setLong(2, accountId);
-        verify(preparedStatementMock, times(1)).setLong(3, id);
-        verify(preparedStatementMock, times(1)).setInt(4, storageType);
-        verify(preparedStatementMock, times(1)).setLong(5, zoneId);
-        verify(preparedStatementMock, times(1)).executeUpdate();
+            verify(transactionMock, times(1)).prepareStatement(UPDATE_DELETED);
+            verify(preparedStatementMock, times(1)).setString(1, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), deleted));
+            verify(preparedStatementMock, times(1)).setLong(2, accountId);
+            verify(preparedStatementMock, times(1)).setLong(3, id);
+            verify(preparedStatementMock, times(1)).setInt(4, storageType);
+            verify(preparedStatementMock, times(1)).setLong(5, zoneId);
+            verify(preparedStatementMock, times(1)).executeUpdate();
+        }
     }
 }
diff --git a/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java
index 767b414..9dc773c 100644
--- a/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java
@@ -17,22 +17,32 @@
 
 package com.cloud.vm.dao;
 
-import com.cloud.utils.Pair;
-import com.cloud.vm.VirtualMachine;
+import static com.cloud.vm.VirtualMachine.State.Running;
+import static com.cloud.vm.VirtualMachine.State.Stopped;
+import static com.cloud.vm.dao.VMInstanceDaoImpl.MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+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.Date;
+
 import org.joda.time.DateTime;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.Assert;
 import org.mockito.Mock;
-
-import static com.cloud.vm.VirtualMachine.State.Running;
-import static com.cloud.vm.VirtualMachine.State.Stopped;
-
-import static org.mockito.Mockito.when;
-import com.cloud.vm.VMInstanceVO;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
+import com.cloud.utils.Pair;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+
 /**
  * Created by sudharma_jain on 3/2/17.
  */
@@ -55,16 +65,130 @@
     }
 
     @Test
-    public void testUpdateState() throws Exception {
+    public void testUpdateState() {
         Long destHostId = null;
-        Pair<Long, Long> opaqueMock = new Pair<Long, Long>(new Long(1), destHostId);
+        Pair<Long, Long> opaqueMock = new Pair<>(1L, destHostId);
         vmInstanceDao.updateState(Stopped, VirtualMachine.Event.FollowAgentPowerOffReport, Stopped, vm , opaqueMock);
     }
 
     @Test
-    public void testIfStateAndHostUnchanged() throws Exception {
-        Assert.assertEquals(vmInstanceDao.ifStateUnchanged(Stopped, Stopped, null, null), true);
-        Assert.assertEquals(vmInstanceDao.ifStateUnchanged(Stopped, Running, null, null), false);
+    public void testIfStateAndHostUnchanged() {
+        assertTrue(vmInstanceDao.ifStateUnchanged(Stopped, Stopped, null, null));
+        assertFalse(vmInstanceDao.ifStateUnchanged(Stopped, Running, null, null));
     }
 
+    @Test
+    public void testUpdatePowerStateDifferentPowerState() {
+        when(vm.getPowerStateUpdateTime()).thenReturn(null);
+        when(vm.getPowerHostId()).thenReturn(1L);
+        when(vm.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOn);
+        doReturn(vm).when(vmInstanceDao).findById(anyLong());
+        doReturn(true).when(vmInstanceDao).update(anyLong(), any());
+
+        boolean result = vmInstanceDao.updatePowerState(1L, 1L, VirtualMachine.PowerState.PowerOff, new Date());
+
+        verify(vm, times(1)).setPowerState(VirtualMachine.PowerState.PowerOff);
+        verify(vm, times(1)).setPowerHostId(1L);
+        verify(vm, times(1)).setPowerStateUpdateCount(1);
+        verify(vm, times(1)).setPowerStateUpdateTime(any(Date.class));
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void testUpdatePowerStateVmNotFound() {
+        when(vm.getPowerStateUpdateTime()).thenReturn(null);
+        when(vm.getPowerHostId()).thenReturn(1L);
+        when(vm.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOn);
+        doReturn(null).when(vmInstanceDao).findById(anyLong());
+
+        boolean result = vmInstanceDao.updatePowerState(1L, 1L, VirtualMachine.PowerState.PowerOff, new Date());
+
+        verify(vm, never()).setPowerState(any());
+        verify(vm, never()).setPowerHostId(anyLong());
+        verify(vm, never()).setPowerStateUpdateCount(any(Integer.class));
+        verify(vm, never()).setPowerStateUpdateTime(any(Date.class));
+
+        assertFalse(result);
+    }
+
+    @Test
+    public void testUpdatePowerStateNoChangeFirstUpdate() {
+        when(vm.getPowerStateUpdateTime()).thenReturn(null);
+        when(vm.getPowerHostId()).thenReturn(1L);
+        when(vm.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOn);
+        when(vm.getState()).thenReturn(Running);
+        when(vm.getPowerStateUpdateCount()).thenReturn(1);
+        doReturn(vm).when(vmInstanceDao).findById(anyLong());
+        doReturn(true).when(vmInstanceDao).update(anyLong(), any());
+
+        boolean result = vmInstanceDao.updatePowerState(1L, 1L, VirtualMachine.PowerState.PowerOn, new Date());
+
+        verify(vm, never()).setPowerState(any());
+        verify(vm, never()).setPowerHostId(anyLong());
+        verify(vm, times(1)).setPowerStateUpdateCount(2);
+        verify(vm, times(1)).setPowerStateUpdateTime(any(Date.class));
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void testUpdatePowerStateNoChangeMaxUpdatesValidState() {
+        when(vm.getPowerStateUpdateTime()).thenReturn(null);
+        when(vm.getPowerHostId()).thenReturn(1L);
+        when(vm.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOn);
+        when(vm.getPowerStateUpdateCount()).thenReturn(MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT);
+        when(vm.getState()).thenReturn(Running);
+        doReturn(vm).when(vmInstanceDao).findById(anyLong());
+        doReturn(true).when(vmInstanceDao).update(anyLong(), any());
+
+        boolean result = vmInstanceDao.updatePowerState(1L, 1L, VirtualMachine.PowerState.PowerOn, new Date());
+
+        verify(vm, never()).setPowerState(any());
+        verify(vm, never()).setPowerHostId(anyLong());
+        verify(vm, never()).setPowerStateUpdateCount(any(Integer.class));
+        verify(vm, never()).setPowerStateUpdateTime(any(Date.class));
+
+        assertFalse(result);
+    }
+
+    @Test
+    public void testUpdatePowerStateNoChangeMaxUpdatesInvalidStateVmStopped() {
+        when(vm.getPowerStateUpdateTime()).thenReturn(null);
+        when(vm.getPowerHostId()).thenReturn(1L);
+        when(vm.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOn);
+        when(vm.getPowerStateUpdateCount()).thenReturn(MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT);
+        when(vm.getState()).thenReturn(Stopped);
+        doReturn(vm).when(vmInstanceDao).findById(anyLong());
+        doReturn(true).when(vmInstanceDao).update(anyLong(), any());
+
+        boolean result = vmInstanceDao.updatePowerState(1L, 1L, VirtualMachine.PowerState.PowerOn, new Date());
+
+        verify(vm, times(1)).setPowerState(any());
+        verify(vm, times(1)).setPowerHostId(anyLong());
+        verify(vm, times(1)).setPowerStateUpdateCount(1);
+        verify(vm, times(1)).setPowerStateUpdateTime(any(Date.class));
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void testUpdatePowerStateNoChangeMaxUpdatesInvalidStateVmRunning() {
+        when(vm.getPowerStateUpdateTime()).thenReturn(null);
+        when(vm.getPowerHostId()).thenReturn(1L);
+        when(vm.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOff);
+        when(vm.getPowerStateUpdateCount()).thenReturn(MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT);
+        when(vm.getState()).thenReturn(Running);
+        doReturn(vm).when(vmInstanceDao).findById(anyLong());
+        doReturn(true).when(vmInstanceDao).update(anyLong(), any());
+
+        boolean result = vmInstanceDao.updatePowerState(1L, 1L, VirtualMachine.PowerState.PowerOff, new Date());
+
+        verify(vm, times(1)).setPowerState(any());
+        verify(vm, times(1)).setPowerHostId(anyLong());
+        verify(vm, times(1)).setPowerStateUpdateCount(1);
+        verify(vm, times(1)).setPowerStateUpdateTime(any(Date.class));
+
+        assertTrue(result);
+    }
 }
diff --git a/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java b/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java
index 471fc52..bfcc38b 100755
--- a/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java
+++ b/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImplTest.java
@@ -34,17 +34,15 @@
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.storage.dao.StoragePoolTagsDao;
 
 import junit.framework.TestCase;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore("javax.management.*")
+@RunWith(MockitoJUnitRunner.class)
 public class PrimaryDataStoreDaoImplTest extends TestCase {
 
     @Mock
@@ -85,9 +83,6 @@
     @Before
     public void setup() throws IOException, ClassNotFoundException, SQLException {
         STORAGE_POOL_DETAILS.put(DETAIL_KEY, DETAIL_VALUE);
-        doReturn(Arrays.asList(storagePoolVO)).when(primaryDataStoreDao).
-                searchStoragePoolsPreparedStatement(nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(Long.class),
-                        nullable(ScopeType.class), nullable(Integer.class));
     }
 
     @Test
@@ -137,6 +132,9 @@
 
     @Test
     public void testFindPoolsByDetailsOrTagsInternalStorageTagsType() {
+        doReturn(Arrays.asList(storagePoolVO)).when(primaryDataStoreDao).
+                searchStoragePoolsPreparedStatement(nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(Long.class),
+                        nullable(ScopeType.class), nullable(Integer.class));
         List<StoragePoolVO> storagePools = primaryDataStoreDao.findPoolsByDetailsOrTagsInternal(DATACENTER_ID, POD_ID, CLUSTER_ID, SCOPE, SQL_VALUES, ValueType.TAGS, STORAGE_TAGS_ARRAY.length);
         assertEquals(Arrays.asList(storagePoolVO), storagePools);
         verify(primaryDataStoreDao).getSqlPreparedStatement(
@@ -147,6 +145,9 @@
 
     @Test
     public void testFindPoolsByDetailsOrTagsInternalDetailsType() {
+        doReturn(Arrays.asList(storagePoolVO)).when(primaryDataStoreDao).
+                searchStoragePoolsPreparedStatement(nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(Long.class),
+                        nullable(ScopeType.class), nullable(Integer.class));
         List<StoragePoolVO> storagePools = primaryDataStoreDao.findPoolsByDetailsOrTagsInternal(DATACENTER_ID, POD_ID, CLUSTER_ID, SCOPE, SQL_VALUES, ValueType.DETAILS, STORAGE_POOL_DETAILS.size());
         assertEquals(Arrays.asList(storagePoolVO), storagePools);
         verify(primaryDataStoreDao).getSqlPreparedStatement(
diff --git a/engine/schema/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/engine/schema/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/engine/schema/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/engine/service/pom.xml b/engine/service/pom.xml
index 57df76a..2c98082 100644
--- a/engine/service/pom.xml
+++ b/engine/service/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <artifactId>cloud-engine-service</artifactId>
     <packaging>war</packaging>
diff --git a/engine/service/src/main/webapp/index.jsp b/engine/service/src/main/webapp/index.jsp
index 6b26cc2..faa0ca0 100644
--- a/engine/service/src/main/webapp/index.jsp
+++ b/engine/service/src/main/webapp/index.jsp
@@ -1,23 +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.

--->

-<html>

-<body>

-<h2>Hello World!</h2>

-</body>

-</html>

+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<html>
+<body>
+<h2>Hello World!</h2>
+</body>
+</html>
diff --git a/engine/storage/cache/pom.xml b/engine/storage/cache/pom.xml
index a0b6f0d..8d605c8 100644
--- a/engine/storage/cache/pom.xml
+++ b/engine/storage/cache/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java
index 0abdf2e..a687ddf 100644
--- a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java
+++ b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java
@@ -253,11 +253,11 @@
             if (obj != null) {
                 State st = obj.getState();
 
-                long miliSeconds = 10000;
+                long milliSeconds = 10000;
                 long timeoutSeconds = 3600;
-                long timeoutMiliSeconds = timeoutSeconds * 1000;
+                long timeoutMilliSeconds = timeoutSeconds * 1000;
                 Date now = new Date();
-                long expiredEpoch = now.getTime() + timeoutMiliSeconds;
+                long expiredEpoch = now.getTime() + timeoutMilliSeconds;
                 Date expiredDate = new Date(expiredEpoch);
 
                 /*
@@ -273,7 +273,7 @@
                      */
                     s_logger.debug("waiting cache copy completion type: " + typeName + ", id: " + obj.getObjectId() + ", lock: " + lock.hashCode());
                     try {
-                        lock.wait(miliSeconds);
+                        lock.wait(milliSeconds);
                     } catch (InterruptedException e) {
                         s_logger.debug("[ignored] interrupted while waiting for cache copy completion.");
                     }
diff --git a/engine/storage/configdrive/pom.xml b/engine/storage/configdrive/pom.xml
index 32289f6..b47f470 100644
--- a/engine/storage/configdrive/pom.xml
+++ b/engine/storage/configdrive/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
index c7c63f8..e02c092 100644
--- a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
+++ b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
@@ -52,6 +52,19 @@
     public static final Logger LOG = Logger.getLogger(ConfigDriveBuilder.class);
 
     /**
+     * This is for mocking the File class. We cannot mock the File class directly because Mockito uses it internally.
+     * @param filepath
+     * @return
+     */
+    static File getFile(String filepath) {
+        return new File(filepath);
+    }
+
+    static File getFile(String dirName, String filename) {
+        return new File(dirName, filename);
+    }
+
+    /**
      * Writes a content {@link String} to a file that is going to be created in a folder. We will not append to the file if it already exists. Therefore, its content will be overwritten.
      * Moreover, the charset used is {@link com.cloud.utils.StringUtils#getPreferredCharset()}.
      *
@@ -137,7 +150,7 @@
      *  [1] https://docs.openstack.org/project-install-guide/baremetal/draft/configdrive.html
      */
     static String generateAndRetrieveIsoAsBase64Iso(String isoFileName, String driveLabel, String tempDirName) throws IOException {
-        File tmpIsoStore = new File(tempDirName, isoFileName);
+        File tmpIsoStore = getFile(tempDirName, isoFileName);
         Script command = new Script(getProgramToGenerateIso(), Duration.standardSeconds(300), LOG);
         command.add("-o", tmpIsoStore.getAbsolutePath());
         command.add("-ldots");
@@ -157,7 +170,7 @@
             LOG.warn(errMsg);
             throw new CloudRuntimeException(errMsg);
         }
-        File tmpIsoFile = new File(tmpIsoStore.getAbsolutePath());
+        File tmpIsoFile = getFile(tmpIsoStore.getAbsolutePath());
         if (tmpIsoFile.length() > (64L * 1024L * 1024L)) {
             throw new CloudRuntimeException("Config drive file exceeds maximum allowed size of 64MB");
         }
@@ -173,11 +186,11 @@
      * </ul> /usr/local/bin/mkisofs
      */
     static String getProgramToGenerateIso() throws IOException {
-        File isoCreator = new File("/usr/bin/genisoimage");
+        File isoCreator = getFile("/usr/bin/genisoimage");
         if (!isoCreator.exists()) {
-            isoCreator = new File("/usr/bin/mkisofs");
+            isoCreator = getFile("/usr/bin/mkisofs");
             if (!isoCreator.exists()) {
-                isoCreator = new File("/usr/local/bin/mkisofs");
+                isoCreator = getFile("/usr/local/bin/mkisofs");
             }
         }
         if (!isoCreator.exists()) {
@@ -284,7 +297,7 @@
      */
     static void linkUserData(String tempDirName) {
         String userDataFilePath = tempDirName + ConfigDrive.cloudStackConfigDriveName + "userdata/user_data.txt";
-        File file = new File(userDataFilePath);
+        File file = getFile(userDataFilePath);
         if (file.exists()) {
             Script hardLink = new Script("ln", Duration.standardSeconds(300), LOG);
             hardLink.add(userDataFilePath);
diff --git a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
index 9de0755..6ef248f 100644
--- a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
+++ b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.cloudstack.storage.configdrive;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyMap;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -25,7 +24,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.lang.reflect.Method;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -39,69 +37,57 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.reflections.ReflectionUtils;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.script.Script;
 import com.google.gson.JsonObject;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({FileUtils.class})
+@RunWith(MockitoJUnitRunner.class)
 public class ConfigDriveBuilderTest {
 
     @Test
-    public void writeFileTest() throws Exception {
-        PowerMockito.mockStatic(FileUtils.class);
+    public void writeFileTest() {
+        try (MockedStatic<FileUtils> fileUtilsMocked = Mockito.mockStatic(FileUtils.class)) {
 
-        ConfigDriveBuilder.writeFile(new File("folder"), "subfolder", "content");
+            ConfigDriveBuilder.writeFile(new File("folder"), "subfolder", "content");
 
-        PowerMockito.verifyStatic(FileUtils.class);
-        FileUtils.write(Mockito.any(File.class), Mockito.anyString(), Mockito.any(Charset.class), Mockito.eq(false));
+            fileUtilsMocked.verify(() -> FileUtils.write(Mockito.any(File.class), Mockito.anyString(), Mockito.any(Charset.class), Mockito.eq(false)));
+        }
     }
 
-    @SuppressWarnings("unchecked")
     @Test(expected = CloudRuntimeException.class)
-    public void writeFileTestwriteFileTestIOExceptionWhileWritingFile() throws Exception {
-        PowerMockito.mockStatic(FileUtils.class);
-
-        //Does not look good, I know... but this is the price of static methods.
-        Method method = ReflectionUtils.getMethods(FileUtils.class, ReflectionUtils.withParameters(File.class, CharSequence.class, Charset.class, Boolean.TYPE)).iterator().next();
-        PowerMockito.when(FileUtils.class, method).withArguments(Mockito.any(File.class), Mockito.anyString(), Mockito.any(Charset.class), Mockito.anyBoolean()).thenThrow(IOException.class);
-
-        ConfigDriveBuilder.writeFile(new File("folder"), "subfolder", "content");
+    public void writeFileTestwriteFileTestIOExceptionWhileWritingFile() {
+        try (MockedStatic<FileUtils> fileUtilsMocked = Mockito.mockStatic(FileUtils.class)) {
+            fileUtilsMocked.when(() -> FileUtils.write(Mockito.any(File.class), Mockito.any(CharSequence.class), Mockito.any(Charset.class), Mockito.anyBoolean())).thenThrow(IOException.class);
+            ConfigDriveBuilder.writeFile(new File("folder"), "subfolder", "content");
+        }
     }
 
     @Test
     public void fileToBase64StringTest() throws Exception {
-        PowerMockito.mockStatic(FileUtils.class);
+        try (MockedStatic<FileUtils> ignored = Mockito.mockStatic(FileUtils.class)) {
 
-        String fileContent = "content";
-        Method method = getFileUtilsReadfileToByteArrayMethod();
-        PowerMockito.when(FileUtils.class, method).withArguments(Mockito.any(File.class)).thenReturn(fileContent.getBytes());
+            String fileContent = "content";
+            Mockito.when(FileUtils.readFileToByteArray(Mockito.any(File.class))).thenReturn(fileContent.getBytes());
 
-        String returnedContentInBase64 = ConfigDriveBuilder.fileToBase64String(new File("file"));
+            String returnedContentInBase64 = ConfigDriveBuilder.fileToBase64String(new File("file"));
 
-        Assert.assertEquals("Y29udGVudA==", returnedContentInBase64);
+            Assert.assertEquals("Y29udGVudA==", returnedContentInBase64);
+        }
     }
 
-    @SuppressWarnings("unchecked")
     @Test(expected = IOException.class)
     public void fileToBase64StringTestIOException() throws Exception {
-        PowerMockito.mockStatic(FileUtils.class);
+        try (MockedStatic<FileUtils> ignored = Mockito.mockStatic(FileUtils.class)) {
 
-        Method method = getFileUtilsReadfileToByteArrayMethod();
-        PowerMockito.when(FileUtils.class, method).withArguments(Mockito.any(File.class)).thenThrow(IOException.class);
+            Mockito.when(FileUtils.readFileToByteArray(Mockito.any(File.class))).thenThrow(IOException.class);
 
-        ConfigDriveBuilder.fileToBase64String(new File("file"));
-    }
-
-    @SuppressWarnings("unchecked")
-    private Method getFileUtilsReadfileToByteArrayMethod() {
-        return ReflectionUtils.getMethods(FileUtils.class, ReflectionUtils.withName("readFileToByteArray")).iterator().next();
+            ConfigDriveBuilder.fileToBase64String(new File("file"));
+        }
     }
 
     @Test
@@ -130,59 +116,40 @@
         ConfigDriveBuilder.buildConfigDrive(null, "teste", "C:", null);
     }
 
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class})
     @Test(expected = CloudRuntimeException.class)
-    public void buildConfigDriveTestIoException() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
-
-        Method method1 = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("writeFile")).iterator().next();
-        Method method = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("writeVendorAndNetworkEmptyJsonFile")).iterator().next();
-
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(nullable(File.class)).thenThrow(CloudRuntimeException.class);
-
-        //This is odd, but it was necessary to allow us to check if we catch the IOexception and re-throw as a CloudRuntimeException
-        //We are mocking the class being tested; therefore, we needed to force the execution of the real method we want to test.
-        PowerMockito.when(ConfigDriveBuilder.class, new ArrayList<>(), "teste", "C:", null).thenCallRealMethod();
-
-        ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
+    public void buildConfigDriveTestIoException() {
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(nullable(File.class))).thenThrow(CloudRuntimeException.class);
+            Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
+            ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
+        }
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class})
-    public void buildConfigDriveTest() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
+    public void buildConfigDriveTest() {
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        Method writeVendorAndNetworkEmptyJsonFileMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("writeVendorAndNetworkEmptyJsonFile")).iterator().next();
-        PowerMockito.doNothing().when(ConfigDriveBuilder.class, writeVendorAndNetworkEmptyJsonFileMethod).withArguments(Mockito.any(File.class));
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class))).then(invocationOnMock -> null);
 
-        Method writeVmMetadataMethod = getWriteVmMetadataMethod();
-        PowerMockito.doNothing().when(ConfigDriveBuilder.class, writeVmMetadataMethod).withArguments(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.any(File.class), anyMap());
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVmMetadata(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.any(File.class), anyMap())).then(invocationOnMock -> null);
 
-        Method linkUserDataMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("linkUserData")).iterator().next();
-        PowerMockito.doNothing().when(ConfigDriveBuilder.class, linkUserDataMethod).withArguments(Mockito.anyString());
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.linkUserData((Mockito.anyString()))).then(invocationOnMock -> null);
 
-        Method generateAndRetrieveIsoAsBase64IsoMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("generateAndRetrieveIsoAsBase64Iso")).iterator().next();
-        PowerMockito.doReturn("mockIsoDataBase64").when(ConfigDriveBuilder.class, generateAndRetrieveIsoAsBase64IsoMethod).withArguments(Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenAnswer(invocation -> "mockIsoDataBase64");
+            //force execution of real method
+            Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
 
-        //force execution of real method
-        PowerMockito.when(ConfigDriveBuilder.class, new ArrayList<>(), "teste", "C:", null).thenCallRealMethod();
+            String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
 
-        String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
+            Assert.assertEquals("mockIsoDataBase64", returnedIsoData);
 
-        Assert.assertEquals("mockIsoDataBase64", returnedIsoData);
-
-        PowerMockito.verifyStatic(ConfigDriveBuilder.class);
-        ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class));
-        ConfigDriveBuilder.writeVmMetadata(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.any(File.class), anyMap());
-        ConfigDriveBuilder.linkUserData(Mockito.anyString());
-        ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
-    }
-
-    @SuppressWarnings("unchecked")
-    private Method getWriteVmMetadataMethod() {
-        return ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("writeVmMetadata")).iterator().next();
+            configDriveBuilderMocked.verify(() -> {
+                ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class));
+                ConfigDriveBuilder.writeVmMetadata(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.any(File.class), anyMap());
+                ConfigDriveBuilder.linkUserData(Mockito.anyString());
+                ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
+            });
+        }
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -202,348 +169,336 @@
     }
 
     @Test
-    @PrepareForTest({ConfigDriveBuilder.class})
-    public void writeVendorAndNetworkEmptyJsonFileTestCreatingFolder() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
+    public void writeVendorAndNetworkEmptyJsonFileTestCreatingFolder() {
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        File folderFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(false).when(folderFileMock).exists();
-        Mockito.doReturn(true).when(folderFileMock).mkdirs();
+            File folderFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(false).when(folderFileMock).exists();
+            Mockito.doReturn(true).when(folderFileMock).mkdirs();
 
-        //force execution of real method
-        Method writeVendorAndNetworkEmptyJsonFileMethod = getWriteVendorAndNetworkEmptyJsonFileMethod();
-        PowerMockito.when(ConfigDriveBuilder.class, writeVendorAndNetworkEmptyJsonFileMethod).withArguments(folderFileMock).thenCallRealMethod();
+            //force execution of real method
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock)).thenCallRealMethod();
 
-        ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock);
+            ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock);
 
-        Mockito.verify(folderFileMock).exists();
-        Mockito.verify(folderFileMock).mkdirs();
+            Mockito.verify(folderFileMock).exists();
+            Mockito.verify(folderFileMock).mkdirs();
 
-        PowerMockito.verifyStatic(ConfigDriveBuilder.class);
-        ConfigDriveBuilder.writeFile(Mockito.any(File.class), Mockito.eq("vendor_data.json"), Mockito.eq("{}"));
-        ConfigDriveBuilder.writeFile(Mockito.any(File.class), Mockito.eq("network_data.json"), Mockito.eq("{}"));
-    }
-
-    @SuppressWarnings("unchecked")
-    private Method getWriteVendorAndNetworkEmptyJsonFileMethod() {
-        return ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("writeVendorAndNetworkEmptyJsonFile")).iterator().next();
+            configDriveBuilderMocked.verify(() -> {
+                ConfigDriveBuilder.writeFile(Mockito.any(File.class), Mockito.eq("vendor_data.json"), Mockito.eq("{}"));
+                ConfigDriveBuilder.writeFile(Mockito.any(File.class), Mockito.eq("network_data.json"), Mockito.eq("{}"));
+            });
+        }
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class})
-    public void writeVmMetadataTest() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
+    public void writeVmMetadataTest() {
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
+            Mockito.when(ConfigDriveBuilder.createJsonObjectWithVmData(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.anyMap())).thenReturn(new JsonObject());
 
-        Method method = getWriteVmMetadataMethod();
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(Mockito.anyListOf(String[].class), anyString(), any(File.class), anyMap()).thenCallRealMethod();
+            List<String[]> vmData = new ArrayList<>();
+            vmData.add(new String[]{"dataType", "fileName", "content"});
+            vmData.add(new String[]{"dataType2", "fileName2", "content2"});
 
-        Method createJsonObjectWithVmDataMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("createJsonObjectWithVmData")).iterator().next();
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVmMetadata(vmData, "metadataFile", new File("folder"), new HashMap<>())).thenCallRealMethod();
 
-        PowerMockito.when(ConfigDriveBuilder.class, createJsonObjectWithVmDataMethod).withArguments(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.anyMap()).thenReturn(new JsonObject());
+            ConfigDriveBuilder.writeVmMetadata(vmData, "metadataFile", new File("folder"), new HashMap<>());
 
-        List<String[]> vmData = new ArrayList<>();
-        vmData.add(new String[] {"dataType", "fileName", "content"});
-        vmData.add(new String[] {"dataType2", "fileName2", "content2"});
-
-        ConfigDriveBuilder.writeVmMetadata(vmData, "metadataFile", new File("folder"), new HashMap<>());
-
-        PowerMockito.verifyStatic(ConfigDriveBuilder.class);
-        ConfigDriveBuilder.createJsonObjectWithVmData(vmData, "metadataFile", new HashMap<>());
-        ConfigDriveBuilder.writeFile(Mockito.any(File.class), Mockito.eq("meta_data.json"), Mockito.eq("{}"));
+            configDriveBuilderMocked.verify(() -> {
+                ConfigDriveBuilder.createJsonObjectWithVmData(vmData, "metadataFile", new HashMap<>());
+                ConfigDriveBuilder.writeFile(Mockito.any(File.class), Mockito.eq("meta_data.json"), Mockito.eq("{}"));
+            });
+        }
     }
 
     @Test
-    @PrepareForTest({File.class, Script.class, ConfigDriveBuilder.class})
-    public void linkUserDataTestUserDataFilePathDoesNotExist() throws Exception {
+    public void linkUserDataTestUserDataFilePathDoesNotExist() {
         File fileMock = Mockito.mock(File.class);
         Mockito.doReturn(false).when(fileMock).exists();
 
-        PowerMockito.mockStatic(File.class, Script.class);
-        PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(fileMock);
-
-        Script scriptMock = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
-
-        ConfigDriveBuilder.linkUserData("test");
-
-        Mockito.verify(scriptMock, times(0)).execute();
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class);
+             MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class)
+        ) {
+            Mockito.when(ConfigDriveBuilder.getFile(anyString())).thenReturn(fileMock);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.linkUserData(anyString())).thenCallRealMethod();
+            ConfigDriveBuilder.linkUserData("test");
+            scriptMock.constructed().forEach(s -> Mockito.verify(s, times(0)).execute());
+        }
     }
 
     @Test(expected = CloudRuntimeException.class)
-    @PrepareForTest({File.class, Script.class, ConfigDriveBuilder.class})
-    public void linkUserDataTestUserDataFilePathExistAndExecutionPresentedSomeError() throws Exception {
+    public void linkUserDataTestUserDataFilePathExistAndExecutionPresentedSomeError() {
         File fileMock = Mockito.mock(File.class);
         Mockito.doReturn(true).when(fileMock).exists();
 
-        PowerMockito.mockStatic(File.class, Script.class);
-        PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(fileMock);
-
-        Script scriptMock = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
-
-        Mockito.doReturn("message").when(scriptMock).execute();
-        ConfigDriveBuilder.linkUserData("test");
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class);
+             MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
+                 Mockito.doReturn("message").when(mock).execute();
+             })
+        ) {
+            Mockito.when(ConfigDriveBuilder.getFile(anyString())).thenReturn(fileMock);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.linkUserData(anyString())).thenCallRealMethod();
+            ConfigDriveBuilder.linkUserData("test");
+        }
     }
 
     @Test
-    @PrepareForTest({File.class, Script.class, ConfigDriveBuilder.class})
-    public void linkUserDataTest() throws Exception {
-        File fileMock = Mockito.mock(File.class);
-        Mockito.doReturn(true).when(fileMock).exists();
-
-        PowerMockito.mockStatic(File.class, Script.class);
-        PowerMockito.whenNew(File.class).withArguments(Mockito.anyString()).thenReturn(fileMock);
-
-        Script scriptMock = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
-
-        Mockito.doReturn(StringUtils.EMPTY).when(scriptMock).execute();
+    public void linkUserDataTest() {
         String tempDirName = "test";
-        ConfigDriveBuilder.linkUserData(tempDirName);
 
-        Mockito.verify(scriptMock).add(tempDirName + ConfigDrive.cloudStackConfigDriveName + "userdata/user_data.txt");
-        Mockito.verify(scriptMock).add(tempDirName + ConfigDrive.openStackConfigDriveName + "user_data");
-        Mockito.verify(scriptMock).execute();
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class);
+             MockedConstruction<Script> scriptMock = Mockito.mockConstruction(Script.class, (mock, context) -> {
+                 Mockito.doReturn(StringUtils.EMPTY).when(mock).execute();
+             })
+        ) {
+            File fileMock = Mockito.mock(File.class);
+            Mockito.doReturn(true).when(fileMock).exists();
+            Mockito.when(ConfigDriveBuilder.getFile(anyString())).thenReturn(fileMock);
+
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.linkUserData(anyString())).thenCallRealMethod();
+
+            ConfigDriveBuilder.linkUserData(tempDirName);
+            Script mockedScript = scriptMock.constructed().get(0);
+            Mockito.verify(mockedScript).add(tempDirName + ConfigDrive.cloudStackConfigDriveName + "userdata/user_data.txt");
+            Mockito.verify(mockedScript).add(tempDirName + ConfigDrive.openStackConfigDriveName + "user_data");
+            Mockito.verify(mockedScript).execute();
+        }
     }
 
-    @SuppressWarnings("unchecked")
     @Test(expected = CloudRuntimeException.class)
-    @PrepareForTest({Script.class, ConfigDriveBuilder.class})
     public void generateAndRetrieveIsoAsBase64IsoTestGenIsoFailure() throws Exception {
-        PowerMockito.mockStatic(Script.class, ConfigDriveBuilder.class);
 
-        Script scriptMock = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class);
+        MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
+            Mockito.doReturn("scriptMessage").when(mock).execute();
+        })) {
+            configDriveBuilderMocked.when(ConfigDriveBuilder::getProgramToGenerateIso).thenReturn("/usr/bin/genisoimage");
 
-        Mockito.doReturn("scriptMessage").when(scriptMock).execute();
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(anyString(), anyString(), anyString())).thenCallRealMethod();
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.getFile(anyString())).thenCallRealMethod();
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.getFile(anyString(), anyString())).thenCallRealMethod();
 
-        Method method = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("generateAndRetrieveIsoAsBase64Iso")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(nullable(String.class), nullable(String.class), nullable(String.class)).thenCallRealMethod();
-
-        Method getProgramToGenerateIsoMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("getProgramToGenerateIso")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, getProgramToGenerateIsoMethod).withNoArguments().thenReturn("/usr/bin/genisoimage");
-
-        ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso("isoFileName", "driveLabel", "tempDirName");
+            ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso("isoFileName", "driveLabel", "tempDirName");
+        }
     }
 
-    @SuppressWarnings("unchecked")
     @Test(expected = CloudRuntimeException.class)
-    @PrepareForTest({File.class, Script.class, ConfigDriveBuilder.class})
     public void generateAndRetrieveIsoAsBase64IsoTestIsoTooBig() throws Exception {
-        PowerMockito.mockStatic(File.class, Script.class, ConfigDriveBuilder.class);
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class);
+             MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
+                 Mockito.doReturn(StringUtils.EMPTY).when(mock).execute();
+             })) {
+            File fileMock = Mockito.mock(File.class);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.getFile(anyString())).thenReturn(fileMock);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.getFile(anyString(), anyString())).thenReturn(fileMock);
 
-        File fileMock = Mockito.mock(File.class);
-        PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(fileMock);
+            Mockito.when(fileMock.getAbsolutePath()).thenReturn("");
+            Mockito.when(fileMock.length()).thenReturn(64L * 1024L * 1024L + 1L);
+            Mockito.when(ConfigDriveBuilder.getProgramToGenerateIso()).thenReturn("/usr/bin/genisoimage");
 
-        Script scriptMock = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
+            Mockito.when(ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(nullable(String.class), nullable(String.class), nullable(String.class))).thenCallRealMethod();
 
-        Mockito.doReturn(StringUtils.EMPTY).when(scriptMock).execute();
-        Mockito.doReturn(64L * 1024L * 1024L + 1l).when(fileMock).length();
-
-        Method method = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("generateAndRetrieveIsoAsBase64Iso")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(nullable(String.class), nullable(String.class), nullable(String.class)).thenCallRealMethod();
-
-        Method getProgramToGenerateIsoMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("getProgramToGenerateIso")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, getProgramToGenerateIsoMethod).withNoArguments().thenReturn("/usr/bin/genisoimage");
-
-        ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso("isoFileName", "driveLabel", "tempDirName");
+            ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso("isoFileName", "driveLabel", "tempDirName");
+        }
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({File.class, Script.class, ConfigDriveBuilder.class})
     public void generateAndRetrieveIsoAsBase64IsoTest() throws Exception {
-        PowerMockito.mockStatic(File.class, Script.class, ConfigDriveBuilder.class);
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class);
+             MockedConstruction<Script> scriptMockedConstruction = Mockito.mockConstruction(Script.class, (mock, context) -> {
+                 Mockito.doReturn(StringUtils.EMPTY).when(mock).execute();
+             })) {
 
-        File fileMock = Mockito.mock(File.class);
-        PowerMockito.whenNew(File.class).withArguments("tempDirName", "isoFileName").thenReturn(fileMock);
+            File fileMock = Mockito.mock(File.class);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.getFile(anyString(), anyString())).thenReturn(fileMock);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.getFile(anyString())).thenReturn(fileMock);
 
-        Script scriptMock = Mockito.mock(Script.class);
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
+            Mockito.when(fileMock.getAbsolutePath()).thenReturn("absolutePath");
+            Mockito.doReturn(64L * 1024L * 1024L).when(fileMock).length();
 
-        Mockito.when(fileMock.getAbsolutePath()).thenReturn("absolutePath");
-        Mockito.doReturn(StringUtils.EMPTY).when(scriptMock).execute();
-        Mockito.doReturn(64L * 1024L * 1024L).when(fileMock).length();
+            Mockito.when(ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(nullable(String.class), nullable(String.class), nullable(String.class))).thenCallRealMethod();
 
-        Method method = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("generateAndRetrieveIsoAsBase64Iso")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(nullable(String.class), nullable(String.class), nullable(String.class)).thenCallRealMethod();
+            Mockito.when(ConfigDriveBuilder.getProgramToGenerateIso()).thenReturn("/usr/bin/genisoimage");
 
-        Method getProgramToGenerateIsoMethod = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("getProgramToGenerateIso")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, getProgramToGenerateIsoMethod).withNoArguments().thenReturn("/usr/bin/genisoimage");
+            ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso("isoFileName", "driveLabel", "tempDirName");
 
-        ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso("isoFileName", "driveLabel", "tempDirName");
+            Script scriptMock = scriptMockedConstruction.constructed().get(0);
+            InOrder inOrder = Mockito.inOrder(scriptMock);
+            inOrder.verify(scriptMock).add("-o", "absolutePath");
+            inOrder.verify(scriptMock).add("-ldots");
+            inOrder.verify(scriptMock).add("-allow-lowercase");
+            inOrder.verify(scriptMock).add("-allow-multidot");
+            inOrder.verify(scriptMock).add("-cache-inodes");
+            inOrder.verify(scriptMock).add("-l");
+            inOrder.verify(scriptMock).add("-quiet");
+            inOrder.verify(scriptMock).add("-J");
+            inOrder.verify(scriptMock).add("-r");
+            inOrder.verify(scriptMock).add("-V", "driveLabel");
+            inOrder.verify(scriptMock).add("tempDirName");
+            inOrder.verify(scriptMock).execute();
 
-        InOrder inOrder = Mockito.inOrder(scriptMock);
-        inOrder.verify(scriptMock).add("-o", "absolutePath");
-        inOrder.verify(scriptMock).add("-ldots");
-        inOrder.verify(scriptMock).add("-allow-lowercase");
-        inOrder.verify(scriptMock).add("-allow-multidot");
-        inOrder.verify(scriptMock).add("-cache-inodes");
-        inOrder.verify(scriptMock).add("-l");
-        inOrder.verify(scriptMock).add("-quiet");
-        inOrder.verify(scriptMock).add("-J");
-        inOrder.verify(scriptMock).add("-r");
-        inOrder.verify(scriptMock).add("-V", "driveLabel");
-        inOrder.verify(scriptMock).add("tempDirName");
-        inOrder.verify(scriptMock).execute();
 
-        PowerMockito.verifyStatic(ConfigDriveBuilder.class);
-        ConfigDriveBuilder.fileToBase64String(nullable(File.class));
-
+            configDriveBuilderMocked.verify(() -> ConfigDriveBuilder.fileToBase64String(nullable(File.class)));
+        }
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class})
-    public void createJsonObjectWithVmDataTesT() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
+    public void createJsonObjectWithVmDataTesT() {
 
-        Method method = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("createJsonObjectWithVmData")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.nullable(Map.class)).thenCallRealMethod();
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        List<String[]> vmData = new ArrayList<>();
-        vmData.add(new String[] {"dataType", "fileName", "content"});
-        vmData.add(new String[] {"dataType2", "fileName2", "content2"});
+            Mockito.when(ConfigDriveBuilder.createJsonObjectWithVmData(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.nullable(Map.class))).thenCallRealMethod();
 
-        ConfigDriveBuilder.createJsonObjectWithVmData(vmData, "tempDirName", new HashMap<>());
+            List<String[]> vmData = new ArrayList<>();
+            vmData.add(new String[]{"dataType", "fileName", "content"});
+            vmData.add(new String[]{"dataType2", "fileName2", "content2"});
 
-        PowerMockito.verifyStatic(ConfigDriveBuilder.class, Mockito.times(1));
-        ConfigDriveBuilder.createFileInTempDirAnAppendOpenStackMetadataToJsonObject(Mockito.eq("tempDirName"), Mockito.any(JsonObject.class), Mockito.eq("dataType"), Mockito.eq("fileName"),
-                Mockito.eq("content"), Mockito.anyMap());
-        ConfigDriveBuilder.createFileInTempDirAnAppendOpenStackMetadataToJsonObject(Mockito.eq("tempDirName"), Mockito.any(JsonObject.class), Mockito.eq("dataType2"), Mockito.eq("fileName2"),
-                Mockito.eq("content2"), Mockito.anyMap());
+            ConfigDriveBuilder.createJsonObjectWithVmData(vmData, "tempDirName", new HashMap<>());
+
+            configDriveBuilderMocked.verify(() -> {
+                ConfigDriveBuilder.createFileInTempDirAnAppendOpenStackMetadataToJsonObject(Mockito.eq("tempDirName"), Mockito.any(JsonObject.class), Mockito.eq("dataType"), Mockito.eq("fileName"),
+                        Mockito.eq("content"), Mockito.anyMap());
+                ConfigDriveBuilder.createFileInTempDirAnAppendOpenStackMetadataToJsonObject(Mockito.eq("tempDirName"), Mockito.any(JsonObject.class), Mockito.eq("dataType2"), Mockito.eq("fileName2"),
+                        Mockito.eq("content2"), Mockito.anyMap());
+            });
+        }
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class})
-    public void buildCustomUserdataParamsMetadataTestNullContent() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
+    public void buildCustomUserdataParamsMetadataTestNullContent() {
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
+            JsonObject metadata = new JsonObject();
+            String dataType = "dataType1";
+            String fileName = "testFileName";
+            String content = null;
+            Map<String, String> customUserdataParams = new HashMap<>();
+            customUserdataParams.put(fileName, content);
 
-        JsonObject metadata = new JsonObject();
-        String dataType = "dataType1";
-        String fileName = "testFileName";
-        String content = null;
-        Map<String, String> customUserdataParams = new HashMap<>();
-        customUserdataParams.put(fileName, content);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.buildCustomUserdataParamsMetaData(metadata, dataType, fileName, content, customUserdataParams)).thenCallRealMethod();
 
-        PowerMockito.when(ConfigDriveBuilder.class, metadata, dataType, fileName, content, customUserdataParams).thenCallRealMethod();
+            ConfigDriveBuilder.buildCustomUserdataParamsMetaData(metadata, dataType, fileName, content, customUserdataParams);
 
-        ConfigDriveBuilder.buildCustomUserdataParamsMetaData(metadata, dataType, fileName, content, customUserdataParams);
-
-        Assert.assertEquals(null, metadata.getAsJsonPrimitive(fileName));
+            Assert.assertNull(metadata.getAsJsonPrimitive(fileName));
+        }
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class})
-    public void buildCustomUserdataParamsMetadataTestWithContent() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
+    public void buildCustomUserdataParamsMetadataTestWithContent() {
+        try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        JsonObject metadata = new JsonObject();
-        String dataType = "metadata";
-        String fileName = "testFileName";
-        String content = "testContent";
-        Map<String, String> customUserdataParams = new HashMap<>();
-        customUserdataParams.put(fileName, content);
+            JsonObject metadata = new JsonObject();
+            String dataType = "metadata";
+            String fileName = "testFileName";
+            String content = "testContent";
+            Map<String, String> customUserdataParams = new HashMap<>();
+            customUserdataParams.put(fileName, content);
 
-        PowerMockito.when(ConfigDriveBuilder.class, metadata, dataType, fileName, content, customUserdataParams).thenCallRealMethod();
-        ConfigDriveBuilder.buildCustomUserdataParamsMetaData(metadata, dataType, fileName, content, customUserdataParams);
+            configDriveBuilderMocked.when(() -> ConfigDriveBuilder.buildCustomUserdataParamsMetaData(metadata, dataType, fileName, content, customUserdataParams)).thenCallRealMethod();
 
-        Assert.assertEquals(content, metadata.getAsJsonPrimitive(fileName).getAsString());
+            ConfigDriveBuilder.buildCustomUserdataParamsMetaData(metadata, dataType, fileName, content, customUserdataParams);
+
+            Assert.assertEquals(content, metadata.getAsJsonPrimitive(fileName).getAsString());
+        }
     }
 
     @Test
-    @PrepareForTest({File.class, ConfigDriveBuilder.class})
     public void getProgramToGenerateIsoTestGenIsoExistsAndIsExecutable() throws Exception {
-        PowerMockito.mockStatic(File.class);
+        try (MockedStatic<ConfigDriveBuilder> ignored = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        File genIsoFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(true).when(genIsoFileMock).exists();
-        Mockito.doReturn(true).when(genIsoFileMock).canExecute();
+            File genIsoFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(true).when(genIsoFileMock).exists();
+            Mockito.doReturn(true).when(genIsoFileMock).canExecute();
 
-        PowerMockito.whenNew(File.class).withArguments("/usr/bin/genisoimage").thenReturn(genIsoFileMock);
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/bin/genisoimage")).thenReturn(genIsoFileMock);
+            Mockito.when(ConfigDriveBuilder.getProgramToGenerateIso()).thenCallRealMethod();
 
-        ConfigDriveBuilder.getProgramToGenerateIso();
+            ConfigDriveBuilder.getProgramToGenerateIso();
 
-        Mockito.verify(genIsoFileMock, Mockito.times(2)).exists();
-        Mockito.verify(genIsoFileMock).canExecute();
-        Mockito.verify(genIsoFileMock).getCanonicalPath();
+            Mockito.verify(genIsoFileMock, Mockito.times(2)).exists();
+            Mockito.verify(genIsoFileMock).canExecute();
+            Mockito.verify(genIsoFileMock).getCanonicalPath();
+        }
     }
 
     @Test(expected = CloudRuntimeException.class)
-    @PrepareForTest({File.class, ConfigDriveBuilder.class})
     public void getProgramToGenerateIsoTestGenIsoExistsbutNotExecutable() throws Exception {
-        PowerMockito.mockStatic(File.class);
+        try (MockedStatic<ConfigDriveBuilder> ignored = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        File genIsoFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(true).when(genIsoFileMock).exists();
-        Mockito.doReturn(false).when(genIsoFileMock).canExecute();
+            File genIsoFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(true).when(genIsoFileMock).exists();
+            Mockito.doReturn(false).when(genIsoFileMock).canExecute();
 
-        PowerMockito.whenNew(File.class).withArguments("/usr/bin/genisoimage").thenReturn(genIsoFileMock);
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/bin/genisoimage")).thenReturn(genIsoFileMock);
 
-        ConfigDriveBuilder.getProgramToGenerateIso();
+            Mockito.when(ConfigDriveBuilder.getProgramToGenerateIso()).thenCallRealMethod();
+
+            ConfigDriveBuilder.getProgramToGenerateIso();
+        }
     }
 
     @Test
-    @PrepareForTest({File.class, ConfigDriveBuilder.class})
     public void getProgramToGenerateIsoTestNotGenIsoMkIsoInLinux() throws Exception {
-        PowerMockito.mockStatic(File.class);
+        try (MockedStatic<ConfigDriveBuilder> ignored = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        File genIsoFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(false).when(genIsoFileMock).exists();
+            File genIsoFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(false).when(genIsoFileMock).exists();
 
-        File mkIsoProgramInLinuxFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(true).when(mkIsoProgramInLinuxFileMock).exists();
-        Mockito.doReturn(true).when(mkIsoProgramInLinuxFileMock).canExecute();
+            File mkIsoProgramInLinuxFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(true).when(mkIsoProgramInLinuxFileMock).exists();
+            Mockito.doReturn(true).when(mkIsoProgramInLinuxFileMock).canExecute();
 
-        PowerMockito.whenNew(File.class).withArguments("/usr/bin/genisoimage").thenReturn(genIsoFileMock);
-        PowerMockito.whenNew(File.class).withArguments("/usr/bin/mkisofs").thenReturn(mkIsoProgramInLinuxFileMock);
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/bin/genisoimage")).thenReturn(genIsoFileMock);
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/bin/mkisofs")).thenReturn(mkIsoProgramInLinuxFileMock);
 
-        ConfigDriveBuilder.getProgramToGenerateIso();
+            Mockito.when(ConfigDriveBuilder.getProgramToGenerateIso()).thenCallRealMethod();
 
-        Mockito.verify(genIsoFileMock, Mockito.times(1)).exists();
-        Mockito.verify(genIsoFileMock, Mockito.times(0)).canExecute();
-        Mockito.verify(genIsoFileMock, Mockito.times(0)).getCanonicalPath();
+            ConfigDriveBuilder.getProgramToGenerateIso();
 
-        Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(2)).exists();
-        Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(1)).canExecute();
-        Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(1)).getCanonicalPath();
+            Mockito.verify(genIsoFileMock, Mockito.times(1)).exists();
+            Mockito.verify(genIsoFileMock, Mockito.times(0)).canExecute();
+            Mockito.verify(genIsoFileMock, Mockito.times(0)).getCanonicalPath();
+
+            Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(2)).exists();
+            Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(1)).canExecute();
+            Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(1)).getCanonicalPath();
+        }
     }
 
     @Test
-    @PrepareForTest({File.class, ConfigDriveBuilder.class})
     public void getProgramToGenerateIsoTestMkIsoMac() throws Exception {
-        PowerMockito.mockStatic(File.class);
+        try (MockedStatic<ConfigDriveBuilder> ignored = Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-        File genIsoFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(false).when(genIsoFileMock).exists();
 
-        File mkIsoProgramInLinuxFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(false).when(mkIsoProgramInLinuxFileMock).exists();
+            File genIsoFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(false).when(genIsoFileMock).exists();
 
-        File mkIsoProgramInMacOsFileMock = Mockito.mock(File.class);
-        Mockito.doReturn(true).when(mkIsoProgramInMacOsFileMock).exists();
-        Mockito.doReturn(true).when(mkIsoProgramInMacOsFileMock).canExecute();
+            File mkIsoProgramInLinuxFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(false).when(mkIsoProgramInLinuxFileMock).exists();
 
-        PowerMockito.whenNew(File.class).withArguments("/usr/bin/genisoimage").thenReturn(genIsoFileMock);
-        PowerMockito.whenNew(File.class).withArguments("/usr/bin/mkisofs").thenReturn(mkIsoProgramInLinuxFileMock);
-        PowerMockito.whenNew(File.class).withArguments("/usr/local/bin/mkisofs").thenReturn(mkIsoProgramInMacOsFileMock);
+            File mkIsoProgramInMacOsFileMock = Mockito.mock(File.class);
+            Mockito.doReturn(true).when(mkIsoProgramInMacOsFileMock).exists();
+            Mockito.doReturn(true).when(mkIsoProgramInMacOsFileMock).canExecute();
 
-        ConfigDriveBuilder.getProgramToGenerateIso();
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/bin/genisoimage")).thenReturn(genIsoFileMock);
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/bin/mkisofs")).thenReturn(mkIsoProgramInLinuxFileMock);
+            Mockito.when(ConfigDriveBuilder.getFile("/usr/local/bin/mkisofs")).thenReturn(mkIsoProgramInMacOsFileMock);
 
-        Mockito.verify(genIsoFileMock, Mockito.times(1)).exists();
-        Mockito.verify(genIsoFileMock, Mockito.times(0)).canExecute();
-        Mockito.verify(genIsoFileMock, Mockito.times(0)).getCanonicalPath();
+            Mockito.when(ConfigDriveBuilder.getProgramToGenerateIso()).thenCallRealMethod();
 
-        Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(1)).exists();
-        Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(0)).canExecute();
-        Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(0)).getCanonicalPath();
 
-        Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).exists();
-        Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).canExecute();
-        Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).getCanonicalPath();
+            ConfigDriveBuilder.getProgramToGenerateIso();
+
+            Mockito.verify(genIsoFileMock, Mockito.times(1)).exists();
+            Mockito.verify(genIsoFileMock, Mockito.times(0)).canExecute();
+            Mockito.verify(genIsoFileMock, Mockito.times(0)).getCanonicalPath();
+
+            Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(1)).exists();
+            Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(0)).canExecute();
+            Mockito.verify(mkIsoProgramInLinuxFileMock, Mockito.times(0)).getCanonicalPath();
+
+            Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).exists();
+            Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).canExecute();
+            Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).getCanonicalPath();
+        }
     }
 }
diff --git a/engine/storage/configdrive/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/engine/storage/configdrive/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/engine/storage/configdrive/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/engine/storage/datamotion/pom.xml b/engine/storage/datamotion/pom.xml
index 370ab8d..b1bb98f 100644
--- a/engine/storage/datamotion/pom.xml
+++ b/engine/storage/datamotion/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java
index e450add..370753e 100644
--- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java
+++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java
@@ -193,7 +193,7 @@
                      destData.getType() == DataObjectType.TEMPLATE)) {
                     // volume transfer from primary to secondary. Volume transfer between primary pools are already handled by copyVolumeBetweenPools
                     // Delete cache in order to certainly transfer a latest image.
-                    s_logger.debug("Delete " + cacheType + " cache(id: " + cacheId +
+                    if (s_logger.isDebugEnabled()) s_logger.debug("Delete " + cacheType + " cache(id: " + cacheId +
                                    ", uuid: " + cacheUuid + ")");
                     cacheMgr.deleteCacheObject(srcForCopy);
                 } else {
@@ -205,7 +205,7 @@
                                       ", uuid: " + cacheUuid + ")");
                         cacheMgr.deleteCacheObject(srcForCopy);
                     } else {
-                        s_logger.debug("Decrease reference count of " + cacheType +
+                        if (s_logger.isDebugEnabled()) s_logger.debug("Decrease reference count of " + cacheType +
                                        " cache(id: " + cacheId + ", uuid: " + cacheUuid + ")");
                         cacheMgr.releaseCacheObject(srcForCopy);
                     }
@@ -213,7 +213,7 @@
             }
             return answer;
         } catch (Exception e) {
-            s_logger.debug("copy object failed: ", e);
+            if (s_logger.isDebugEnabled()) s_logger.debug("copy object failed: ", e);
             if (cacheData != null) {
                 cacheMgr.deleteCacheObject(cacheData);
             }
@@ -331,7 +331,7 @@
             }
             return answer;
         } catch (Exception e) {
-            s_logger.debug("Failed to send to storage pool", e);
+            if (s_logger.isDebugEnabled()) s_logger.debug("Failed to send to storage pool", e);
             throw new CloudRuntimeException("Failed to send to storage pool", e);
         }
     }
@@ -388,7 +388,7 @@
 
                 if (answer == null || !answer.getResult()) {
                     if (answer != null) {
-                        s_logger.debug("copy to image store failed: " + answer.getDetails());
+                        if (s_logger.isDebugEnabled()) s_logger.debug("copy to image store failed: " + answer.getDetails());
                     }
                     objOnImageStore.processEvent(Event.OperationFailed);
                     imageStore.delete(objOnImageStore);
@@ -411,7 +411,7 @@
 
                 if (answer == null || !answer.getResult()) {
                     if (answer != null) {
-                        s_logger.debug("copy to primary store failed: " + answer.getDetails());
+                        if (s_logger.isDebugEnabled()) s_logger.debug("copy to primary store failed: " + answer.getDetails());
                     }
                     objOnImageStore.processEvent(Event.OperationFailed);
                     imageStore.delete(objOnImageStore);
@@ -471,13 +471,17 @@
             s_logger.error(errMsg);
             answer = new Answer(command, false, errMsg);
         } else {
+            if (s_logger.isDebugEnabled()) s_logger.debug("Sending MIGRATE_COPY request to node " + ep);
             answer = ep.sendMessage(command);
+            if (s_logger.isDebugEnabled()) s_logger.debug("Received MIGRATE_COPY response from node with answer: " + answer);
         }
 
         if (answer == null || !answer.getResult()) {
             throw new CloudRuntimeException("Failed to migrate volume " + volume + " to storage pool " + destPool);
         } else {
             // Update the volume details after migration.
+            if (s_logger.isDebugEnabled()) s_logger.debug("MIGRATE_COPY updating volume");
+
             VolumeVO volumeVo = volDao.findById(volume.getId());
             Long oldPoolId = volume.getPoolId();
             volumeVo.setPath(((MigrateVolumeAnswer)answer).getVolumePath());
@@ -496,6 +500,8 @@
             }
             volumeVo.setFolder(folder);
             volDao.update(volume.getId(), volumeVo);
+            if (s_logger.isDebugEnabled()) s_logger.debug("MIGRATE_COPY update volume data complete");
+
         }
 
         return answer;
@@ -507,7 +513,7 @@
         Answer answer = null;
         String errMsg = null;
         try {
-            s_logger.debug("copyAsync inspecting src type " + srcData.getType().toString() + " copyAsync inspecting dest type " + destData.getType().toString());
+            if (s_logger.isDebugEnabled()) s_logger.debug("copyAsync inspecting src type " + srcData.getType().toString() + " copyAsync inspecting dest type " + destData.getType().toString());
             if (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.VOLUME) {
                 answer = copyVolumeFromSnapshot(srcData, destData);
             } else if (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.TEMPLATE) {
@@ -516,11 +522,16 @@
                 answer = cloneVolume(srcData, destData);
             } else if (destData.getType() == DataObjectType.VOLUME && srcData.getType() == DataObjectType.VOLUME &&
                 srcData.getDataStore().getRole() == DataStoreRole.Primary && destData.getDataStore().getRole() == DataStoreRole.Primary) {
+                if (s_logger.isDebugEnabled()) s_logger.debug("About to MIGRATE copy between datasources");
                 if (srcData.getId() == destData.getId()) {
                     // The volume has to be migrated across storage pools.
+                    if (s_logger.isDebugEnabled()) s_logger.debug("MIGRATE copy using migrateVolumeToPool STARTING");
                     answer = migrateVolumeToPool(srcData, destData);
+                    if (s_logger.isDebugEnabled()) s_logger.debug("MIGRATE copy using migrateVolumeToPool DONE: " + answer.getResult());
                 } else {
+                    if (s_logger.isDebugEnabled()) s_logger.debug("MIGRATE copy using copyVolumeBetweenPools STARTING");
                     answer = copyVolumeBetweenPools(srcData, destData);
+                    if (s_logger.isDebugEnabled()) s_logger.debug("MIGRATE copy using copyVolumeBetweenPools DONE: " + answer.getResult());
                 }
             } else if (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.SNAPSHOT) {
                 answer = copySnapshot(srcData, destData);
@@ -532,7 +543,7 @@
                 errMsg = answer.getDetails();
             }
         } catch (Exception e) {
-            s_logger.debug("copy failed", e);
+            if (s_logger.isDebugEnabled()) s_logger.debug("copy failed", e);
             errMsg = e.toString();
         }
         CopyCommandResult result = new CopyCommandResult(null, answer);
@@ -627,7 +638,7 @@
             }
             return answer;
         } catch (Exception e) {
-            s_logger.debug("copy snasphot failed: ", e);
+            if (s_logger.isDebugEnabled()) s_logger.debug("copy snasphot failed: ", e);
             if (cacheData != null) {
                 cacheMgr.deleteCacheObject(cacheData);
             }
diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
index a63aa52..a93f624 100644
--- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
+++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java
@@ -107,6 +107,7 @@
 import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.Storage.ProvisioningType;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
@@ -187,6 +188,8 @@
     private EndPointSelector selector;
     @Inject
     VMTemplatePoolDao templatePoolDao;
+    @Inject
+    private VolumeDataFactory _volFactory;
 
     @Override
     public StrategyPriority canHandle(DataObject srcData, DataObject destData) {
@@ -401,15 +404,15 @@
                 } else if (!isVolumeOnManagedStorage(destVolumeInfo)) {
                     handleVolumeMigrationFromManagedStorageToNonManagedStorage(srcVolumeInfo, destVolumeInfo, callback);
                 } else {
-                    String errMsg = "The source volume to migrate and the destination volume are both on managed storage. " +
-                            "Migration in this case is not yet supported.";
-
-                    handleError(errMsg, callback);
+                    handleVolumeMigrationFromManagedStorageToManagedStorage(srcVolumeInfo, destVolumeInfo, callback);
                 }
             } else if (!isVolumeOnManagedStorage(destVolumeInfo)) {
-                String errMsg = "The 'StorageSystemDataMotionStrategy' does not support this migration use case.";
-
-                handleError(errMsg, callback);
+                if (!HypervisorType.KVM.equals(srcVolumeInfo.getHypervisorType())) {
+                    String errMsg = String.format("Currently migrating volumes between managed storage providers is not supported on %s hypervisor", srcVolumeInfo.getHypervisorType().toString());
+                    handleError(errMsg, callback);
+                } else {
+                    handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback);
+                }
             } else {
                 handleVolumeMigrationFromNonManagedStorageToManagedStorage(srcVolumeInfo, destVolumeInfo, callback);
             }
@@ -454,7 +457,7 @@
         String volumePath = null;
 
         try {
-            if (!ImageFormat.QCOW2.equals(srcVolumeInfo.getFormat())) {
+            if (!HypervisorType.KVM.equals(srcVolumeInfo.getHypervisorType())) {
                 throw new CloudRuntimeException("Currently, only the KVM hypervisor type is supported for the migration of a volume " +
                         "from managed storage to non-managed storage.");
             }
@@ -486,7 +489,7 @@
             errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeCopyFromManagedStorageToSecondaryStorage': " +
                     ex.getMessage();
 
-            throw new CloudRuntimeException(errMsg);
+            throw new CloudRuntimeException(errMsg, ex);
         }
         finally {
             CopyCmdAnswer copyCmdAnswer;
@@ -513,12 +516,22 @@
         }
     }
 
+    private void handleVolumeMigrationFromManagedStorageToManagedStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
+                                                                AsyncCompletionCallback<CopyCommandResult> callback) {
+        if (!HypervisorType.KVM.equals(srcVolumeInfo.getHypervisorType())) {
+            String errMsg = String.format("Currently migrating volumes between managed storage providers is not supported on %s hypervisor", srcVolumeInfo.getHypervisorType().toString());
+            handleError(errMsg, callback);
+        } else {
+            handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback);
+        }
+    }
+
     private void handleVolumeMigrationFromManagedStorageToNonManagedStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo,
                                                                             AsyncCompletionCallback<CopyCommandResult> callback) {
         String errMsg = null;
 
         try {
-            if (!ImageFormat.QCOW2.equals(srcVolumeInfo.getFormat())) {
+            if (!HypervisorType.KVM.equals(srcVolumeInfo.getHypervisorType())) {
                 throw new CloudRuntimeException("Currently, only the KVM hypervisor type is supported for the migration of a volume " +
                         "from managed storage to non-managed storage.");
             }
@@ -526,10 +539,7 @@
             HypervisorType hypervisorType = HypervisorType.KVM;
             VirtualMachine vm = srcVolumeInfo.getAttachedVM();
 
-            if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
-                throw new CloudRuntimeException("Currently, if a volume to migrate from managed storage to non-managed storage is attached to " +
-                        "a VM, the VM must be in the Stopped state.");
-            }
+            checkAvailableForMigration(vm);
 
             long destStoragePoolId = destVolumeInfo.getPoolId();
             StoragePoolVO destStoragePoolVO = _storagePoolDao.findById(destStoragePoolId);
@@ -554,7 +564,7 @@
             errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeMigrationFromManagedStorageToNonManagedStorage': " +
                     ex.getMessage();
 
-            throw new CloudRuntimeException(errMsg);
+            throw new CloudRuntimeException(errMsg, ex);
         }
         finally {
             CopyCmdAnswer copyCmdAnswer;
@@ -580,9 +590,10 @@
 
     private void verifyFormatWithPoolType(ImageFormat imageFormat, StoragePoolType poolType) {
         if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA && imageFormat != ImageFormat.QCOW2 &&
-                !(imageFormat == ImageFormat.RAW && StoragePoolType.PowerFlex == poolType)) {
-            throw new CloudRuntimeException("Only the following image types are currently supported: " +
-                    ImageFormat.VHD.toString() + ", " + ImageFormat.OVA.toString() + ", " + ImageFormat.QCOW2.toString() + ", and " + ImageFormat.RAW.toString() + "(for PowerFlex)");
+                !(imageFormat == ImageFormat.RAW && (StoragePoolType.PowerFlex == poolType ||
+                StoragePoolType.FiberChannel == poolType))) {
+            throw new CloudRuntimeException(String.format("Only the following image types are currently supported: %s, %s, %s, %s (for PowerFlex and FiberChannel)",
+                ImageFormat.VHD.toString(), ImageFormat.OVA.toString(), ImageFormat.QCOW2.toString(), ImageFormat.RAW.toString()));
         }
     }
 
@@ -686,14 +697,14 @@
                 handleVolumeMigrationForXenServer(srcVolumeInfo, destVolumeInfo);
             }
             else {
-                handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo);
+                handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback);
             }
         }
         catch (Exception ex) {
             errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeMigrationFromNonManagedStorageToManagedStorage': " +
                     ex.getMessage();
 
-            throw new CloudRuntimeException(errMsg);
+            throw new CloudRuntimeException(errMsg, ex);
         }
         finally {
             CopyCmdAnswer copyCmdAnswer;
@@ -827,24 +838,73 @@
         _volumeDao.update(srcVolumeInfo.getId(), volumeVO);
     }
 
-    private void handleVolumeMigrationForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) {
+    private void handleVolumeMigrationForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
         VirtualMachine vm = srcVolumeInfo.getAttachedVM();
 
-        if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
-            throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage on KVM is attached to " +
-                    "a VM, the VM must be in the Stopped state.");
+        checkAvailableForMigration(vm);
+
+        String errMsg = null;
+        try {
+            destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
+            VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
+            updatePathFromScsiName(volumeVO);
+            destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
+            HostVO hostVO = getHostOnWhichToExecuteMigrationCommand(srcVolumeInfo, destVolumeInfo);
+
+            // migrate the volume via the hypervisor
+            String path = migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage");
+
+            updateVolumePath(destVolumeInfo.getId(), path);
+            volumeVO = _volumeDao.findById(destVolumeInfo.getId());
+            // only set this if it was not set.  default to QCOW2 for KVM
+            if (volumeVO.getFormat() == null) {
+                volumeVO.setFormat(ImageFormat.QCOW2);
+                _volumeDao.update(volumeVO.getId(), volumeVO);
+            }
+        } catch (Exception ex) {
+            errMsg = "Primary storage migration failed due to an unexpected error: " +
+                    ex.getMessage();
+            if (ex instanceof CloudRuntimeException) {
+                throw ex;
+            } else {
+                throw new CloudRuntimeException(errMsg, ex);
+            }
+        } finally {
+            CopyCmdAnswer copyCmdAnswer;
+            if (errMsg != null) {
+                copyCmdAnswer = new CopyCmdAnswer(errMsg);
+            }
+            else {
+                destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
+                DataTO dataTO = destVolumeInfo.getTO();
+                copyCmdAnswer = new CopyCmdAnswer(dataTO);
+            }
+
+            CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
+            result.setResult(errMsg);
+            callback.complete(result);
         }
+    }
 
-        destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
+    private void checkAvailableForMigration(VirtualMachine vm) {
+        if (vm != null && (vm.getState() != VirtualMachine.State.Stopped && vm.getState() != VirtualMachine.State.Migrating)) {
+            throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage on KVM is attached to " +
+                    "a VM, the VM must be in the Stopped or Migrating state.");
+        }
+    }
 
-        VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
+    /**
+     * Only update the path from the iscsiName if the iscsiName is set.  Otherwise take no action to avoid nullifying the path
+     * with a previously set path value.
+     */
+    private void updatePathFromScsiName(VolumeVO volumeVO) {
+        if (volumeVO.get_iScsiName() != null) {
+            volumeVO.setPath(volumeVO.get_iScsiName());
+            _volumeDao.update(volumeVO.getId(), volumeVO);
+        }
+    }
 
-        volumeVO.setPath(volumeVO.get_iScsiName());
-
-        _volumeDao.update(volumeVO.getId(), volumeVO);
-
-        destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
-
+    private HostVO getHostOnWhichToExecuteMigrationCommand(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) {
         long srcStoragePoolId = srcVolumeInfo.getPoolId();
         StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(srcStoragePoolId);
 
@@ -857,14 +917,7 @@
             hostVO = getHost(destVolumeInfo.getDataCenterId(), HypervisorType.KVM, false);
         }
 
-        // migrate the volume via the hypervisor
-        migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage");
-
-        volumeVO = _volumeDao.findById(destVolumeInfo.getId());
-
-        volumeVO.setFormat(ImageFormat.QCOW2);
-
-        _volumeDao.update(volumeVO.getId(), volumeVO);
+        return hostVO;
     }
 
     /**
@@ -1076,7 +1129,7 @@
         catch (Exception ex) {
             errMsg = ex.getMessage();
 
-            throw new CloudRuntimeException(errMsg);
+            throw new CloudRuntimeException(errMsg, ex);
         }
         finally {
             if (usingBackendSnapshot) {
@@ -1294,7 +1347,7 @@
         catch (Exception ex) {
             errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateManagedVolumeFromNonManagedSnapshot': " + ex.getMessage();
 
-            throw new CloudRuntimeException(errMsg);
+            throw new CloudRuntimeException(errMsg, ex);
         }
         finally {
             handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
@@ -1676,6 +1729,42 @@
     }
 
     /**
+     * Use normal volume semantics (create a volume known to cloudstack, ask the storage driver to create it as a copy of the snapshot)
+
+     * @param volumeVO
+     * @param snapshotInfo
+     */
+    public void prepTempVolumeForCopyFromSnapshot(SnapshotInfo snapshotInfo) {
+        VolumeVO volumeVO = null;
+        try {
+            volumeVO = new VolumeVO(Volume.Type.DATADISK, snapshotInfo.getName() + "_" + System.currentTimeMillis() + ".TMP",
+                snapshotInfo.getDataCenterId(), snapshotInfo.getDomainId(), snapshotInfo.getAccountId(), 0, ProvisioningType.THIN, snapshotInfo.getSize(), 0L, 0L, "");
+                volumeVO.setPoolId(snapshotInfo.getDataStore().getId());
+            _volumeDao.persist(volumeVO);
+            VolumeInfo tempVolumeInfo = this._volFactory.getVolume(volumeVO.getId());
+
+            if (snapshotInfo.getDataStore().getDriver().canCopy(snapshotInfo, tempVolumeInfo)) {
+                snapshotInfo.getDataStore().getDriver().copyAsync(snapshotInfo, tempVolumeInfo, null, null);
+                // refresh volume info as data could have changed
+                tempVolumeInfo = this._volFactory.getVolume(volumeVO.getId());
+                // save the "temp" volume info into the snapshot details (we need this to clean up at the end)
+                _snapshotDetailsDao.addDetail(snapshotInfo.getId(), "TemporaryVolumeCopyUUID", tempVolumeInfo.getUuid(), true);
+                _snapshotDetailsDao.addDetail(snapshotInfo.getId(), "TemporaryVolumeCopyPath", tempVolumeInfo.getPath(), true);
+                // NOTE: for this to work, the Driver must return a custom SnapshotObjectTO object from getTO()
+                // whenever the TemporaryVolumeCopyPath is set.
+            } else {
+                throw new CloudRuntimeException("Storage driver indicated it could create a volume from the snapshot but rejected the subsequent request to do so");
+            }
+        } catch (Throwable e) {
+            // cleanup temporary volume
+            if (volumeVO != null) {
+                _volumeDao.remove(volumeVO.getId());
+            }
+            throw e;
+        }
+    }
+
+    /**
      * If the underlying storage system is making use of read-only snapshots, this gives the storage system the opportunity to
      * create a volume from the snapshot so that we can copy the VHD file that should be inside of the snapshot to secondary storage.
      *
@@ -1686,8 +1775,13 @@
      * resign the SR and the VDI that should be inside of the snapshot before copying the VHD file to secondary storage.
      */
     private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
-        SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "create");
+        if ("true".equalsIgnoreCase(snapshotInfo.getDataStore().getDriver().getCapabilities().get("CAN_CREATE_TEMP_VOLUME_FROM_SNAPSHOT"))) {
+            prepTempVolumeForCopyFromSnapshot(snapshotInfo);
+            return;
 
+        }
+
+        SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "create");
         try {
             snapshotInfo.getDataStore().getDriver().createAsync(snapshotInfo.getDataStore(), snapshotInfo, null);
         }
@@ -1702,6 +1796,24 @@
      * invocation of createVolumeFromSnapshot(SnapshotInfo).
      */
     private void deleteVolumeFromSnapshot(SnapshotInfo snapshotInfo) {
+        VolumeVO volumeVO = null;
+        // cleanup any temporary volume previously created for copy from a snapshot
+        if ("true".equalsIgnoreCase(snapshotInfo.getDataStore().getDriver().getCapabilities().get("CAN_CREATE_TEMP_VOLUME_FROM_SNAPSHOT"))) {
+            SnapshotDetailsVO tempUuid = null;
+            tempUuid = _snapshotDetailsDao.findDetail(snapshotInfo.getId(), "TemporaryVolumeCopyUUID");
+            if (tempUuid == null || tempUuid.getValue() == null) {
+                return;
+            }
+
+            volumeVO = _volumeDao.findByUuid(tempUuid.getValue());
+            if (volumeVO != null) {
+                _volumeDao.remove(volumeVO.getId());
+            }
+            _snapshotDetailsDao.remove(tempUuid.getId());
+            _snapshotDetailsDao.removeDetail(snapshotInfo.getId(), "TemporaryVolumeCopyUUID");
+            return;
+        }
+
         SnapshotDetailsVO snapshotDetails = handleSnapshotDetails(snapshotInfo.getId(), "delete");
 
         try {
@@ -2370,7 +2482,10 @@
         try {
             StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
 
-            if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat()) && !(ImageFormat.RAW.equals(volumeInfo.getFormat()) && StoragePoolType.PowerFlex == storagePoolVO.getPoolType())) {
+            if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat()) &&
+                !(ImageFormat.RAW.equals(volumeInfo.getFormat()) && (
+                    StoragePoolType.PowerFlex == storagePoolVO.getPoolType() ||
+                    StoragePoolType.FiberChannel == storagePoolVO.getPoolType()))) {
                 throw new CloudRuntimeException("When using managed storage, you can only create a template from a volume on KVM currently.");
             }
 
@@ -2513,7 +2628,13 @@
 
         long snapshotId = snapshotInfo.getId();
 
-        if (storagePoolVO.getPoolType() == StoragePoolType.PowerFlex) {
+        // if the snapshot required a temporary volume be created check if the UUID is set so we can
+        // retrieve the temporary volume's path to use during remote copy
+        List<SnapshotDetailsVO> storedDetails = _snapshotDetailsDao.findDetails(snapshotInfo.getId(), "TemporaryVolumeCopyPath");
+        if (storedDetails != null && storedDetails.size() > 0) {
+            String value = storedDetails.get(0).getValue();
+            snapshotDetails.put(DiskTO.PATH, value);
+        } else if (storagePoolVO.getPoolType() == StoragePoolType.PowerFlex || storagePoolVO.getPoolType() == StoragePoolType.FiberChannel) {
             snapshotDetails.put(DiskTO.IQN, snapshotInfo.getPath());
         } else {
             snapshotDetails.put(DiskTO.IQN, getSnapshotProperty(snapshotId, DiskTO.IQN));
@@ -2725,8 +2846,6 @@
     }
 
     private String migrateVolumeForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
-        boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
-
         try {
             Map<String, String> srcDetails = getVolumeDetails(srcVolumeInfo);
             Map<String, String> destDetails = getVolumeDetails(destVolumeInfo);
@@ -2734,16 +2853,11 @@
             MigrateVolumeCommand migrateVolumeCommand = new MigrateVolumeCommand(srcVolumeInfo.getTO(), destVolumeInfo.getTO(),
                     srcDetails, destDetails, StorageManager.KvmStorageOfflineMigrationWait.value());
 
-            if (srcVolumeDetached) {
-                _volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
-            }
-
+            _volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
             handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
-
             _volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
 
             MigrateVolumeAnswer migrateVolumeAnswer = (MigrateVolumeAnswer)agentManager.send(hostVO.getId(), migrateVolumeCommand);
-
             if (migrateVolumeAnswer == null || !migrateVolumeAnswer.getResult()) {
                 if (migrateVolumeAnswer != null && StringUtils.isNotEmpty(migrateVolumeAnswer.getDetails())) {
                     throw new CloudRuntimeException(migrateVolumeAnswer.getDetails());
@@ -2752,42 +2866,22 @@
                     throw new CloudRuntimeException(errMsg);
                 }
             }
-
-            if (srcVolumeDetached) {
-                _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
-            }
-
-            try {
-                _volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
-            }
-            catch (Exception e) {
-                // This volume should be deleted soon, so just log a warning here.
-                LOGGER.warn(e.getMessage(), e);
-            }
-
             return migrateVolumeAnswer.getVolumePath();
-        }
-        catch (Exception ex) {
+        } catch (CloudRuntimeException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new CloudRuntimeException("Unexpected error during volume migration: " + ex.getMessage(), ex);
+        } finally {
             try {
-                _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
-            }
-            catch (Exception e) {
-                // This volume should be deleted soon, so just log a warning here.
-                LOGGER.warn(e.getMessage(), e);
-            }
-
-            if (srcVolumeDetached) {
                 _volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
+                _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
+                handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
+            } catch (Throwable e) {
+                LOGGER.warn("During cleanup post-migration and exception occured: " + e);
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Exception during post-migration cleanup.", e);
+                }
             }
-
-            String msg = "Failed to perform volume migration : ";
-
-            LOGGER.warn(msg, ex);
-
-            throw new CloudRuntimeException(msg + ex.getMessage(), ex);
-        }
-        finally {
-            handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
         }
     }
 
diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java
index 92b6b5b..56e0948 100755
--- a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java
+++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java
@@ -36,17 +36,14 @@
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.to.DataTO;
-import com.cloud.capacity.CapacityManager;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CapacityManager.class)
+
+@RunWith(MockitoJUnitRunner.class)
 public class AncientDataMotionStrategyTest {
 
     @Spy
@@ -69,8 +66,6 @@
 
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
         replaceVmwareCreateCloneFullField();
 
         when(vmwareKey.valueIn(POOL_ID)).thenReturn(FULL_CLONE_FLAG);
@@ -99,7 +94,6 @@
 
     @Test
     public void testAddFullCloneFlagOnNotVmwareDest(){
-        when(dataTO.getHypervisorType()).thenReturn(HypervisorType.Any);
         verify(dataStoreTO, never()).setFullCloneFlag(any(Boolean.class));
     }
 
diff --git a/engine/storage/image/pom.xml b/engine/storage/image/pom.xml
index 365d7d1..8b5dd06 100644
--- a/engine/storage/image/pom.xml
+++ b/engine/storage/image/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java
index 1a6a31f..3557921 100644
--- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java
+++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java
@@ -218,7 +218,7 @@
     private void updateDataObject(DataObject srcData, DataObject destData) {
         if (destData instanceof SnapshotInfo) {
             SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySourceSnapshot(srcData.getId(), DataStoreRole.Image);
-            SnapshotDataStoreVO destSnapshotStore = snapshotStoreDao.findBySnapshot(srcData.getId(), DataStoreRole.Image);
+            SnapshotDataStoreVO destSnapshotStore = snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, srcData.getDataStore().getId(), srcData.getId());
             if (snapshotStore != null && destSnapshotStore != null) {
                 destSnapshotStore.setPhysicalSize(snapshotStore.getPhysicalSize());
                 destSnapshotStore.setCreated(snapshotStore.getCreated());
diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java
index 41e83f4..5bb0d19 100644
--- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java
+++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java
@@ -94,7 +94,7 @@
     @Override
     public ImageStoreEntity getImageStore(String uuid) {
         ImageStoreVO dataStore = dataStoreDao.findByUuid(uuid);
-        return getImageStore(dataStore.getId());
+        return dataStore == null ? null : getImageStore(dataStore.getId());
     }
 
     @Override
@@ -249,6 +249,16 @@
     }
 
     @Override
+    public List<DataStore> listImageStoresFilteringByZoneIds(Long... zoneIds) {
+        List<ImageStoreVO> stores = dataStoreDao.listImageStoresByZoneIds(zoneIds);
+        List<DataStore> imageStores = new ArrayList<>();
+        for (ImageStoreVO store : stores) {
+            imageStores.add(getImageStore(store.getId()));
+        }
+        return imageStores;
+    }
+
+    @Override
     public String getConfigComponentName() {
         return ImageStoreProviderManager.class.getSimpleName();
     }
@@ -257,4 +267,10 @@
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey<?>[] { ImageStoreAllocationAlgorithm };
     }
+
+    @Override
+    public long getImageStoreZoneId(long dataStoreId) {
+        ImageStoreVO dataStore = dataStoreDao.findById(dataStoreId);
+        return dataStore.getDataCenterId();
+    }
 }
diff --git a/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImplTest.java b/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImplTest.java
new file mode 100644
index 0000000..c046203
--- /dev/null
+++ b/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImplTest.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.storage.image.manager;
+
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+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 ImageStoreProviderManagerImplTest {
+
+    @Mock
+    ImageStoreDao imageStoreDao;
+
+    @InjectMocks
+    ImageStoreProviderManagerImpl imageStoreProviderManager = new ImageStoreProviderManagerImpl();
+    @Test
+    public void testGetImageStoreZoneId() {
+        final long storeId = 1L;
+        final long zoneId = 1L;
+        ImageStoreVO imageStoreVO = Mockito.mock(ImageStoreVO.class);
+        Mockito.when(imageStoreVO.getDataCenterId()).thenReturn(zoneId);
+        Mockito.when(imageStoreDao.findById(storeId)).thenReturn(imageStoreVO);
+        long value = imageStoreProviderManager.getImageStoreZoneId(storeId);
+        Assert.assertEquals(zoneId, value);
+    }
+}
diff --git a/engine/storage/integration-test/pom.xml b/engine/storage/integration-test/pom.xml
index 76def52..16042db 100644
--- a/engine/storage/integration-test/pom.xml
+++ b/engine/storage/integration-test/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/integration-test/src/test/resources/component.xml b/engine/storage/integration-test/src/test/resources/component.xml
index aee3714..d384d54 100644
--- a/engine/storage/integration-test/src/test/resources/component.xml
+++ b/engine/storage/integration-test/src/test/resources/component.xml
@@ -121,6 +121,11 @@
     <property name="name" value="KVM Agent"/>
   </bean>
 
+  <bean id="CustomServerDiscoverer"
+        class="com.cloud.hypervisor.discoverer.CustomServerDiscoverer">
+    <property name="name" value="CustomHW Agent" />
+  </bean>
+
   <bean id="BareMetalDiscoverer" class="com.cloud.baremetal.BareMetalDiscoverer">
     <property name="name" value="Bare Metal Agent"/>
   </bean>
diff --git a/engine/storage/integration-test/src/test/resources/storageContext.xml b/engine/storage/integration-test/src/test/resources/storageContext.xml
index fc24753..7c95345 100644
--- a/engine/storage/integration-test/src/test/resources/storageContext.xml
+++ b/engine/storage/integration-test/src/test/resources/storageContext.xml
@@ -87,4 +87,8 @@
   <bean id="storageStrategyFactoryImpl" class="org.apache.cloudstack.storage.helper.StorageStrategyFactoryImpl" />
   <bean id="vmsnapshotDetailsDao" class="com.cloud.vm.snapshot.dao.VMSnapshotDetailsDaoImpl" />
   <bean id="snapshotManager" class="com.cloud.storage.snapshot.SnapshotManagerImpl" />
+  <bean id="objectStoreProviderManagerImpl" class="org.apache.cloudstack.storage.object.manager.ObjectStoreProviderManagerImpl" />
+  <bean id="objectStoreDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ObjectStoreDaoImpl" />
+  <bean id="objectStoreDetailsDaoImpl" class="org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDaoImpl" />
+  <bean id="objectStoreHelper" class="org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper" />
 </beans>
diff --git a/engine/storage/object/pom.xml b/engine/storage/object/pom.xml
new file mode 100644
index 0000000..cd824bc
--- /dev/null
+++ b/engine/storage/object/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-engine-storage-object</artifactId>
+    <name>Apache CloudStack Engine Storage Object Component</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloud-engine</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/BucketObject.java b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/BucketObject.java
new file mode 100644
index 0000000..4181215
--- /dev/null
+++ b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/BucketObject.java
@@ -0,0 +1,196 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.object;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataTO;
+import org.apache.cloudstack.engine.subsystem.api.storage.BucketInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
+
+import java.util.Date;
+
+public class BucketObject implements BucketInfo {
+
+    private String name;
+
+    @Override
+    public long getDomainId() {
+        return 0;
+    }
+
+    @Override
+    public long getAccountId() {
+        return 0;
+    }
+
+    @Override
+    public Class<?> getEntityType() {
+        return null;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public void addPayload(Object data) {
+
+    }
+
+    @Override
+    public Object getPayload() {
+        return null;
+    }
+
+    @Override
+    public Bucket getBucket() {
+        return null;
+    }
+
+    @Override
+    public long getId() {
+        return 0;
+    }
+
+    @Override
+    public String getUri() {
+        return null;
+    }
+
+    @Override
+    public DataTO getTO() {
+        return null;
+    }
+
+    @Override
+    public DataStore getDataStore() {
+        return null;
+    }
+
+    @Override
+    public Long getSize() {
+        return null;
+    }
+
+    @Override
+    public Integer getQuota() {
+        return null;
+    }
+
+    @Override
+    public boolean isVersioning() {
+        return false;
+    }
+
+    @Override
+    public boolean isEncryption() {
+        return false;
+    }
+
+    @Override
+    public boolean isObjectLock() {
+        return false;
+    }
+
+    @Override
+    public String getPolicy() {
+        return null;
+    }
+
+    @Override
+    public String getBucketURL() {
+        return null;
+    }
+
+    @Override
+    public String getAccessKey() {
+        return null;
+    }
+
+    @Override
+    public String getSecretKey() {
+        return null;
+    }
+
+    @Override
+    public long getPhysicalSize() {
+        return 0;
+    }
+
+    @Override
+    public DataObjectType getType() {
+        return null;
+    }
+
+    @Override
+    public String getUuid() {
+        return null;
+    }
+
+    @Override
+    public boolean delete() {
+        return false;
+    }
+
+    @Override
+    public void processEvent(ObjectInDataStoreStateMachine.Event event) {
+
+    }
+
+    @Override
+    public void processEvent(ObjectInDataStoreStateMachine.Event event, Answer answer) {
+
+    }
+
+    @Override
+    public void incRefCount() {
+
+    }
+
+    @Override
+    public void decRefCount() {
+
+    }
+
+    @Override
+    public Long getRefCount() {
+        return null;
+    }
+
+    @Override
+    public long getObjectStoreId() {
+        return 0;
+    }
+
+    @Override
+    public Date getCreated() {
+        return null;
+    }
+
+    @Override
+    public State getState() {
+        return null;
+    }
+}
diff --git a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/ObjectStorageServiceImpl.java b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/ObjectStorageServiceImpl.java
new file mode 100644
index 0000000..a0db89b
--- /dev/null
+++ b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/ObjectStorageServiceImpl.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.storage.object;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectStorageService;
+import org.apache.log4j.Logger;
+
+public class ObjectStorageServiceImpl implements ObjectStorageService {
+
+    private static final Logger s_logger = Logger.getLogger(ObjectStorageServiceImpl.class);
+
+
+}
diff --git a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java
new file mode 100644
index 0000000..40f5036
--- /dev/null
+++ b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.object.manager;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectStoreProvider;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.ObjectStoreEntity;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
+import org.apache.cloudstack.storage.object.store.ObjectStoreImpl;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ObjectStoreProviderManagerImpl implements ObjectStoreProviderManager, Configurable {
+    private static final Logger s_logger = Logger.getLogger(ObjectStoreProviderManagerImpl.class);
+    @Inject
+    ObjectStoreDao objectStoreDao;
+
+    @Inject
+    DataStoreProviderManager providerManager;
+
+    Map<String, ObjectStoreDriver> driverMaps;
+
+    @PostConstruct
+    public void config() {
+        driverMaps = new HashMap<String, ObjectStoreDriver>();
+    }
+
+    @Override
+    public ObjectStoreEntity getObjectStore(long objectStoreId) {
+        ObjectStoreVO objectStore = objectStoreDao.findById(objectStoreId);
+        String providerName = objectStore.getProviderName();
+        ObjectStoreProvider provider = (ObjectStoreProvider)providerManager.getDataStoreProvider(providerName);
+        ObjectStoreEntity objStore = ObjectStoreImpl.getDataStore(objectStore, driverMaps.get(provider.getName()), provider);
+        return objStore;
+    }
+
+    @Override
+    public boolean registerDriver(String providerName, ObjectStoreDriver driver) {
+        if (driverMaps.containsKey(providerName)) {
+            return false;
+        }
+        driverMaps.put(providerName, driver);
+        return true;
+    }
+
+    @Override
+    public ObjectStoreEntity getObjectStore(String uuid) {
+        ObjectStoreVO objectStore = objectStoreDao.findByUuid(uuid);
+        return getObjectStore(objectStore.getId());
+    }
+
+    @Override
+    public List<DataStore> listObjectStores() {
+        List<ObjectStoreVO> stores = objectStoreDao.listObjectStores();
+        List<DataStore> ObjectStores = new ArrayList<DataStore>();
+        for (ObjectStoreVO store : stores) {
+            ObjectStores.add(getObjectStore(store.getId()));
+        }
+        return ObjectStores;
+    }
+
+    @Override
+    public List<DataStore> listObjectStoreByProvider(String provider) {
+        List<ObjectStoreVO> stores = objectStoreDao.findByProvider(provider);
+        List<DataStore> ObjectStores = new ArrayList<DataStore>();
+        for (ObjectStoreVO store : stores) {
+            ObjectStores.add(getObjectStore(store.getId()));
+        }
+        return ObjectStores;
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return ObjectStoreProviderManager.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {  };
+    }
+}
diff --git a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/store/ObjectStoreImpl.java b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/store/ObjectStoreImpl.java
new file mode 100644
index 0000000..825b349
--- /dev/null
+++ b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/store/ObjectStoreImpl.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.storage.object.store;
+
+import com.cloud.agent.api.to.DataStoreTO;
+import org.apache.cloudstack.storage.object.Bucket;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.utils.component.ComponentContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectStoreProvider;
+import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.ObjectStoreEntity;
+import org.apache.log4j.Logger;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+public class ObjectStoreImpl implements ObjectStoreEntity {
+    private static final Logger s_logger = Logger.getLogger(ObjectStoreImpl.class);
+
+    protected ObjectStoreDriver driver;
+    protected ObjectStoreVO objectStoreVO;
+    protected ObjectStoreProvider provider;
+
+    public ObjectStoreImpl() {
+        super();
+    }
+
+    protected void configure(ObjectStoreVO objectStoreVO, ObjectStoreDriver objectStoreDriver, ObjectStoreProvider provider) {
+        this.driver = objectStoreDriver;
+        this.objectStoreVO = objectStoreVO;
+        this.provider = provider;
+    }
+
+    public static ObjectStoreEntity getDataStore(ObjectStoreVO objectStoreVO, ObjectStoreDriver objectStoreDriver, ObjectStoreProvider provider) {
+        ObjectStoreImpl instance = ComponentContext.inject(ObjectStoreImpl.class);
+        instance.configure(objectStoreVO, objectStoreDriver, provider);
+        return instance;
+    }
+
+    @Override
+    public DataStoreDriver getDriver() {
+        return this.driver;
+    }
+
+    @Override
+    public DataStoreRole getRole() {
+        return null;
+    }
+
+    @Override
+    public long getId() {
+        return this.objectStoreVO.getId();
+    }
+
+    @Override
+    public String getUri() {
+        return this.objectStoreVO.getUrl();
+    }
+
+    @Override
+    public Scope getScope() {
+        return null;
+    }
+
+
+    @Override
+    public String getUuid() {
+        return this.objectStoreVO.getUuid();
+    }
+
+    public Date getCreated() {
+        return this.objectStoreVO.getCreated();
+    }
+
+    @Override
+    public String getName() {
+        return objectStoreVO.getName();
+    }
+
+    @Override
+    public DataObject create(DataObject obj) {
+        return null;
+    }
+
+    @Override
+    public Bucket createBucket(Bucket bucket, boolean objectLock) {
+        return driver.createBucket(bucket, objectLock);
+    }
+
+    @Override
+    public boolean deleteBucket(String bucketName) {
+        return driver.deleteBucket(bucketName, objectStoreVO.getId());
+    }
+
+    @Override
+    public boolean setBucketEncryption(String bucketName) {
+        return driver.setBucketEncryption(bucketName, objectStoreVO.getId());
+    }
+
+    @Override
+    public boolean deleteBucketEncryption(String bucketName) {
+        return driver.deleteBucketEncryption(bucketName, objectStoreVO.getId());
+    }
+
+    @Override
+    public boolean setBucketVersioning(String bucketName) {
+        return driver.setBucketVersioning(bucketName, objectStoreVO.getId());
+    }
+
+    @Override
+    public boolean deleteBucketVersioning(String bucketName) {
+        return driver.deleteBucketVersioning(bucketName, objectStoreVO.getId());
+    }
+
+    @Override
+    public void setBucketPolicy(String bucketName, String policy) {
+        driver.setBucketPolicy(bucketName, policy, objectStoreVO.getId());
+    }
+
+    @Override
+    public void setQuota(String bucketName, int quota) {
+        driver.setBucketQuota(bucketName, objectStoreVO.getId(), quota);
+    }
+
+    @Override
+    public Map<String, Long> getAllBucketsUsage() {
+        return driver.getAllBucketsUsage(objectStoreVO.getId());
+    }
+
+    @Override
+    public List<Bucket> listBuckets() {
+        return driver.listBuckets(objectStoreVO.getId());
+    }
+
+    /*
+    Create user if not exists
+     */
+    @Override
+    public boolean createUser(long accountId) {
+        return driver.createUser(accountId, objectStoreVO.getId());
+    }
+
+    @Override
+    public boolean delete(DataObject obj) {
+        return false;
+    }
+
+    @Override
+    public DataStoreTO getTO() {
+        return null;
+    }
+
+    @Override
+    public String getProviderName() {
+        return objectStoreVO.getProviderName();
+    }
+
+    @Override
+    public String getUrl() {
+        return objectStoreVO.getUrl();
+    }
+
+}
diff --git a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/store/lifecycle/ObjectStoreLifeCycle.java b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/store/lifecycle/ObjectStoreLifeCycle.java
new file mode 100644
index 0000000..ff88262
--- /dev/null
+++ b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/store/lifecycle/ObjectStoreLifeCycle.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.storage.object.store.lifecycle;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
+
+public interface ObjectStoreLifeCycle extends DataStoreLifeCycle {
+}
diff --git a/engine/storage/object/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-object-core-context.xml b/engine/storage/object/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-object-core-context.xml
new file mode 100644
index 0000000..57bd9f8
--- /dev/null
+++ b/engine/storage/object/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-object-core-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+    <bean id="objectStoreDao" class="org.apache.cloudstack.storage.datastore.db.ObjectStoreDaoImpl" />
+    <bean id="objectStoreProviderMgr"
+        class="org.apache.cloudstack.storage.object.manager.ObjectStoreProviderManagerImpl" />
+    <bean id="objectStoreHelper"
+          class="org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper" />
+    <bean id="bucketDao" class="com.cloud.storage.dao.BucketDaoImpl" />
+
+</beans>
diff --git a/engine/storage/object/src/test/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImplTest.java b/engine/storage/object/src/test/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImplTest.java
new file mode 100644
index 0000000..392388c
--- /dev/null
+++ b/engine/storage/object/src/test/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImplTest.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.storage.object.manager;
+
+import junit.framework.TestCase;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectStoreProvider;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.ObjectStoreEntity;
+import org.apache.cloudstack.storage.object.store.ObjectStoreImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ObjectStoreProviderManagerImplTest extends TestCase{
+
+    ObjectStoreProviderManagerImpl objectStoreProviderManagerImplSpy;
+
+    @Mock
+    ObjectStoreDao objectStoreDao;
+
+    @Mock
+    ObjectStoreVO objectStoreVO;
+
+    @Mock
+    DataStoreProviderManager providerManager;
+
+    @Mock
+    ObjectStoreProvider provider;
+
+    @Mock
+    Map<String, ObjectStoreDriver> driverMaps;
+
+    @Mock
+    ObjectStoreDriver objectStoreDriver;
+
+    @Mock
+    ObjectStoreEntity objectStoreEntity;
+
+    MockedStatic<ObjectStoreImpl> mockObjectStoreImpl;
+
+    @Before
+    public void setup(){
+        objectStoreProviderManagerImplSpy = Mockito.spy(new ObjectStoreProviderManagerImpl());
+        objectStoreProviderManagerImplSpy.objectStoreDao = objectStoreDao;
+        objectStoreProviderManagerImplSpy.providerManager = providerManager;
+        objectStoreProviderManagerImplSpy.driverMaps = driverMaps;
+        mockObjectStoreImpl = mockStatic(ObjectStoreImpl.class);
+    }
+
+    @Test
+    public void getObjectStoreTest() {
+        Mockito.doReturn(objectStoreVO).when(objectStoreDao).findById(Mockito.anyLong());
+        Mockito.doReturn(provider).when(providerManager).getDataStoreProvider(Mockito.anyString());
+        Mockito.doReturn(objectStoreDriver).when(driverMaps).get(Mockito.anyString());
+        Mockito.doReturn("Simulator").when(provider).getName();
+        Mockito.doReturn("Simulator").when(objectStoreVO).getProviderName();
+
+        when(ObjectStoreImpl.getDataStore(Mockito.any(ObjectStoreVO.class), Mockito.any(ObjectStoreDriver.class),
+                Mockito.any(ObjectStoreProvider.class))).thenReturn(objectStoreEntity);
+        assertNotNull(objectStoreProviderManagerImplSpy.getObjectStore(1L));
+    }
+
+    @Test
+    public void listObjectStoresTest() {
+        List<ObjectStoreVO> stores = new ArrayList<>();
+        stores.add(objectStoreVO);
+        Mockito.doReturn(objectStoreVO).when(objectStoreDao).findById(Mockito.anyLong());
+        Mockito.doReturn(provider).when(providerManager).getDataStoreProvider(Mockito.anyString());
+        Mockito.doReturn(objectStoreDriver).when(driverMaps).get(Mockito.anyString());
+        Mockito.doReturn("Simulator").when(provider).getName();
+        Mockito.doReturn("Simulator").when(objectStoreVO).getProviderName();
+        when(ObjectStoreImpl.getDataStore(Mockito.any(ObjectStoreVO.class), Mockito.any(ObjectStoreDriver.class),
+                Mockito.any(ObjectStoreProvider.class))).thenReturn(objectStoreEntity);
+        Mockito.doReturn(stores).when(objectStoreDao).listObjectStores();
+        assertEquals(1, objectStoreProviderManagerImplSpy.listObjectStores().size());
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        mockObjectStoreImpl.close();
+        super.tearDown();
+    }
+}
diff --git a/engine/storage/object/src/test/resource/testContext.xml b/engine/storage/object/src/test/resource/testContext.xml
new file mode 100644
index 0000000..7352b11
--- /dev/null
+++ b/engine/storage/object/src/test/resource/testContext.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+  xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans
+                        http://www.springframework.org/schema/beans/spring-beans.xsd
+                         http://www.springframework.org/schema/tx 
+       http://www.springframework.org/schema/tx/spring-tx.xsd
+       http://www.springframework.org/schema/aop
+       http://www.springframework.org/schema/aop/spring-aop.xsd
+                                 http://www.springframework.org/schema/context
+                                          http://www.springframework.org/schema/context/spring-context.xsd">
+  <context:annotation-config />
+  <context:component-scan base-package="org.apache.cloudstack.storage" />
+  <context:component-scan
+    base-package="org.apache.cloudstack.engine.subsystem.api.storage" />
+  <context:component-scan base-package="com.cloud.utils.db" />
+  <context:component-scan base-package="com.cloud.utils.component" />
+  <context:component-scan base-package="com.cloud.host.dao" />
+  <context:component-scan base-package="com.cloud.dc.dao" />
+  <context:component-scan base-package="com.cloud.dc.dao" />
+  <context:component-scan base-package="org.apache.cloudstack.framework" />
+  
+  <tx:annotation-driven transaction-manager="transactionManager" />
+  <bean class="org.apache.cloudstack.storage.volume.test.TestConfiguration" />
+  <aop:config proxy-target-class="true">
+    <aop:aspect id="dbContextBuilder" ref="transactionContextBuilder">
+      <aop:pointcut id="captureAnyMethod" expression="@annotation(com.cloud.utils.db.DB)" />
+      <aop:around pointcut-ref="captureAnyMethod" method="AroundAnyMethod" />
+    </aop:aspect>
+  </aop:config>
+
+  <bean id="transactionContextBuilder" class="com.cloud.utils.db.TransactionContextBuilder" />
+  
+  <bean id="onwireRegistry" class="org.apache.cloudstack.framework.serializer.OnwireClassRegistry"
+    init-method="scan" >
+    <property name="packages">
+      <list>
+        <value>org.apache.cloudstack.framework</value>
+      </list>
+    </property>
+  </bean>
+  
+  <bean id="messageSerializer" class="org.apache.cloudstack.framework.serializer.JsonMessageSerializer">
+    <property name="onwireClassRegistry" ref="onwireRegistry" />
+  </bean>
+
+  <bean id="transportProvider" class="org.apache.cloudstack.framework.server.ServerTransportProvider"  init-method="initialize">
+    <property name="workerPoolSize" value="5" />
+    <property name="nodeId" value="Node1" />
+    <property name="messageSerializer" ref="messageSerializer" />
+  </bean>
+  
+  <bean id="rpcProvider" class="org.apache.cloudstack.framework.rpc.RpcProviderImpl" init-method="initialize">
+    <constructor-arg ref="transportProvider" />
+    <property name="messageSerializer" ref="messageSerializer" />
+  </bean>
+
+  <bean id="eventBus" class = "org.apache.cloudstack.framework.eventbus.EventBusBase" />
+  
+</beans>
diff --git a/engine/storage/object/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/engine/storage/object/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/engine/storage/object/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/engine/storage/pom.xml b/engine/storage/pom.xml
index c534336..4d3ac1d 100644
--- a/engine/storage/pom.xml
+++ b/engine/storage/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/snapshot/pom.xml b/engine/storage/snapshot/pom.xml
index d19217c..b43af7b 100644
--- a/engine/storage/snapshot/pom.xml
+++ b/engine/storage/snapshot/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java
index 02672f2..19b3fc8 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java
@@ -21,7 +21,6 @@
 import javax.inject.Inject;
 
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
-import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
 import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@@ -48,7 +47,7 @@
     private static final Logger s_logger = Logger.getLogger(CephSnapshotStrategy.class);
 
     @Override
-    public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
+    public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
         long volumeId = snapshot.getVolumeId();
         VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
         boolean baseVolumeExists = volumeVO.getRemoved() == null;
@@ -56,7 +55,7 @@
             return StrategyPriority.CANT_HANDLE;
         }
 
-        if (!isSnapshotStoredOnRbdStoragePool(snapshot)) {
+        if (!isSnapshotStoredOnRbdStoragePoolAndOperationForSameZone(snapshot, zoneId)) {
             return StrategyPriority.CANT_HANDLE;
         }
 
@@ -81,12 +80,18 @@
         return true;
     }
 
-    protected boolean isSnapshotStoredOnRbdStoragePool(Snapshot snapshot) {
-        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+    protected boolean isSnapshotStoredOnRbdStoragePoolAndOperationForSameZone(Snapshot snapshot, Long zoneId) {
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
         if (snapshotStore == null) {
             return false;
         }
         StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapshotStore.getDataStoreId());
-        return storagePoolVO != null && storagePoolVO.getPoolType() == StoragePoolType.RBD;
+        if (storagePoolVO == null) {
+            return false;
+        }
+        if (zoneId != null && !zoneId.equals(storagePoolVO.getDataCenterId())) {
+            return false;
+        }
+        return storagePoolVO.getPoolType() == StoragePoolType.RBD;
     }
 }
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java
index 85e0a02..f1f073d 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java
@@ -16,18 +16,13 @@
 // under the License.
 package org.apache.cloudstack.storage.snapshot;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
 
 import javax.inject.Inject;
 
-import com.cloud.storage.VolumeDetailVO;
-import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.BooleanUtils;
-import org.apache.log4j.Logger;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
@@ -41,11 +36,15 @@
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.jobs.AsyncJob;
 import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
-import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
 import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.event.EventTypes;
@@ -57,28 +56,28 @@
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StoragePoolStatus;
 import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotZoneDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.storage.snapshot.SnapshotManager;
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.db.DB;
 import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallbackNoReturn;
 import com.cloud.utils.db.TransactionStatus;
-import com.cloud.storage.snapshot.SnapshotManager;
-import com.cloud.storage.Storage.ImageFormat;
-import com.cloud.storage.Storage.StoragePoolType;
-import com.cloud.utils.NumbersUtil;
-import com.cloud.utils.db.DB;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
 
 public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
-    private static final String SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER = "secondary storage";
-    private static final String PRIMARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER = "primary storage";
 
     private static final Logger s_logger = Logger.getLogger(DefaultSnapshotStrategy.class);
 
@@ -100,6 +99,18 @@
     private SnapshotDetailsDao _snapshotDetailsDao;
     @Inject
     VolumeDetailsDao _volumeDetailsDaoImpl;
+    @Inject
+    SnapshotZoneDao snapshotZoneDao;
+
+    public SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
+        List<SnapshotDataStoreVO> snaps = snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
+        for (SnapshotDataStoreVO ref : snaps) {
+            if (zoneId == dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
+                return ref;
+            }
+        }
+        return null;
+    }
 
     @Override
     public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
@@ -107,7 +118,8 @@
 
         if (parentSnapshot != null && snapshot.getPath().equalsIgnoreCase(parentSnapshot.getPath())) {
             // don't need to backup this snapshot
-            SnapshotDataStoreVO parentSnapshotOnBackupStore = snapshotStoreDao.findBySnapshot(parentSnapshot.getId(), DataStoreRole.Image);
+            SnapshotDataStoreVO parentSnapshotOnBackupStore = getSnapshotImageStoreRef(parentSnapshot.getId(),
+                    dataStoreMgr.getStoreZoneId(parentSnapshot.getDataStore().getId(), parentSnapshot.getDataStore().getRole()));
             if (parentSnapshotOnBackupStore != null && parentSnapshotOnBackupStore.getState() == State.Ready) {
                 DataStore store = dataStoreMgr.getDataStore(parentSnapshotOnBackupStore.getDataStoreId(), parentSnapshotOnBackupStore.getRole());
 
@@ -159,7 +171,7 @@
                         if (prevBackupId == 0) {
                             break;
                         }
-                        parentSnapshotOnBackupStore = snapshotStoreDao.findBySnapshot(prevBackupId, DataStoreRole.Image);
+                        parentSnapshotOnBackupStore = getSnapshotImageStoreRef(prevBackupId, volume.getDataCenterId());
                         if (parentSnapshotOnBackupStore == null) {
                             break;
                         }
@@ -171,7 +183,7 @@
                         fullBackup = false;
                     }
                 } else if (oldestSnapshotOnPrimary.getId() != parentSnapshotOnPrimaryStore.getId()){
-                    // if there is an snapshot entry for previousPool(primary storage) of migrated volume, delete it becasue CS created one more snapshot entry for current pool
+                    // if there is an snapshot entry for previousPool(primary storage) of migrated volume, delete it because CS created one more snapshot entry for current pool
                     snapshotStoreDao.remove(oldestSnapshotOnPrimary.getId());
                 }
             }
@@ -181,20 +193,19 @@
         return snapshotSvr.backupSnapshot(snapshot);
     }
 
-    private final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
-
-    protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storage) {
+    protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storageToString) {
         DataTO snapshotTo = snapshot.getTO();
         s_logger.debug(String.format("Deleting %s chain of snapshots.", snapshotTo));
 
         boolean result = false;
         boolean resultIsSet = false;
+        final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.BackedUp, Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
         try {
             while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState())) {
                 SnapshotInfo child = snapshot.getChild();
 
                 if (child != null) {
-                    s_logger.debug(String.format("Snapshot [%s] has child [%s], not deleting it on the storage [%s]", snapshotTo, child.getTO(), storage));
+                    s_logger.debug(String.format("Snapshot [%s] has child [%s], not deleting it on the storage [%s]", snapshotTo, child.getTO(), storageToString));
                     break;
                 }
 
@@ -207,8 +218,6 @@
                         //NOTE: if both snapshots share the same path, it's for xenserver's empty delta snapshot. We can't delete the snapshot on the backend, as parent snapshot still reference to it
                         //Instead, mark it as destroyed in the db.
                         s_logger.debug(String.format("Snapshot [%s] is an empty delta snapshot; therefore, we will only mark it as destroyed in the database.", snapshotTo));
-                        snapshot.processEvent(Event.DestroyRequested);
-                        snapshot.processEvent(Event.OperationSuccessed);
                         deleted = true;
                         if (!resultIsSet) {
                             result = true;
@@ -233,22 +242,25 @@
                             resultIsSet = true;
                         }
                     } catch (Exception e) {
-                        s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storage, e.getMessage()), e);
+                        s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storageToString, e.getMessage()), e);
                     }
                 }
 
                 snapshot = parent;
             }
         } catch (Exception e) {
-            s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storage, e.getMessage()), e);
+            s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storageToString, e.getMessage()), e);
         }
         return result;
     }
 
     @Override
-    public boolean deleteSnapshot(Long snapshotId) {
+    public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
         SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
 
+        if (zoneId != null && List.of(Snapshot.State.Allocated, Snapshot.State.CreatedOnPrimary).contains(snapshotVO.getState())) {
+            throw new InvalidParameterValueException(String.format("Snapshot in %s can not be deleted for a zone", snapshotVO.getState()));
+        }
         if (snapshotVO.getState() == Snapshot.State.Allocated) {
             snapshotDao.remove(snapshotId);
             return true;
@@ -260,10 +272,21 @@
 
         if (Snapshot.State.Error.equals(snapshotVO.getState())) {
             List<SnapshotDataStoreVO> storeRefs = snapshotStoreDao.findBySnapshotId(snapshotId);
+            List<Long> deletedRefs = new ArrayList<>();
             for (SnapshotDataStoreVO ref : storeRefs) {
-                snapshotStoreDao.expunge(ref.getId());
+                boolean refZoneIdMatch = false;
+                if (zoneId != null) {
+                    Long refZoneId = dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole());
+                    refZoneIdMatch = zoneId.equals(refZoneId);
+                }
+                if (zoneId == null || refZoneIdMatch) {
+                    snapshotStoreDao.expunge(ref.getId());
+                    deletedRefs.add(ref.getId());
+                }
             }
-            snapshotDao.remove(snapshotId);
+            if (deletedRefs.size() == storeRefs.size()) {
+                snapshotDao.remove(snapshotId);
+            }
             return true;
         }
 
@@ -278,20 +301,26 @@
             throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
         }
 
-        return destroySnapshotEntriesAndFiles(snapshotVO);
+        return destroySnapshotEntriesAndFiles(snapshotVO, zoneId);
     }
 
     /**
      * Destroys the snapshot entries and files on both primary and secondary storage (if it exists).
      * @return true if destroy successfully, else false.
      */
-    protected boolean destroySnapshotEntriesAndFiles(SnapshotVO snapshotVo) {
-        if (!deleteSnapshotInfos(snapshotVo)) {
+    protected boolean destroySnapshotEntriesAndFiles(SnapshotVO snapshotVo, Long zoneId) {
+        if (!deleteSnapshotInfos(snapshotVo, zoneId)) {
             return false;
         }
-
+        if (zoneId != null) {
+            snapshotZoneDao.removeSnapshotFromZone(snapshotVo.getId(), zoneId);
+        } else {
+            snapshotZoneDao.removeSnapshotFromZones(snapshotVo.getId());
+        }
+        if (CollectionUtils.isNotEmpty(retrieveSnapshotEntries(snapshotVo.getId(), null))) {
+            return true;
+        }
         updateSnapshotToDestroyed(snapshotVo);
-
         return true;
     }
 
@@ -303,12 +332,12 @@
         snapshotDao.update(snapshotVo.getId(), snapshotVo);
     }
 
-    protected boolean deleteSnapshotInfos(SnapshotVO snapshotVo) {
-        Map<String, SnapshotInfo> snapshotInfos = retrieveSnapshotEntries(snapshotVo.getId());
+    protected boolean deleteSnapshotInfos(SnapshotVO snapshotVo, Long zoneId) {
+        List<SnapshotInfo> snapshotInfos = retrieveSnapshotEntries(snapshotVo.getId(), zoneId);
 
         boolean result = false;
-        for (var infoEntry : snapshotInfos.entrySet()) {
-            if (BooleanUtils.toBooleanDefaultIfNull(deleteSnapshotInfo(infoEntry.getValue(), infoEntry.getKey(), snapshotVo), false)) {
+        for (var snapshotInfo : snapshotInfos) {
+            if (BooleanUtils.toBooleanDefaultIfNull(deleteSnapshotInfo(snapshotInfo, snapshotVo), false)) {
                 result = true;
             }
         }
@@ -320,50 +349,53 @@
      * Destroys the snapshot entry and file.
      * @return true if destroy successfully, else false.
      */
-    protected Boolean deleteSnapshotInfo(SnapshotInfo snapshotInfo, String storage, SnapshotVO snapshotVo) {
-        if (snapshotInfo == null) {
-            s_logger.debug(String.format("Could not find %s entry on %s. Skipping deletion on %s.", snapshotVo, storage, storage));
-            return SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER.equals(storage) ? null : true;
-        }
-
+    protected Boolean deleteSnapshotInfo(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo) {
         DataStore dataStore = snapshotInfo.getDataStore();
-        String storageToString = String.format("%s {uuid: \"%s\", name: \"%s\"}", storage, dataStore.getUuid(), dataStore.getName());
-
+        String storageToString = String.format("%s {uuid: \"%s\", name: \"%s\"}", dataStore.getRole().name(), dataStore.getUuid(), dataStore.getName());
+        List<SnapshotDataStoreVO> snapshotStoreRefs = snapshotStoreDao.findBySnapshotId(snapshotVo.getId());
+        boolean isLastSnapshotRef = CollectionUtils.isEmpty(snapshotStoreRefs) || snapshotStoreRefs.size() == 1;
         try {
             SnapshotObject snapshotObject = castSnapshotInfoToSnapshotObject(snapshotInfo);
-            snapshotObject.processEvent(Snapshot.Event.DestroyRequested);
-
-            if (SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER.equals(storage)) {
-
+            if (isLastSnapshotRef) {
+                snapshotObject.processEvent(Snapshot.Event.DestroyRequested);
+            }
+            if (!DataStoreRole.Primary.equals(dataStore.getRole())) {
                 verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObject);
-
                 if (deleteSnapshotChain(snapshotInfo, storageToString)) {
                     s_logger.debug(String.format("%s was deleted on %s. We will mark the snapshot as destroyed.", snapshotVo, storageToString));
                 } else {
                     s_logger.debug(String.format("%s was not deleted on %s; however, we will mark the snapshot as destroyed for future garbage collecting.", snapshotVo,
                         storageToString));
                 }
-
-                snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
+                snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotVo.getId(), dataStore.getId(), dataStore.getRole(), false);
+                if (isLastSnapshotRef) {
+                    snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
+                }
                 return true;
-            } else if (deleteSnapshotInPrimaryStorage(snapshotInfo, snapshotVo, storageToString, snapshotObject)) {
+            } else if (deleteSnapshotInPrimaryStorage(snapshotInfo, snapshotVo, storageToString, snapshotObject, isLastSnapshotRef)) {
+                snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotVo.getId(), dataStore.getId(), dataStore.getRole(), false);
                 return true;
             }
-
             s_logger.debug(String.format("Failed to delete %s on %s.", snapshotVo, storageToString));
-            snapshotObject.processEvent(Snapshot.Event.OperationFailed);
+            if (isLastSnapshotRef) {
+                snapshotObject.processEvent(Snapshot.Event.OperationFailed);
+            }
         } catch (NoTransitionException ex) {
             s_logger.warn(String.format("Failed to delete %s on %s due to %s.", snapshotVo, storageToString, ex.getMessage()), ex);
         }
-
         return false;
     }
 
-    protected boolean deleteSnapshotInPrimaryStorage(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo, String storageToString, SnapshotObject snapshotObject) throws NoTransitionException {
+    protected boolean deleteSnapshotInPrimaryStorage(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo,
+         String storageToString, SnapshotObject snapshotObject, boolean isLastSnapshotRef) throws NoTransitionException {
         try {
             if (snapshotSvr.deleteSnapshot(snapshotInfo)) {
-                snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
-                s_logger.debug(String.format("%s was deleted on %s. We will mark the snapshot as destroyed.", snapshotVo, storageToString));
+                String msg = String.format("%s was deleted on %s.", snapshotVo, storageToString);
+                if (isLastSnapshotRef) {
+                    msg = String.format("%s We will mark the snapshot as destroyed.", msg);
+                    snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
+                }
+                s_logger.debug(msg);
                 return true;
             }
         } catch (CloudRuntimeException ex) {
@@ -396,18 +428,15 @@
     /**
      * Retrieves the snapshot infos on primary and secondary storage.
      * @param snapshotId The snapshot to retrieve the infos.
-     * @return A map of snapshot infos.
+     * @return A list of snapshot infos.
      */
-    protected Map<String, SnapshotInfo> retrieveSnapshotEntries(long snapshotId) {
-        Map<String, SnapshotInfo> snapshotInfos = new LinkedHashMap<>();
-        snapshotInfos.put(SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER, snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Image, false));
-        snapshotInfos.put(PRIMARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER, snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary, false));
-        return snapshotInfos;
+    protected List<SnapshotInfo> retrieveSnapshotEntries(long snapshotId, Long zoneId) {
+        return snapshotDataFactory.getSnapshots(snapshotId, zoneId);
     }
 
     @Override
     public boolean revertSnapshot(SnapshotInfo snapshot) {
-        if (canHandle(snapshot, SnapshotOperation.REVERT) == StrategyPriority.CANT_HANDLE) {
+        if (canHandle(snapshot, null, SnapshotOperation.REVERT) == StrategyPriority.CANT_HANDLE) {
             throw new CloudRuntimeException("Reverting not supported. Create a template or volume based on the snapshot instead.");
         }
 
@@ -542,19 +571,31 @@
     }
 
     @Override
-    public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
+    public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
         if (SnapshotOperation.REVERT.equals(op)) {
             long volumeId = snapshot.getVolumeId();
             VolumeVO volumeVO = volumeDao.findById(volumeId);
 
-            if (volumeVO != null && ImageFormat.QCOW2.equals(volumeVO.getFormat())) {
+            if (isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO)) {
                 return StrategyPriority.DEFAULT;
             }
 
             return StrategyPriority.CANT_HANDLE;
         }
-
+        if (zoneId != null && SnapshotOperation.DELETE.equals(op)) {
+            s_logger.debug(String.format("canHandle for zone ID: %d, operation: %s - %s", zoneId, op, StrategyPriority.DEFAULT));
+        }
         return StrategyPriority.DEFAULT;
     }
 
+    protected boolean isSnapshotStoredOnSameZoneStoreForQCOW2Volume(Snapshot snapshot, VolumeVO volumeVO) {
+        if (volumeVO == null || !ImageFormat.QCOW2.equals(volumeVO.getFormat())) {
+            return false;
+        }
+        List<SnapshotDataStoreVO> snapshotStores = snapshotStoreDao.listBySnapshotIdAndState(snapshot.getId(), State.Ready);
+        return CollectionUtils.isNotEmpty(snapshotStores) &&
+                snapshotStores.stream().anyMatch(s -> Objects.equals(
+                        dataStoreMgr.getStoreZoneId(s.getDataStoreId(), s.getRole()), volumeVO.getDataCenterId()));
+    }
+
 }
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java
index dfe4750..3dee4f4 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java
@@ -45,7 +45,7 @@
     private static final Logger LOG = Logger.getLogger(ScaleIOSnapshotStrategy.class);
 
     @Override
-    public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
+    public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
         long volumeId = snapshot.getVolumeId();
         VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
         boolean baseVolumeExists = volumeVO.getRemoved() == null;
@@ -53,7 +53,7 @@
             return StrategyPriority.CANT_HANDLE;
         }
 
-        if (!isSnapshotStoredOnScaleIOStoragePool(snapshot)) {
+        if (!isSnapshotStoredOnScaleIOStoragePoolAndOperationForSameZone(snapshot, zoneId)) {
             return StrategyPriority.CANT_HANDLE;
         }
 
@@ -82,12 +82,18 @@
         return true;
     }
 
-    protected boolean isSnapshotStoredOnScaleIOStoragePool(Snapshot snapshot) {
-        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+    protected boolean isSnapshotStoredOnScaleIOStoragePoolAndOperationForSameZone(Snapshot snapshot, Long zoneId) {
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
         if (snapshotStore == null) {
             return false;
         }
         StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapshotStore.getDataStoreId());
-        return storagePoolVO != null && storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex;
+        if (storagePoolVO == null) {
+            return false;
+        }
+        if (zoneId != null && !zoneId.equals(storagePoolVO.getDataCenterId())) {
+            return false;
+        }
+        return storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex;
     }
 }
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java
index d894d79..fc5e61e 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java
@@ -64,7 +64,7 @@
     }
 
     @Override
-    public List<SnapshotInfo> getSnapshots(long volumeId, DataStoreRole role) {
+    public List<SnapshotInfo> getSnapshotsForVolumeAndStoreRole(long volumeId, DataStoreRole role) {
         List<SnapshotDataStoreVO> allSnapshotsFromVolumeAndDataStore = snapshotStoreDao.listAllByVolumeAndDataStore(volumeId, role);
         if (CollectionUtils.isEmpty(allSnapshotsFromVolumeAndDataStore)) {
             return new ArrayList<>();
@@ -84,23 +84,90 @@
     }
 
     @Override
-    public SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role) {
-        return getSnapshot(snapshotId, role, true);
+    public List<SnapshotInfo> getSnapshots(long snapshotId, Long zoneId) {
+        SnapshotVO snapshot = snapshotDao.findById(snapshotId);
+        if (snapshot == null) { //snapshot may have been removed;
+            return new ArrayList<>();
+        }
+        List<SnapshotDataStoreVO> allSnapshotsAndDataStore = snapshotStoreDao.findBySnapshotId(snapshotId);
+        if (CollectionUtils.isEmpty(allSnapshotsAndDataStore)) {
+            return new ArrayList<>();
+        }
+        List<SnapshotInfo> infos = new ArrayList<>();
+        for (SnapshotDataStoreVO snapshotDataStoreVO : allSnapshotsAndDataStore) {
+            Long entryZoneId = storeMgr.getStoreZoneId(snapshotDataStoreVO.getDataStoreId(), snapshotDataStoreVO.getRole());
+            if (zoneId != null && !zoneId.equals(entryZoneId)) {
+                continue;
+            }
+            DataStore store = storeMgr.getDataStore(snapshotDataStoreVO.getDataStoreId(), snapshotDataStoreVO.getRole());
+            SnapshotObject info = SnapshotObject.getSnapshotObject(snapshot, store);
+
+            infos.add(info);
+        }
+        return infos;
     }
 
+
+
     @Override
-    public SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, boolean retrieveAnySnapshotFromVolume) {
+    public SnapshotInfo getSnapshot(long snapshotId, long storeId, DataStoreRole role) {
         SnapshotVO snapshot = snapshotDao.findById(snapshotId);
         if (snapshot == null) {
             return null;
         }
-        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshotId, role);
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findByStoreSnapshot(role, storeId, snapshotId);
+        if (snapshotStore == null) {
+            return null;
+        }
+        DataStore store = storeMgr.getDataStore(snapshotStore.getDataStoreId(), role);
+        return SnapshotObject.getSnapshotObject(snapshot, store);
+    }
+
+    @Override
+    public SnapshotInfo getSnapshotWithRoleAndZone(long snapshotId, DataStoreRole role, long zoneId) {
+        return getSnapshot(snapshotId, role, zoneId, true);
+    }
+
+    @Override
+    public SnapshotInfo getSnapshotOnPrimaryStore(long snapshotId) {
+        SnapshotVO snapshot = snapshotDao.findById(snapshotId);
+        if (snapshot == null) {
+            return null;
+        }
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotId, DataStoreRole.Primary);
+        if (snapshotStore == null) {
+            return null;
+        }
+        DataStore store = storeMgr.getDataStore(snapshotStore.getDataStoreId(), snapshotStore.getRole());
+        SnapshotObject so = SnapshotObject.getSnapshotObject(snapshot, store);
+        return so;
+    }
+
+    @Override
+    public SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, long zoneId, boolean retrieveAnySnapshotFromVolume) {
+        SnapshotVO snapshot = snapshotDao.findById(snapshotId);
+        if (snapshot == null) {
+            return null;
+        }
+        List<SnapshotDataStoreVO> snapshotStores = snapshotStoreDao.listReadyBySnapshot(snapshotId, role);
+        SnapshotDataStoreVO snapshotStore = null;
+        for (SnapshotDataStoreVO ref : snapshotStores) {
+            if (zoneId == storeMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
+                snapshotStore = ref;
+                break;
+            }
+        }
         if (snapshotStore == null) {
             if (!retrieveAnySnapshotFromVolume) {
                 return null;
             }
-
-            snapshotStore = snapshotStoreDao.findByVolume(snapshotId, snapshot.getVolumeId(), role);
+            snapshotStores = snapshotStoreDao.findByVolume(snapshotId, snapshot.getVolumeId(), role);
+            for (SnapshotDataStoreVO ref : snapshotStores) {
+                if (zoneId == storeMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())); {
+                    snapshotStore = ref;
+                    break;
+                }
+            }
             if (snapshotStore == null) {
                 return null;
             }
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java
index 2e45bee..6cf68f6 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java
@@ -26,6 +26,7 @@
 
 import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
@@ -64,6 +65,7 @@
     private DataStore store;
     private Object payload;
     private Boolean fullBackup;
+    private String url;
     @Inject
     protected SnapshotDao snapshotDao;
     @Inject
@@ -80,8 +82,12 @@
     SnapshotDataStoreDao snapshotStoreDao;
     @Inject
     StorageStrategyFactory storageStrategyFactory;
+    @Inject
+    DataStoreManager dataStoreManager;
     private String installPath; // temporarily set installPath before passing to resource for entries with empty installPath for object store migration case
 
+    private Long zoneId = null;
+
     public SnapshotObject() {
 
     }
@@ -142,7 +148,7 @@
         List<SnapshotInfo> children = new ArrayList<>();
         if (vos != null) {
             for (SnapshotDataStoreVO vo : vos) {
-                SnapshotInfo info = snapshotFactory.getSnapshot(vo.getSnapshotId(), DataStoreRole.Image);
+                SnapshotInfo info = snapshotFactory.getSnapshot(vo.getSnapshotId(), vo.getDataStoreId(), DataStoreRole.Image);
                 if (info != null) {
                     children.add(info);
                 }
@@ -164,7 +170,7 @@
     @Override
     public long getPhysicalSize() {
         long physicalSize = 0;
-        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), snapshot.getId());
         if (snapshotStore != null) {
             physicalSize = snapshotStore.getPhysicalSize();
         }
@@ -194,9 +200,16 @@
 
     @Override
     public String getUri() {
+        if (url != null) {
+            return url;
+        }
         return snapshot.getUuid();
     }
 
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
     @Override
     public DataStore getDataStore() {
         return store;
@@ -309,7 +322,10 @@
 
     @Override
     public Long getDataCenterId() {
-        return snapshot.getDataCenterId();
+        if (zoneId == null) {
+            zoneId = dataStoreManager.getStoreZoneId(store.getId(), store.getRole());
+        }
+        return zoneId;
     }
 
     public void processEvent(Snapshot.Event event) throws NoTransitionException {
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java
index 4d106f5..9c7ee98 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java
@@ -25,8 +25,11 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
+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.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
@@ -42,16 +45,27 @@
 import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
 import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 import org.apache.cloudstack.framework.async.AsyncRpcContext;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.jobs.AsyncJob;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
 import org.apache.cloudstack.storage.command.CommandResult;
 import org.apache.cloudstack.storage.command.CopyCmdAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
+import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper;
+import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.log4j.Logger;
 
-import com.cloud.storage.CreateSnapshotPayload;
+import com.cloud.agent.api.Answer;
+import com.cloud.configuration.Config;
+import com.cloud.dc.DataCenter;
 import com.cloud.event.EventTypes;
 import com.cloud.event.UsageEventUtils;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.storage.CreateSnapshotPayload;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
@@ -82,6 +96,13 @@
     private SnapshotDetailsDao _snapshotDetailsDao;
     @Inject
     VolumeDataFactory volFactory;
+    @Inject
+    EndPointSelector epSelector;
+    @Inject
+    ConfigurationDao _configDao;
+
+    @Inject
+    private HeuristicRuleHelper heuristicRuleHelper;
 
     static private class CreateSnapshotContext<T> extends AsyncRpcContext<T> {
         final SnapshotInfo snapshot;
@@ -120,6 +141,20 @@
 
     }
 
+    static private class PrepareCopySnapshotContext<T> extends AsyncRpcContext<T> {
+        final SnapshotInfo snapshot;
+        final String copyUrlBase;
+        final AsyncCallFuture<CreateCmdResult> future;
+
+        public PrepareCopySnapshotContext(AsyncCompletionCallback<T> callback, SnapshotInfo snapshot, String copyUrlBase, AsyncCallFuture<CreateCmdResult> future) {
+            super(callback);
+            this.snapshot = snapshot;
+            this.copyUrlBase = copyUrlBase;
+            this.future = future;
+        }
+
+    }
+
     static private class RevertSnapshotContext<T> extends AsyncRpcContext<T> {
         final SnapshotInfo snapshot;
         final AsyncCallFuture<SnapshotResult> future;
@@ -132,6 +167,30 @@
 
     }
 
+    private String generateCopyUrlBase(String hostname, String dir) {
+        String scheme = "http";
+        boolean _sslCopy = false;
+        String sslCfg = _configDao.getValue(Config.SecStorageEncryptCopy.toString());
+        String _ssvmUrlDomain = _configDao.getValue("secstorage.ssl.cert.domain");
+        if (sslCfg != null) {
+            _sslCopy = Boolean.parseBoolean(sslCfg);
+        }
+        if(_sslCopy && (_ssvmUrlDomain == null || _ssvmUrlDomain.isEmpty())){
+            s_logger.warn("Empty secondary storage url domain, ignoring SSL");
+            _sslCopy = false;
+        }
+        if (_sslCopy) {
+            if(_ssvmUrlDomain.startsWith("*")) {
+                hostname = hostname.replace(".", "-");
+                hostname = hostname + _ssvmUrlDomain.substring(1);
+            } else {
+                hostname = _ssvmUrlDomain;
+            }
+            scheme = "https";
+        }
+        return scheme + "://" + hostname + "/copy/SecStorage/" + dir;
+    }
+
     protected Void createSnapshotAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CreateCmdResult> callback, CreateSnapshotContext<CreateCmdResult> context) {
         CreateCmdResult result = callback.getResult();
         SnapshotObject snapshot = (SnapshotObject)context.snapshot;
@@ -243,7 +302,7 @@
             fullSnapshot = snapshotFullBackup;
         }
         if (fullSnapshot) {
-            return dataStoreMgr.getImageStoreWithFreeCapacity(snapshot.getDataCenterId());
+            return getImageStoreForSnapshot(snapshot.getDataCenterId(), snapshot);
         } else {
             SnapshotInfo parentSnapshot = snapshot.getParent();
             // Note that DataStore information in parentSnapshot is for primary
@@ -251,15 +310,34 @@
             // find the image store where the parent snapshot backup is located
             SnapshotDataStoreVO parentSnapshotOnBackupStore = null;
             if (parentSnapshot != null) {
-                parentSnapshotOnBackupStore = _snapshotStoreDao.findBySnapshot(parentSnapshot.getId(), DataStoreRole.Image);
+                List<SnapshotDataStoreVO> snaps = _snapshotStoreDao.listReadyBySnapshot(snapshot.getId(), DataStoreRole.Image);
+                for (SnapshotDataStoreVO ref : snaps) {
+                    if (snapshot.getDataCenterId() != null && snapshot.getDataCenterId().equals(dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole()))) {
+                        parentSnapshotOnBackupStore = ref;
+                        break;
+                    }
+                }
             }
             if (parentSnapshotOnBackupStore == null) {
-                return dataStoreMgr.getImageStoreWithFreeCapacity(snapshot.getDataCenterId());
+                return getImageStoreForSnapshot(snapshot.getDataCenterId(), snapshot);
             }
             return dataStoreMgr.getDataStore(parentSnapshotOnBackupStore.getDataStoreId(), parentSnapshotOnBackupStore.getRole());
         }
     }
 
+    /**
+     * Verify if the data center has heuristic rules for allocating snapshots; if there is then returns the {@link DataStore} returned by the JS script.
+     * Otherwise, returns {@link DataStore}s with free capacity.
+     */
+    protected DataStore getImageStoreForSnapshot(Long dataCenterId, SnapshotInfo snapshot) {
+        DataStore imageStore = heuristicRuleHelper.getImageStoreIfThereIsHeuristicRule(dataCenterId, HeuristicType.SNAPSHOT, snapshot);
+
+        if (imageStore == null) {
+            imageStore = dataStoreMgr.getImageStoreWithFreeCapacity(snapshot.getDataCenterId());
+        }
+        return imageStore;
+    }
+
     @Override
     public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
         SnapshotObject snapObj = (SnapshotObject)snapshot;
@@ -356,6 +434,49 @@
         return null;
     }
 
+    protected Void copySnapshotZoneAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CreateCmdResult> callback, CopySnapshotContext<CommandResult> context) {
+        CreateCmdResult result = callback.getResult();
+        SnapshotInfo destSnapshot = context.destSnapshot;
+        AsyncCallFuture<SnapshotResult> future = context.future;
+        SnapshotResult snapResult = new SnapshotResult(destSnapshot, result.getAnswer());
+        if (result.isFailed()) {
+            snapResult.setResult(result.getResult());
+            destSnapshot.processEvent(Event.OperationFailed);
+            future.complete(snapResult);
+            return null;
+        }
+        try {
+            Answer answer = result.getAnswer();
+            destSnapshot.processEvent(Event.OperationSuccessed);
+            snapResult = new SnapshotResult(_snapshotFactory.getSnapshot(destSnapshot.getId(), destSnapshot.getDataStore()), answer);
+            future.complete(snapResult);
+        } catch (Exception e) {
+            s_logger.debug("Failed to update snapshot state", e);
+            snapResult.setResult(e.toString());
+            future.complete(snapResult);
+        }
+        return null;
+    }
+
+    protected Void prepareCopySnapshotZoneAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, QuerySnapshotZoneCopyAnswer> callback, PrepareCopySnapshotContext<CommandResult> context) {
+        QuerySnapshotZoneCopyAnswer answer = callback.getResult();
+        if (answer == null || !answer.getResult()) {
+            CreateCmdResult result = new CreateCmdResult(null, answer);
+            result.setResult(answer != null ? answer.getDetails() : "Unsupported answer");
+            context.future.complete(result);
+            return null;
+        }
+        List<String> files = answer.getFiles();
+        final String copyUrlBase = context.copyUrlBase;
+        StringBuilder url = new StringBuilder();
+        for (String file : files) {
+            url.append(copyUrlBase).append("/").append(file).append("\n");
+        }
+        CreateCmdResult result = new CreateCmdResult(url.toString().trim(), answer);
+        context.future.complete(result);
+        return null;
+    }
+
     protected Void deleteSnapshotCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CommandResult> callback, DeleteSnapshotContext<CommandResult> context) {
 
         CommandResult result = callback.getResult();
@@ -364,7 +485,7 @@
         SnapshotResult res = null;
         try {
             if (result.isFailed()) {
-                s_logger.debug("delete snapshot failed" + result.getResult());
+                s_logger.debug(String.format("Failed to delete snapshot [%s] due to: [%s].", snapshot.getUuid(), result.getResult()));
                 snapshot.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
                 res = new SnapshotResult(context.snapshot, null);
                 res.setResult(result.getResult());
@@ -373,7 +494,8 @@
                 res = new SnapshotResult(context.snapshot, null);
             }
         } catch (Exception e) {
-            s_logger.debug("Failed to in deleteSnapshotCallback", e);
+            s_logger.error(String.format("An exception occurred while processing an event in delete snapshot callback from snapshot [%s].", snapshot.getUuid()));
+            s_logger.debug(String.format("Exception while processing an event in delete snapshot callback from snapshot [%s].", snapshot.getUuid()), e);
             res.setResult(e.toString());
         }
         future.complete(res);
@@ -418,21 +540,20 @@
             if (result.isFailed()) {
                 throw new CloudRuntimeException(result.getResult());
             }
+            s_logger.debug(String.format("Successfully deleted snapshot [%s] with ID [%s].", snapInfo.getName(), snapInfo.getUuid()));
             return true;
-        } catch (InterruptedException e) {
-            s_logger.debug("delete snapshot is failed: " + e.toString());
-        } catch (ExecutionException e) {
-            s_logger.debug("delete snapshot is failed: " + e.toString());
+        } catch (InterruptedException | ExecutionException e) {
+            s_logger.error(String.format("Failed to delete snapshot [%s] due to: [%s].", snapInfo.getUuid(), e.getMessage()));
+            s_logger.debug(String.format("Failed to delete snapshot [%s].", snapInfo.getUuid()), e);
         }
 
         return false;
-
     }
 
     @Override
     public boolean revertSnapshot(SnapshotInfo snapshot) {
         PrimaryDataStore store = null;
-        SnapshotInfo snapshotOnPrimaryStore = _snapshotFactory.getSnapshot(snapshot.getId(), DataStoreRole.Primary);
+        SnapshotInfo snapshotOnPrimaryStore = _snapshotFactory.getSnapshotOnPrimaryStore(snapshot.getId());
         if (snapshotOnPrimaryStore == null) {
             s_logger.warn("Cannot find an entry for snapshot " + snapshot.getId() + " on primary storage pools, searching with volume's primary storage pool");
             VolumeInfo volumeInfo = volFactory.getVolume(snapshot.getVolumeId(), DataStoreRole.Primary);
@@ -608,4 +729,56 @@
 
     }
 
+    @Override
+    public AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore store) throws ResourceUnavailableException {
+        SnapshotObject snapshotForCopy = (SnapshotObject)_snapshotFactory.getSnapshot(snapshot, store);
+        snapshotForCopy.setUrl(copyUrl);
+
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Mark snapshot_store_ref entry as Creating");
+        }
+        AsyncCallFuture<SnapshotResult> future = new AsyncCallFuture<SnapshotResult>();
+        DataObject snapshotOnStore = store.create(snapshotForCopy);
+        ((SnapshotObject)snapshotOnStore).setUrl(copyUrl);
+        snapshotOnStore.processEvent(Event.CreateOnlyRequested);
+
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Invoke datastore driver createAsync to create snapshot on destination store");
+        }
+        try {
+            CopySnapshotContext<CommandResult> context = new CopySnapshotContext<>(null, (SnapshotObject)snapshotOnStore, snapshotForCopy, future);
+            AsyncCallbackDispatcher<SnapshotServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
+            caller.setCallback(caller.getTarget().copySnapshotZoneAsyncCallback(null, null)).setContext(context);
+            store.getDriver().createAsync(store, snapshotOnStore, caller);
+        } catch (CloudRuntimeException ex) {
+            // clean up already persisted snapshot_store_ref entry
+            SnapshotDataStoreVO snapshotStoreVO = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), snapshot.getId());
+            if (snapshotStoreVO != null) {
+                snapshotForCopy.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
+            }
+            SnapshotResult res = new SnapshotResult((SnapshotObject)snapshotOnStore, null);
+            res.setResult(ex.getMessage());
+            future.complete(res);
+        }
+        return future;
+    }
+
+    @Override
+    public AsyncCallFuture<CreateCmdResult> queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException {
+        AsyncCallFuture<CreateCmdResult> future = new AsyncCallFuture<>();
+        EndPoint ep = epSelector.select(snapshot);
+        if (ep == null) {
+            s_logger.error(String.format("Failed to find endpoint for generating copy URL for snapshot %d with store %d", snapshot.getId(), snapshot.getDataStore().getId()));
+            throw new ResourceUnavailableException("No secondary VM in running state in source snapshot zone", DataCenter.class, snapshot.getDataCenterId());
+        }
+        DataStore store = snapshot.getDataStore();
+        String copyUrlBase = generateCopyUrlBase(ep.getPublicAddr(), ((ImageStoreEntity)store).getMountPoint());
+        PrepareCopySnapshotContext<CreateCmdResult> context = new PrepareCopySnapshotContext<>(null, snapshot, copyUrlBase, future);
+        AsyncCallbackDispatcher<SnapshotServiceImpl, QuerySnapshotZoneCopyAnswer> caller = AsyncCallbackDispatcher.create(this);
+        caller.setCallback(caller.getTarget().prepareCopySnapshotZoneAsyncCallback(null, null)).setContext(context);
+        caller.setContext(context);
+        QuerySnapshotZoneCopyCommand cmd = new QuerySnapshotZoneCopyCommand((SnapshotObjectTO)(snapshot.getTO()));
+        ep.sendMessageAsync(cmd, caller);
+        return future;
+    }
 }
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
index 6401f8a..dabb8d1 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java
@@ -44,6 +44,7 @@
 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.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -150,7 +151,7 @@
     }
 
     @Override
-    public boolean deleteSnapshot(Long snapshotId) {
+    public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
         Preconditions.checkArgument(snapshotId != null, "'snapshotId' cannot be 'null'.");
 
         SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
@@ -181,7 +182,7 @@
      */
     @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_OFF_PRIMARY, eventDescription = "deleting snapshot", async = true)
     private boolean cleanupSnapshotOnPrimaryStore(long snapshotId) {
-        SnapshotObject snapshotObj = (SnapshotObject)snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
+        SnapshotObject snapshotObj = (SnapshotObject)snapshotDataFactory.getSnapshotOnPrimaryStore(snapshotId);
 
         if (snapshotObj == null) {
             s_logger.debug("Can't find snapshot; deleting it in DB");
@@ -293,7 +294,7 @@
 
         verifySnapshotType(snapshotInfo);
 
-        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshotInfo.getId(), DataStoreRole.Primary);
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotInfo.getId(), DataStoreRole.Primary);
 
         if (snapshotStore != null) {
             long snapshotStoragePoolId = snapshotStore.getDataStoreId();
@@ -911,7 +912,7 @@
     }
 
     @Override
-    public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
+    public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
         Snapshot.LocationType locationType = snapshot.getLocationType();
 
         // If the snapshot exists on Secondary Storage, we can't delete it.
@@ -920,20 +921,26 @@
                 return StrategyPriority.CANT_HANDLE;
             }
 
-            SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
+            List<SnapshotDataStoreVO> snapshotOnImageStores = snapshotStoreDao.listReadyBySnapshot(snapshot.getId(), DataStoreRole.Image);
 
             // If the snapshot exists on Secondary Storage, we can't delete it.
-            if (snapshotStore != null) {
+            if (CollectionUtils.isNotEmpty(snapshotOnImageStores)) {
                 return StrategyPriority.CANT_HANDLE;
             }
 
-            snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+            SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
 
             if (snapshotStore == null) {
                 return StrategyPriority.CANT_HANDLE;
             }
 
             long snapshotStoragePoolId = snapshotStore.getDataStoreId();
+            if (zoneId != null) { // If zoneId is present, then it should be same as the zoneId of primary store
+                StoragePoolVO storagePoolVO = storagePoolDao.findById(snapshotStoragePoolId);
+                if (!zoneId.equals(storagePoolVO.getDataCenterId())) {
+                    return StrategyPriority.CANT_HANDLE;
+                }
+            }
 
             boolean storageSystemSupportsCapability = storageSystemSupportsCapability(snapshotStoragePoolId, DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
 
@@ -953,7 +960,7 @@
                 boolean acceptableFormat = isAcceptableRevertFormat(volumeVO);
 
                 if (acceptableFormat) {
-                    SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+                    SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
 
                     boolean usingBackendSnapshot = usingBackendSnapshotFor(snapshot.getId());
 
diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java
index b654974..f5d7081 100644
--- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java
+++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java
@@ -55,7 +55,6 @@
 import com.cloud.exception.OperationTimedoutException;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.storage.CreateSnapshotPayload;
-import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.GuestOSVO;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
@@ -176,7 +175,7 @@
                         thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
                         throw new CloudRuntimeException("Could not take snapshot for volume with id=" + vol.getId());
                     }
-                    s_logger.info(String.format("Snapshot with id=%s, took  %s miliseconds", snapInfo.getId(),
+                    s_logger.info(String.format("Snapshot with id=%s, took  %s milliseconds", snapInfo.getId(),
                             TimeUnit.MILLISECONDS.convert(elapsedTime(startSnapshtot), TimeUnit.NANOSECONDS)));
                 }
                 answer = new CreateVMSnapshotAnswer(ccmd, true, "");
@@ -184,7 +183,7 @@
                 thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
                 if (thawAnswer != null && thawAnswer.getResult()) {
                     s_logger.info(String.format(
-                            "Virtual machne is thawed. The freeze of virtual machine took %s miliseconds.",
+                            "Virtual machne is thawed. The freeze of virtual machine took %s milliseconds.",
                             TimeUnit.MILLISECONDS.convert(elapsedTime(startFreeze), TimeUnit.NANOSECONDS)));
                 }
             } else {
@@ -218,7 +217,7 @@
             throw new CloudRuntimeException(e.getMessage());
         } finally {
             if (thawAnswer == null && freezeAnswer != null) {
-                s_logger.info(String.format("Freeze of virtual machine took %s miliseconds.", TimeUnit.MILLISECONDS
+                s_logger.info(String.format("Freeze of virtual machine took %s milliseconds.", TimeUnit.MILLISECONDS
                                                 .convert(elapsedTime(startFreeze), TimeUnit.NANOSECONDS)));
                 try {
                     thawAnswer = (FreezeThawVMAnswer) agentMgr.send(hostId, thawCmd);
@@ -390,7 +389,7 @@
         //The snapshot could not be deleted separately, that's why we set snapshot state to BackedUp for operation delete VM snapshots and rollback
         SnapshotStrategy strategy = storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.DELETE);
         if (strategy != null) {
-            boolean snapshotForDelete = strategy.deleteSnapshot(snapshot.getId());
+            boolean snapshotForDelete = strategy.deleteSnapshot(snapshot.getId(), null);
             if (!snapshotForDelete) {
                 throw new CloudRuntimeException("Failed to delete snapshot");
             }
@@ -415,7 +414,7 @@
     protected void revertDiskSnapshot(VMSnapshot vmSnapshot) {
         List<VMSnapshotDetailsVO> listSnapshots = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), STORAGE_SNAPSHOT);
         for (VMSnapshotDetailsVO vmSnapshotDetailsVO : listSnapshots) {
-            SnapshotInfo sInfo = snapshotDataFactory.getSnapshot(Long.parseLong(vmSnapshotDetailsVO.getValue()), DataStoreRole.Primary);
+            SnapshotInfo sInfo = snapshotDataFactory.getSnapshotOnPrimaryStore(Long.parseLong(vmSnapshotDetailsVO.getValue()));
             SnapshotStrategy snapshotStrategy = storageStrategyFactory.getSnapshotStrategy(sInfo, SnapshotOperation.REVERT);
             if (snapshotStrategy == null) {
                 throw new CloudRuntimeException(String.format("Could not find strategy for snapshot uuid [%s]", sInfo.getId()));
diff --git a/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-snapshot-core-context.xml b/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-snapshot-core-context.xml
index 75545a89..1d1c831 100644
--- a/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-snapshot-core-context.xml
+++ b/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-snapshot-core-context.xml
@@ -36,4 +36,4 @@
     <bean id="snapshotStateMachineManagerImpl"
         class="org.apache.cloudstack.storage.snapshot.SnapshotStateMachineManagerImpl" />
     
-</beans>
\ No newline at end of file
+</beans>
diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java
index dcc6acf..b33f57c 100644
--- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java
+++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategyTest.java
@@ -81,10 +81,10 @@
         VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
         Mockito.when(volumeVO.getRemoved()).thenReturn(removed);
         Mockito.when(volumeDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(volumeVO);
-        Mockito.lenient().doReturn(isSnapshotStoredOnRbdStoragePool).when(cephSnapshotStrategy).isSnapshotStoredOnRbdStoragePool(Mockito.any());
+        Mockito.lenient().doReturn(isSnapshotStoredOnRbdStoragePool).when(cephSnapshotStrategy).isSnapshotStoredOnRbdStoragePoolAndOperationForSameZone(Mockito.any(), Mockito.any());
 
         for (int i = 0; i < snapshotOps.length - 1; i++) {
-            StrategyPriority strategyPriority = cephSnapshotStrategy.canHandle(snapshot, snapshotOps[i]);
+            StrategyPriority strategyPriority = cephSnapshotStrategy.canHandle(snapshot, null, snapshotOps[i]);
             if (snapshotOps[i] == SnapshotOperation.REVERT && isSnapshotStoredOnRbdStoragePool) {
                 Assert.assertEquals(StrategyPriority.HIGHEST, strategyPriority);
             } else {
diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategyTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategyTest.java
index a092f8f..09e5c85 100644
--- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategyTest.java
+++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategyTest.java
@@ -18,17 +18,16 @@
 package org.apache.cloudstack.storage.snapshot;
 
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
-import com.cloud.storage.VolumeDetailVO;
-import com.cloud.storage.dao.VolumeDetailsDao;
-import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,7 +40,13 @@
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotZoneDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -74,27 +79,31 @@
     @Mock
     SnapshotService snapshotServiceMock;
 
-    Map<String, SnapshotInfo> mapStringSnapshotInfoInstance = new LinkedHashMap<>();
+    @Mock
+    SnapshotZoneDao snapshotZoneDaoMock;
+
+    @Mock
+    SnapshotDataStoreDao snapshotDataStoreDao;
+
+    @Mock
+    DataStoreManager dataStoreManager;
+
+    List<SnapshotInfo> mockSnapshotInfos = new ArrayList<>();
 
     @Before
     public void setup() {
-        mapStringSnapshotInfoInstance.put("secondary storage", snapshotInfo1Mock);
-        mapStringSnapshotInfoInstance.put("primary storage", snapshotInfo1Mock);
+        mockSnapshotInfos.add(snapshotInfo1Mock);
+        mockSnapshotInfos.add(snapshotInfo2Mock);
     }
 
     @Test
     public void validateRetrieveSnapshotEntries() {
         Long snapshotId = 1l;
-        Mockito.doReturn(snapshotInfo1Mock, snapshotInfo2Mock).when(snapshotDataFactoryMock).getSnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class), Mockito.anyBoolean());
-        Map<String, SnapshotInfo> result = defaultSnapshotStrategySpy.retrieveSnapshotEntries(snapshotId);
+        Mockito.doReturn(mockSnapshotInfos).when(snapshotDataFactoryMock).getSnapshots(Mockito.anyLong(), Mockito.any());
+        List<SnapshotInfo> result = defaultSnapshotStrategySpy.retrieveSnapshotEntries(snapshotId, null);
 
-        Mockito.verify(snapshotDataFactoryMock).getSnapshot(snapshotId, DataStoreRole.Image, false);
-        Mockito.verify(snapshotDataFactoryMock).getSnapshot(snapshotId, DataStoreRole.Primary, false);
-
-        Assert.assertTrue(result.containsKey("secondary storage"));
-        Assert.assertTrue(result.containsKey("primary storage"));
-        Assert.assertEquals(snapshotInfo1Mock, result.get("secondary storage"));
-        Assert.assertEquals(snapshotInfo2Mock, result.get("primary storage"));
+        Assert.assertTrue(result.contains(snapshotInfo1Mock));
+        Assert.assertTrue(result.contains(snapshotInfo2Mock));
     }
 
     @Test
@@ -107,38 +116,29 @@
 
     @Test
     public void validateDestroySnapshotEntriesAndFilesFailToDeleteReturnsFalse() {
-        Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any());
-        Assert.assertFalse(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock));
+        Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any(), Mockito.any());
+        Assert.assertFalse(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock, null));
     }
 
     @Test
     public void validateDestroySnapshotEntriesAndFilesDeletesSuccessfullyReturnsTrue() {
-        Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any());
-        Assert.assertTrue(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock));
+        Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any(), Mockito.any());
+        Mockito.doNothing().when(snapshotZoneDaoMock).removeSnapshotFromZones(Mockito.anyLong());
+        Assert.assertTrue(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock, null));
     }
 
     @Test
     public void validateDeleteSnapshotInfosFailToDeleteReturnsFalse() {
-        Mockito.doReturn(mapStringSnapshotInfoInstance).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong());
-        Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.anyString(), Mockito.any());
-        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock));
+        Mockito.doReturn(mockSnapshotInfos).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong(), Mockito.any());
+        Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.any());
+        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock, null));
     }
 
     @Test
     public void validateDeleteSnapshotInfosDeletesSuccessfullyReturnsTrue() {
-        Mockito.doReturn(mapStringSnapshotInfoInstance).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong());
-        Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.anyString(), Mockito.any());
-        Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock));
-    }
-
-    @Test
-    public void validateDeleteSnapshotInfoSnapshotInfoIsNullOnSecondaryStorageReturnsTrue() {
-        Assert.assertNull(defaultSnapshotStrategySpy.deleteSnapshotInfo(null, "secondary storage", snapshotVoMock));
-    }
-
-    @Test
-    public void validateDeleteSnapshotInfoSnapshotInfoIsNullOnPrimaryStorageReturnsFalse() {
-        Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInfo(null, "primary storage", snapshotVoMock));
+        Mockito.doReturn(mockSnapshotInfos).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong(), Mockito.any());
+        Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.any());
+        Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock, null));
     }
 
     @Test
@@ -147,8 +147,9 @@
         Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
         Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
         Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
+        Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Primary);
 
-        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "primary storage", snapshotVoMock);
+        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
         Assert.assertTrue(result);
     }
 
@@ -158,8 +159,9 @@
         Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
         Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
         Mockito.doReturn(false).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
+        Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Primary);
 
-        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "primary storage", snapshotVoMock);
+        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
         Assert.assertFalse(result);
     }
 
@@ -169,8 +171,9 @@
         Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
         Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
         Mockito.doThrow(CloudRuntimeException.class).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
+        Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Primary);
 
-        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "primary storage", snapshotVoMock);
+        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
         Assert.assertFalse(result);
     }
 
@@ -181,8 +184,9 @@
         Mockito.doNothing().when(defaultSnapshotStrategySpy).verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObjectMock);
         Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
         Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotChain(Mockito.any(), Mockito.anyString());
+        Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Image);
 
-        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "secondary storage", snapshotVoMock);
+        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
         Assert.assertTrue(result);
     }
 
@@ -193,8 +197,9 @@
         Mockito.doNothing().when(defaultSnapshotStrategySpy).verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObjectMock);
         Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
         Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotChain(Mockito.any(), Mockito.anyString());
+        Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Image);
 
-        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "secondary storage", snapshotVoMock);
+        boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
         Assert.assertTrue(result);
     }
 
@@ -203,8 +208,9 @@
         Mockito.doReturn(dataStoreMock).when(snapshotInfo1Mock).getDataStore();
         Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
         Mockito.doThrow(NoTransitionException.class).when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
+        Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Image);
 
-        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "secondary storage", snapshotVoMock));
+        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock));
     }
 
     @Test
@@ -231,18 +237,97 @@
     public void deleteSnapshotInPrimaryStorageTestReturnTrueIfDeleteReturnsTrue() throws NoTransitionException {
         Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
         Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
-        Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, snapshotObjectMock));
+        Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, snapshotObjectMock, true));
+    }
+
+    @Test
+    public void deleteSnapshotInPrimaryStorageTestReturnTrueIfDeleteNotLastRefReturnsTrue() throws NoTransitionException {
+        Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
+        Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, snapshotObjectMock, false));
     }
 
     @Test
     public void deleteSnapshotInPrimaryStorageTestReturnFalseIfDeleteReturnsFalse() throws NoTransitionException {
         Mockito.doReturn(false).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
-        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null));
+        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null, true));
     }
 
     @Test
     public void deleteSnapshotInPrimaryStorageTestReturnFalseIfDeleteThrowsException() throws NoTransitionException {
         Mockito.doThrow(CloudRuntimeException.class).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
-        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null));
+        Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null, true));
+    }
+
+    @Test
+    public void testGetSnapshotImageStoreRefNull() {
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
+        Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
+        Mockito.when(snapshotDataStoreDao.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
+        Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(2L);
+        Assert.assertNull(defaultSnapshotStrategySpy.getSnapshotImageStoreRef(1L, 1L));
+    }
+
+    @Test
+    public void testGetSnapshotImageStoreRefNotNull() {
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
+        Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
+        Mockito.when(snapshotDataStoreDao.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
+        Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(1L);
+        Assert.assertNotNull(defaultSnapshotStrategySpy.getSnapshotImageStoreRef(1L, 1L));
+    }
+
+    @Test
+    public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeNull() {
+        Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(Mockito.mock(Snapshot.class), null));
+    }
+
+    @Test
+    public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeVHD() {
+        VolumeVO volumeVO = Mockito.mock((VolumeVO.class));
+        Mockito.when(volumeVO.getFormat()).thenReturn(Storage.ImageFormat.VHD);
+        Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(Mockito.mock(Snapshot.class), volumeVO));
+    }
+
+    private void prepareMocksForIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeTest(Long matchingZoneId) {
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(201L);
+        Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
+        SnapshotDataStoreVO ref2 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref2.getDataStoreId()).thenReturn(202L);
+        Mockito.when(ref2.getRole()).thenReturn(DataStoreRole.Image);
+        SnapshotDataStoreVO ref3 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref3.getDataStoreId()).thenReturn(203L);
+        Mockito.when(ref3.getRole()).thenReturn(DataStoreRole.Image);
+        Mockito.when(snapshotDataStoreDao.listBySnapshotIdAndState(1L, ObjectInDataStoreStateMachine.State.Ready)).thenReturn(List.of(ref1, ref2, ref3));
+        Mockito.when(dataStoreManager.getStoreZoneId(201L, DataStoreRole.Image)).thenReturn(111L);
+        Mockito.when(dataStoreManager.getStoreZoneId(202L, DataStoreRole.Image)).thenReturn(matchingZoneId != null ? matchingZoneId : 112L);
+        Mockito.when(dataStoreManager.getStoreZoneId(203L, DataStoreRole.Image)).thenReturn(113L);
+
+    }
+
+    @Test
+    public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeNoRef() {
+        Snapshot snapshot = Mockito.mock((Snapshot.class));
+        Mockito.when(snapshot.getId()).thenReturn(1L);
+        VolumeVO volumeVO = Mockito.mock((VolumeVO.class));
+        Mockito.when(volumeVO.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
+        Mockito.when(snapshotDataStoreDao.listBySnapshotIdAndState(1L, ObjectInDataStoreStateMachine.State.Ready)).thenReturn(new ArrayList<>());
+        Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO));
+
+        prepareMocksForIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeTest(null);
+        Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO));
+    }
+
+    @Test
+    public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeHasRef() {
+        Snapshot snapshot = Mockito.mock((Snapshot.class));
+        Mockito.when(snapshot.getId()).thenReturn(1L);
+        VolumeVO volumeVO = Mockito.mock((VolumeVO.class));
+        Mockito.when(volumeVO.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(100L);
+        prepareMocksForIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeTest(100L);
+        Assert.assertTrue(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO));
     }
 }
diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImplTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImplTest.java
index 25de9cd..94e2481 100644
--- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImplTest.java
+++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImplTest.java
@@ -31,18 +31,18 @@
 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.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.utils.component.ComponentContext;
 
-@RunWith(PowerMockRunner.class)
+
+@RunWith(MockitoJUnitRunner.class)
 public class SnapshotDataFactoryImplTest {
 
     @Spy
@@ -62,49 +62,44 @@
     public void getSnapshotsByVolumeAndDataStoreTestNoSnapshotDataStoreVOFound() {
         Mockito.doReturn(new ArrayList<>()).when(snapshotStoreDaoMock).listAllByVolumeAndDataStore(volumeMockId, DataStoreRole.Primary);
 
-        List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshots(volumeMockId, DataStoreRole.Primary);
+        List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshotsForVolumeAndStoreRole(volumeMockId, DataStoreRole.Primary);
 
         Assert.assertTrue(snapshots.isEmpty());
     }
 
     @Test
-    @PrepareForTest({ComponentContext.class})
     public void getSnapshotsByVolumeAndDataStoreTest() {
-        PowerMockito.mockStatic(ComponentContext.class);
-        PowerMockito.when(ComponentContext.inject(SnapshotObject.class)).thenReturn(new SnapshotObject());
+        try (MockedStatic<ComponentContext> componentContextMockedStatic = Mockito.mockStatic(ComponentContext.class)) {
+            Mockito.when(ComponentContext.inject(SnapshotObject.class)).thenReturn(new SnapshotObject());
 
-        SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class);
-        Mockito.doReturn(volumeMockId).when(snapshotDataStoreVoMock).getVolumeId();
+            SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class);
 
-        long snapshotId = 1223;
-        long dataStoreId = 34567;
-        Mockito.doReturn(snapshotId).when(snapshotDataStoreVoMock).getSnapshotId();
-        Mockito.doReturn(dataStoreId).when(snapshotDataStoreVoMock).getDataStoreId();
+            long snapshotId = 1223;
+            long dataStoreId = 34567;
+            Mockito.doReturn(snapshotId).when(snapshotDataStoreVoMock).getSnapshotId();
+            Mockito.doReturn(dataStoreId).when(snapshotDataStoreVoMock).getDataStoreId();
 
-        SnapshotVO snapshotVoMock  = Mockito.mock(SnapshotVO.class);
-        Mockito.doReturn(snapshotId).when(snapshotVoMock).getId();
+            SnapshotVO snapshotVoMock = Mockito.mock(SnapshotVO.class);
 
-        DataStoreRole dataStoreRole = DataStoreRole.Primary;
-        DataStore dataStoreMock = Mockito.mock(DataStore.class);
-        Mockito.doReturn(dataStoreId).when(dataStoreMock).getId();
-        Mockito.doReturn(dataStoreRole).when(dataStoreMock).getRole();
+            DataStoreRole dataStoreRole = DataStoreRole.Primary;
+            DataStore dataStoreMock = Mockito.mock(DataStore.class);
 
-        List<SnapshotDataStoreVO> snapshotDataStoreVOs = new ArrayList<>();
-        snapshotDataStoreVOs.add(snapshotDataStoreVoMock);
+            List<SnapshotDataStoreVO> snapshotDataStoreVOs = new ArrayList<>();
+            snapshotDataStoreVOs.add(snapshotDataStoreVoMock);
 
-        Mockito.doReturn(snapshotDataStoreVOs).when(snapshotStoreDaoMock).listAllByVolumeAndDataStore(volumeMockId, dataStoreRole);
-        Mockito.doReturn(dataStoreMock).when(dataStoreManagerMock).getDataStore(dataStoreId, dataStoreRole);
-        Mockito.doReturn(snapshotVoMock).when(snapshotDaoMock).findById(snapshotId);
+            Mockito.doReturn(snapshotDataStoreVOs).when(snapshotStoreDaoMock).listAllByVolumeAndDataStore(volumeMockId, dataStoreRole);
+            Mockito.doReturn(dataStoreMock).when(dataStoreManagerMock).getDataStore(dataStoreId, dataStoreRole);
+            Mockito.doReturn(snapshotVoMock).when(snapshotDaoMock).findById(snapshotId);
 
-        List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshots(volumeMockId, dataStoreRole);
+            List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshotsForVolumeAndStoreRole(volumeMockId, dataStoreRole);
 
-        Assert.assertEquals(1, snapshots.size());
+            Assert.assertEquals(1, snapshots.size());
 
-        SnapshotInfo snapshotInfo = snapshots.get(0);
-        Assert.assertEquals(dataStoreMock, snapshotInfo.getDataStore());
-        Assert.assertEquals(snapshotVoMock, ((SnapshotObject)snapshotInfo).getSnapshotVO());
+            SnapshotInfo snapshotInfo = snapshots.get(0);
+            Assert.assertEquals(dataStoreMock, snapshotInfo.getDataStore());
+            Assert.assertEquals(snapshotVoMock, ((SnapshotObject) snapshotInfo).getSnapshotVO());
 
-        PowerMockito.verifyStatic(ComponentContext.class);
-        ComponentContext.inject(SnapshotObject.class);
+            componentContextMockedStatic.verify(() -> ComponentContext.inject(SnapshotObject.class), Mockito.times(1));
+        }
     }
 }
diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImplTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImplTest.java
index ec5c355..6d59b6f 100644
--- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImplTest.java
+++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImplTest.java
@@ -19,6 +19,8 @@
 package org.apache.cloudstack.storage.snapshot;
 
 import com.cloud.storage.DataStoreRole;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
@@ -27,25 +29,21 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.framework.async.AsyncCallFuture;
-import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
-import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper;
 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.MockedConstruction;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({SnapshotServiceImpl.class})
+@RunWith(MockitoJUnitRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
 public class SnapshotServiceImplTest {
 
@@ -60,39 +58,60 @@
     SnapshotDataFactory _snapshotFactory;
 
     @Mock
-    AsyncCallFuture<SnapshotResult> futureMock;
+    HeuristicRuleHelper heuristicRuleHelperMock;
 
     @Mock
-    AsyncCallbackDispatcher<SnapshotServiceImpl, CommandResult> caller;
+    SnapshotInfo snapshotMock;
 
-    @Before
-    public void testSetUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-    }
+    @Mock
+    VolumeInfo volumeInfoMock;
+
+    @Mock
+    DataStoreManager dataStoreManagerMock;
+
+    private static final long DUMMY_ID = 1L;
 
     @Test
     public void testRevertSnapshotWithNoPrimaryStorageEntry() throws Exception {
-        SnapshotInfo snapshot = Mockito.mock(SnapshotInfo.class);
-        VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
-
-        Mockito.when(snapshot.getId()).thenReturn(1L);
-        Mockito.when(snapshot.getVolumeId()).thenReturn(1L);
-        Mockito.when(_snapshotFactory.getSnapshot(1L, DataStoreRole.Primary)).thenReturn(null);
-        Mockito.when(volFactory.getVolume(1L, DataStoreRole.Primary)).thenReturn(volumeInfo);
+        Mockito.when(snapshotMock.getId()).thenReturn(DUMMY_ID);
+        Mockito.when(snapshotMock.getVolumeId()).thenReturn(DUMMY_ID);
+        Mockito.when(_snapshotFactory.getSnapshotOnPrimaryStore(1L)).thenReturn(null);
+        Mockito.when(volFactory.getVolume(DUMMY_ID, DataStoreRole.Primary)).thenReturn(volumeInfoMock);
 
         PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class);
-        Mockito.when(volumeInfo.getDataStore()).thenReturn(store);
+        Mockito.when(volumeInfoMock.getDataStore()).thenReturn(store);
 
         PrimaryDataStoreDriver driver = Mockito.mock(PrimaryDataStoreDriver.class);
         Mockito.when(store.getDriver()).thenReturn(driver);
-        Mockito.doNothing().when(driver).revertSnapshot(snapshot, null, caller);
 
         SnapshotResult result = Mockito.mock(SnapshotResult.class);
-        PowerMockito.whenNew(AsyncCallFuture.class).withNoArguments().thenReturn(futureMock);
-        Mockito.when(futureMock.get()).thenReturn(result);
-        Mockito.when(result.isFailed()).thenReturn(false);
-
-        Assert.assertEquals(true, snapshotService.revertSnapshot(snapshot));
+        try (MockedConstruction<AsyncCallFuture> ignored = Mockito.mockConstruction(AsyncCallFuture.class, (mock, context) -> {
+            Mockito.when(mock.get()).thenReturn(result);
+            Mockito.when(result.isFailed()).thenReturn(false);
+        })) {
+            Assert.assertTrue(snapshotService.revertSnapshot(snapshotMock));
+        }
     }
 
+    @Test
+    public void getImageStoreForSnapshotTestShouldListFreeImageStoresWithNoHeuristicRule() {
+        Mockito.when(heuristicRuleHelperMock.getImageStoreIfThereIsHeuristicRule(Mockito.anyLong(), Mockito.any(HeuristicType.class), Mockito.any(SnapshotInfo.class))).
+                thenReturn(null);
+        Mockito.when(snapshotMock.getDataCenterId()).thenReturn(DUMMY_ID);
+
+        snapshotService.getImageStoreForSnapshot(DUMMY_ID, snapshotMock);
+
+        Mockito.verify(dataStoreManagerMock, Mockito.times(1)).getImageStoreWithFreeCapacity(Mockito.anyLong());
+    }
+
+    @Test
+    public void getImageStoreForSnapshotTestShouldReturnImageStoreReturnedByTheHeuristicRule() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        Mockito.when(heuristicRuleHelperMock.getImageStoreIfThereIsHeuristicRule(Mockito.anyLong(), Mockito.any(HeuristicType.class), Mockito.any(SnapshotInfo.class))).
+                thenReturn(dataStore);
+
+        snapshotService.getImageStoreForSnapshot(DUMMY_ID, snapshotMock);
+
+        Mockito.verify(dataStoreManagerMock, Mockito.times(0)).getImageStoreWithFreeCapacity(Mockito.anyLong());
+    }
 }
diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java
index 7ba14c9..d438fef 100644
--- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java
+++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java
@@ -49,7 +49,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.Matchers;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
@@ -163,7 +162,6 @@
         SnapshotVO snapshot = new SnapshotVO(vol.getDataCenterId(), vol.getAccountId(), vol.getDomainId(),
                                vol.getId(),vol.getDiskOfferingId(), vmUuid + "_" + volUuid,(short) SnapshotVO.MANUAL_POLICY_ID,
                                "MANUAL",vol.getSize(),vol.getMinIops(),vol.getMaxIops(), Hypervisor.HypervisorType.KVM, null);
-        PowerMockito.whenNew(SnapshotVO.class).withAnyArguments().thenReturn(snapshot);
         when(vmSnapshot.getUuid()).thenReturn(vmUuid);
         when(vol.getUuid()).thenReturn(volUuid);
         when(_snapshotDao.persist(any())).thenReturn(snapshot);
@@ -175,7 +173,6 @@
 
         when(strategy.takeSnapshot(any())).thenReturn(snapshotInfo);
         VMSnapshotDetailsVO vmDetails = new VMSnapshotDetailsVO(vmSnapshot.getId(), volUuid, String.valueOf(snapshot.getId()), false);
-        PowerMockito.whenNew(VMSnapshotDetailsVO.class).withAnyArguments().thenReturn(vmDetails);
         when(vmSnapshotDetailsDao.persist(any())).thenReturn(vmDetails);
 
         info =  vmStrategy.createDiskSnapshot(vmSnapshot, forRollback, vol);
diff --git a/engine/storage/snapshot/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/engine/storage/snapshot/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/engine/storage/snapshot/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java
index 2966f68..2a65fad 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java
@@ -28,6 +28,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.api.query.dao.StoragePoolJoinDao;
 import com.cloud.exception.StorageUnavailableException;
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.StoragePoolStatus;
@@ -85,6 +86,9 @@
      */
     private SecureRandom secureRandom = new SecureRandom();
 
+    @Inject
+    protected StoragePoolJoinDao storagePoolJoinDao;
+
     @Override
     public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
         super.configure(name, params);
@@ -102,16 +106,20 @@
         return false;
     }
 
-    protected abstract List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck);
+    protected abstract List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword);
 
     @Override
     public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) {
-        return allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo, false);
+        return allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo, false, null);
     }
 
     @Override
     public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
-        List<StoragePool> pools = select(dskCh, vmProfile, plan, avoid, returnUpTo, bypassStorageTypeCheck);
+        return allocateToPool(dskCh, vmProfile, plan, avoid, returnUpTo, bypassStorageTypeCheck, null);
+    }
+
+    public List<StoragePool> allocateToPool(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
+        List<StoragePool> pools = select(dskCh, vmProfile, plan, avoid, returnUpTo, bypassStorageTypeCheck, keyword);
         return reorderPools(pools, vmProfile, plan, dskCh);
     }
 
@@ -263,13 +271,9 @@
     }
 
     protected boolean filter(ExcludeList avoid, StoragePool pool, DiskProfile dskCh, DeploymentPlan plan) {
-        if (s_logger.isDebugEnabled()) {
-            s_logger.debug("Checking if storage pool is suitable, name: " + pool.getName() + " ,poolId: " + pool.getId());
-        }
+        s_logger.debug(String.format("Checking if storage pool [%s] is suitable to disk [%s].", pool, dskCh));
         if (avoid.shouldAvoid(pool)) {
-            if (s_logger.isDebugEnabled()) {
-                s_logger.debug("StoragePool is in avoid set, skipping this pool");
-            }
+            s_logger.debug(String.format("StoragePool [%s] is in avoid set, skipping this pool to allocation of disk [%s].", pool, dskCh));
             return false;
         }
 
@@ -297,6 +301,8 @@
         }
 
         if (!checkDiskProvisioningSupport(dskCh, pool)) {
+            s_logger.debug(String.format("Storage pool [%s] does not have support to disk provisioning of disk [%s].", pool, ReflectionToStringBuilderUtils.reflectOnlySelectedFields(dskCh,
+                    "type", "name", "diskOfferingId", "templateId", "volumeId", "provisioningType", "hyperType")));
             return false;
         }
 
@@ -306,10 +312,12 @@
 
         Volume volume = volumeDao.findById(dskCh.getVolumeId());
         if(!storageMgr.storagePoolCompatibleWithVolumePool(pool, volume)) {
+            s_logger.debug(String.format("Pool [%s] is not compatible with volume [%s], skipping it.", pool, volume));
             return false;
         }
 
         if (pool.isManaged() && !storageUtil.managedStoragePoolCanScale(pool, plan.getClusterId(), plan.getHostId())) {
+            s_logger.debug(String.format("Cannot allocate pool [%s] to volume [%s] because the max number of managed clustered filesystems has been exceeded.", pool, volume));
             return false;
         }
 
@@ -317,14 +325,14 @@
         List<Pair<Volume, DiskProfile>> requestVolumeDiskProfilePairs = new ArrayList<>();
         requestVolumeDiskProfilePairs.add(new Pair<>(volume, dskCh));
         if (dskCh.getHypervisorType() == HypervisorType.VMware) {
-            // Skip the parent datastore cluster, consider only child storage pools in it
             if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster && storageMgr.isStoragePoolDatastoreClusterParent(pool)) {
+                s_logger.debug(String.format("Skipping allocation of pool [%s] to volume [%s] because this pool is a parent datastore cluster.", pool, volume));
                 return false;
             }
-            // Skip the storage pool whose parent datastore cluster is not in UP state.
             if (pool.getParent() != 0L) {
                 StoragePoolVO datastoreCluster = storagePoolDao.findById(pool.getParent());
                 if (datastoreCluster == null || (datastoreCluster != null && datastoreCluster.getStatus() != StoragePoolStatus.Up)) {
+                    s_logger.debug(String.format("Skipping allocation of pool [%s] to volume [%s] because this pool is not in [%s] state.", datastoreCluster, volume, StoragePoolStatus.Up));
                     return false;
                 }
             }
@@ -332,6 +340,7 @@
             try {
                 boolean isStoragePoolStoragepolicyComplaince = storageMgr.isStoragePoolCompliantWithStoragePolicy(requestVolumeDiskProfilePairs, pool);
                 if (!isStoragePoolStoragepolicyComplaince) {
+                    s_logger.debug(String.format("Skipping allocation of pool [%s] to volume [%s] because this pool is not compliant with the storage policy required by the volume.", pool, volume));
                     return false;
                 }
             } catch (StorageUnavailableException e) {
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java
index e7c9b7e..da35baf 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java
@@ -24,6 +24,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.storage.VolumeApiServiceImpl;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -45,7 +46,7 @@
     DiskOfferingDao _diskOfferingDao;
 
     @Override
-    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
+    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
         logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck);
 
         if (!bypassStorageTypeCheck && dskCh.useLocalStorage()) {
@@ -78,11 +79,12 @@
             logDisabledStoragePools(dcId, podId, clusterId, ScopeType.CLUSTER);
         }
 
-        List<StoragePoolVO> pools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags());
+        List<StoragePoolVO> pools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags(), true, VolumeApiServiceImpl.storageTagRuleExecutionTimeout.value());
+        pools.addAll(storagePoolJoinDao.findStoragePoolByScopeAndRuleTags(dcId, podId, clusterId, ScopeType.CLUSTER, List.of(dskCh.getTags())));
         s_logger.debug(String.format("Found pools [%s] that match with tags [%s].", pools, Arrays.toString(dskCh.getTags())));
 
         // add remaining pools in cluster, that did not match tags, to avoid set
-        List<StoragePoolVO> allPools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, null);
+        List<StoragePoolVO> allPools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, null, false, 0);
         allPools.removeAll(pools);
         for (StoragePoolVO pool : allPools) {
             s_logger.trace(String.format("Adding pool [%s] to the 'avoid' set since it did not match any tags.", pool));
@@ -100,9 +102,10 @@
             }
             StoragePool storagePool = (StoragePool)dataStoreMgr.getPrimaryDataStore(pool.getId());
             if (filter(avoid, storagePool, dskCh, plan)) {
-                s_logger.trace(String.format("Found suitable local storage pool [%s], adding to list.", pool));
+                s_logger.debug(String.format("Found suitable local storage pool [%s] to allocate disk [%s] to it, adding to list.", pool, dskCh));
                 suitablePools.add(storagePool);
             } else {
+                s_logger.debug(String.format("Adding storage pool [%s] to avoid set during allocation of disk [%s].", pool, dskCh));
                 avoid.addPool(pool.getId());
             }
         }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java
index 3fa6949..9b9f56d 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java
@@ -47,7 +47,7 @@
     boolean _storagePoolCleanupEnabled;
 
     @Override
-    public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
+    public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
         logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck);
         if (!_storagePoolCleanupEnabled) {
             s_logger.debug("Storage pool cleanup is not enabled, so GarbageCollectingStoragePoolAllocator is being skipped.");
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java
index 4fbaa8c..7ec2f26 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java
@@ -60,7 +60,7 @@
     ConfigurationDao _configDao;
 
     @Override
-    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
+    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
         logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck);
 
         if (!bypassStorageTypeCheck && !dskCh.useLocalStorage()) {
@@ -82,9 +82,10 @@
                 if (pool != null && pool.isLocal()) {
                     StoragePool storagePool = (StoragePool)this.dataStoreMgr.getPrimaryDataStore(pool.getId());
                     if (filter(avoid, storagePool, dskCh, plan)) {
-                        s_logger.trace(String.format("Found suitable local storage pool [%s], adding to list.", pool));
+                        s_logger.debug(String.format("Found suitable local storage pool [%s] to allocate disk [%s] to it, adding to list.", pool, dskCh));
                         suitablePools.add(storagePool);
                     } else {
+                        s_logger.debug(String.format("Adding storage pool [%s] to avoid set during allocation of disk [%s].", pool, dskCh));
                         avoid.addPool(pool.getId());
                     }
                 }
@@ -100,21 +101,24 @@
                 return null;
             }
             List<StoragePoolVO> availablePools =
-                storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), dskCh.getTags());
+                storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), dskCh.getTags(), true, keyword);
+            availablePools.addAll(storagePoolJoinDao.findStoragePoolByScopeAndRuleTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), ScopeType.HOST, List.of(dskCh.getTags())));
             for (StoragePoolVO pool : availablePools) {
                 if (suitablePools.size() == returnUpTo) {
                     break;
                 }
                 StoragePool storagePool = (StoragePool)this.dataStoreMgr.getPrimaryDataStore(pool.getId());
                 if (filter(avoid, storagePool, dskCh, plan)) {
+                    s_logger.debug(String.format("Found suitable local storage pool [%s] to allocate disk [%s] to it, adding to list.", pool, dskCh));
                     suitablePools.add(storagePool);
                 } else {
+                    s_logger.debug(String.format("Adding storage pool [%s] to avoid set during allocation of disk [%s].", pool, dskCh));
                     avoid.addPool(pool.getId());
                 }
             }
 
             // add remaining pools in cluster to the 'avoid' set which did not match tags
-            List<StoragePoolVO> allPools = storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), null);
+            List<StoragePoolVO> allPools = storagePoolDao.findLocalStoragePoolsByTags(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), null, false);
             allPools.removeAll(availablePools);
             for (StoragePoolVO pool : allPools) {
                 avoid.addPool(pool.getId());
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java
index 2902871..b02d437 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java
@@ -50,7 +50,7 @@
     private CapacityDao capacityDao;
 
     @Override
-    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
+    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
         logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck);
 
         if (!bypassStorageTypeCheck && dskCh.useLocalStorage()) {
@@ -63,9 +63,9 @@
         }
 
         List<StoragePool> suitablePools = new ArrayList<>();
-
-        List<StoragePoolVO> storagePools = storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), dskCh.getTags());
-        if (storagePools == null) {
+        List<StoragePoolVO> storagePools = storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), dskCh.getTags(), true);
+        storagePools.addAll(storagePoolJoinDao.findStoragePoolByScopeAndRuleTags(plan.getDataCenterId(), null, null, ScopeType.ZONE, List.of(dskCh.getTags())));
+        if (storagePools.isEmpty()) {
             LOGGER.debug(String.format("Could not find any zone wide storage pool that matched with any of the following tags [%s].", Arrays.toString(dskCh.getTags())));
             storagePools = new ArrayList<>();
         }
@@ -82,7 +82,7 @@
         storagePools.addAll(anyHypervisorStoragePools);
 
         // add remaining pools in zone, that did not match tags, to avoid set
-        List<StoragePoolVO> allPools = storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), null);
+        List<StoragePoolVO> allPools = storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), null, false);
         allPools.removeAll(storagePools);
         for (StoragePoolVO pool : allPools) {
             avoid.addPool(pool.getId());
@@ -94,10 +94,11 @@
             }
             StoragePool storagePool = (StoragePool)this.dataStoreMgr.getPrimaryDataStore(storage.getId());
             if (filter(avoid, storagePool, dskCh, plan)) {
-                LOGGER.trace(String.format("Found suitable local storage pool [%s], adding to list.", storage));
+                LOGGER.debug(String.format("Found suitable local storage pool [%s] to allocate disk [%s] to it, adding to list.", storagePool, dskCh));
                 suitablePools.add(storagePool);
             } else {
                 if (canAddStoragePoolToAvoidSet(storage)) {
+                    LOGGER.debug(String.format("Adding storage pool [%s] to avoid set during allocation of disk [%s].", storagePool, dskCh));
                     avoid.addPool(storagePool.getId());
                 }
             }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java
index ff6c4fb..757623e 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java
@@ -26,6 +26,7 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
 import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
 import org.springframework.stereotype.Component;
 
 import org.apache.cloudstack.storage.image.datastore.ImageStoreProviderManager;
@@ -40,6 +41,8 @@
     PrimaryDataStoreProviderManager primaryStoreMgr;
     @Inject
     ImageStoreProviderManager imageDataStoreMgr;
+    @Inject
+    ObjectStoreProviderManager objectStoreProviderMgr;
 
     @Override
     public DataStore getDataStore(long storeId, DataStoreRole role) {
@@ -50,6 +53,8 @@
                 return imageDataStoreMgr.getImageStore(storeId);
             } else if (role == DataStoreRole.ImageCache) {
                 return imageDataStoreMgr.getImageStore(storeId);
+            } else if (role == DataStoreRole.Object) {
+                return objectStoreProviderMgr.getObjectStore(storeId);
             }
         } catch (CloudRuntimeException e) {
             throw e;
@@ -63,6 +68,8 @@
             return primaryStoreMgr.getPrimaryDataStore(uuid);
         } else if (role == DataStoreRole.Image) {
             return imageDataStoreMgr.getImageStore(uuid);
+        } else if (role == DataStoreRole.Object) {
+            return objectStoreProviderMgr.getObjectStore(uuid);
         }
         throw new CloudRuntimeException("un recognized type" + role);
     }
@@ -78,6 +85,16 @@
     }
 
     @Override
+    public List<DataStore> getImageStoresByZoneIds(Long... zoneIds) {
+        return imageDataStoreMgr.listImageStoresFilteringByZoneIds(zoneIds);
+    }
+
+    @Override
+    public DataStore getImageStoreByUuid(String uuid) {
+        return imageDataStoreMgr.getImageStore(uuid);
+    }
+
+    @Override
     public DataStore getRandomImageStore(long zoneId) {
         List<DataStore> stores = getImageStoresByScope(new ZoneScope(zoneId));
         if (stores == null || stores.size() == 0) {
@@ -170,4 +187,16 @@
     public void setImageDataStoreMgr(ImageStoreProviderManager imageDataStoreMgr) {
         this.imageDataStoreMgr = imageDataStoreMgr;
     }
+
+    @Override
+    public Long getStoreZoneId(long storeId, DataStoreRole role) {
+        try {
+            if (role == DataStoreRole.Primary) {
+                return primaryStoreMgr.getPrimaryDataStoreZoneId(storeId);
+            } else  {
+                return imageDataStoreMgr.getImageStoreZoneId(storeId);
+            }
+        } catch (CloudRuntimeException ignored) {}
+        return null;
+    }
 }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManager.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManager.java
index 48aceca..e822201 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManager.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManager.java
@@ -40,6 +40,4 @@
     DataObjectInStore findObject(long objId, DataObjectType type, long dataStoreId, DataStoreRole role, String deployAsIsConfiguration);
 
     DataObjectInStore findObject(DataObject obj, DataStore store);
-
-    DataStore findStore(long objId, DataObjectType type, DataStoreRole role);
 }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java
index da97b22..3059018 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java
@@ -102,7 +102,7 @@
         stateMachines.addTransition(State.Destroying, Event.OperationFailed, State.Destroying);
         stateMachines.addTransition(State.Failed, Event.DestroyRequested, State.Destroying);
         // TODO: further investigate why an extra event is sent when it is
-        // alreay Ready for DownloadListener
+        // already Ready for DownloadListener
         stateMachines.addTransition(State.Ready, Event.OperationSuccessed, State.Ready);
         // State transitions for data object migration
         stateMachines.addTransition(State.Ready, Event.MigrateDataRequested, State.Migrating);
@@ -382,27 +382,4 @@
 
     }
 
-    @Override
-    public DataStore findStore(long objId, DataObjectType type, DataStoreRole role) {
-        DataStore store = null;
-        if (role == DataStoreRole.Image) {
-            DataObjectInStore vo = null;
-            switch (type) {
-                case TEMPLATE:
-                    vo = templateDataStoreDao.findByTemplate(objId, role);
-                    break;
-                case SNAPSHOT:
-                    vo = snapshotDataStoreDao.findBySnapshot(objId, role);
-                    break;
-                case VOLUME:
-                    vo = volumeDataStoreDao.findByVolume(objId);
-                    break;
-            }
-            if (vo != null) {
-                store = this.storeMgr.getDataStore(vo.getDataStoreId(), role);
-            }
-        }
-        return store;
-    }
-
 }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreProviderManager.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreProviderManager.java
index bb7911d..8c8919c 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreProviderManager.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreProviderManager.java
@@ -30,4 +30,6 @@
     boolean registerDriver(String providerName, PrimaryDataStoreDriver driver);
 
     boolean registerHostListener(String providerName, HypervisorHostListener listener);
+
+    public long getPrimaryDataStoreZoneId(long dataStoreId);
 }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java
index 98eeb6b..35e758a 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java
@@ -30,6 +30,8 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -56,6 +58,8 @@
     PrimaryDataStoreProviderManager primaryDataStoreProviderMgr;
     @Inject
     ImageStoreProviderManager imageStoreProviderMgr;
+    @Inject
+    ObjectStoreProviderManager objectStoreProviderMgr;
 
     @Override
     public DataStoreProvider getDataStoreProvider(String name) {
@@ -144,6 +148,8 @@
                 primaryDataStoreProviderMgr.registerHostListener(provider.getName(), provider.getHostListener());
             } else if (types.contains(DataStoreProviderType.IMAGE)) {
                 imageStoreProviderMgr.registerDriver(provider.getName(), (ImageStoreDriver)provider.getDataStoreDriver());
+            } else if (types.contains(DataStoreProviderType.OBJECT)) {
+                objectStoreProviderMgr.registerDriver(provider.getName(), (ObjectStoreDriver)provider.getDataStoreDriver());
             }
         } catch (Exception e) {
             s_logger.debug("configure provider failed", e);
@@ -170,6 +176,11 @@
     }
 
     @Override
+    public DataStoreProvider getDefaultObjectStoreProvider() {
+        return this.getDataStoreProvider(DataStoreProvider.S3_IMAGE);
+    }
+
+    @Override
     public List<StorageProviderResponse> getDataStoreProviders(String type) {
         if (type == null) {
             throw new InvalidParameterValueException("Invalid parameter, need to specify type: either primary or image");
@@ -180,7 +191,9 @@
             return this.getImageDataStoreProviders();
         } else if (type.equalsIgnoreCase(DataStoreProvider.DataStoreProviderType.ImageCache.toString())) {
             return this.getCacheDataStoreProviders();
-        } else {
+        } else if (type.equalsIgnoreCase(DataStoreProviderType.OBJECT.toString())) {
+            return this.getObjectStoreProviders();
+        }else {
             throw new InvalidParameterValueException("Invalid parameter: " + type);
         }
     }
@@ -223,4 +236,16 @@
         return providers;
     }
 
+    public List<StorageProviderResponse> getObjectStoreProviders() {
+        List<StorageProviderResponse> providers = new ArrayList<StorageProviderResponse>();
+        for (DataStoreProvider provider : providerMap.values()) {
+            if (provider.getTypes().contains(DataStoreProviderType.OBJECT)) {
+                StorageProviderResponse response = new StorageProviderResponse();
+                response.setName(provider.getName());
+                response.setType(DataStoreProviderType.OBJECT.toString());
+                providers.add(response);
+            }
+        }
+        return providers;
+    }
 }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java
index 4c13759..bc16baf 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java
@@ -428,7 +428,7 @@
         }
 
         // If ssvm doesn't exist then find any ssvm in the zone.
-        s_logger.debug("Coudn't find ssvm for url" +downloadUrl);
+        s_logger.debug("Couldn't find ssvm for url" +downloadUrl);
         return findEndpointForImageStorage(store);
     }
 
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java
index 9dbaf13..ec76bbb 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java
@@ -66,10 +66,15 @@
 
     @Override
     public SnapshotStrategy getSnapshotStrategy(final Snapshot snapshot, final SnapshotOperation op) {
+        return getSnapshotStrategy(snapshot, null, op);
+    }
+
+    @Override
+    public SnapshotStrategy getSnapshotStrategy(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
         return bestMatch(snapshotStrategies, new CanHandle<SnapshotStrategy>() {
             @Override
             public StrategyPriority canHandle(SnapshotStrategy strategy) {
-                return strategy.canHandle(snapshot, op);
+                return strategy.canHandle(snapshot, zoneId, op);
             }
         });
     }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
index 3ef9fbc..369630a 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java
@@ -32,17 +32,11 @@
 
 import javax.inject.Inject;
 
-import com.cloud.agent.api.to.NfsTO;
-import com.cloud.agent.api.to.OVFInformationTO;
-import com.cloud.storage.DataStoreRole;
-import com.cloud.storage.Upload;
-import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper;
-import org.apache.log4j.Logger;
-
 import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
 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.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
@@ -53,11 +47,15 @@
 import org.apache.cloudstack.storage.command.CommandResult;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.DeleteCommand;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
 import org.apache.cloudstack.storage.endpoint.DefaultEndPointSelector;
+import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper;
+import org.apache.log4j.Logger;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
@@ -68,6 +66,8 @@
 import com.cloud.agent.api.to.DataObjectType;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.DatadiskTO;
+import com.cloud.agent.api.to.NfsTO;
+import com.cloud.agent.api.to.OVFInformationTO;
 import com.cloud.alert.AlertManager;
 import com.cloud.configuration.Config;
 import com.cloud.exception.AgentUnavailableException;
@@ -76,7 +76,8 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.secstorage.CommandExecLogDao;
 import com.cloud.secstorage.CommandExecLogVO;
-import com.cloud.storage.StorageManager;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Upload;
 import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VolumeVO;
@@ -84,8 +85,6 @@
 import com.cloud.storage.dao.VMTemplateZoneDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.download.DownloadMonitor;
-import com.cloud.user.ResourceLimitService;
-import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.exception.CloudRuntimeException;
@@ -107,6 +106,8 @@
     @Inject
     TemplateDataStoreDao _templateStoreDao;
     @Inject
+    SnapshotDataStoreDao snapshotDataStoreDao;
+    @Inject
     EndPointSelector _epSelector;
     @Inject
     ConfigurationDao configDao;
@@ -117,21 +118,17 @@
     @Inject
     DefaultEndPointSelector _defaultEpSelector;
     @Inject
-    AccountDao _accountDao;
-    @Inject
-    ResourceLimitService _resourceLimitMgr;
-    @Inject
     DeployAsIsHelper deployAsIsHelper;
     @Inject
     HostDao hostDao;
     @Inject
     CommandExecLogDao _cmdExecLogDao;
     @Inject
-    StorageManager storageMgr;
-    @Inject
     protected SecondaryStorageVmDao _secStorageVmDao;
     @Inject
     AgentManager agentMgr;
+    @Inject
+    DataStoreManager dataStoreManager;
 
     protected String _proxy = null;
 
@@ -192,6 +189,12 @@
                 LOGGER.debug("Downloading volume to data store " + dataStore.getId());
             }
             _downloadMonitor.downloadVolumeToStorage(data, caller);
+        } else if (data.getType() == DataObjectType.SNAPSHOT) {
+            caller.setCallback(caller.getTarget().createSnapshotAsyncCallback(null, null));
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Downloading volume to data store " + dataStore.getId());
+            }
+            _downloadMonitor.downloadSnapshotToStorage(data, caller);
         }
     }
 
@@ -313,6 +316,53 @@
         return null;
     }
 
+    protected Void createSnapshotAsyncCallback(AsyncCallbackDispatcher<? extends BaseImageStoreDriverImpl, DownloadAnswer> callback, CreateContext<CreateCmdResult> context) {
+        DownloadAnswer answer = callback.getResult();
+        DataObject obj = context.data;
+        DataStore store = obj.getDataStore();
+
+        SnapshotDataStoreVO snapshotStoreVO = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), obj.getId());
+        if (snapshotStoreVO != null) {
+            if (VMTemplateStorageResourceAssoc.Status.DOWNLOADED.equals(snapshotStoreVO.getDownloadState())) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Snapshot is already in DOWNLOADED state, ignore further incoming DownloadAnswer");
+                }
+                return null;
+            }
+            SnapshotDataStoreVO updateBuilder = snapshotDataStoreDao.createForUpdate();
+            updateBuilder.setDownloadPercent(answer.getDownloadPct());
+            updateBuilder.setDownloadState(answer.getDownloadStatus());
+            updateBuilder.setLastUpdated(new Date());
+            updateBuilder.setErrorString(answer.getErrorString());
+            updateBuilder.setJobId(answer.getJobId());
+            updateBuilder.setLocalDownloadPath(answer.getDownloadPath());
+            updateBuilder.setInstallPath(answer.getInstallPath());
+            updateBuilder.setSize(answer.getTemplateSize());
+            updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize());
+            snapshotDataStoreDao.update(snapshotStoreVO.getId(), updateBuilder);
+        }
+
+        AsyncCompletionCallback<CreateCmdResult> caller = context.getParentCallback();
+
+        if (List.of(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR,
+                VMTemplateStorageResourceAssoc.Status.ABANDONED,
+                VMTemplateStorageResourceAssoc.Status.UNKNOWN).contains(answer.getDownloadStatus())) {
+            CreateCmdResult result = new CreateCmdResult(null, null);
+            result.setSuccess(false);
+            result.setResult(answer.getErrorString());
+            caller.complete(result);
+            String msg = "Failed to copy snapshot: " + obj.getUuid() + " with error: " + answer.getErrorString();
+            Long zoneId = dataStoreManager.getStoreZoneId(store.getId(), store.getRole());
+            _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED,
+                    zoneId, null, msg, msg);
+            LOGGER.error(msg);
+        } else if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
+            CreateCmdResult result = new CreateCmdResult(null, null);
+            caller.complete(result);
+        }
+        return null;
+    }
+
     @Override
     public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
         CommandResult result = new CommandResult();
@@ -331,7 +381,7 @@
                 result.setResult(answer.getDetails());
             }
         } catch (Exception ex) {
-            LOGGER.debug("Unable to destoy " + data.getType().toString() + ": " + data.getId(), ex);
+            LOGGER.debug("Unable to destroy " + data.getType().toString() + ": " + data.getId(), ex);
             result.setResult(ex.toString());
         }
         callback.complete(result);
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreProviderManager.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreProviderManager.java
index 7e2f720..39f42e8 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreProviderManager.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreProviderManager.java
@@ -80,4 +80,8 @@
     List<DataStore> listImageStoresWithFreeCapacity(List<DataStore> imageStores);
 
     List<DataStore> orderImageStoresOnFreeCapacity(List<DataStore> imageStores);
+
+    List<DataStore> listImageStoresFilteringByZoneIds(Long... zoneIds);
+
+    long getImageStoreZoneId(long dataStoreId);
 }
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java
index ef3d20a..cb14506 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java
@@ -70,6 +70,8 @@
     private SearchBuilder<TemplateDataStoreVO> downloadTemplateSearch;
     private SearchBuilder<TemplateDataStoreVO> uploadTemplateStateSearch;
     private SearchBuilder<TemplateDataStoreVO> directDownloadTemplateSeach;
+    private SearchBuilder<TemplateDataStoreVO> imageStoreAndInstallPathSearch;
+    private SearchBuilder<TemplateDataStoreVO> storeIdAndTemplateIdsSearch;
     private SearchBuilder<VMTemplateVO> templateOnlySearch;
     private static final String EXPIRE_DOWNLOAD_URLS_FOR_ZONE = "update template_store_ref set download_url_created=? where download_url_created is not null and store_id in (select id from image_store where data_center_id=?)";
 
@@ -163,6 +165,16 @@
         uploadTemplateStateSearch.and("destroyed", uploadTemplateStateSearch.entity().getDestroyed(), SearchCriteria.Op.EQ);
         uploadTemplateStateSearch.done();
 
+        imageStoreAndInstallPathSearch = createSearchBuilder();
+        imageStoreAndInstallPathSearch.and("store_id", imageStoreAndInstallPathSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
+        imageStoreAndInstallPathSearch.and("install_pathIN", imageStoreAndInstallPathSearch.entity().getInstallPath(), SearchCriteria.Op.IN);
+        imageStoreAndInstallPathSearch.done();
+
+        storeIdAndTemplateIdsSearch = createSearchBuilder();
+        storeIdAndTemplateIdsSearch.and("store_id", storeIdAndTemplateIdsSearch.entity().getDataStoreId(), Op.EQ);
+        storeIdAndTemplateIdsSearch.and("template_idIN", storeIdAndTemplateIdsSearch.entity().getTemplateId(), Op.IN);
+        storeIdAndTemplateIdsSearch.done();
+
         return true;
     }
 
@@ -302,7 +314,7 @@
 
     @Override
     public List<TemplateDataStoreVO> listByTemplateZoneDownloadStatus(long templateId, Long zoneId, Status... status) {
-        // get all elgible image stores
+        // get all eligible image stores
         List<DataStore> imgStores = _storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
         if (imgStores != null) {
             List<TemplateDataStoreVO> result = new ArrayList<TemplateDataStoreVO>();
@@ -329,7 +341,7 @@
 
     @Override
     public TemplateDataStoreVO findByTemplateZoneDownloadStatus(long templateId, Long zoneId, Status... status) {
-        // get all elgible image stores
+        // get all eligible image stores
         List<DataStore> imgStores = _storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
         if (imgStores != null) {
             for (DataStore store : imgStores) {
@@ -345,7 +357,7 @@
 
     @Override
     public TemplateDataStoreVO findByTemplateZoneStagingDownloadStatus(long templateId, Long zoneId, Status... status) {
-        // get all elgible image stores
+        // get all eligible image stores
         List<DataStore> cacheStores = _storeMgr.getImageCacheStores(new ZoneScope(zoneId));
         if (cacheStores != null) {
             for (DataStore store : cacheStores) {
@@ -436,7 +448,7 @@
 
     @Override
     public TemplateDataStoreVO findByTemplateZone(long templateId, Long zoneId, DataStoreRole role) {
-        // get all elgible image stores
+        // get all eligible image stores
         List<DataStore> imgStores = null;
         if (role == DataStoreRole.Image) {
             imgStores = _storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
@@ -558,6 +570,29 @@
     }
 
     @Override
+    public List<TemplateDataStoreVO> listByStoreIdAndInstallPaths(long storeId, List<String> installPaths) {
+        if (CollectionUtils.isEmpty(installPaths)) {
+            return Collections.emptyList();
+        }
+
+        SearchCriteria<TemplateDataStoreVO> sc = imageStoreAndInstallPathSearch.create();
+        sc.setParameters("store_id", storeId);
+        sc.setParameters("install_pathIN", installPaths.toArray());
+        return listBy(sc);
+    }
+
+    @Override
+    public List<TemplateDataStoreVO> listByStoreIdAndTemplateIds(long storeId, List<Long> templateIds) {
+        if (CollectionUtils.isEmpty(templateIds)) {
+            return Collections.emptyList();
+        }
+        SearchCriteria<TemplateDataStoreVO> sc = storeIdAndTemplateIdsSearch.create();
+        sc.setParameters("store_id", storeId);
+        sc.setParameters("template_idIN", templateIds.toArray());
+        return listBy(sc);
+    }
+
+    @Override
     public void expireDnldUrlsForZone(Long dcId){
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         PreparedStatement pstmt = null;
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java
index dca2e9a..dcdc9ea 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java
@@ -18,6 +18,7 @@
 
 import java.sql.PreparedStatement;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +28,7 @@
 
 import com.cloud.utils.db.Filter;
 import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
@@ -60,12 +62,13 @@
     private SearchBuilder<VolumeDataStoreVO> uploadVolumeSearch;
     private SearchBuilder<VolumeVO> volumeOnlySearch;
     private SearchBuilder<VolumeDataStoreVO> uploadVolumeStateSearch;
+    private SearchBuilder<VolumeDataStoreVO> imageStoreAndInstallPathSearch;
     private static final String EXPIRE_DOWNLOAD_URLS_FOR_ZONE = "update volume_store_ref set download_url_created=? where download_url_created is not null and store_id in (select id from image_store where data_center_id=?)";
 
     @Inject
     DataStoreManager storeMgr;
     @Inject
-    VolumeDao volumeDao;
+    VolumeDao volumeDao;;
 
     @Override
     public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
@@ -118,6 +121,11 @@
         uploadVolumeStateSearch.join("volumeOnlySearch", volumeOnlySearch, volumeOnlySearch.entity().getId(), uploadVolumeStateSearch.entity().getVolumeId(), JoinType.LEFT);
         uploadVolumeStateSearch.and("destroyed", uploadVolumeStateSearch.entity().getDestroyed(), SearchCriteria.Op.EQ);
         uploadVolumeStateSearch.done();
+
+        imageStoreAndInstallPathSearch = createSearchBuilder();
+        imageStoreAndInstallPathSearch.and("store_id", imageStoreAndInstallPathSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
+        imageStoreAndInstallPathSearch.and("install_pathIN", imageStoreAndInstallPathSearch.entity().getInstallPath(), SearchCriteria.Op.IN);
+        imageStoreAndInstallPathSearch.done();
         return true;
     }
 
@@ -340,6 +348,18 @@
     }
 
     @Override
+    public List<VolumeDataStoreVO> listByStoreIdAndInstallPaths(Long storeId, List<String> paths) {
+        if (CollectionUtils.isEmpty(paths)) {
+            return Collections.emptyList();
+        }
+
+        SearchCriteria<VolumeDataStoreVO> sc =  imageStoreAndInstallPathSearch.create();
+        sc.setParameters("store_id", storeId);
+        sc.setParameters("install_pathIN", paths.toArray());
+        return listBy(sc);
+    }
+
+    @Override
     public List<VolumeDataStoreVO> listUploadedVolumesByStoreId(long id) {
         SearchCriteria<VolumeDataStoreVO> sc = uploadVolumeSearch.create();
         sc.setParameters("store_id", id);
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/object/BaseObjectStoreDriverImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/BaseObjectStoreDriverImpl.java
new file mode 100644
index 0000000..e6027a1
--- /dev/null
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/BaseObjectStoreDriverImpl.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.storage.object;
+
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.host.Host;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.framework.async.AsyncRpcContext;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+
+public abstract class BaseObjectStoreDriverImpl implements ObjectStoreDriver {
+    private static final Logger LOGGER = Logger.getLogger(BaseObjectStoreDriverImpl.class);
+
+    @Override
+    public Map<String, String> getCapabilities() {
+        return null;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data) {
+        return null;
+    }
+
+    protected class CreateContext<T> extends AsyncRpcContext<T> {
+        final DataObject data;
+
+        public CreateContext(AsyncCompletionCallback<T> callback, DataObject data) {
+            super(callback);
+            this.data = data;
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
+    }
+
+    @Override
+    public void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
+        copyAsync(srcData, destData, callback);
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData) {
+        return false;
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
+    }
+}
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/object/ObjectStoreDriver.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/ObjectStoreDriver.java
new file mode 100644
index 0000000..4953b9b
--- /dev/null
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/ObjectStoreDriver.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.storage.object;
+
+import com.amazonaws.services.s3.model.AccessControlList;
+import com.amazonaws.services.s3.model.BucketPolicy;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ObjectStoreDriver extends DataStoreDriver {
+    Bucket createBucket(Bucket bucket, boolean objectLock);
+
+    List<Bucket> listBuckets(long storeId);
+
+    boolean deleteBucket(String bucketName, long storeId);
+
+    AccessControlList getBucketAcl(String bucketName, long storeId);
+
+    void setBucketAcl(String bucketName, AccessControlList acl, long storeId);
+
+    void setBucketPolicy(String bucketName, String policyType, long storeId);
+
+    BucketPolicy getBucketPolicy(String bucketName, long storeId);
+
+    void deleteBucketPolicy(String bucketName, long storeId);
+
+    boolean createUser(long accountId, long storeId);
+
+    boolean setBucketEncryption(String bucketName, long storeId);
+
+    boolean deleteBucketEncryption(String bucketName, long storeId);
+
+
+    boolean setBucketVersioning(String bucketName, long storeId);
+
+    boolean deleteBucketVersioning(String bucketName, long storeId);
+
+    void setBucketQuota(String bucketName, long storeId, long size);
+
+    Map<String, Long> getAllBucketsUsage(long storeId);
+}
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.java
new file mode 100644
index 0000000..c58d801
--- /dev/null
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.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.storage.object.datastore;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailVO;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+@Component
+public class ObjectStoreHelper {
+    @Inject
+    ObjectStoreDao ObjectStoreDao;
+    @Inject
+    ObjectStoreDetailsDao ObjectStoreDetailsDao;
+
+    public ObjectStoreVO createObjectStore(Map<String, Object> params, Map<String, String> details) {
+        ObjectStoreVO store = new ObjectStoreVO();
+
+        store.setProviderName((String)params.get("providerName"));
+        store.setUuid(UUID.randomUUID().toString());
+        store.setUrl((String)params.get("url"));
+        store.setName((String)params.get("name"));
+
+        store = ObjectStoreDao.persist(store);
+
+        // persist details
+        if (details != null) {
+            Iterator<String> keyIter = details.keySet().iterator();
+            while (keyIter.hasNext()) {
+                String key = keyIter.next().toString();
+                String value = details.get(key);
+                ObjectStoreDetailVO detail = new ObjectStoreDetailVO(store.getId(), key, value);
+                ObjectStoreDetailsDao.persist(detail);
+            }
+        }
+        return store;
+    }
+
+    public boolean deleteObjectStore(long id) {
+        ObjectStoreVO store = ObjectStoreDao.findById(id);
+        if (store == null) {
+            throw new CloudRuntimeException("can't find Object store:" + id);
+        }
+
+        ObjectStoreDao.remove(id);
+        return true;
+    }
+}
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreProviderManager.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreProviderManager.java
new file mode 100644
index 0000000..b23f319
--- /dev/null
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreProviderManager.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.storage.object.datastore;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.ObjectStoreEntity;
+
+import java.util.List;
+
+public interface ObjectStoreProviderManager {
+    ObjectStoreEntity getObjectStore(String uuid);
+
+    List<DataStore> listObjectStores();
+
+    List<DataStore> listObjectStoreByProvider(String provider);
+
+    ObjectStoreEntity getObjectStore(long objectStoreId);
+
+    boolean registerDriver(String uuid, ObjectStoreDriver driver);
+
+}
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java
index c3379ad..fbb4a6e 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java
@@ -136,7 +136,7 @@
                 storageTags.add(tag);
             }
         }
-        dataStoreVO = dataStoreDao.persist(dataStoreVO, details, storageTags);
+        dataStoreVO = dataStoreDao.persist(dataStoreVO, details, storageTags, params.isTagARule());
         return dataStoreMgr.getDataStore(dataStoreVO.getId(), DataStoreRole.Primary);
     }
 
diff --git a/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/module.properties b/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/module.properties
index 6c96e91..7beaf83 100644
--- a/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/module.properties
+++ b/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-allocator
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/engine/storage/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/StrategyPriorityTest.java b/engine/storage/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/StrategyPriorityTest.java
index bddd5a2..493ea08 100644
--- a/engine/storage/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/StrategyPriorityTest.java
+++ b/engine/storage/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/StrategyPriorityTest.java
@@ -25,14 +25,17 @@
 import java.util.List;
 import java.util.Map;
 
-import org.junit.Test;
-
-import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
 import org.apache.cloudstack.storage.helper.StorageStrategyFactoryImpl;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.host.Host;
 import com.cloud.storage.Snapshot;
 
+
+@RunWith(MockitoJUnitRunner.class)
 public class StrategyPriorityTest {
 
     @Test
@@ -42,31 +45,31 @@
         SnapshotStrategy hyperStrategy = mock(SnapshotStrategy.class);
         SnapshotStrategy highestStrategy = mock(SnapshotStrategy.class);
 
-        doReturn(StrategyPriority.CANT_HANDLE).when(cantHandleStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
-        doReturn(StrategyPriority.DEFAULT).when(defaultStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
-        doReturn(StrategyPriority.HYPERVISOR).when(hyperStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
-        doReturn(StrategyPriority.HIGHEST).when(highestStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
+        doReturn(StrategyPriority.CANT_HANDLE).when(cantHandleStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
+        doReturn(StrategyPriority.DEFAULT).when(defaultStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
+        doReturn(StrategyPriority.HYPERVISOR).when(hyperStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
+        doReturn(StrategyPriority.HIGHEST).when(highestStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
 
-        List<SnapshotStrategy> strategies = new ArrayList<SnapshotStrategy>(5);
+        List<SnapshotStrategy> strategies = new ArrayList<>(5);
         SnapshotStrategy strategy = null;
 
         StorageStrategyFactoryImpl factory = new StorageStrategyFactoryImpl();
         factory.setSnapshotStrategies(strategies);
 
         strategies.add(cantHandleStrategy);
-        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
+        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
         assertEquals("A strategy was found when it shouldn't have been.", null, strategy);
 
         strategies.add(defaultStrategy);
-        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
+        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
         assertEquals("Default strategy was not picked.", defaultStrategy, strategy);
 
         strategies.add(hyperStrategy);
-        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
+        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
         assertEquals("Hypervisor strategy was not picked.", hyperStrategy, strategy);
 
         strategies.add(highestStrategy);
-        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
+        strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
         assertEquals("Highest strategy was not picked.", highestStrategy, strategy);
     }
 
diff --git a/engine/storage/src/test/java/org/apache/cloudstack/storage/BaseTypeTest.java b/engine/storage/src/test/java/org/apache/cloudstack/storage/BaseTypeTest.java
index 48759da..379ebfa 100644
--- a/engine/storage/src/test/java/org/apache/cloudstack/storage/BaseTypeTest.java
+++ b/engine/storage/src/test/java/org/apache/cloudstack/storage/BaseTypeTest.java
@@ -32,8 +32,8 @@
 
     @Test
     public void testIsSameTypeAs() {
-        Assert.assertTrue("'a' and 'A' should be considdered the same type", new TestType("a").isSameTypeAs("A"));
-        Assert.assertTrue("'B' and 'b' should be considdered the same address", new TestType("B").isSameTypeAs(new TestType("b")));
+        Assert.assertTrue("'a' and 'A' should be considered the same type", new TestType("a").isSameTypeAs("A"));
+        Assert.assertTrue("'B' and 'b' should be considered the same address", new TestType("B").isSameTypeAs(new TestType("b")));
     }
     class TestType extends BaseType {
         String content;
diff --git a/engine/storage/src/test/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocatorTest.java b/engine/storage/src/test/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocatorTest.java
index 58b6fc9..cddbc9e 100644
--- a/engine/storage/src/test/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocatorTest.java
+++ b/engine/storage/src/test/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocatorTest.java
@@ -17,13 +17,13 @@
 package org.apache.cloudstack.storage.allocator;
 
 
-import com.cloud.deploy.DeploymentPlan;
-import com.cloud.deploy.DeploymentPlanner;
-import com.cloud.storage.Storage;
-import com.cloud.storage.StoragePool;
-import com.cloud.user.Account;
-import com.cloud.vm.DiskProfile;
-import com.cloud.vm.VirtualMachineProfile;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.junit.After;
 import org.junit.Assert;
@@ -34,10 +34,14 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import com.cloud.deploy.DeploymentPlan;
+import com.cloud.deploy.DeploymentPlanner;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.Account;
+import com.cloud.vm.DiskProfile;
+import com.cloud.vm.VirtualMachineProfile;
 
 @RunWith(MockitoJUnitRunner.class)
 public class AbstractStoragePoolAllocatorTest {
@@ -51,6 +55,9 @@
     Account account;
     private List<StoragePool> pools;
 
+    @Mock
+    VolumeDao volumeDao;
+
     @Before
     public void setUp() {
         pools = new ArrayList<>();
@@ -84,6 +91,29 @@
     }
 
     @Test
+    public void reorderStoragePoolsBasedOnAlgorithm_userdispersing_reorder_check() {
+        allocator.allocationAlgorithm = "userdispersing";
+        allocator.volumeDao = volumeDao;
+
+        when(plan.getDataCenterId()).thenReturn(1l);
+        when(plan.getPodId()).thenReturn(1l);
+        when(plan.getClusterId()).thenReturn(1l);
+        when(account.getAccountId()).thenReturn(1l);
+        List<Long> poolIds = new ArrayList<>();
+        poolIds.add(1l);
+        poolIds.add(9l);
+        when(volumeDao.listPoolIdsByVolumeCount(1l,1l,1l,1l)).thenReturn(poolIds);
+
+        List<StoragePool> reorderedPools = allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
+        Assert.assertEquals(poolIds.size(),reorderedPools.size());
+
+        Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
+        Mockito.verify(allocator, Mockito.times(1)).reorderPoolsByNumberOfVolumes(plan, pools, account);
+        Mockito.verify(allocator, Mockito.times(0)).reorderRandomPools(pools);
+        Mockito.verify(volumeDao, Mockito.times(1)).listPoolIdsByVolumeCount(1l,1l,1l,1l);
+    }
+
+    @Test
     public void reorderStoragePoolsBasedOnAlgorithm_firstfitleastconsumed() {
         allocator.allocationAlgorithm = "firstfitleastconsumed";
         Mockito.doReturn(pools).when(allocator).reorderPoolsByCapacity(plan, pools);
@@ -107,7 +137,7 @@
 class MockStorapoolAllocater extends AbstractStoragePoolAllocator {
 
     @Override
-    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, DeploymentPlanner.ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
+    protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, DeploymentPlanner.ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
         return null;
     }
 }
diff --git a/engine/storage/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java b/engine/storage/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java
index b3b9efc..cea6ac2 100644
--- a/engine/storage/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java
+++ b/engine/storage/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java
@@ -19,12 +19,6 @@
 
 package org.apache.cloudstack.storage.datastore.db;
 
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.storage.DataStoreRole;
-import com.cloud.storage.SnapshotVO;
-import com.cloud.storage.dao.SnapshotDao;
-import com.cloud.utils.db.SearchBuilder;
-import com.cloud.utils.db.SearchCriteria;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -33,6 +27,13 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
 @RunWith(MockitoJUnitRunner.class)
 public class SnapshotDataStoreDaoImplTest {
 
@@ -61,17 +62,15 @@
 
     @Test
     public void validateExpungeReferenceBySnapshotIdAndDataStoreRoleNullReference(){
-        Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
-        Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findOneBy(searchCriteriaMock);
-        Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, DataStoreRole.Image));
+        Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
+        Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, 1L, DataStoreRole.Image));
     }
 
     @Test
     public void validateExpungeReferenceBySnapshotIdAndDataStoreRole(){
-        Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
-        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findOneBy(searchCriteriaMock);
+        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
         Mockito.doReturn(true).when(snapshotDataStoreDaoImplSpy).expunge(Mockito.anyLong());
-        Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, DataStoreRole.Image));
+        Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, 1L, DataStoreRole.Image));
     }
 
     @Test
@@ -112,33 +111,30 @@
 
     @Test
     public void expungeReferenceBySnapshotIdAndDataStoreRoleTestSnapshotDataStoreIsNullReturnTrue() {
-        Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
-        Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findOneBy(Mockito.any());
+        Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
 
         for (DataStoreRole value : DataStoreRole.values()) {
-            Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, value));
+            Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, 1, value));
         }
     }
 
     @Test
     public void expungeReferenceBySnapshotIdAndDataStoreRoleTestSnapshotDataStoreIsNotNullAndExpungeIsTrueReturnTrue() {
-        Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
-        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findOneBy(Mockito.any());
+        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
         Mockito.doReturn(true).when(snapshotDataStoreDaoImplSpy).expunge(Mockito.anyLong());
 
         for (DataStoreRole value : DataStoreRole.values()) {
-            Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, value));
+            Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, 1, value));
         }
     }
 
     @Test
     public void expungeReferenceBySnapshotIdAndDataStoreRoleTestSnapshotDataStoreIsNotNullAndExpungeIsFalseReturnTrue() {
-        Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
-        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findOneBy(Mockito.any());
+        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
         Mockito.doReturn(false).when(snapshotDataStoreDaoImplSpy).expunge(Mockito.anyLong());
 
         for (DataStoreRole value : DataStoreRole.values()) {
-            Assert.assertFalse(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, value));
+            Assert.assertFalse(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, 1, value));
         }
     }
 
diff --git a/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImplTest.java b/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImplTest.java
index 0949dd9..6027cfa 100644
--- a/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImplTest.java
+++ b/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImplTest.java
@@ -16,14 +16,15 @@
 // under the License.
 package org.apache.cloudstack.storage.image.db;
 
-import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import java.util.Arrays;
+import java.util.List;
+
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.List;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 
 public class TemplateDataStoreDaoImplTest {
 
diff --git a/engine/storage/volume/pom.xml b/engine/storage/volume/pom.xml
index 52b82f7..c103903 100644
--- a/engine/storage/volume/pom.xml
+++ b/engine/storage/volume/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-engine</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java
index b799c8b..59ac995 100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java
@@ -85,4 +85,13 @@
     public boolean registerHostListener(String providerName, HypervisorHostListener listener) {
         return storageMgr.registerHostListener(providerName, listener);
     }
+
+    @Override
+    public long getPrimaryDataStoreZoneId(long dataStoreId) {
+        StoragePoolVO dataStoreVO = dataStoreDao.findByIdIncludingRemoved(dataStoreId);
+        if (dataStoreVO == null) {
+            throw new CloudRuntimeException("Unable to locate datastore with id " + dataStoreId);
+        }
+        return dataStoreVO.getDataCenterId();
+    }
 }
diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java
index e344a87..a453f2d 100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java
@@ -57,6 +57,12 @@
 
 public class DefaultHostListener implements HypervisorHostListener {
     private static final Logger s_logger = Logger.getLogger(DefaultHostListener.class);
+
+    /**
+     * Wait time for modify storage pool command to complete. We should wait for 5 minutes for the command to complete.
+     * This should ideally be externalised as a global configuration parameter in the future (See #8506).
+     **/
+    private final int modifyStoragePoolCommandWait = 300; // 5 minutes
     @Inject
     AgentManager agentMgr;
     @Inject
@@ -84,7 +90,6 @@
     @Inject
     NetworkDao networkDao;
 
-
     @Override
     public boolean hostAdded(long hostId) {
         return true;
@@ -121,6 +126,9 @@
     public boolean hostConnect(long hostId, long poolId) throws StorageConflictException {
         StoragePool pool = (StoragePool) this.dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
         ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, pool);
+        cmd.setWait(modifyStoragePoolCommandWait);
+        s_logger.debug(String.format("Sending modify storage pool command to agent: %d for storage pool: %d with timeout %d seconds",
+                hostId, poolId, cmd.getWait()));
         final Answer answer = agentMgr.easySend(hostId, cmd);
 
         if (answer == null) {
diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
index b0f426a..2e49698 100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
@@ -854,7 +854,7 @@
 
     @Override
     public boolean delete() {
-        return dataStore == null ? true : dataStore.delete(this);
+        return dataStore == null || dataStore.delete(this);
     }
 
     @Override
diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
index 03460d0..75f652d 100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
@@ -32,12 +32,10 @@
 
 import javax.inject.Inject;
 
-import org.apache.cloudstack.secret.dao.PassphraseDao;
-import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.storage.resource.StorageProcessor;
+import com.cloud.storage.VolumeApiServiceImpl;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
 import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
@@ -66,6 +64,7 @@
 import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 import org.apache.cloudstack.framework.async.AsyncRpcContext;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.secret.dao.PassphraseDao;
 import org.apache.cloudstack.storage.RemoteHostEndPoint;
 import org.apache.cloudstack.storage.command.CommandResult;
 import org.apache.cloudstack.storage.command.CopyCmdAnswer;
@@ -83,15 +82,20 @@
 import org.apache.cloudstack.storage.image.store.TemplateObject;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.ModifyTargetsCommand;
+import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
+import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
 import com.cloud.agent.api.storage.ListVolumeAnswer;
 import com.cloud.agent.api.storage.ListVolumeCommand;
 import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.to.DataObjectType;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.alert.AlertManager;
@@ -112,6 +116,7 @@
 import com.cloud.org.Grouping.AllocationState;
 import com.cloud.resource.ResourceState;
 import com.cloud.server.ManagementService;
+import com.cloud.storage.CheckAndRepairVolumePayload;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.RegisterVolumePayload;
 import com.cloud.storage.ScopeType;
@@ -122,13 +127,16 @@
 import com.cloud.storage.VMTemplateStoragePoolVO;
 import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
+import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.Volume.State;
 import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.storage.resource.StorageProcessor;
 import com.cloud.storage.snapshot.SnapshotApiService;
 import com.cloud.storage.snapshot.SnapshotManager;
 import com.cloud.storage.template.TemplateConstants;
@@ -142,7 +150,6 @@
 import com.cloud.utils.db.GlobalLock;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VirtualMachine;
-import org.apache.commons.lang3.StringUtils;
 
 @Component
 public class VolumeServiceImpl implements VolumeService {
@@ -198,7 +205,7 @@
     @Inject
     private VolumeOrchestrationService _volumeMgr;
     @Inject
-    private StorageManager _storageMgr;
+    protected StorageManager _storageMgr;
     @Inject
     private AnnotationDao annotationDao;
     @Inject
@@ -206,8 +213,6 @@
     @Inject
     private PassphraseDao passphraseDao;
 
-    private final static String SNAPSHOT_ID = "SNAPSHOT_ID";
-
     public VolumeServiceImpl() {
     }
 
@@ -895,9 +900,7 @@
      */
     private TemplateInfo createManagedTemplateVolume(TemplateInfo srcTemplateInfo, PrimaryDataStore destPrimaryDataStore) {
         // create a template volume on primary storage
-        AsyncCallFuture<VolumeApiResult> createTemplateFuture = new AsyncCallFuture<>();
         TemplateInfo templateOnPrimary = (TemplateInfo)destPrimaryDataStore.create(srcTemplateInfo, srcTemplateInfo.getDeployAsIsConfiguration());
-
         VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId(), srcTemplateInfo.getDeployAsIsConfiguration());
 
         if (templatePoolRef == null) {
@@ -910,7 +913,6 @@
         // At this point, we have an entry in the DB that points to our cached template.
         // We need to lock it as there may be other VMs that may get started using the same template.
         // We want to avoid having to create multiple cache copies of the same template.
-
         int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
         long templatePoolRefId = templatePoolRef.getId();
 
@@ -922,28 +924,27 @@
 
         try {
             // create a cache volume on the back-end
-
             templateOnPrimary.processEvent(Event.CreateOnlyRequested);
+            CreateAsyncCompleteCallback callback = new CreateAsyncCompleteCallback();
 
-            CreateVolumeContext<CreateCmdResult> createContext = new CreateVolumeContext<>(null, templateOnPrimary, createTemplateFuture);
-            AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> createCaller = AsyncCallbackDispatcher.create(this);
-
-            createCaller.setCallback(createCaller.getTarget().createManagedTemplateImageCallback(null, null)).setContext(createContext);
-
-            destPrimaryDataStore.getDriver().createAsync(destPrimaryDataStore, templateOnPrimary, createCaller);
-
-            VolumeApiResult result = createTemplateFuture.get();
-
-            if (result.isFailed()) {
-                String errMesg = result.getResult();
-
+            destPrimaryDataStore.getDriver().createAsync(destPrimaryDataStore, templateOnPrimary, callback);
+            // validate we got a good result back
+            if (callback.result == null || callback.result.isFailed()) {
+                String errMesg;
+                if (callback.result == null) {
+                    errMesg = "Unknown/unable to determine result";
+                } else {
+                    errMesg = callback.result.getResult();
+                }
+                templateOnPrimary.processEvent(Event.OperationFailed);
                 throw new CloudRuntimeException("Unable to create template " + templateOnPrimary.getId() + " on primary storage " + destPrimaryDataStore.getId() + ":" + errMesg);
             }
+
+            templateOnPrimary.processEvent(Event.OperationSuccessed);
+
         } catch (Throwable e) {
             s_logger.debug("Failed to create template volume on storage", e);
-
             templateOnPrimary.processEvent(Event.OperationFailed);
-
             throw new CloudRuntimeException(e.getMessage());
         } finally {
             _tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
@@ -952,6 +953,17 @@
         return templateOnPrimary;
     }
 
+    private static class CreateAsyncCompleteCallback implements AsyncCompletionCallback<CreateCmdResult> {
+
+        public CreateCmdResult result;
+
+        @Override
+        public void complete(CreateCmdResult result) {
+            this.result = result;
+        }
+
+    }
+
     /**
      * This function copies a template from secondary storage to a template volume
      * created on managed storage. This template volume will be used as a cache.
@@ -1477,6 +1489,16 @@
                 if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
                     copyTemplateToManagedTemplateVolume(srcTemplateInfo, templateOnPrimary, templatePoolRef, destPrimaryDataStore, destHost);
                 }
+            } catch (Exception e) {
+                if (templateOnPrimary != null) {
+                    templateOnPrimary.processEvent(Event.OperationFailed);
+                }
+                VolumeApiResult result = new VolumeApiResult(volumeInfo);
+                result.setResult(e.getLocalizedMessage());
+                result.setSuccess(false);
+                future.complete(result);
+                s_logger.warn("Failed to create template on primary storage", e);
+                return future;
             } finally {
                 if (lock != null) {
                     lock.unlock();
@@ -2758,6 +2780,62 @@
         return snapshot;
     }
 
+    @Override
+    public void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host) {
+        if (HypervisorType.KVM.equals(host.getHypervisorType()) && DataObjectType.VOLUME.equals(dataObject.getType())) {
+            VolumeInfo volumeInfo = volFactory.getVolume(dataObject.getId());
+            if (VolumeApiServiceImpl.AllowCheckAndRepairVolume.valueIn(volumeInfo.getPoolId())) {
+                s_logger.info(String.format("Trying to check and repair the volume %d", dataObject.getId()));
+                String repair = CheckAndRepairVolumeCmd.RepairValues.LEAKS.name().toLowerCase();
+                CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
+                volumeInfo.addPayload(payload);
+                checkAndRepairVolumeThroughHost(volumeInfo, host);
+            }
+        }
+    }
+
+    @Override
+    public Pair<String, String> checkAndRepairVolume(VolumeInfo volume) {
+        Long poolId = volume.getPoolId();
+        List<Long> hostIds = _storageMgr.getUpHostsInPool(poolId);
+        if (CollectionUtils.isEmpty(hostIds)) {
+            throw new CloudRuntimeException("Unable to find Up hosts to run the check volume command");
+        }
+        Collections.shuffle(hostIds);
+        Host host = _hostDao.findById(hostIds.get(0));
+
+        return checkAndRepairVolumeThroughHost(volume, host);
+
+    }
+
+    private Pair<String, String> checkAndRepairVolumeThroughHost(VolumeInfo volume, Host host) {
+        Long poolId = volume.getPoolId();
+        StoragePool pool = _storageMgr.getStoragePool(poolId);
+        CheckAndRepairVolumePayload payload = (CheckAndRepairVolumePayload) volume.getpayload();
+        CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
+                volume.getPassphrase(), volume.getEncryptFormat());
+
+        try {
+            grantAccess(volume, host, volume.getDataStore());
+            CheckAndRepairVolumeAnswer answer = (CheckAndRepairVolumeAnswer) _storageMgr.sendToPool(pool, new long[]{host.getId()}, command);
+            if (answer != null && answer.getResult()) {
+                s_logger.debug(String.format("Check volume response result: %s", answer.getDetails()));
+                return new Pair<>(answer.getVolumeCheckExecutionResult(), answer.getVolumeRepairExecutionResult());
+            } else {
+                String errMsg = (answer == null) ? null : answer.getDetails();
+                s_logger.debug(String.format("Failed to check and repair the volume with error %s", errMsg));
+            }
+
+        } catch (Exception e) {
+            s_logger.debug("sending check and repair volume command failed", e);
+        } finally {
+            revokeAccess(volume, host, volume.getDataStore());
+            command.clearPassphrase();
+        }
+
+        return null;
+    }
+
     // For managed storage on Xen and VMware, we need to potentially make space for hypervisor snapshots.
     // The disk offering can collect this information and pass it on to the volume that's about to be created.
     // Ex. if you want a 10 GB CloudStack volume to reside on managed storage on Xen, this leads to an SR
diff --git a/engine/storage/volume/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-volume-core-context.xml b/engine/storage/volume/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-volume-core-context.xml
index 860929c..a0cb6a7 100644
--- a/engine/storage/volume/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-volume-core-context.xml
+++ b/engine/storage/volume/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-volume-core-context.xml
@@ -46,4 +46,4 @@
     <bean id="primaryDataStoreProviderMgr"
         class="org.apache.cloudstack.storage.datastore.manager.PrimaryDataStoreProviderManagerImpl" />
  
-</beans>
\ No newline at end of file
+</beans>
diff --git a/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImplTest.java b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImplTest.java
new file mode 100644
index 0000000..d1118ed
--- /dev/null
+++ b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImplTest.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.storage.datastore.manager;
+
+
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+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 PrimaryDataStoreProviderManagerImplTest {
+
+    @Mock
+    PrimaryDataStoreDao primaryDataStoreDao;
+
+    @InjectMocks
+    PrimaryDataStoreProviderManagerImpl primaryDataStoreProviderManager = new PrimaryDataStoreProviderManagerImpl();
+    @Test
+    public void testGetImageStoreZoneId() {
+        final long storeId = 1L;
+        final long zoneId = 1L;
+        StoragePoolVO storagePoolVO = Mockito.mock(StoragePoolVO.class);
+        Mockito.when(storagePoolVO.getDataCenterId()).thenReturn(zoneId);
+        Mockito.when(primaryDataStoreDao.findByIdIncludingRemoved(storeId)).thenReturn(storagePoolVO);
+        long value = primaryDataStoreProviderManager.getPrimaryDataStoreZoneId(storeId);
+        Assert.assertEquals(zoneId, value);
+    }
+}
diff --git a/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/VolumeServiceTest.java b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/VolumeServiceTest.java
index ee4b77c..55ff2f6 100644
--- a/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/VolumeServiceTest.java
+++ b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/VolumeServiceTest.java
@@ -19,6 +19,15 @@
 
 package org.apache.cloudstack.storage.volume;
 
+import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
+import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.exception.StorageUnavailableException;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.storage.CheckAndRepairVolumePayload;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePool;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.snapshot.SnapshotManager;
@@ -26,6 +35,8 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
+
+import com.cloud.utils.Pair;
 import junit.framework.TestCase;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
@@ -66,14 +77,25 @@
     SnapshotManager snapshotManagerMock;
 
     @Mock
+    StorageManager storageManagerMock;
+
+    @Mock
     VolumeVO volumeVoMock;
 
+    @Mock
+    HostVO hostMock;
+
+    @Mock
+    HostDao hostDaoMock;
+
     @Before
     public void setup(){
         volumeServiceImplSpy = Mockito.spy(new VolumeServiceImpl());
         volumeServiceImplSpy.volFactory = volumeDataFactoryMock;
         volumeServiceImplSpy.volDao = volumeDaoMock;
         volumeServiceImplSpy.snapshotMgr = snapshotManagerMock;
+        volumeServiceImplSpy._storageMgr = storageManagerMock;
+        volumeServiceImplSpy._hostDao = hostDaoMock;
     }
 
     @Test(expected = InterruptedException.class)
@@ -210,4 +232,75 @@
         volumeServiceImplSpy.destroySourceVolumeAfterMigration(ObjectInDataStoreStateMachine.Event.DestroyRequested, null, volumeObject,
           volumeObject, true);
     }
+
+    @Test
+    public void testCheckAndRepairVolume() throws StorageUnavailableException {
+        VolumeInfo volume = Mockito.mock(VolumeInfo.class);
+        Mockito.when(volume.getPoolId()).thenReturn(1L);
+        StoragePool pool = Mockito.mock(StoragePool.class);
+        Mockito.when(storageManagerMock.getStoragePool(1L)).thenReturn(pool);
+        List<Long> hostIds = new ArrayList<>();
+        hostIds.add(1L);
+        Mockito.when(storageManagerMock.getUpHostsInPool(1L)).thenReturn(hostIds);
+        Mockito.when(hostMock.getId()).thenReturn(1L);
+        Mockito.when(hostDaoMock.findById(1L)).thenReturn(hostMock);
+
+        CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(null);
+        Mockito.when(volume.getpayload()).thenReturn(payload);
+        Mockito.when(volume.getPath()).thenReturn("cbac516a-0f1f-4559-921c-1a7c6c408ccf");
+        Mockito.when(volume.getPassphrase()).thenReturn(new byte[] {3, 1, 2, 3});
+        Mockito.when(volume.getEncryptFormat()).thenReturn("LUKS");
+
+        String checkResult = "{\n" +
+                "    \"image-end-offset\": 6442582016,\n" +
+                "    \"total-clusters\": 163840,\n" +
+                "    \"check-errors\": 0,\n" +
+                "    \"leaks\": 124,\n" +
+                "    \"allocated-clusters\": 98154,\n" +
+                "    \"filename\": \"/var/lib/libvirt/images/26be20c7-b9d0-43f6-a76e-16c70737a0e0\",\n" +
+                "    \"format\": \"qcow2\",\n" +
+                "    \"fragmented-clusters\": 96135\n" +
+                "}";
+
+        CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
+                volume.getPassphrase(), volume.getEncryptFormat());
+
+        CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, checkResult);
+        answer.setVolumeCheckExecutionResult(checkResult);
+        Mockito.when(storageManagerMock.sendToPool(pool, new long[]{1L}, command)).thenReturn(answer);
+
+        Pair<String, String> result = volumeServiceImplSpy.checkAndRepairVolume(volume);
+
+        Assert.assertEquals(result.first(), checkResult);
+        Assert.assertEquals(result.second(), null);
+    }
+
+    @Test
+    public void testCheckAndRepairVolumeWhenFailure() throws StorageUnavailableException {
+        VolumeInfo volume = Mockito.mock(VolumeInfo.class);
+        Mockito.when(volume.getPoolId()).thenReturn(1L);
+        StoragePool pool = Mockito.mock(StoragePool.class);
+        Mockito.when(storageManagerMock.getStoragePool(1L)).thenReturn(pool);
+        List<Long> hostIds = new ArrayList<>();
+        hostIds.add(1L);
+        Mockito.when(storageManagerMock.getUpHostsInPool(1L)).thenReturn(hostIds);
+        Mockito.when(hostMock.getId()).thenReturn(1L);
+        Mockito.when(hostDaoMock.findById(1L)).thenReturn(hostMock);
+
+        CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(null);
+        Mockito.when(volume.getpayload()).thenReturn(payload);
+        Mockito.when(volume.getPath()).thenReturn("cbac516a-0f1f-4559-921c-1a7c6c408ccf");
+        Mockito.when(volume.getPassphrase()).thenReturn(new byte[] {3, 1, 2, 3});
+        Mockito.when(volume.getEncryptFormat()).thenReturn("LUKS");
+
+        CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
+                volume.getPassphrase(), volume.getEncryptFormat());
+
+        CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, false, "Unable to execute qemu command");
+        Mockito.when(storageManagerMock.sendToPool(pool, new long[]{1L}, command)).thenReturn(answer);
+
+        Pair<String, String> result = volumeServiceImplSpy.checkAndRepairVolume(volume);
+
+        Assert.assertEquals(null, result);
+    }
 }
diff --git a/engine/storage/volume/src/test/resource/testContext.xml b/engine/storage/volume/src/test/resource/testContext.xml
index da2f5a2..7352b11 100644
--- a/engine/storage/volume/src/test/resource/testContext.xml
+++ b/engine/storage/volume/src/test/resource/testContext.xml
@@ -76,4 +76,4 @@
 
   <bean id="eventBus" class = "org.apache.cloudstack.framework.eventbus.EventBusBase" />
   
-</beans>
\ No newline at end of file
+</beans>
diff --git a/engine/userdata/cloud-init/pom.xml b/engine/userdata/cloud-init/pom.xml
new file mode 100644
index 0000000..82c3fc8
--- /dev/null
+++ b/engine/userdata/cloud-init/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<modelVersion>4.0.0</modelVersion>
+<artifactId>cloud-engine-userdata-cloud-init</artifactId>
+<name>Apache CloudStack Engine Cloud-Init Userdata Component</name>
+<parent>
+    <artifactId>cloud-engine</artifactId>
+    <groupId>org.apache.cloudstack</groupId>
+    <version>4.19.1.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+</parent>
+<dependencies>
+    <dependency>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloud-engine-userdata</artifactId>
+        <version>${project.version}</version>
+    </dependency>
+</dependencies>
+</project>
diff --git a/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java b/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java
new file mode 100644
index 0000000..65996f1
--- /dev/null
+++ b/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java
@@ -0,0 +1,286 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.userdata;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.zip.GZIPInputStream;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.sun.mail.util.BASE64DecoderStream;
+
+public class CloudInitUserDataProvider extends AdapterBase implements UserDataProvider {
+
+    protected enum FormatType {
+        CLOUD_CONFIG, BASH_SCRIPT, MIME, CLOUD_BOOTHOOK, INCLUDE_FILE
+    }
+
+    private static final String CLOUD_CONFIG_CONTENT_TYPE = "text/cloud-config";
+    private static final String BASH_SCRIPT_CONTENT_TYPE = "text/x-shellscript";
+    private static final String INCLUDE_FILE_CONTENT_TYPE = "text/x-include-url";
+    private static final String CLOUD_BOOTHOOK_CONTENT_TYPE = "text/cloud-boothook";
+
+    private static final Map<FormatType, String> formatContentTypeMap = Map.ofEntries(
+            Map.entry(FormatType.CLOUD_CONFIG, CLOUD_CONFIG_CONTENT_TYPE),
+            Map.entry(FormatType.BASH_SCRIPT, BASH_SCRIPT_CONTENT_TYPE),
+            Map.entry(FormatType.CLOUD_BOOTHOOK, CLOUD_BOOTHOOK_CONTENT_TYPE),
+            Map.entry(FormatType.INCLUDE_FILE, INCLUDE_FILE_CONTENT_TYPE)
+    );
+
+    private static final Logger LOGGER = Logger.getLogger(CloudInitUserDataProvider.class);
+
+    private static final Session session = Session.getDefaultInstance(new Properties());
+
+    @Override
+    public String getName() {
+        return "cloud-init";
+    }
+
+    protected boolean isGZipped(String encodedUserdata) {
+        if (StringUtils.isEmpty(encodedUserdata)) {
+            return false;
+        }
+        byte[] data = Base64.decodeBase64(encodedUserdata);
+        if (data.length < 2) {
+            return false;
+        }
+        int magic = data[0] & 0xff | ((data[1] << 8) & 0xff00);
+        return magic == GZIPInputStream.GZIP_MAGIC;
+    }
+
+    protected String extractUserDataHeader(String userdata) {
+        List<String> lines = Arrays.stream(userdata.split("\n"))
+                .filter(x -> (x.startsWith("#") && !x.startsWith("##")) || (x.startsWith("Content-Type:")))
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(lines)) {
+            throw new CloudRuntimeException("Failed to detect the user data format type as it " +
+                    "does not contain a header");
+        }
+        return lines.get(0);
+    }
+
+    protected FormatType mapUserDataHeaderToFormatType(String header) {
+        if (header.equalsIgnoreCase("#cloud-config")) {
+            return FormatType.CLOUD_CONFIG;
+        } else if (header.startsWith("#!")) {
+            return FormatType.BASH_SCRIPT;
+        } else if (header.equalsIgnoreCase("#cloud-boothook")) {
+            return FormatType.CLOUD_BOOTHOOK;
+        } else if (header.startsWith("#include")) {
+            return FormatType.INCLUDE_FILE;
+        } else if (header.startsWith("Content-Type:")) {
+            return FormatType.MIME;
+        } else {
+            String msg = String.format("Cannot recognise the user data format type from the header line: %s." +
+                    "Supported types are: cloud-config, bash script, cloud-boothook, include file or MIME", header);
+            LOGGER.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
+    /**
+     * Detect the user data type
+     * Reference: <a href="https://canonical-cloud-init.readthedocs-hosted.com/en/latest/explanation/format.html#user-data-formats" />
+     */
+    protected FormatType getUserDataFormatType(String userdata) {
+        if (StringUtils.isBlank(userdata)) {
+            String msg = "User data expected but provided empty user data";
+            LOGGER.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+
+        String header = extractUserDataHeader(userdata);
+        return mapUserDataHeaderToFormatType(header);
+    }
+
+    private String getContentType(String userData, FormatType formatType) throws MessagingException {
+        if (formatType == FormatType.MIME) {
+            NoIdMimeMessage msg = new NoIdMimeMessage(session, new ByteArrayInputStream(userData.getBytes()));
+            return msg.getContentType();
+        }
+        if (!formatContentTypeMap.containsKey(formatType)) {
+            throw new CloudRuntimeException(String.format("Cannot get the user data content type as " +
+                    "its format type %s is invalid", formatType.name()));
+        }
+        return formatContentTypeMap.get(formatType);
+    }
+
+    protected String getBodyPartContentAsString(BodyPart bodyPart) throws MessagingException, IOException {
+        Object content = bodyPart.getContent();
+        if (content instanceof BASE64DecoderStream) {
+            return new String(((BASE64DecoderStream)bodyPart.getContent()).readAllBytes());
+        } else if (content instanceof ByteArrayInputStream) {
+            return new String(((ByteArrayInputStream)bodyPart.getContent()).readAllBytes());
+        } else if (content instanceof String) {
+            return (String)bodyPart.getContent();
+        }
+        throw new CloudRuntimeException(String.format("Failed to get content for multipart data with content type: %s", getBodyPartContentType(bodyPart)));
+    }
+
+    private String getBodyPartContentType(BodyPart bodyPart) throws MessagingException {
+        String contentType = StringUtils.defaultString(bodyPart.getDataHandler().getContentType(), bodyPart.getContentType());
+        return  contentType.contains(";") ? contentType.substring(0, contentType.indexOf(';')) : contentType;
+    }
+
+    protected MimeBodyPart generateBodyPartMimeMessage(String userData, String contentType) throws MessagingException {
+        MimeBodyPart bodyPart = new MimeBodyPart();
+        bodyPart.setContent(userData, contentType);
+        bodyPart.addHeader("Content-Transfer-Encoding", "base64");
+        return bodyPart;
+    }
+
+    protected MimeBodyPart generateBodyPartMimeMessage(String userData, FormatType formatType) throws MessagingException {
+        return generateBodyPartMimeMessage(userData, getContentType(userData, formatType));
+    }
+
+    private Multipart getMessageContent(NoIdMimeMessage message) {
+        Multipart messageContent;
+        try {
+            messageContent = (MimeMultipart) message.getContent();
+        } catch (IOException | MessagingException e) {
+            messageContent = new MimeMultipart();
+        }
+        return messageContent;
+    }
+
+    private void addBodyPartToMultipart(Multipart existingMultipart, MimeBodyPart bodyPart) throws MessagingException, IOException {
+        boolean added = false;
+        final int existingCount = existingMultipart.getCount();
+        for (int j = 0; j < existingCount; ++j) {
+            MimeBodyPart existingBodyPart = (MimeBodyPart)existingMultipart.getBodyPart(j);
+            String existingContentType = getBodyPartContentType(existingBodyPart);
+            String newContentType = getBodyPartContentType(bodyPart);
+            if (existingContentType.equals(newContentType)) {
+                String existingContent = getBodyPartContentAsString(existingBodyPart);
+                String newContent = getBodyPartContentAsString(bodyPart);
+                // generating a combined content MimeBodyPart to replace
+                MimeBodyPart combinedBodyPart = generateBodyPartMimeMessage(
+                        simpleAppendSameFormatTypeUserData(existingContent, newContent), existingContentType);
+                existingMultipart.removeBodyPart(j);
+                existingMultipart.addBodyPart(combinedBodyPart, j);
+                added = true;
+                break;
+            }
+        }
+        if (!added) {
+            existingMultipart.addBodyPart(bodyPart);
+        }
+    }
+
+    private void addBodyPartsToMessageContentFromUserDataContent(Multipart existingMultipart,
+                                                                 NoIdMimeMessage msgFromUserdata) throws MessagingException, IOException {
+        MimeMultipart newMultipart = (MimeMultipart)msgFromUserdata.getContent();
+        final int existingCount = existingMultipart.getCount();
+        final int newCount = newMultipart.getCount();
+        for (int i = 0; i < newCount; ++i) {
+            BodyPart bodyPart = newMultipart.getBodyPart(i);
+            if (existingCount == 0) {
+                existingMultipart.addBodyPart(bodyPart);
+                continue;
+            }
+            addBodyPartToMultipart(existingMultipart, (MimeBodyPart)bodyPart);
+        }
+    }
+
+    private NoIdMimeMessage createMultipartMessageAddingUserdata(String userData, FormatType formatType,
+                                                           NoIdMimeMessage message) throws MessagingException, IOException {
+        NoIdMimeMessage newMessage = new NoIdMimeMessage(session);
+        Multipart messageContent = getMessageContent(message);
+
+        if (formatType == FormatType.MIME) {
+            NoIdMimeMessage msgFromUserdata = new NoIdMimeMessage(session, new ByteArrayInputStream(userData.getBytes()));
+            addBodyPartsToMessageContentFromUserDataContent(messageContent, msgFromUserdata);
+        } else {
+            MimeBodyPart part = generateBodyPartMimeMessage(userData, formatType);
+            addBodyPartToMultipart(messageContent, part);
+        }
+        newMessage.setContent(messageContent);
+        return newMessage;
+    }
+
+    private String simpleAppendSameFormatTypeUserData(String userData1, String userData2) {
+        return String.format("%s\n\n%s", userData1, userData2.substring(userData2.indexOf('\n')+1));
+    }
+
+    private void checkGzipAppend(String encodedUserData1, String encodedUserData2) {
+        if (isGZipped(encodedUserData1) || isGZipped(encodedUserData2)) {
+            throw new CloudRuntimeException("Gzipped user data can not be used together with other user data formats");
+        }
+    }
+
+    @Override
+    public String appendUserData(String encodedUserData1, String encodedUserData2) {
+        try {
+            checkGzipAppend(encodedUserData1, encodedUserData2);
+            String userData1 = new String(Base64.decodeBase64(encodedUserData1));
+            String userData2 = new String(Base64.decodeBase64(encodedUserData2));
+            FormatType formatType1 = getUserDataFormatType(userData1);
+            FormatType formatType2 = getUserDataFormatType(userData2);
+            if (formatType1.equals(formatType2) && List.of(FormatType.CLOUD_CONFIG, FormatType.BASH_SCRIPT).contains(formatType1)) {
+                return simpleAppendSameFormatTypeUserData(userData1, userData2);
+            }
+            NoIdMimeMessage message = new NoIdMimeMessage(session);
+            message = createMultipartMessageAddingUserdata(userData1, formatType1, message);
+            message = createMultipartMessageAddingUserdata(userData2, formatType2, message);
+            ByteArrayOutputStream output = new ByteArrayOutputStream();
+            message.writeTo(output);
+            return output.toString();
+        } catch (MessagingException | IOException | CloudRuntimeException e) {
+            String msg = String.format("Error attempting to merge user data as a multipart user data. " +
+                    "Reason: %s", e.getMessage());
+            LOGGER.error(msg, e);
+            throw new CloudRuntimeException(msg, e);
+        }
+    }
+
+    /* This is a wrapper class just to remove Message-ID header from the resultant
+       multipart data which may contain server details.
+     */
+    private class NoIdMimeMessage extends MimeMessage {
+        NoIdMimeMessage (Session session) {
+            super(session);
+        }
+        NoIdMimeMessage (Session session, InputStream is) throws MessagingException {
+            super(session, is);
+        }
+        @Override
+        protected void updateMessageID() throws MessagingException {
+            removeHeader("Message-ID");
+        }
+    }
+}
diff --git a/engine/userdata/cloud-init/src/main/resources/META-INF/cloudstack/core/spring-userdata-cloud-init-context.xml b/engine/userdata/cloud-init/src/main/resources/META-INF/cloudstack/core/spring-userdata-cloud-init-context.xml
new file mode 100644
index 0000000..742398e
--- /dev/null
+++ b/engine/userdata/cloud-init/src/main/resources/META-INF/cloudstack/core/spring-userdata-cloud-init-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+>
+    <bean id="cloudInitUserDataProvider" class="org.apache.cloudstack.userdata.CloudInitUserDataProvider">
+        <property name="name" value="cloud-init" />
+    </bean>
+</beans>
diff --git a/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java b/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java
new file mode 100644
index 0000000..4ca9fb7
--- /dev/null
+++ b/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java
@@ -0,0 +1,206 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.userdata;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+import java.util.zip.GZIPOutputStream;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.apache.commons.codec.binary.Base64;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class CloudInitUserDataProviderTest {
+
+    private final CloudInitUserDataProvider provider = new CloudInitUserDataProvider();
+    private final static String CLOUD_CONFIG_USERDATA = "## template: jinja\n" +
+            "#cloud-config\n" +
+            "runcmd:\n" +
+            "   - echo 'TestVariable {{ ds.meta_data.variable1 }}' >> /tmp/variable\n" +
+            "   - echo 'Hostname {{ ds.meta_data.public_hostname }}' > /tmp/hostname";
+    private final static String CLOUD_CONFIG_USERDATA1 = "#cloud-config\n" +
+            "password: atomic\n" +
+            "chpasswd: { expire: False }\n" +
+            "ssh_pwauth: True";
+    private final static String SHELL_SCRIPT_USERDATA = "#!/bin/bash\n" +
+            "date > /provisioned";
+    private final static String SHELL_SCRIPT_USERDATA1 = "#!/bin/bash\n" +
+            "mkdir /tmp/test";
+    private final static String SINGLE_BODYPART_CLOUDCONFIG_MULTIPART_USERDATA =
+            "Content-Type: multipart/mixed; boundary=\"//\"\n" +
+            "MIME-Version: 1.0\n" +
+            "\n" +
+            "--//\n" +
+            "Content-Type: text/cloud-config; charset=\"us-ascii\"\n" +
+            "MIME-Version: 1.0\n" +
+            "Content-Transfer-Encoding: 7bit\n" +
+            "Content-Disposition: attachment; filename=\"cloud-config.txt\"\n" +
+            "\n" +
+            "#cloud-config\n" +
+            "\n" +
+            "# Upgrade the instance on first boot\n" +
+            "# (ie run apt-get upgrade)\n" +
+            "#\n" +
+            "# Default: false\n" +
+            "# Aliases: apt_upgrade\n" +
+            "package_upgrade: true";
+    private static final Session session = Session.getDefaultInstance(new Properties());
+
+    @Test
+    public void testGetUserDataFormatType() {
+        CloudInitUserDataProvider.FormatType type = provider.getUserDataFormatType(CLOUD_CONFIG_USERDATA);
+        Assert.assertEquals(CloudInitUserDataProvider.FormatType.CLOUD_CONFIG, type);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testGetUserDataFormatTypeNoHeader() {
+        String userdata = "password: password\nchpasswd: { expire: False }\nssh_pwauth: True";
+        provider.getUserDataFormatType(userdata);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testGetUserDataFormatTypeInvalidType() {
+        String userdata = "#invalid-type\n" +
+                "password: password\nchpasswd: { expire: False }\nssh_pwauth: True";
+        provider.getUserDataFormatType(userdata);
+    }
+
+    private MimeMultipart getCheckedMultipartFromMultipartData(String multipartUserData, int count) {
+        MimeMultipart multipart = null;
+        Assert.assertTrue(multipartUserData.contains("Content-Type: multipart"));
+        try {
+            MimeMessage msgFromUserdata = new MimeMessage(session,
+                    new ByteArrayInputStream(multipartUserData.getBytes()));
+            multipart = (MimeMultipart)msgFromUserdata.getContent();
+            Assert.assertEquals(count, multipart.getCount());
+        } catch (MessagingException | IOException e) {
+            Assert.fail(String.format("Failed with exception, %s", e.getMessage()));
+        }
+        return multipart;
+    }
+
+    @Test
+    public void testAppendUserData() {
+        String multipartUserData = provider.appendUserData(Base64.encodeBase64String(CLOUD_CONFIG_USERDATA1.getBytes()),
+                Base64.encodeBase64String(SHELL_SCRIPT_USERDATA.getBytes()));
+        getCheckedMultipartFromMultipartData(multipartUserData, 2);
+    }
+
+    @Test
+    public void testAppendSameShellScriptTypeUserData() {
+        String result = SHELL_SCRIPT_USERDATA + "\n\n" +
+                SHELL_SCRIPT_USERDATA1.replace("#!/bin/bash\n", "");
+        String appendUserData = provider.appendUserData(Base64.encodeBase64String(SHELL_SCRIPT_USERDATA.getBytes()),
+                Base64.encodeBase64String(SHELL_SCRIPT_USERDATA1.getBytes()));
+        Assert.assertEquals(result, appendUserData);
+    }
+
+    @Test
+    public void testAppendSameCloudConfigTypeUserData() {
+        String result = CLOUD_CONFIG_USERDATA + "\n\n" +
+                CLOUD_CONFIG_USERDATA1.replace("#cloud-config\n", "");
+        String appendUserData = provider.appendUserData(Base64.encodeBase64String(CLOUD_CONFIG_USERDATA.getBytes()),
+                Base64.encodeBase64String(CLOUD_CONFIG_USERDATA1.getBytes()));
+        Assert.assertEquals(result, appendUserData);
+    }
+
+    @Test
+    public void testAppendUserDataMIMETemplateData() {
+        String multipartUserData = provider.appendUserData(
+                Base64.encodeBase64String(SINGLE_BODYPART_CLOUDCONFIG_MULTIPART_USERDATA.getBytes()),
+                Base64.encodeBase64String(SHELL_SCRIPT_USERDATA.getBytes()));
+        getCheckedMultipartFromMultipartData(multipartUserData, 2);
+    }
+
+    @Test
+    public void testAppendUserDataExistingMultipartWithSameType() {
+        String templateData = provider.appendUserData(Base64.encodeBase64String(CLOUD_CONFIG_USERDATA1.getBytes()),
+                Base64.encodeBase64String(SHELL_SCRIPT_USERDATA.getBytes()));
+        String multipartUserData = provider.appendUserData(Base64.encodeBase64String(templateData.getBytes()),
+                Base64.encodeBase64String(SHELL_SCRIPT_USERDATA1.getBytes()));
+        String resultantShellScript = SHELL_SCRIPT_USERDATA + "\n\n" +
+                SHELL_SCRIPT_USERDATA1.replace("#!/bin/bash\n", "");
+        MimeMultipart mimeMultipart = getCheckedMultipartFromMultipartData(multipartUserData, 2);
+        try {
+            for (int i = 0; i < mimeMultipart.getCount(); ++i) {
+                BodyPart bodyPart = mimeMultipart.getBodyPart(i);
+                if (bodyPart.getContentType().startsWith("text/x-shellscript")) {
+                    Assert.assertEquals(resultantShellScript, provider.getBodyPartContentAsString(bodyPart));
+                } else if (bodyPart.getContentType().startsWith("text/cloud-config")) {
+                    Assert.assertEquals(CLOUD_CONFIG_USERDATA1, provider.getBodyPartContentAsString(bodyPart));
+                }
+            }
+        } catch (MessagingException | IOException | CloudRuntimeException e) {
+            Assert.fail(String.format("Failed with exception, %s", e.getMessage()));
+        }
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testAppendUserDataInvalidUserData() {
+        String templateData = CLOUD_CONFIG_USERDATA1.replace("#cloud-config\n", "");
+        provider.appendUserData(Base64.encodeBase64String(templateData.getBytes()),
+                Base64.encodeBase64String(SHELL_SCRIPT_USERDATA.getBytes()));
+    }
+
+    @Test
+    public void testIsGzippedUserDataWithCloudConfigData() {
+        Assert.assertFalse(provider.isGZipped(CLOUD_CONFIG_USERDATA));
+    }
+
+    private String createBase64EncodedGzipDataAsString() throws IOException {
+        byte[] input = CLOUD_CONFIG_USERDATA.getBytes(StandardCharsets.ISO_8859_1);
+
+        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
+        GZIPOutputStream outputStream = new GZIPOutputStream(arrayOutputStream);
+        outputStream.write(input,0, input.length);
+        outputStream.close();
+
+        return Base64.encodeBase64String(arrayOutputStream.toByteArray());
+    }
+
+    @Test
+    public void testIsGzippedUserDataWithValidGzipData() {
+        try {
+            String gzipped = createBase64EncodedGzipDataAsString();
+            Assert.assertTrue(provider.isGZipped(gzipped));
+        } catch (IOException e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testAppendUserDataWithGzippedData() {
+        try {
+            provider.appendUserData(Base64.encodeBase64String(CLOUD_CONFIG_USERDATA.getBytes()),
+                    createBase64EncodedGzipDataAsString());
+            Assert.fail("Gzipped data shouldn't be appended with other data");
+        } catch (IOException e) {
+            Assert.fail("Exception encountered: " + e.getMessage());
+        }
+    }
+}
diff --git a/engine/userdata/pom.xml b/engine/userdata/pom.xml
new file mode 100644
index 0000000..603fed6
--- /dev/null
+++ b/engine/userdata/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-engine-userdata</artifactId>
+    <name>Apache CloudStack Engine Userdata Component</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloud-engine</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-utils</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-components-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java
new file mode 100644
index 0000000..91f24fe
--- /dev/null
+++ b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.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
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.userdata;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+
+import com.cloud.configuration.ConfigurationManager;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class UserDataManagerImpl extends ManagerBase implements UserDataManager {
+
+
+    private static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
+    private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES;
+    private static final int NUM_OF_2K_BLOCKS = 512;
+    private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES;
+    private List<UserDataProvider> userDataProviders;
+    private static Map<String, UserDataProvider> userDataProvidersMap = new HashMap<>();
+
+    public void setUserDataProviders(final List<UserDataProvider> userDataProviders) {
+        this.userDataProviders = userDataProviders;
+    }
+
+    private void initializeUserdataProvidersMap() {
+        if (userDataProviders != null) {
+            for (final UserDataProvider provider : userDataProviders) {
+                userDataProvidersMap.put(provider.getName().toLowerCase(), provider);
+            }
+        }
+    }
+
+    @Override
+    public boolean start() {
+        initializeUserdataProvidersMap();
+        return true;
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return UserDataManagerImpl.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey[] {};
+    }
+
+    protected UserDataProvider getUserdataProvider(String name) {
+        if (StringUtils.isEmpty(name)) {
+            // Use cloud-init as the default userdata provider
+            name = "cloud-init";
+        }
+        if (!userDataProvidersMap.containsKey(name)) {
+            throw new CloudRuntimeException("Failed to find userdata provider by the name: " + name);
+        }
+        return userDataProvidersMap.get(name);
+    }
+
+    @Override
+    public String concatenateUserData(String userdata1, String userdata2, String userdataProvider) {
+        UserDataProvider provider = getUserdataProvider(userdataProvider);
+        String appendUserData = provider.appendUserData(userdata1, userdata2);
+        return Base64.encodeBase64String(appendUserData.getBytes());
+    }
+
+    @Override
+    public String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod) {
+        byte[] decodedUserData = null;
+        if (userData != null) {
+
+            if (userData.contains("%")) {
+                try {
+                    userData = URLDecoder.decode(userData, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    throw new InvalidParameterValueException("Url decoding of userdata failed.");
+                }
+            }
+
+            if (!Base64.isBase64(userData)) {
+                throw new InvalidParameterValueException("User data is not base64 encoded");
+            }
+            // If GET, use 4K. If POST, support up to 1M.
+            if (httpmethod.equals(BaseCmd.HTTPMethod.GET)) {
+                decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
+            } else if (httpmethod.equals(BaseCmd.HTTPMethod.POST)) {
+                decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_POST_LENGTH, BaseCmd.HTTPMethod.POST);
+            }
+
+            if (decodedUserData == null || decodedUserData.length < 1) {
+                throw new InvalidParameterValueException("User data is too short");
+            }
+            // Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
+            return Base64.encodeBase64String(decodedUserData);
+        }
+        return null;
+    }
+
+    private byte[] validateAndDecodeByHTTPMethod(String userData, int maxHTTPLength, BaseCmd.HTTPMethod httpMethod) {
+        byte[] decodedUserData = null;
+
+        if (userData.length() >= maxHTTPLength) {
+            throw new InvalidParameterValueException(String.format("User data is too long for an http %s request", httpMethod.toString()));
+        }
+        if (userData.length() > ConfigurationManager.VM_USERDATA_MAX_LENGTH.value()) {
+            throw new InvalidParameterValueException("User data has exceeded configurable max length : " + ConfigurationManager.VM_USERDATA_MAX_LENGTH.value());
+        }
+        decodedUserData = Base64.decodeBase64(userData.getBytes());
+        if (decodedUserData.length > maxHTTPLength) {
+            throw new InvalidParameterValueException(String.format("User data is too long for http %s request", httpMethod.toString()));
+        }
+        return decodedUserData;
+    }
+}
diff --git a/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataProvider.java b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataProvider.java
new file mode 100644
index 0000000..4cdcd51
--- /dev/null
+++ b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataProvider.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.userdata;
+
+public interface UserDataProvider {
+    String getName();
+
+    /**
+     * Append user data into a single user data.
+     * NOTE: userData1 and userData2 are Base64 encoded user data strings
+     * @return a non-encrypted string containing both user data inputs
+     */
+    String appendUserData(String encodedUserData1, String encodedUserData2);
+}
diff --git a/engine/userdata/src/main/resources/META-INF/cloudstack/core/spring-engine-userdata-core-context.xml b/engine/userdata/src/main/resources/META-INF/cloudstack/core/spring-engine-userdata-core-context.xml
new file mode 100644
index 0000000..0d4c647
--- /dev/null
+++ b/engine/userdata/src/main/resources/META-INF/cloudstack/core/spring-engine-userdata-core-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+
+    <bean id="userDataManager" class="org.apache.cloudstack.userdata.UserDataManagerImpl">
+        <property name="userDataProviders" value="#{userDataProvidersRegistry.registered}" />
+    </bean>
+ 
+</beans>
diff --git a/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.java b/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.java
new file mode 100644
index 0000000..67e7b38
--- /dev/null
+++ b/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.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.userdata;
+
+import static org.junit.Assert.assertEquals;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.cloudstack.api.BaseCmd;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UserDataManagerImplTest {
+
+    @Spy
+    @InjectMocks
+    private UserDataManagerImpl userDataManager;
+
+    @Test
+    public void testValidateBase64WithoutPadding() {
+        // fo should be encoded in base64 either as Zm8 or Zm8=
+        String encodedUserdata = "Zm8";
+        String encodedUserdataWithPadding = "Zm8=";
+
+        // Verify that we accept both but return the padded version
+        assertEquals("validate return the value with padding", encodedUserdataWithPadding, userDataManager.validateUserData(encodedUserdata, BaseCmd.HTTPMethod.GET));
+        assertEquals("validate return the value with padding", encodedUserdataWithPadding, userDataManager.validateUserData(encodedUserdataWithPadding, BaseCmd.HTTPMethod.GET));
+    }
+
+    @Test
+    public void testValidateUrlEncodedBase64() {
+        // fo should be encoded in base64 either as Zm8 or Zm8=
+        String encodedUserdata = "Zm+8/w8=";
+        String urlEncodedUserdata = java.net.URLEncoder.encode(encodedUserdata, StandardCharsets.UTF_8);
+
+        // Verify that we accept both but return the padded version
+        assertEquals("validate return the value with padding", encodedUserdata, userDataManager.validateUserData(encodedUserdata, BaseCmd.HTTPMethod.GET));
+        assertEquals("validate return the value with padding", encodedUserdata, userDataManager.validateUserData(urlEncodedUserdata, BaseCmd.HTTPMethod.GET));
+    }
+
+}
diff --git a/framework/agent-lb/pom.xml b/framework/agent-lb/pom.xml
index 2bd8ab5..8f9a154 100644
--- a/framework/agent-lb/pom.xml
+++ b/framework/agent-lb/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>cloudstack-framework</artifactId>
         <groupId>org.apache.cloudstack</groupId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/framework/ca/pom.xml b/framework/ca/pom.xml
index 185eb16..43b3710 100644
--- a/framework/ca/pom.xml
+++ b/framework/ca/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/framework/cluster/pom.xml b/framework/cluster/pom.xml
index 08704ec..1c24ddd 100644
--- a/framework/cluster/pom.xml
+++ b/framework/cluster/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java
index 121b939..2918ccd 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java
@@ -125,6 +125,11 @@
     }
 
     @Override
+    public Class<?> getEntityType() {
+        return ManagementServerHost.class;
+    }
+
+    @Override
     public String getName() {
         return name;
     }
@@ -196,4 +201,14 @@
     public String toString() {
         return new StringBuilder("ManagementServer[").append("-").append(id).append("-").append(msid).append("-").append(state).append("]").toString();
     }
+
+    @Override
+    public long getDomainId() {
+        return 1L;
+    }
+
+    @Override
+    public long getAccountId() {
+        return 1L;
+    }
 }
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDao.java b/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDao.java
index be81b5f..96d57ee 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDao.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDao.java
@@ -55,4 +55,6 @@
     List<Long> listOrphanMsids();
 
     ManagementServerHostVO findOneInUpState(Filter filter);
+
+    ManagementServerHostVO findOneByLongestRuntime();
 }
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java b/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java
index 74f8481..715dfe2 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java
@@ -25,6 +25,7 @@
 import java.util.TimeZone;
 
 
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.cluster.ClusterInvalidSessionException;
@@ -204,6 +205,7 @@
 
         StateSearch = createSearchBuilder();
         StateSearch.and("state", StateSearch.entity().getState(), SearchCriteria.Op.IN);
+        StateSearch.and("runid", StateSearch.entity().getRunid(), SearchCriteria.Op.GT);
         StateSearch.done();
     }
 
@@ -272,4 +274,14 @@
         return null;
     }
 
+    @Override
+    public ManagementServerHostVO findOneByLongestRuntime() {
+        SearchCriteria<ManagementServerHostVO> sc = StateSearch.create();
+        sc.setParameters("state", ManagementServerHost.State.Up);
+        sc.setParameters("runid", 0);
+        Filter filter = new Filter(ManagementServerHostVO.class, "runid", true, 0L, 1L);
+        List<ManagementServerHostVO> msHosts = listBy(sc, filter);
+        return CollectionUtils.isNotEmpty(msHosts) ? msHosts.get(0) : null;
+    }
+
 }
diff --git a/framework/config/pom.xml b/framework/config/pom.xml
index a53c2c2..178fa49 100644
--- a/framework/config/pom.xml
+++ b/framework/config/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
index bd38ba9..df93f78 100644
--- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
+++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
@@ -150,7 +150,7 @@
 
     public ConfigKey(Class<T> type, String name, String category, String defaultValue, String description, boolean isDynamic, Scope scope, T multiplier,
                      String displayText, String parent, Ternary<String, String, Long> group, Pair<String, Long> subGroup) {
-        this(type, name, category, defaultValue, description, isDynamic, scope, multiplier, null, parent, null, null, null, null);
+        this(type, name, category, defaultValue, description, isDynamic, scope, multiplier, displayText, parent, group, subGroup, null, null);
     }
 
     public ConfigKey(Class<T> type, String name, String category, String defaultValue, String description, boolean isDynamic, Scope scope, T multiplier,
diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
index 08ec9bf..c705cc6 100644
--- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
+++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
@@ -170,7 +170,7 @@
 
     @Override
     public boolean isEncrypted() {
-        return "Hidden".equals(getCategory()) || "Secure".equals(getCategory());
+        return StringUtils.equalsAny(getCategory(), "Hidden", "Secure");
     }
 
     @Override
diff --git a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java
index 4649033..54e30eb 100644
--- a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java
+++ b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java
@@ -98,6 +98,8 @@
         ConfigurationVO staticIntCV = new ConfigurationVO("UnitTestComponent", StaticIntCK);
         dynamicIntCV.setValue("200");
         ConfigurationVO testCV = new ConfigurationVO("UnitTestComponent", TestCK);
+        ConfigurationGroupVO groupVO = new ConfigurationGroupVO();
+        ConfigurationSubGroupVO subGroupVO = new ConfigurationSubGroupVO();
 
         when(_configurable.getConfigComponentName()).thenReturn("UnitTestComponent");
         when(_configurable.getConfigKeys()).thenReturn(new ConfigKey<?>[] {DynamicIntCK, StaticIntCK, TestCK});
@@ -105,6 +107,8 @@
         when(_configDao.findById(DynamicIntCK.key())).thenReturn(dynamicIntCV);
         when(_configDao.findById(TestCK.key())).thenReturn(testCV);
         when(_configDao.persist(any(ConfigurationVO.class))).thenReturn(dynamicIntCV);
+        when(_configGroupDao.persist(any(ConfigurationGroupVO.class))).thenReturn(groupVO);
+        when(_configSubGroupDao.persist(any(ConfigurationSubGroupVO.class))).thenReturn(subGroupVO);
         _depotAdmin.populateConfigurations();
 
         // This is once because DynamicIntCK is returned.
diff --git a/framework/db/pom.xml b/framework/db/pom.xml
index 3f19107..a5e0f45 100644
--- a/framework/db/pom.xml
+++ b/framework/db/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -53,6 +53,11 @@
             <artifactId>cloud-utils</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.mariadb.jdbc</groupId>
+            <artifactId>mariadb-java-client</artifactId>
+            <version>3.1.4</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/framework/db/src/main/java/com/cloud/dao/EntityManagerImpl.java b/framework/db/src/main/java/com/cloud/dao/EntityManagerImpl.java
index e738f1e..beee1b6 100644
--- a/framework/db/src/main/java/com/cloud/dao/EntityManagerImpl.java
+++ b/framework/db/src/main/java/com/cloud/dao/EntityManagerImpl.java
@@ -86,7 +86,7 @@
 
     public <T, K> GenericSearchBuilder<T, K> createGenericSearchBuilder(Class<T> entityType, Class<K> resultType) {
         GenericDao<T, ? extends Serializable> dao = (GenericDao<T, ? extends Serializable>)GenericDaoBase.getDao(entityType);
-        return dao.createSearchBuilder((Class<K>)resultType.getClass());
+        return dao.createSearchBuilder((Class<K>)resultType);
     }
 
     @Override
diff --git a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
index 88830b3..961c537 100644
--- a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
+++ b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
@@ -475,6 +475,8 @@
 
             // migrate resource details values
             migrateHostDetails(conn);
+            migrateEncryptedAccountDetails(conn);
+            migrateEncryptedDomainDetails(conn);
             migrateClusterDetails(conn);
             migrateImageStoreDetails(conn);
             migrateStoragePoolDetails(conn);
@@ -497,6 +499,30 @@
         return true;
     }
 
+    private void migrateEncryptedAccountDetails(Connection conn) {
+        System.out.println("Beginning migration of account_details encrypted values");
+
+        String tableName = "account_details";
+        String selectSql = "SELECT details.id, details.value from account_details details, cloud.configuration c " +
+                "WHERE details.name = c.name AND c.category IN ('Hidden', 'Secure') AND details.value <> \"\" ORDER BY details.id;";
+        String updateSql = "UPDATE cloud.account_details SET value = ? WHERE id = ?;";
+        migrateValueAndUpdateDatabaseById(conn, tableName, selectSql, updateSql, false);
+
+        System.out.println("End migration of account details values");
+    }
+
+    private void migrateEncryptedDomainDetails(Connection conn) {
+        System.out.println("Beginning migration of domain_details encrypted values");
+
+        String tableName = "domain_details";
+        String selectSql = "SELECT details.id, details.value from domain_details details, cloud.configuration c " +
+                "WHERE details.name = c.name AND c.category IN ('Hidden', 'Secure') AND details.value <> \"\" ORDER BY details.id;";
+        String updateSql = "UPDATE cloud.domain_details SET value = ? WHERE id = ?;";
+        migrateValueAndUpdateDatabaseById(conn, tableName, selectSql, updateSql, false);
+
+        System.out.println("End migration of domain details values");
+    }
+
     protected String migrateValue(String value) {
         if (StringUtils.isEmpty(value)) {
             return value;
diff --git a/framework/db/src/main/java/com/cloud/utils/db/Attribute.java b/framework/db/src/main/java/com/cloud/utils/db/Attribute.java
index 82c2bdb..3e5128d 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/Attribute.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/Attribute.java
@@ -81,6 +81,7 @@
 
     protected String table;
     protected String columnName;
+    protected Object value;
     protected Field field;
     protected int flags;
     protected Column column;
@@ -100,6 +101,10 @@
         this.column = null;
     }
 
+    public Attribute(Object value) {
+        this.value = value;
+    }
+
     protected void setupColumnInfo(Class<?> clazz, AttributeOverride[] overrides, String tableName, boolean isEmbedded, boolean isId) {
         flags = Flag.Selectable.setTrue(flags);
         GeneratedValue gv = field.getAnnotation(GeneratedValue.class);
@@ -214,6 +219,10 @@
         return field;
     }
 
+    public Object getValue() {
+        return value;
+    }
+
     public Object get(Object entity) {
         try {
             return field.get(entity);
diff --git a/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java b/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java
index 86a61bc..68424bc 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java
@@ -124,7 +124,7 @@
     public static Field findField(Class<?> clazz, String columnName) {
         for (Field field : clazz.getDeclaredFields()) {
             if (field.getAnnotation(Embedded.class) != null || field.getAnnotation(EmbeddedId.class) != null) {
-                findField(field.getClass(), columnName);
+                findField(field.getType(), columnName);
             } else {
                 if (columnName.equals(DbUtil.getColumnName(field))) {
                     return field;
@@ -170,7 +170,7 @@
         }
 
         if (field.getAnnotation(EmbeddedId.class) != null) {
-            assert (field.getClass().getAnnotation(Embeddable.class) != null) : "Class " + field.getClass().getName() + " must be Embeddable to be used as Embedded Id";
+            assert (field.getType().getAnnotation(Embeddable.class) != null) : "Class " + field.getType().getName() + " must be Embeddable to be used as Embedded Id";
             return true;
         }
 
diff --git a/framework/db/src/main/java/com/cloud/utils/db/DriverLoader.java b/framework/db/src/main/java/com/cloud/utils/db/DriverLoader.java
index 14bd286..55fc1db 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/DriverLoader.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/DriverLoader.java
@@ -16,6 +16,8 @@
 // under the License.
 package com.cloud.utils.db;
 
+import java.lang.reflect.InvocationTargetException;
+import java.sql.Driver;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -36,6 +38,7 @@
         DRIVERS.put("jdbc:mysql", "com.mysql.cj.jdbc.Driver");
         DRIVERS.put("jdbc:postgresql", "org.postgresql.Driver");
         DRIVERS.put("jdbc:h2", "org.h2.Driver");
+        DRIVERS.put("jdbc:mariadb", "org.mariadb.jdbc.Driver");
 
         LOADED_DRIVERS = new ArrayList<String>();
     }
@@ -56,12 +59,13 @@
         }
 
         try {
-            Class.forName(driverClass).newInstance();
+            Class<Driver> klazz = (Class<Driver>) Class.forName(driverClass);
+            klazz.getDeclaredConstructor().newInstance();
             LOADED_DRIVERS.add(dbDriver);
             if (LOGGER.isDebugEnabled()) {
                 LOGGER.debug("Successfully loaded DB driver " + driverClass);
             }
-        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
             LOGGER.error("Failed to load DB driver " + driverClass);
             throw new CloudRuntimeException("Failed to load DB driver " + driverClass, e);
         }
diff --git a/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java b/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java
index 4e266f6..dcda5bc 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java
@@ -57,10 +57,10 @@
                     rawClass = HashSet.class;
                 } else if (List.class == rawClazz) {
                     rawClass = ArrayList.class;
-                } else if (Collection.class == Collection.class) {
+                } else if (Collection.class == rawClazz) {
                     rawClass = ArrayList.class;
                 } else {
-                    assert (false) : " We don't know how to create this calss " + rawType.toString() + " for " + attr.field.getName();
+                    assert (false) : " We don't know how to create this class " + rawType.toString() + " for " + attr.field.getName();
                 }
             } catch (NoSuchMethodException e) {
                 throw new CloudRuntimeException("Write your own support for " + rawClazz + " defined by " + attr.field.getName());
diff --git a/framework/db/src/main/java/com/cloud/utils/db/Filter.java b/framework/db/src/main/java/com/cloud/utils/db/Filter.java
index 59dc8c1..15161ab 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/Filter.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/Filter.java
@@ -51,6 +51,10 @@
         addOrderBy(clazz, field, ascending);
     }
 
+    public Filter(Class<?> clazz, String field, boolean ascending) {
+        this(clazz, field, ascending, null, null);
+    }
+
     public Filter(long limit) {
         _orderBy = " ORDER BY RAND() LIMIT " + limit;
     }
diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java
index 1eae0ed..2fc0230 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java
@@ -258,6 +258,8 @@
 
     public T findOneBy(final SearchCriteria<T> sc);
 
+    T findOneBy(SearchCriteria<T> sc, Filter filter);
+
     /**
      * @return
      */
@@ -284,4 +286,6 @@
     Pair<List<T>, Integer> searchAndDistinctCount(final SearchCriteria<T> sc, final Filter filter, final String[] distinctColumns);
 
     Integer countAll();
+
+    List<T> findByUuids(String... uuidArray);
 }
diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
index 4fe86b4..0eb4543 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
@@ -56,6 +56,8 @@
 import javax.persistence.Table;
 import javax.persistence.TableGenerator;
 
+import com.amazonaws.util.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.utils.DateUtil;
@@ -373,10 +375,11 @@
         }
 
         Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
+        List<Attribute> joinAttrList = null;
         if (sc != null) {
             joins = sc.getJoins();
             if (joins != null) {
-                addJoins(str, joins);
+                joinAttrList = addJoins(str, joins);
             }
         }
 
@@ -396,6 +399,13 @@
         try {
             pstmt = txn.prepareAutoCloseStatement(sql);
             int i = 1;
+
+            if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
+                for (Attribute attr : joinAttrList) {
+                    prepareAttribute(i++, pstmt, attr, null);
+                }
+            }
+
             if (clause != null) {
                 for (final Pair<Attribute, Object> value : sc.getValues()) {
                     prepareAttribute(i++, pstmt, value.first(), value.second());
@@ -422,7 +432,7 @@
             return result;
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught: " + pstmt, e);
         }
     }
@@ -448,8 +458,9 @@
 
         Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
         joins = sc.getJoins();
+        List<Attribute> joinAttrList = null;
         if (joins != null) {
-            addJoins(str, joins);
+            joinAttrList = addJoins(str, joins);
         }
 
         List<Object> groupByValues = addGroupBy(str, sc);
@@ -462,6 +473,13 @@
         try {
             pstmt = txn.prepareAutoCloseStatement(sql);
             int i = 1;
+
+            if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
+                for (Attribute attr : joinAttrList) {
+                    prepareAttribute(i++, pstmt, attr, null);
+                }
+            }
+
             if (clause != null) {
                 for (final Pair<Attribute, Object> value : sc.getValues()) {
                     prepareAttribute(i++, pstmt, value.first(), value.second());
@@ -499,7 +517,7 @@
             return results;
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught: " + pstmt, e);
         }
     }
@@ -874,7 +892,8 @@
         if (_idField.getAnnotation(EmbeddedId.class) == null) {
             sql.append(_table).append(".").append(DbUtil.getColumnName(_idField, null)).append(" = ? ");
         } else {
-            final Class<?> clazz = _idField.getClass();
+            s_logger.debug(String.format("field type vs declarator : %s vs %s", _idField.getType(), _idField.getDeclaringClass()));
+            final Class<?> clazz = _idField.getType();
             final AttributeOverride[] overrides = DbUtil.getAttributeOverrides(_idField);
             for (final Field field : clazz.getDeclaredFields()) {
                 sql.append(_table).append(".").append(DbUtil.getColumnName(field, overrides)).append(" = ? AND ");
@@ -906,6 +925,15 @@
         return findOneIncludingRemovedBy(sc);
     }
 
+    @Override
+    @DB()
+    public T findOneBy(SearchCriteria<T> sc, final Filter filter) {
+        sc = checkAndSetRemovedIsNull(sc);
+        filter.setLimit(1L);
+        List<T> results = searchIncludingRemoved(sc, filter, null, false);
+        return results.isEmpty() ? null : results.get(0);
+    }
+
     @DB()
     protected List<T> listBy(SearchCriteria<T> sc, final Filter filter) {
         sc = checkAndSetRemovedIsNull(sc);
@@ -919,7 +947,7 @@
     }
 
     @DB()
-    protected List<T> listBy(final SearchCriteria<T> sc) {
+    public List<T> listBy(final SearchCriteria<T> sc) {
         return listBy(sc, null);
     }
 
@@ -1144,7 +1172,7 @@
             return result;
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught: " + pstmt, e);
         }
     }
@@ -1226,7 +1254,7 @@
             return pstmt.executeUpdate();
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught: " + pstmt, e);
         }
     }
@@ -1262,7 +1290,13 @@
     }
 
     @DB()
-    protected void addJoins(StringBuilder str, Collection<JoinBuilder<SearchCriteria<?>>> joins) {
+    protected List<Attribute> addJoins(StringBuilder str, Collection<JoinBuilder<SearchCriteria<?>>> joins) {
+        return addJoins(str, joins, new HashMap<>());
+    }
+
+    @DB()
+    protected List<Attribute> addJoins(StringBuilder str, Collection<JoinBuilder<SearchCriteria<?>>> joins, Map<String, String> joinedTableNames) {
+        List<Attribute> joinAttrList = new ArrayList<>();
         boolean hasWhereClause = true;
         int fromIndex = str.lastIndexOf("WHERE");
         if (fromIndex == -1) {
@@ -1273,22 +1307,52 @@
         }
 
         for (JoinBuilder<SearchCriteria<?>> join : joins) {
+            String joinTableName = join.getSecondAttribute()[0].table;
+            String joinTableAlias;
+            if (StringUtils.isNotEmpty(join.getName())) {
+                joinTableAlias = join.getName();
+                joinedTableNames.put(joinTableName, joinTableAlias);
+            } else {
+                joinTableAlias = joinedTableNames.getOrDefault(joinTableName, joinTableName);
+            }
             StringBuilder onClause = new StringBuilder();
             onClause.append(" ")
             .append(join.getType().getName())
             .append(" ")
-            .append(join.getSecondAttribute().table)
-            .append(" ON ")
-            .append(join.getFirstAttribute().table)
-            .append(".")
-            .append(join.getFirstAttribute().columnName)
-            .append("=")
-            .append(join.getSecondAttribute().table)
-            .append(".")
-            .append(join.getSecondAttribute().columnName)
-            .append(" ");
+            .append(joinTableName);
+            if (!joinTableAlias.equals(joinTableName)) {
+                onClause.append(" ").append(joinTableAlias);
+            }
+            onClause.append(" ON ");
+            for (int i = 0; i < join.getFirstAttributes().length; i++) {
+                if (i > 0) {
+                    onClause.append(join.getCondition().getName());
+                }
+                if (join.getFirstAttributes()[i].getValue() != null) {
+                    onClause.append("?");
+                    joinAttrList.add(join.getFirstAttributes()[i]);
+                } else {
+                    onClause.append(joinedTableNames.getOrDefault(join.getFirstAttributes()[i].table, join.getFirstAttributes()[i].table))
+                    .append(".")
+                    .append(join.getFirstAttributes()[i].columnName);
+                }
+                onClause.append("=");
+                if (join.getSecondAttribute()[i].getValue() != null) {
+                    onClause.append("?");
+                    joinAttrList.add(join.getSecondAttribute()[i]);
+                } else {
+                    if(!joinTableAlias.equals(joinTableName)) {
+                        onClause.append(joinTableAlias);
+                    } else {
+                        onClause.append(joinTableName);
+                    }
+                    onClause.append(".")
+                    .append(join.getSecondAttribute()[i].columnName);
+                }
+            }
+            onClause.append(" ");
             str.insert(fromIndex, onClause);
-            String whereClause = join.getT().getWhereClause();
+            String whereClause = join.getT().getWhereClause(joinTableAlias);
             if (StringUtils.isNotEmpty(whereClause)) {
                 if (!hasWhereClause) {
                     str.append(" WHERE ");
@@ -1305,9 +1369,10 @@
 
         for (JoinBuilder<SearchCriteria<?>> join : joins) {
             if (join.getT().getJoins() != null) {
-                addJoins(str, join.getT().getJoins());
+                joinAttrList.addAll(addJoins(str, join.getT().getJoins(), joinedTableNames));
             }
         }
+        return joinAttrList;
     }
 
     private void removeAndClause(StringBuilder sql) {
@@ -1560,10 +1625,24 @@
                 return;
             }
         }
-        if (attr.field.getType() == String.class) {
-            final String str = (String)value;
-            if (str == null) {
-                pstmt.setString(j, null);
+        if (attr.getValue() != null && attr.getValue() instanceof String) {
+            pstmt.setString(j, (String)attr.getValue());
+        } else if (attr.getValue() != null && attr.getValue() instanceof Long) {
+            pstmt.setLong(j, (Long)attr.getValue());
+        } else if (attr.field.getType() == String.class) {
+            final String str;
+            try {
+                str = (String) value;
+                if (str == null) {
+                    pstmt.setString(j, null);
+                    return;
+                }
+            } catch (ClassCastException ex) {
+                // This happens when we pass in an integer, long or any other object which can't be cast to String.
+                // Converting to string in case of integer or long can result in different results. Required specifically for details tables.
+                // So, we set the value for the object directly.
+                s_logger.debug("ClassCastException when casting value to String. Setting the value of the object directly.");
+                pstmt.setObject(j, value);
                 return;
             }
             final Column column = attr.field.getAnnotation(Column.class);
@@ -1983,10 +2062,11 @@
         }
 
         Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
+        List<Attribute> joinAttrList = null;
         if (sc != null) {
             joins = sc.getJoins();
             if (joins != null) {
-                addJoins(str, joins);
+                joinAttrList = addJoins(str, joins);
             }
         }
 
@@ -1999,6 +2079,13 @@
         try {
             pstmt = txn.prepareAutoCloseStatement(sql);
             int i = 1;
+
+            if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
+                for (Attribute attr : joinAttrList) {
+                    prepareAttribute(i++, pstmt, attr, null);
+                }
+            }
+
             if (clause != null) {
                 for (final Pair<Attribute, Object> value : sc.getValues()) {
                     prepareAttribute(i++, pstmt, value.first(), value.second());
@@ -2024,7 +2111,7 @@
             return 0;
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught: " + pstmt, e);
         }
     }
@@ -2046,10 +2133,11 @@
         }
 
         Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
+        List<Attribute> joinAttrList = null;
         if (sc != null) {
             joins = sc.getJoins();
             if (joins != null) {
-                addJoins(str, joins);
+                joinAttrList = addJoins(str, joins);
             }
         }
 
@@ -2058,6 +2146,13 @@
 
         try (PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql)) {
             int i = 1;
+
+            if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
+                for (Attribute attr : joinAttrList) {
+                    prepareAttribute(i++, pstmt, attr, null);
+                }
+            }
+
             if (clause != null) {
                 for (final Pair<Attribute, Object> value : sc.getValues()) {
                     prepareAttribute(i++, pstmt, value.first(), value.second());
@@ -2075,7 +2170,7 @@
             return 0;
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception in executing: " + sql, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught exception in : " + sql, e);
         }
     }
@@ -2084,6 +2179,16 @@
         return getCount(null);
     }
 
+    @Override
+    public List<T> findByUuids(String... uuidArray) {
+        if (ArrayUtils.isEmpty(uuidArray)) {
+            return new ArrayList<T>();
+        }
+        SearchCriteria<T> sc = createSearchCriteria();
+        sc.addAnd("uuid", SearchCriteria.Op.IN, uuidArray);
+        return search(sc, null);
+    }
+
     public Integer getCount(SearchCriteria<T> sc) {
         sc = checkAndSetRemovedIsNull(sc);
         return getCountIncludingRemoved(sc);
@@ -2101,10 +2206,11 @@
         }
 
         Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
+        List<Attribute> joinAttrList = null;
         if (sc != null) {
             joins = sc.getJoins();
             if (joins != null) {
-                addJoins(str, joins);
+                joinAttrList = addJoins(str, joins);
             }
         }
 
@@ -2115,6 +2221,13 @@
         try {
             pstmt = txn.prepareAutoCloseStatement(sql);
             int i = 1;
+
+            if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
+                for (Attribute attr : joinAttrList) {
+                    prepareAttribute(i++, pstmt, attr, null);
+                }
+            }
+
             if (clause != null) {
                 for (final Pair<Attribute, Object> value : sc.getValues()) {
                     prepareAttribute(i++, pstmt, value.first(), value.second());
@@ -2132,7 +2245,7 @@
             return 0;
         } catch (final SQLException e) {
             throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
-        } catch (final Throwable e) {
+        } catch (final Exception e) {
             throw new CloudRuntimeException("Caught: " + pstmt, e);
         }
     }
diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericSearchBuilder.java b/framework/db/src/main/java/com/cloud/utils/db/GenericSearchBuilder.java
index df6f1f7..f4385ef 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/GenericSearchBuilder.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/GenericSearchBuilder.java
@@ -80,6 +80,12 @@
         return this;
     }
 
+    public GenericSearchBuilder<T, K> and(String joinName, String name, Object field, Op op) {
+        SearchBase<?, ?, ?> join = _joins.get(joinName).getT();
+        constructCondition(joinName, name, " AND ", join._specifiedAttrs.get(0), op);
+        return this;
+    }
+
     /**
      * Adds an AND condition.  Some prefer this method because it looks like
      * the actual SQL query.
@@ -134,6 +140,12 @@
         return this;
     }
 
+    protected GenericSearchBuilder<T, K> left(String joinName, Object field, Op op, String name) {
+        SearchBase<?, ?, ?> joinSb = _joins.get(joinName).getT();
+        constructCondition(joinName, name, " ( ", joinSb._specifiedAttrs.get(0), op);
+        return this;
+    }
+
     protected Preset left(Object field, Op op) {
         Condition condition = constructCondition(UUID.randomUUID().toString(), " ( ", _specifiedAttrs.get(0), op);
         return new Preset(this, condition);
@@ -169,6 +181,10 @@
         return left(field, op, name);
     }
 
+    public GenericSearchBuilder<T, K> op(String joinName, String name, Object field, Op op) {
+        return left(joinName, field, op, name);
+    }
+
     /**
      * Adds an OR condition to the SearchBuilder.
      *
@@ -182,6 +198,12 @@
         return this;
     }
 
+    public GenericSearchBuilder<T, K> or(String joinName, String name, Object field, Op op) {
+        SearchBase<?, ?, ?> join = _joins.get(joinName).getT();
+        constructCondition(joinName, name, " OR ", join._specifiedAttrs.get(0), op);
+        return this;
+    }
+
     /**
      * Adds an OR condition
      *
diff --git a/framework/db/src/main/java/com/cloud/utils/db/JoinBuilder.java b/framework/db/src/main/java/com/cloud/utils/db/JoinBuilder.java
index 60e3921..f0a3ad8 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/JoinBuilder.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/JoinBuilder.java
@@ -31,19 +31,57 @@
             return _name;
         }
     }
+    public enum JoinCondition {
+        AND(" AND "), OR(" OR ");
+
+        private final String name;
+
+        JoinCondition(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
 
     private final T t;
+    private final String name;
     private JoinType type;
-    private Attribute firstAttribute;
-    private Attribute secondAttribute;
 
-    public JoinBuilder(T t, Attribute firstAttribute, Attribute secondAttribute, JoinType type) {
+    private JoinCondition condition;
+    private Attribute[] firstAttributes;
+    private Attribute[] secondAttribute;
+
+    public JoinBuilder(String name, T t, Attribute firstAttributes, Attribute secondAttribute, JoinType type) {
+        this.name = name;
         this.t = t;
-        this.firstAttribute = firstAttribute;
+        this.firstAttributes = new Attribute[]{firstAttributes};
+        this.secondAttribute = new Attribute[]{secondAttribute};
+        this.type = type;
+    }
+
+    public JoinBuilder(String name, T t, Attribute[] firstAttributes, Attribute[] secondAttribute, JoinType type) {
+        this.name = name;
+        this.t = t;
+        this.firstAttributes = firstAttributes;
         this.secondAttribute = secondAttribute;
         this.type = type;
     }
 
+    public JoinBuilder(String name, T t, Attribute[] firstAttributes, Attribute[] secondAttribute, JoinType type, JoinCondition condition) {
+        this.name = name;
+        this.t = t;
+        this.firstAttributes = firstAttributes;
+        this.secondAttribute = secondAttribute;
+        this.type = type;
+        this.condition = condition;
+    }
+
+    public String getName() {
+        return name;
+    }
+
     public T getT() {
         return t;
     }
@@ -56,19 +94,23 @@
         this.type = type;
     }
 
-    public Attribute getFirstAttribute() {
-        return firstAttribute;
+    public JoinCondition getCondition() {
+        return condition;
     }
 
-    public void setFirstAttribute(Attribute firstAttribute) {
-        this.firstAttribute = firstAttribute;
+    public Attribute[] getFirstAttributes() {
+        return firstAttributes;
     }
 
-    public Attribute getSecondAttribute() {
+    public void setFirstAttributes(Attribute[] firstAttributes) {
+        this.firstAttributes = firstAttributes;
+    }
+
+    public Attribute[] getSecondAttribute() {
         return secondAttribute;
     }
 
-    public void setSecondAttribute(Attribute secondAttribute) {
+    public void setSecondAttribute(Attribute[] secondAttribute) {
         this.secondAttribute = secondAttribute;
     }
 
diff --git a/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java b/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java
index 3d41a62..fcc9ded 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java
@@ -36,6 +36,7 @@
 import net.sf.cglib.proxy.Factory;
 import net.sf.cglib.proxy.MethodInterceptor;
 import net.sf.cglib.proxy.MethodProxy;
+import org.apache.commons.lang3.StringUtils;
 
 /**
  * SearchBase contains the methods that are used to build up search
@@ -70,6 +71,14 @@
         init(entityType, resultType);
     }
 
+    public SearchBase<?, ?, ?> getJoinSB(String name) {
+        JoinBuilder<SearchBase<?, ?, ?>> jb = null;
+        if (_joins != null) {
+            jb = _joins.get(name);
+        }
+        return jb == null ? null : jb.getT();
+    }
+
     protected void init(final Class<T> entityType, final Class<K> resultType) {
         _dao = (GenericDaoBase<? extends T, ? extends Serializable>)GenericDaoBase.getDao(entityType);
         if (_dao == null) {
@@ -194,15 +203,45 @@
      * @param joinType type of join
      * @return itself
      */
-    @SuppressWarnings("unchecked")
     public J join(final String name, final SearchBase<?, ?, ?> builder, final Object joinField1, final Object joinField2, final JoinBuilder.JoinType joinType) {
-        assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
-        assert _specifiedAttrs.size() == 1 : "You didn't select the attribute.";
-        assert builder._entity != null : "SearchBuilder cannot be modified once it has been setup";
-        assert builder._specifiedAttrs.size() == 1 : "You didn't select the attribute.";
-        assert builder != this : "You can't add yourself, can you?  Really think about it!";
+        if (_specifiedAttrs.size() != 1)
+            throw new CloudRuntimeException("You didn't select the attribute.");
+        if (builder._specifiedAttrs.size() != 1)
+            throw new CloudRuntimeException("You didn't select the attribute.");
 
-        final JoinBuilder<SearchBase<?, ?, ?>> t = new JoinBuilder<SearchBase<?, ?, ?>>(builder, _specifiedAttrs.get(0), builder._specifiedAttrs.get(0), joinType);
+        return join(name, builder, joinType, null, joinField1, joinField2);
+    }
+
+
+    /**
+     * joins this search with another search with multiple conditions in the join clause
+     *
+     * @param name name given to the other search.  used for setJoinParameters.
+     * @param builder The other search
+     * @param joinType type of join
+     * @param condition condition to be used for multiple conditions in the join clause
+     * @param joinFields fields the first and second table used to perform the join.
+     *                   The fields should be in the order of the checks between the two tables.
+     *
+     * @return
+     */
+    public J join(final String name, final SearchBase<?, ?, ?> builder, final JoinBuilder.JoinType joinType, final
+            JoinBuilder.JoinCondition condition, final Object... joinFields) {
+        if (_entity == null)
+            throw new CloudRuntimeException("SearchBuilder cannot be modified once it has been setup");
+        if (_specifiedAttrs.isEmpty())
+            throw new CloudRuntimeException("Attribute not specified.");
+        if (builder._entity == null)
+            throw new CloudRuntimeException("SearchBuilder cannot be modified once it has been setup");
+        if (builder._specifiedAttrs.isEmpty())
+            throw new CloudRuntimeException("Attribute not specified.");
+        if (builder == this)
+            throw new CloudRuntimeException("Can't join with itself. Create a new SearchBuilder for the same entity and use that.");
+        if (_specifiedAttrs.size() != builder._specifiedAttrs.size())
+            throw new CloudRuntimeException("Number of attributes to join on must be the same.");
+
+        final JoinBuilder<SearchBase<?, ?, ?>> t = new JoinBuilder<>(name, builder, _specifiedAttrs.toArray(new Attribute[0]),
+                builder._specifiedAttrs.toArray(new Attribute[0]), joinType, condition);
         if (_joins == null) {
             _joins = new HashMap<String, JoinBuilder<SearchBase<?, ?, ?>>>();
         }
@@ -223,6 +262,16 @@
         _specifiedAttrs.add(attr);
     }
 
+    /*
+        Allows to set conditions in join where one entity is equivalent to a string or a long
+        e.g. join("vm", vmSearch, VmDetailVO.class, entity.getName(), "vm.id", SearchCriteria.Op.EQ);
+        will create a condition 'vm.name = "vm.id"'
+     */
+    protected void setAttr(final Object obj) {
+        final Attribute attr = new Attribute(obj);
+        _specifiedAttrs.add(attr);
+    }
+
     /**
      * @return entity object.  This allows the caller to use the entity return
      * to specify the field to be selected in many of the search parameters.
@@ -242,17 +291,26 @@
         return _specifiedAttrs;
     }
 
-    protected Condition constructCondition(final String conditionName, final String cond, final Attribute attr, final Op op) {
+    protected Condition constructCondition(final String joinName, final String conditionName, final String cond, final Attribute attr, final Op op) {
         assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
         assert op == null || _specifiedAttrs.size() == 1 : "You didn't select the attribute.";
         assert op != Op.SC : "Call join";
 
         final Condition condition = new Condition(conditionName, cond, attr, op);
+        if (StringUtils.isNotEmpty(joinName)) {
+            condition.setJoinName(joinName);
+        }
         _conditions.add(condition);
         _specifiedAttrs.clear();
         return condition;
     }
 
+
+    protected Condition constructCondition(final String conditionName, final String cond, final Attribute attr, final Op op) {
+        return constructCondition(null, conditionName, cond, attr, op);
+    }
+
+
     /**
      * creates the SearchCriteria so the actual values can be filled in.
      *
@@ -364,6 +422,7 @@
     protected static class Condition {
         protected final String name;
         protected final String cond;
+        protected String joinName;
         protected final Op op;
         protected final Attribute attr;
         protected Object[] presets;
@@ -388,11 +447,15 @@
             this.presets = presets;
         }
 
+        public void setJoinName(final String joinName) {
+            this.joinName = joinName;
+        }
+
         public Object[] getPresets() {
             return presets;
         }
 
-        public void toSql(final StringBuilder sql, final Object[] params, final int count) {
+        public void toSql(final StringBuilder sql, String tableAlias, final Object[] params, final int count) {
             if (count > 0) {
                 sql.append(cond);
             }
@@ -414,7 +477,15 @@
                 sql.append(" FIND_IN_SET(?, ");
             }
 
-            sql.append(attr.table).append(".").append(attr.columnName).append(op.toString());
+            if (tableAlias == null) {
+                if (joinName != null) {
+                    tableAlias = joinName;
+                } else {
+                    tableAlias = attr.table;
+                }
+            }
+
+            sql.append(tableAlias).append(".").append(attr.columnName).append(op.toString());
             if (op == Op.IN && params.length == 1) {
                 sql.delete(sql.length() - op.toString().length(), sql.length());
                 sql.append("=?");
@@ -485,6 +556,8 @@
                     final String fieldName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
                     set(fieldName);
                     return null;
+                } else if (name.equals("setLong") || name.equals("setString")) {
+                    setAttr(args[0]);
                 } else {
                     final Column ann = method.getAnnotation(Column.class);
                     if (ann != null) {
diff --git a/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java b/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
index 6f90d23..8affbd5 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
@@ -106,7 +106,7 @@
             for (Map.Entry<String, JoinBuilder<SearchBase<?, ?, ?>>> entry : sb._joins.entrySet()) {
                 JoinBuilder<SearchBase<?, ?, ?>> value = entry.getValue();
                 _joins.put(entry.getKey(),
-                    new JoinBuilder<SearchCriteria<?>>(value.getT().create(), value.getFirstAttribute(), value.getSecondAttribute(), value.getType()));
+                    new JoinBuilder<SearchCriteria<?>>(entry.getKey(), value.getT().create(), value.getFirstAttributes(), value.getSecondAttribute(), value.getType(), value.getCondition()));
             }
         }
         _selects = sb._selects;
@@ -250,7 +250,7 @@
         _additionals.add(condition);
     }
 
-    public String getWhereClause() {
+    public String getWhereClause(String tableAlias) {
         StringBuilder sql = new StringBuilder();
         int i = 0;
         for (Condition condition : _conditions) {
@@ -259,7 +259,7 @@
             }
             Object[] params = _params.get(condition.name);
             if ((condition.op == null || condition.op.params == 0) || (params != null)) {
-                condition.toSql(sql, params, i++);
+                condition.toSql(sql, tableAlias, params, i++);
             }
         }
 
@@ -269,13 +269,17 @@
             }
             Object[] params = _params.get(condition.name);
             if ((condition.op.params == 0) || (params != null)) {
-                condition.toSql(sql, params, i++);
+                condition.toSql(sql, tableAlias, params, i++);
             }
         }
 
         return sql.toString();
     }
 
+    public String getWhereClause() {
+        return getWhereClause(null);
+    }
+
     public List<Pair<Attribute, Object>> getValues() {
         ArrayList<Pair<Attribute, Object>> params = new ArrayList<Pair<Attribute, Object>>(_params.size());
         for (Condition condition : _conditions) {
diff --git a/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java b/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java
index eb6b09c..d3fa6af 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java
@@ -38,6 +38,7 @@
 import org.apache.commons.dbcp2.PoolableConnection;
 import org.apache.commons.dbcp2.PoolableConnectionFactory;
 import org.apache.commons.dbcp2.PoolingDataSource;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.pool2.ObjectPool;
 import org.apache.commons.pool2.impl.GenericObjectPool;
 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
@@ -81,6 +82,7 @@
     public static final short SIMULATOR_DB = 3;
 
     public static final short CONNECTED_DB = -1;
+    public static final String CONNECTION_PARAMS = "scrollTolerantForwardOnly=true";
 
     private static AtomicLong s_id = new AtomicLong();
     private static final TransactionMBeanImpl s_mbean = new TransactionMBeanImpl();
@@ -1001,7 +1003,7 @@
     private static DataSource s_ds;
     private static DataSource s_usageDS;
     private static DataSource s_simulatorDS;
-    private static boolean s_dbHAEnabled;
+    protected static boolean s_dbHAEnabled;
 
     static {
         // Initialize with assumed db.properties file
@@ -1032,11 +1034,6 @@
             final long cloudMaxWait = Long.parseLong(dbProps.getProperty("db.cloud.maxWait"));
             final String cloudUsername = dbProps.getProperty("db.cloud.username");
             final String cloudPassword = dbProps.getProperty("db.cloud.password");
-            final String cloudHost = dbProps.getProperty("db.cloud.host");
-            final String cloudDriver = dbProps.getProperty("db.cloud.driver");
-            final int cloudPort = Integer.parseInt(dbProps.getProperty("db.cloud.port"));
-            final String cloudDbName = dbProps.getProperty("db.cloud.name");
-            final boolean cloudAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.cloud.autoReconnect"));
             final String cloudValidationQuery = dbProps.getProperty("db.cloud.validationQuery");
             final String cloudIsolationLevel = dbProps.getProperty("db.cloud.isolation.level");
 
@@ -1059,16 +1056,6 @@
             final boolean cloudTestWhileIdle = Boolean.parseBoolean(dbProps.getProperty("db.cloud.testWhileIdle"));
             final long cloudTimeBtwEvictionRunsMillis = Long.parseLong(dbProps.getProperty("db.cloud.timeBetweenEvictionRunsMillis"));
             final long cloudMinEvcitableIdleTimeMillis = Long.parseLong(dbProps.getProperty("db.cloud.minEvictableIdleTimeMillis"));
-            final boolean cloudPoolPreparedStatements = Boolean.parseBoolean(dbProps.getProperty("db.cloud.poolPreparedStatements"));
-            final String url = dbProps.getProperty("db.cloud.url.params");
-
-            String cloudDbHAParams = null;
-            String cloudReplicas = null;
-            if (s_dbHAEnabled) {
-                cloudDbHAParams = getDBHAParams("cloud", dbProps);
-                cloudReplicas = dbProps.getProperty("db.cloud.replicas");
-                s_logger.info("The replicas configured for Cloud Data base is/are : " + cloudReplicas);
-            }
 
             final boolean useSSL = Boolean.parseBoolean(dbProps.getProperty("db.cloud.useSSL"));
             if (useSSL) {
@@ -1078,13 +1065,12 @@
                 System.setProperty("javax.net.ssl.trustStorePassword", dbProps.getProperty("db.cloud.trustStorePassword"));
             }
 
-            final String cloudConnectionUri = cloudDriver + "://" + cloudHost + (s_dbHAEnabled ? "," + cloudReplicas : "") + ":" + cloudPort + "/" + cloudDbName +
-                    "?autoReconnect=" + cloudAutoReconnect + (url != null ? "&" + url : "") + (useSSL ? "&useSSL=true" : "") +
-                    (s_dbHAEnabled ? "&" + cloudDbHAParams : "") + (s_dbHAEnabled ? "&loadBalanceStrategy=" + loadBalanceStrategy : "");
-            DriverLoader.loadDriver(cloudDriver);
+            Pair<String, String> cloudUriAndDriver = getConnectionUriAndDriver(dbProps, loadBalanceStrategy, useSSL, "cloud");
+
+            DriverLoader.loadDriver(cloudUriAndDriver.second());
 
             // Default Data Source for CloudStack
-            s_ds = createDataSource(cloudConnectionUri, cloudUsername, cloudPassword, cloudMaxActive, cloudMaxIdle, cloudMaxWait,
+            s_ds = createDataSource(cloudUriAndDriver.first(), cloudUsername, cloudPassword, cloudMaxActive, cloudMaxIdle, cloudMaxWait,
                     cloudTimeBtwEvictionRunsMillis, cloudMinEvcitableIdleTimeMillis, cloudTestWhileIdle, cloudTestOnBorrow,
                     cloudValidationQuery, isolationLevel);
 
@@ -1094,20 +1080,13 @@
             final long usageMaxWait = Long.parseLong(dbProps.getProperty("db.usage.maxWait"));
             final String usageUsername = dbProps.getProperty("db.usage.username");
             final String usagePassword = dbProps.getProperty("db.usage.password");
-            final String usageHost = dbProps.getProperty("db.usage.host");
-            final String usageDriver = dbProps.getProperty("db.usage.driver");
-            final int usagePort = Integer.parseInt(dbProps.getProperty("db.usage.port"));
-            final String usageDbName = dbProps.getProperty("db.usage.name");
-            final boolean usageAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.usage.autoReconnect"));
-            final String usageUrl = dbProps.getProperty("db.usage.url.params");
 
-            final String usageConnectionUri = usageDriver + "://" + usageHost + (s_dbHAEnabled ? "," + dbProps.getProperty("db.cloud.replicas") : "") + ":" + usagePort +
-                    "/" + usageDbName + "?autoReconnect=" + usageAutoReconnect + (usageUrl != null ? "&" + usageUrl : "") +
-                    (s_dbHAEnabled ? "&" + getDBHAParams("usage", dbProps) : "") + (s_dbHAEnabled ? "&loadBalanceStrategy=" + loadBalanceStrategy : "");
-            DriverLoader.loadDriver(usageDriver);
+            Pair<String, String> usageUriAndDriver = getConnectionUriAndDriver(dbProps, loadBalanceStrategy, useSSL, "usage");
+
+            DriverLoader.loadDriver(usageUriAndDriver.second());
 
             // Data Source for usage server
-            s_usageDS = createDataSource(usageConnectionUri, usageUsername, usagePassword,
+            s_usageDS = createDataSource(usageUriAndDriver.first(), usageUsername, usagePassword,
                     usageMaxActive, usageMaxIdle, usageMaxWait, null, null, null, null,
                     null, isolationLevel);
 
@@ -1118,14 +1097,28 @@
                 final long simulatorMaxWait = Long.parseLong(dbProps.getProperty("db.simulator.maxWait"));
                 final String simulatorUsername = dbProps.getProperty("db.simulator.username");
                 final String simulatorPassword = dbProps.getProperty("db.simulator.password");
-                final String simulatorHost = dbProps.getProperty("db.simulator.host");
-                final String simulatorDriver = dbProps.getProperty("db.simulator.driver");
-                final int simulatorPort = Integer.parseInt(dbProps.getProperty("db.simulator.port"));
-                final String simulatorDbName = dbProps.getProperty("db.simulator.name");
-                final boolean simulatorAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.simulator.autoReconnect"));
 
-                final String simulatorConnectionUri = simulatorDriver + "://" + simulatorHost + ":" + simulatorPort + "/" + simulatorDbName + "?autoReconnect=" +
-                        simulatorAutoReconnect;
+                String simulatorDriver;
+                String simulatorConnectionUri;
+                String simulatorUri = dbProps.getProperty("db.simulator.uri");
+
+                if (StringUtils.isEmpty(simulatorUri)) {
+                    simulatorDriver = dbProps.getProperty("db.simulator.driver");
+                    final int simulatorPort = Integer.parseInt(dbProps.getProperty("db.simulator.port"));
+                    final String simulatorDbName = dbProps.getProperty("db.simulator.name");
+                    final boolean simulatorAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.simulator.autoReconnect"));
+                    final String simulatorHost = dbProps.getProperty("db.simulator.host");
+
+                    simulatorConnectionUri = simulatorDriver + "://" + simulatorHost + ":" + simulatorPort + "/" + simulatorDbName + "?autoReconnect=" +
+                            simulatorAutoReconnect;
+                } else {
+                    s_logger.warn("db.simulator.uri was set, ignoring the following properties on db.properties: [db.simulator.driver, db.simulator.host, db.simulator.port, "
+                            + "db.simulator.name, db.simulator.autoReconnect].");
+                    String[] splitUri = simulatorUri.split(":");
+                    simulatorDriver = String.format("%s:%s", splitUri[0], splitUri[1]);
+                    simulatorConnectionUri = simulatorUri;
+                }
+
                 DriverLoader.loadDriver(simulatorDriver);
 
                 s_simulatorDS = createDataSource(simulatorConnectionUri, simulatorUsername, simulatorPassword,
@@ -1143,6 +1136,88 @@
         }
     }
 
+    protected static Pair<String, String> getConnectionUriAndDriver(Properties dbProps, String loadBalanceStrategy, boolean useSSL, String schema) {
+        String connectionUri;
+        String driver;
+        String propertyUri = dbProps.getProperty(String.format("db.%s.uri", schema));
+
+        if (StringUtils.isEmpty(propertyUri)) {
+            driver = dbProps.getProperty(String.format("db.%s.driver", schema));
+            connectionUri = getPropertiesAndBuildConnectionUri(dbProps, loadBalanceStrategy, driver, useSSL, schema);
+        } else {
+            s_logger.warn(String.format("db.%s.uri was set, ignoring the following properties for schema %s of db.properties: [host, port, name, driver, autoReconnect, url.params,"
+                    + " replicas, ha.loadBalanceStrategy, ha.enable, failOverReadOnly, reconnectAtTxEnd, autoReconnectForPools, secondsBeforeRetrySource, queriesBeforeRetrySource, "
+                    + "initialTimeout].", schema, schema));
+
+            String[] splitUri = propertyUri.split(":");
+            driver = String.format("%s:%s", splitUri[0], splitUri[1]);
+
+            connectionUri = propertyUri;
+        }
+        s_logger.info(String.format("Using the following URI to connect to %s database [%s].", schema, connectionUri));
+        return new Pair<>(connectionUri, driver);
+    }
+
+    protected static String getPropertiesAndBuildConnectionUri(Properties dbProps, String loadBalanceStrategy, String driver, boolean useSSL, String schema) {
+        String host = dbProps.getProperty(String.format("db.%s.host", schema));
+        int port = Integer.parseInt(dbProps.getProperty(String.format("db.%s.port", schema)));
+        String dbName = dbProps.getProperty(String.format("db.%s.name", schema));
+        boolean autoReconnect = Boolean.parseBoolean(dbProps.getProperty(String.format("db.%s.autoReconnect", schema)));
+        String urlParams = dbProps.getProperty(String.format("db.%s.url.params", schema));
+
+        String replicas = null;
+        String dbHaParams = null;
+        if (s_dbHAEnabled) {
+            dbHaParams = getDBHAParams(schema, dbProps);
+            replicas = dbProps.getProperty(String.format("db.%s.replicas", schema));
+            s_logger.info(String.format("The replicas configured for %s data base are %s.", schema, replicas));
+        }
+
+        return buildConnectionUri(loadBalanceStrategy, driver, useSSL, host, replicas, port, dbName, autoReconnect, urlParams, dbHaParams);
+    }
+
+    protected static String buildConnectionUri(String loadBalanceStrategy, String driver, boolean useSSL, String host, String replicas, int port, String dbName, boolean autoReconnect,
+            String urlParams, String dbHaParams) {
+
+        StringBuilder connectionUri = new StringBuilder();
+        connectionUri.append(driver);
+        connectionUri.append("://");
+        connectionUri.append(host);
+
+        if (s_dbHAEnabled) {
+            connectionUri.append(",");
+            connectionUri.append(replicas);
+        }
+
+        connectionUri.append(":");
+        connectionUri.append(port);
+        connectionUri.append("/");
+        connectionUri.append(dbName);
+        connectionUri.append("?autoReconnect=");
+        connectionUri.append(autoReconnect);
+
+        if (urlParams != null) {
+            connectionUri.append("&");
+            connectionUri.append(urlParams);
+        }
+
+        if (useSSL) {
+            connectionUri.append("&useSSL=true");
+        }
+
+        if (s_dbHAEnabled) {
+            connectionUri.append("&");
+            connectionUri.append(dbHaParams);
+            connectionUri.append("&loadBalanceStrategy=");
+            connectionUri.append(loadBalanceStrategy);
+        }
+
+        connectionUri.append("&");
+        connectionUri.append(CONNECTION_PARAMS);
+
+        return connectionUri.toString();
+    }
+
     /**
      * Creates a data source
      */
@@ -1204,7 +1279,7 @@
 
     @SuppressWarnings({"unchecked", "rawtypes"})
     private static DataSource getDefaultDataSource(final String database) {
-        final ConnectionFactory connectionFactory = new DriverManagerConnectionFactory("jdbc:mysql://localhost:3306/" + database, "cloud", "cloud");
+        final ConnectionFactory connectionFactory = new DriverManagerConnectionFactory("jdbc:mysql://localhost:3306/" + database  + "?" + CONNECTION_PARAMS, "cloud", "cloud");
         final PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null);
         final GenericObjectPool connectionPool = new GenericObjectPool(poolableConnectionFactory);
         return new PoolingDataSource(connectionPool);
diff --git a/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java b/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java
index 352ea73..3086003 100644
--- a/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java
+++ b/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java
@@ -18,6 +18,8 @@
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -36,6 +38,7 @@
     @Mock
     SQLException mockedSQLException;
 
+    private static final DbTestDao dbTestDao = new DbTestDao();
     private static final String INTEGRITY_CONSTRAINT_VIOLATION = "23000";
     private static final int DUPLICATE_ENTRY_ERRO_CODE = 1062;
 
@@ -214,4 +217,50 @@
 
         Assert.assertEquals(resultSetSize, result);
     }
+
+    @Test
+    public void addJoinsTest() {
+        StringBuilder joinString = new StringBuilder();
+        Collection<JoinBuilder<SearchCriteria<?>>> joins = new ArrayList<>();
+
+        Attribute attr1 = new Attribute("table1", "column1");
+        Attribute attr2 = new Attribute("table2", "column2");
+        Attribute attr3 = new Attribute("table3", "column1");
+        Attribute attr4 = new Attribute("table4", "column2");
+        Attribute attr5 = new Attribute("table3", "column1");
+        Attribute attr6 = new Attribute("XYZ");
+
+        joins.add(new JoinBuilder<>("", dbTestDao.createSearchCriteria(), attr1, attr2, JoinBuilder.JoinType.INNER));
+        joins.add(new JoinBuilder<>("", dbTestDao.createSearchCriteria(),
+                new Attribute[]{attr3, attr5}, new Attribute[]{attr4, attr6}, JoinBuilder.JoinType.INNER, JoinBuilder.JoinCondition.OR));
+        dbTestDao.addJoins(joinString, joins);
+
+        Assert.assertEquals(" INNER JOIN table2 ON table1.column1=table2.column2 " +
+                " INNER JOIN table4 ON table3.column1=table4.column2 OR table3.column1=? ", joinString.toString());
+    }
+
+    @Test
+    public void multiJoinSameTableTest() {
+        StringBuilder joinString = new StringBuilder();
+        Collection<JoinBuilder<SearchCriteria<?>>> joins = new ArrayList<>();
+
+        Attribute tAc1 = new Attribute("tableA", "column1");
+        Attribute tAc2 = new Attribute("tableA", "column2");
+        Attribute tAc3 = new Attribute("tableA", "column3");
+        Attribute tBc2 = new Attribute("tableB", "column2");
+        Attribute tCc3 = new Attribute("tableC", "column3");
+        Attribute tDc4 = new Attribute("tableD", "column4");
+        Attribute tDc5 = new Attribute("tableD", "column5");
+        Attribute attr = new Attribute(123);
+
+        joins.add(new JoinBuilder<>("tableA1Alias", dbTestDao.createSearchCriteria(), tBc2, tAc1, JoinBuilder.JoinType.INNER));
+        joins.add(new JoinBuilder<>("tableA2Alias", dbTestDao.createSearchCriteria(), tCc3, tAc2, JoinBuilder.JoinType.INNER));
+        joins.add(new JoinBuilder<>("tableA3Alias", dbTestDao.createSearchCriteria(),
+                new Attribute[]{tDc4, tDc5}, new Attribute[]{tAc3, attr}, JoinBuilder.JoinType.INNER, JoinBuilder.JoinCondition.AND));
+        dbTestDao.addJoins(joinString, joins);
+
+        Assert.assertEquals(" INNER JOIN tableA tableA1Alias ON tableB.column2=tableA1Alias.column1 " +
+                " INNER JOIN tableA tableA2Alias ON tableC.column3=tableA2Alias.column2 " +
+                " INNER JOIN tableA tableA3Alias ON tableD.column4=tableA3Alias.column3 AND tableD.column5=? ", joinString.toString());
+    }
 }
diff --git a/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.java b/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.java
new file mode 100644
index 0000000..2e0af6f
--- /dev/null
+++ b/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.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 com.cloud.utils.db;
+
+import com.cloud.utils.Pair;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Properties;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransactionLegacyTest {
+
+    Properties properties;
+
+    @Before
+    public void setup(){
+        properties = new Properties();
+        properties.setProperty("db.cloud.host", "host");
+        properties.setProperty("db.cloud.port", "5555");
+        properties.setProperty("db.cloud.name", "name");
+        properties.setProperty("db.cloud.autoReconnect", "false");
+        properties.setProperty("db.cloud.url.params", "someParams");
+        TransactionLegacy.s_dbHAEnabled = false;
+    }
+    @Test
+    public void getConnectionUriAndDriverTestWithoutUri() {
+        properties.setProperty("db.cloud.uri", "");
+        properties.setProperty("db.cloud.driver", "driver");
+
+        Pair<String, String> result = TransactionLegacy.getConnectionUriAndDriver(properties, null, false, "cloud");
+
+        Assert.assertEquals("driver://host:5555/name?autoReconnect=false&someParams", result.first());
+        Assert.assertEquals("driver", result.second());
+    }
+
+    @Test
+    public void getConnectionUriAndDriverTestWithUri() {
+        properties.setProperty("db.cloud.uri", "jdbc:driver:myFavoriteUri");
+
+        Pair<String, String> result = TransactionLegacy.getConnectionUriAndDriver(properties, null, false, "cloud");
+
+        Assert.assertEquals("jdbc:driver:myFavoriteUri", result.first());
+        Assert.assertEquals("jdbc:driver", result.second());
+    }
+
+    @Test
+    public void getPropertiesAndBuildConnectionUriTestDbHaDisabled() {
+        String result = TransactionLegacy.getPropertiesAndBuildConnectionUri(properties, "strat", "driver", true, "cloud");
+
+        Assert.assertEquals("driver://host:5555/name?autoReconnect=false&someParams&useSSL=true", result);
+    }
+
+    @Test
+    public void getPropertiesAndBuildConnectionUriTestDbHaEnabled() {
+        TransactionLegacy.s_dbHAEnabled = true;
+        properties.setProperty("db.cloud.failOverReadOnly", "true");
+        properties.setProperty("db.cloud.reconnectAtTxEnd", "false");
+        properties.setProperty("db.cloud.autoReconnectForPools", "true");
+        properties.setProperty("db.cloud.secondsBeforeRetrySource", "25");
+        properties.setProperty("db.cloud.queriesBeforeRetrySource", "105");
+        properties.setProperty("db.cloud.initialTimeout", "1000");
+        properties.setProperty("db.cloud.replicas", "second_host");
+
+        String result = TransactionLegacy.getPropertiesAndBuildConnectionUri(properties, "strat", "driver", true, "cloud");
+
+        Assert.assertEquals("driver://host,second_host:5555/name?autoReconnect=false&someParams&useSSL=true&failOverReadOnly=true&reconnectAtTxEnd=false&autoReconnectFor"
+                + "Pools=true&secondsBeforeRetrySource=25&queriesBeforeRetrySource=105&initialTimeout=1000&loadBalanceStrategy=strat", result);
+    }
+
+    @Test
+    public void buildConnectionUriTestDbHaDisabled() {
+        String result = TransactionLegacy.buildConnectionUri(null, "driver", false, "host", null, 5555, "cloud", false, null, null);
+
+        Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false", result);
+    }
+
+    @Test
+    public void buildConnectionUriTestDbHaEnabled() {
+        TransactionLegacy.s_dbHAEnabled = true;
+
+        String result = TransactionLegacy.buildConnectionUri("strat", "driver", false, "host", "second_host", 5555, "cloud", false, null, "dbHaParams");
+
+        Assert.assertEquals("driver://host,second_host:5555/cloud?autoReconnect=false&dbHaParams&loadBalanceStrategy=strat", result);
+    }
+
+    @Test
+    public void buildConnectionUriTestUrlParamsNotNull() {
+        String result = TransactionLegacy.buildConnectionUri(null, "driver", false, "host", null, 5555, "cloud", false, "urlParams", null);
+
+        Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false&urlParams", result);
+    }
+
+    @Test
+    public void buildConnectionUriTestUseSslTrue() {
+        String result = TransactionLegacy.buildConnectionUri(null, "driver", true, "host", null, 5555, "cloud", false, null, null);
+
+        Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false&useSSL=true", result);
+    }
+}
diff --git a/framework/db/src/test/resources/db.properties b/framework/db/src/test/resources/db.properties
index e3a5d22..bb7c398 100644
--- a/framework/db/src/test/resources/db.properties
+++ b/framework/db/src/test/resources/db.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Just here to make the unit test not blow up
\ No newline at end of file
+# Just here to make the unit test not blow up
diff --git a/framework/direct-download/pom.xml b/framework/direct-download/pom.xml
index caa346e..d14507d 100644
--- a/framework/direct-download/pom.xml
+++ b/framework/direct-download/pom.xml
@@ -25,14 +25,14 @@
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-utils</artifactId>
-            <version>4.18.3.0-SNAPSHOT</version>
+            <version>${project.version}</version>
             <scope>compile</scope>
         </dependency>
     </dependencies>
     <parent>
         <artifactId>cloudstack-framework</artifactId>
         <groupId>org.apache.cloudstack</groupId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
-</project>
\ No newline at end of file
+</project>
diff --git a/framework/events/pom.xml b/framework/events/pom.xml
index 12c89f0..4b6bc45 100644
--- a/framework/events/pom.xml
+++ b/framework/events/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/ipc/pom.xml b/framework/ipc/pom.xml
index 28ad9e1..e1a1e8d 100644
--- a/framework/ipc/pom.xml
+++ b/framework/ipc/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageDetector.java b/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageDetector.java
index c771d6b..4bcf9b1 100644
--- a/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageDetector.java
+++ b/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageDetector.java
@@ -31,15 +31,15 @@
         _subjects = null;
     }
 
-    public void waitAny(long timeoutInMiliseconds) {
-        if (timeoutInMiliseconds < 100) {
-            s_logger.warn("waitAny is passed with a too short time-out interval. " + timeoutInMiliseconds + "ms");
-            timeoutInMiliseconds = 100;
+    public void waitAny(long timeoutInMilliseconds) {
+        if (timeoutInMilliseconds < 100) {
+            s_logger.warn("waitAny is passed with a too short time-out interval. " + timeoutInMilliseconds + "ms");
+            timeoutInMilliseconds = 100;
         }
 
         synchronized (this) {
             try {
-                wait(timeoutInMiliseconds);
+                wait(timeoutInMilliseconds);
             } catch (InterruptedException e) {
                 s_logger.debug("[ignored] interrupted while waiting on any message.");
             }
diff --git a/framework/ipc/src/main/resources/META-INF/cloudstack/core/spring-framework-ipc-core-context.xml b/framework/ipc/src/main/resources/META-INF/cloudstack/core/spring-framework-ipc-core-context.xml
index 15b6f80..926a84a 100644
--- a/framework/ipc/src/main/resources/META-INF/cloudstack/core/spring-framework-ipc-core-context.xml
+++ b/framework/ipc/src/main/resources/META-INF/cloudstack/core/spring-framework-ipc-core-context.xml
@@ -56,4 +56,4 @@
 
   <bean id="messageBus" class = "org.apache.cloudstack.framework.messagebus.MessageBusBase" />
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/framework/jobs/pom.xml b/framework/jobs/pom.xml
index 16a94ef..d201a48 100644
--- a/framework/jobs/pom.xml
+++ b/framework/jobs/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJob.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJob.java
index b8200bf..bde9b4a 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJob.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJob.java
@@ -90,6 +90,8 @@
     @Override
     Long getExecutingMsid();
 
+    void setExecutingMsid(Long msid);
+
     @Override
     Long getCompleteMsid();
 
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobManager.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobManager.java
index 8542407..9f50a54 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobManager.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobManager.java
@@ -117,12 +117,12 @@
      *
      * @param wakupTopicsOnMessageBus topic on message bus to wakeup the wait
      * @param checkIntervalInMilliSeconds time to break out wait for checking predicate condition
-     * @param timeoutInMiliseconds time out to break out the whole wait process
+     * @param timeoutInMilliseconds time out to break out the whole wait process
      * @param predicate
      * @return true, predicate condition is satisfied
      *             false, wait is timed out
      */
-    boolean waitAndCheck(AsyncJob job, String[] wakupTopicsOnMessageBus, long checkIntervalInMilliSeconds, long timeoutInMiliseconds, Predicate predicate);
+    boolean waitAndCheck(AsyncJob job, String[] wakupTopicsOnMessageBus, long checkIntervalInMilliSeconds, long timeoutInMilliseconds, Predicate predicate);
 
     AsyncJob queryJob(long jobId, boolean updatePollTime);
 
@@ -133,4 +133,14 @@
     List<AsyncJobVO> findFailureAsyncJobs(String... cmds);
 
     long countPendingJobs(String havingInfo, String... cmds);
+
+    // Returns the number of pending jobs for the given Management server msids.
+    // NOTE: This is the msid and NOT the id
+    long countPendingNonPseudoJobs(Long... msIds);
+
+    void enableAsyncJobs();
+
+    void disableAsyncJobs();
+
+    boolean isAsyncJobsEnabled();
 }
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java
index 2696e10..9f7a4ad 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java
@@ -46,4 +46,8 @@
     List<AsyncJobVO> getFailureJobsSinceLastMsStart(long msId, String... cmds);
 
     long countPendingJobs(String havingInfo, String... cmds);
+
+    // Returns the number of pending jobs for the given Management server msids.
+    // NOTE: This is the msid and NOT the id
+    long countPendingNonPseudoJobs(Long... msIds);
 }
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java
index 7dd7343..1914ff7 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java
@@ -48,6 +48,7 @@
     private final SearchBuilder<AsyncJobVO> expiringCompletedAsyncJobSearch;
     private final SearchBuilder<AsyncJobVO> failureMsidAsyncJobSearch;
     private final GenericSearchBuilder<AsyncJobVO, Long> asyncJobTypeSearch;
+    private final GenericSearchBuilder<AsyncJobVO, Long> pendingNonPseudoAsyncJobsSearch;
 
     public AsyncJobDaoImpl() {
         pendingAsyncJobSearch = createSearchBuilder();
@@ -103,6 +104,11 @@
         asyncJobTypeSearch.and("status", asyncJobTypeSearch.entity().getStatus(), SearchCriteria.Op.EQ);
         asyncJobTypeSearch.done();
 
+        pendingNonPseudoAsyncJobsSearch = createSearchBuilder(Long.class);
+        pendingNonPseudoAsyncJobsSearch.select(null, SearchCriteria.Func.COUNT, pendingNonPseudoAsyncJobsSearch.entity().getId());
+        pendingNonPseudoAsyncJobsSearch.and("instanceTypeNEQ", pendingNonPseudoAsyncJobsSearch.entity().getInstanceType(), SearchCriteria.Op.NEQ);
+        pendingNonPseudoAsyncJobsSearch.and("jobStatusEQ", pendingNonPseudoAsyncJobsSearch.entity().getStatus(), SearchCriteria.Op.EQ);
+        pendingNonPseudoAsyncJobsSearch.and("executingMsidIN", pendingNonPseudoAsyncJobsSearch.entity().getExecutingMsid(), SearchCriteria.Op.IN);
     }
 
     @Override
@@ -237,6 +243,20 @@
         return listBy(sc);
     }
 
+    // Returns the number of pending jobs for the given Management server msids.
+    // NOTE: This is the msid and NOT the id
+    @Override
+    public long countPendingNonPseudoJobs(Long... msIds) {
+        SearchCriteria<Long> sc = pendingNonPseudoAsyncJobsSearch.create();
+        sc.setParameters("instanceTypeNEQ", AsyncJobVO.PSEUDO_JOB_INSTANCE_TYPE);
+        sc.setParameters("jobStatusEQ", JobInfo.Status.IN_PROGRESS);
+        if (msIds != null) {
+            sc.setParameters("executingMsidIN", (Object[])msIds);
+        }
+        List<Long> results = customSearch(sc, null);
+        return results.get(0);
+    }
+
     @Override
     public long countPendingJobs(String havingInfo, String... cmds) {
         SearchCriteria<Long> sc = asyncJobTypeSearch.create();
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java
index a963357..3c0f81d 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java
@@ -17,6 +17,8 @@
 
 package org.apache.cloudstack.framework.jobs.impl;
 
+import static com.cloud.utils.HumanReadableJson.getHumanReadableBytesJson;
+
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Collections;
@@ -33,15 +35,16 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import com.cloud.storage.dao.VolumeDetailsDao;
 import org.apache.cloudstack.api.ApiCommandResourceType;
-import org.apache.log4j.Logger;
-import org.apache.log4j.NDC;
 import org.apache.cloudstack.api.ApiErrorCode;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.jobs.AsyncJob;
@@ -49,26 +52,34 @@
 import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext;
 import org.apache.cloudstack.framework.jobs.AsyncJobManager;
 import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
-import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao;
 import org.apache.cloudstack.framework.jobs.dao.AsyncJobJoinMapDao;
 import org.apache.cloudstack.framework.jobs.dao.AsyncJobJournalDao;
 import org.apache.cloudstack.framework.jobs.dao.SyncQueueItemDao;
+import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
 import org.apache.cloudstack.framework.messagebus.MessageDetector;
 import org.apache.cloudstack.framework.messagebus.PublishScope;
 import org.apache.cloudstack.jobs.JobInfo;
 import org.apache.cloudstack.jobs.JobInfo.Status;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.management.ManagementServerHost;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.log4j.Logger;
 import org.apache.log4j.MDC;
+import org.apache.log4j.NDC;
 
 import com.cloud.cluster.ClusterManagerListener;
-import org.apache.cloudstack.management.ManagementServerHost;
-import com.cloud.storage.DataStoreRole;
+import com.cloud.network.Network;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
 import com.cloud.storage.Snapshot;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.SnapshotDetailsDao;
 import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
 import com.cloud.utils.DateUtil;
 import com.cloud.utils.Pair;
 import com.cloud.utils.Predicate;
@@ -91,11 +102,12 @@
 import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.exception.ExceptionUtil;
+import com.cloud.utils.fsm.NoTransitionException;
 import com.cloud.utils.mgmt.JmxUtil;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
 import com.cloud.vm.dao.VMInstanceDao;
-import com.cloud.storage.dao.VolumeDao;
-
-import static com.cloud.utils.HumanReadableJson.getHumanReadableBytesJson;
 
 public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager, ClusterManagerListener, Configurable {
     // Advanced
@@ -149,12 +161,23 @@
     @Inject
     private SnapshotDetailsDao _snapshotDetailsDao;
 
+    @Inject
+    private VolumeDataFactory volFactory;
+    @Inject
+    private VirtualMachineManager virtualMachineManager;
+    @Inject
+    private NetworkDao networkDao;
+    @Inject
+    private NetworkOrchestrationService networkOrchestrationService;
+
     private volatile long _executionRunNumber = 1;
 
     private final ScheduledExecutorService _heartbeatScheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AsyncJobMgr-Heartbeat"));
     private ExecutorService _apiJobExecutor;
     private ExecutorService _workerJobExecutor;
 
+    private boolean asyncJobsEnabled = true;
+
     @Override
     public String getConfigComponentName() {
         return AsyncJobManager.class.getSimpleName();
@@ -197,12 +220,21 @@
         return submitAsyncJob(job, false);
     }
 
+    private void checkShutdown() {
+        if (!isAsyncJobsEnabled()) {
+            throw new CloudRuntimeException("A shutdown has been triggered. Can not accept new jobs");
+        }
+    }
+
     @SuppressWarnings("unchecked")
     @DB
     public long submitAsyncJob(AsyncJob job, boolean scheduleJobExecutionInContext) {
+        checkShutdown();
+
         @SuppressWarnings("rawtypes")
         GenericDao dao = GenericDaoBase.getDao(job.getClass());
         job.setInitMsid(getMsid());
+        job.setExecutingMsid(getMsid());
         job.setSyncSource(null);        // no sync source originally
         dao.persist(job);
 
@@ -218,6 +250,8 @@
     @Override
     @DB
     public long submitAsyncJob(final AsyncJob job, final String syncObjType, final long syncObjId) {
+        checkShutdown();
+
         try {
             @SuppressWarnings("rawtypes")
             final GenericDao dao = GenericDaoBase.getDao(job.getClass());
@@ -744,7 +778,7 @@
     }
 
     @Override
-    public boolean waitAndCheck(AsyncJob job, String[] wakeupTopicsOnMessageBus, long checkIntervalInMilliSeconds, long timeoutInMiliseconds, Predicate predicate) {
+    public boolean waitAndCheck(AsyncJob job, String[] wakeupTopicsOnMessageBus, long checkIntervalInMilliSeconds, long timeoutInMilliseconds, Predicate predicate) {
 
         MessageDetector msgDetector = new MessageDetector();
         String[] topics = Arrays.copyOf(wakeupTopicsOnMessageBus, wakeupTopicsOnMessageBus.length + 1);
@@ -753,7 +787,7 @@
         msgDetector.open(_messageBus, topics);
         try {
             long startTick = System.currentTimeMillis();
-            while (timeoutInMiliseconds < 0 || System.currentTimeMillis() - startTick < timeoutInMiliseconds) {
+            while (timeoutInMilliseconds < 0 || System.currentTimeMillis() - startTick < timeoutInMilliseconds) {
                 msgDetector.waitAny(checkIntervalInMilliSeconds);
                 job = _jobDao.findById(job.getId());
                 if (job != null && job.getStatus().done()) {
@@ -827,6 +861,11 @@
 
             protected void reallyRun() {
                 try {
+                    if (!isAsyncJobsEnabled()) {
+                        s_logger.info("A shutdown has been triggered. Not executing any async job");
+                        return;
+                    }
+
                     List<SyncQueueItemVO> l = _queueMgr.dequeueFromAny(getMsid(), MAX_ONETIME_SCHEDULE_SIZE);
                     if (l != null && l.size() > 0) {
                         for (SyncQueueItemVO item : l) {
@@ -1072,6 +1111,7 @@
                         if (s_logger.isDebugEnabled()) {
                             s_logger.debug("Cancel left-over job-" + job.getId());
                         }
+                        cleanupResources(job);
                         job.setStatus(JobInfo.Status.FAILED);
                         job.setResultCode(ApiErrorCode.INTERNAL_ERROR.getHttpCode());
                         job.setResult("job cancelled because of management server restart or shutdown");
@@ -1084,26 +1124,8 @@
                             s_logger.debug("Purge queue item for cancelled job-" + job.getId());
                         }
                         _queueMgr.purgeAsyncJobQueueItemId(job.getId());
-                        if (ApiCommandResourceType.Volume.toString().equals(job.getInstanceType())) {
-
-                            try {
-                                _volumeDetailsDao.removeDetail(job.getInstanceId(), "SNAPSHOT_ID");
-                                _volsDao.remove(job.getInstanceId());
-                            } catch (Exception e) {
-                                s_logger.error("Unexpected exception while removing concurrent request meta data :" + e.getLocalizedMessage());
-                            }
-                        }
                     }
-                    final List<SnapshotDetailsVO> snapshotList = _snapshotDetailsDao.findDetails(AsyncJob.Constants.MS_ID, Long.toString(msid), false);
-                    for (final SnapshotDetailsVO snapshotDetailsVO : snapshotList) {
-                        SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotDetailsVO.getResourceId(), DataStoreRole.Primary);
-                        if (snapshot == null) {
-                            _snapshotDetailsDao.remove(snapshotDetailsVO.getId());
-                            continue;
-                        }
-                        snapshotSrv.processEventOnSnapshotObject(snapshot, Snapshot.Event.OperationFailed);
-                        _snapshotDetailsDao.removeDetail(snapshotDetailsVO.getResourceId(), AsyncJob.Constants.MS_ID);
-                    }
+                    cleanupFailedSnapshotsCreatedWithDefaultStrategy(msid);
                 }
             });
         } catch (Throwable e) {
@@ -1111,6 +1133,106 @@
         }
     }
 
+    /*
+    Cleanup Resources in transition state and move them to appropriate state
+    This will allow other operation on the resource, instead of being stuck in transition state
+     */
+    protected boolean cleanupResources(AsyncJobVO job) {
+        try {
+            ApiCommandResourceType resourceType = ApiCommandResourceType.fromString(job.getInstanceType());
+            if (resourceType == null) {
+                s_logger.warn("Unknown ResourceType. Skip Cleanup: " + job.getInstanceType());
+                return true;
+            }
+            switch (resourceType) {
+                case Volume:
+                    return cleanupVolume(job.getInstanceId());
+                case VirtualMachine:
+                    return cleanupVirtualMachine(job.getInstanceId());
+                case Network:
+                    return cleanupNetwork(job.getInstanceId());
+            }
+        } catch (Exception e) {
+            s_logger.warn("Error while cleaning up resource: [" + job.getInstanceType().toString()  + "] with Id: " + job.getInstanceId(), e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean cleanupVolume(final long volumeId) {
+        VolumeInfo vol = volFactory.getVolume(volumeId);
+        if (vol == null) {
+            s_logger.warn("Volume not found. Skip Cleanup. VolumeId: " + volumeId);
+            return true;
+        }
+        if (vol.getState().isTransitional()) {
+            s_logger.debug("Cleaning up volume with Id: " + volumeId);
+            boolean status = vol.stateTransit(Volume.Event.OperationFailed);
+            cleanupFailedVolumesCreatedFromSnapshots(volumeId);
+            return status;
+        }
+        s_logger.debug("Volume not in transition state. Skip cleanup. VolumeId: " + volumeId);
+        return true;
+    }
+
+    private boolean cleanupVirtualMachine(final long vmId) throws Exception {
+        VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(vmId);
+        if (vmInstanceVO == null) {
+            s_logger.warn("Instance not found. Skip Cleanup. InstanceId: " + vmId);
+            return true;
+        }
+        if (vmInstanceVO.getState().isTransitional()) {
+            s_logger.debug("Cleaning up Instance with Id: " + vmId);
+            return virtualMachineManager.stateTransitTo(vmInstanceVO, VirtualMachine.Event.OperationFailed, vmInstanceVO.getHostId());
+        }
+        s_logger.debug("Instance not in transition state. Skip cleanup. InstanceId: " + vmId);
+        return true;
+    }
+
+    private boolean cleanupNetwork(final long networkId) throws Exception {
+        NetworkVO networkVO = networkDao.findById(networkId);
+        if (networkVO == null) {
+            s_logger.warn("Network not found. Skip Cleanup. NetworkId: " + networkId);
+            return true;
+        }
+        if (Network.State.Implementing.equals(networkVO.getState())) {
+            try {
+                s_logger.debug("Cleaning up Network with Id: " + networkId);
+                return networkOrchestrationService.stateTransitTo(networkVO, Network.Event.OperationFailed);
+            } catch (final NoTransitionException e) {
+                networkVO.setState(Network.State.Shutdown);
+                networkDao.update(networkVO.getId(), networkVO);
+            }
+        }
+        s_logger.debug("Network not in transition state. Skip cleanup. NetworkId: " + networkId);
+        return true;
+    }
+
+    private void cleanupFailedVolumesCreatedFromSnapshots(final long volumeId) {
+        try {
+            VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, VolumeService.SNAPSHOT_ID);
+            if (volumeDetail != null) {
+                _volumeDetailsDao.removeDetail(volumeId, VolumeService.SNAPSHOT_ID);
+                _volsDao.remove(volumeId);
+            }
+        } catch (Exception e) {
+            s_logger.error("Unexpected exception while removing concurrent request meta data :" + e.getLocalizedMessage());
+        }
+    }
+
+    private void cleanupFailedSnapshotsCreatedWithDefaultStrategy(final long msid) {
+        final List<SnapshotDetailsVO> snapshotList = _snapshotDetailsDao.findDetails(AsyncJob.Constants.MS_ID, Long.toString(msid), false);
+        for (final SnapshotDetailsVO snapshotDetailsVO : snapshotList) {
+            SnapshotInfo snapshot = snapshotFactory.getSnapshotOnPrimaryStore(snapshotDetailsVO.getResourceId());
+            if (snapshot == null) {
+                _snapshotDetailsDao.remove(snapshotDetailsVO.getId());
+                continue;
+            }
+            snapshotSrv.processEventOnSnapshotObject(snapshot, Snapshot.Event.OperationFailed);
+            _snapshotDetailsDao.removeDetail(snapshotDetailsVO.getResourceId(), AsyncJob.Constants.MS_ID);
+        }
+    }
+
     @Override
     public void onManagementNodeJoined(List<? extends ManagementServerHost> nodeList, long selfNodeId) {
     }
@@ -1171,4 +1293,26 @@
     public long countPendingJobs(String havingInfo, String... cmds) {
         return _jobDao.countPendingJobs(havingInfo, cmds);
     }
+
+    // Returns the number of pending jobs for the given Management server msids.
+    // NOTE: This is the msid and NOT the id
+    @Override
+    public long countPendingNonPseudoJobs(Long... msIds) {
+    return _jobDao.countPendingNonPseudoJobs(msIds);
+    }
+
+    @Override
+    public void enableAsyncJobs() {
+        this.asyncJobsEnabled = true;
+    }
+
+    @Override
+    public void disableAsyncJobs() {
+        this.asyncJobsEnabled = false;
+    }
+
+    @Override
+    public boolean isAsyncJobsEnabled() {
+        return asyncJobsEnabled;
+    }
 }
diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java
index 8f3c033..6b85ae2 100644
--- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java
+++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java
@@ -294,6 +294,7 @@
         return executingMsid;
     }
 
+    @Override
     public void setExecutingMsid(Long executingMsid) {
         this.executingMsid = executingMsid;
     }
diff --git a/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImplTest.java b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImplTest.java
new file mode 100644
index 0000000..0be5dbc
--- /dev/null
+++ b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImplTest.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.framework.jobs.impl;
+
+import com.cloud.network.Network;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.storage.Volume;
+import com.cloud.utils.fsm.NoTransitionException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AsyncJobManagerImplTest {
+    @Spy
+    @InjectMocks
+    AsyncJobManagerImpl asyncJobManager;
+    @Mock
+    VolumeDataFactory volFactory;
+    @Mock
+    VMInstanceDao vmInstanceDao;
+    @Mock
+    VirtualMachineManager virtualMachineManager;
+    @Mock
+    NetworkDao networkDao;
+    @Mock
+    NetworkOrchestrationService networkOrchestrationService;
+
+    @Test
+    public void testCleanupVolumeResource() {
+        AsyncJobVO job = new AsyncJobVO();
+        job.setInstanceType(ApiCommandResourceType.Volume.toString());
+        job.setInstanceId(1L);
+        VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
+        when(volFactory.getVolume(Mockito.anyLong())).thenReturn(volumeInfo);
+        when(volumeInfo.getState()).thenReturn(Volume.State.Attaching);
+        asyncJobManager.cleanupResources(job);
+        Mockito.verify(volumeInfo, Mockito.times(1)).stateTransit(Volume.Event.OperationFailed);
+    }
+
+    @Test
+    public void testCleanupVmResource() throws NoTransitionException {
+        AsyncJobVO job = new AsyncJobVO();
+        job.setInstanceType(ApiCommandResourceType.VirtualMachine.toString());
+        job.setInstanceId(1L);
+        VMInstanceVO vmInstanceVO = Mockito.mock(VMInstanceVO.class);
+        when(vmInstanceDao.findById(Mockito.anyLong())).thenReturn(vmInstanceVO);
+        when(vmInstanceVO.getState()).thenReturn(VirtualMachine.State.Starting);
+        when(vmInstanceVO.getHostId()).thenReturn(1L);
+        asyncJobManager.cleanupResources(job);
+        Mockito.verify(virtualMachineManager, Mockito.times(1)).stateTransitTo(vmInstanceVO, VirtualMachine.Event.OperationFailed, 1L);
+    }
+
+    @Test
+    public void testCleanupNetworkResource() throws NoTransitionException {
+        AsyncJobVO job = new AsyncJobVO();
+        job.setInstanceType(ApiCommandResourceType.Network.toString());
+        job.setInstanceId(1L);
+        NetworkVO networkVO = Mockito.mock(NetworkVO.class);
+        when(networkDao.findById(Mockito.anyLong())).thenReturn(networkVO);
+        when(networkVO.getState()).thenReturn(Network.State.Implementing);
+        asyncJobManager.cleanupResources(job);
+        Mockito.verify(networkOrchestrationService, Mockito.times(1)).stateTransitTo(networkVO,
+                Network.Event.OperationFailed);
+    }
+}
diff --git a/framework/jobs/src/test/resources/log4j.properties b/framework/jobs/src/test/resources/log4j.properties
index fdf675a..7ffdca8 100644
--- a/framework/jobs/src/test/resources/log4j.properties
+++ b/framework/jobs/src/test/resources/log4j.properties
@@ -32,4 +32,3 @@
 #log4j.category.com.cloud.utils.db.Transaction=ALL
 log4j.category.org.apache.cloudstack.network.contrail=ALL
 log4j.category.com.cloud.network=ALL
-
diff --git a/framework/managed-context/pom.xml b/framework/managed-context/pom.xml
index 78a02d5..479597e 100644
--- a/framework/managed-context/pom.xml
+++ b/framework/managed-context/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/pom.xml b/framework/pom.xml
index a8025b6..ca34cf0 100644
--- a/framework/pom.xml
+++ b/framework/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <build>
         <plugins>
diff --git a/framework/quota/pom.xml b/framework/quota/pom.xml
index 07cf209..02ff7f7 100644
--- a/framework/quota/pom.xml
+++ b/framework/quota/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java
index 8ef2c96..555757e 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java
@@ -17,10 +17,12 @@
 package org.apache.cloudstack.quota;
 
 import java.math.BigDecimal;
+import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -36,6 +38,8 @@
 import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
 import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -47,6 +51,7 @@
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.user.dao.UserDao;
+import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.db.TransactionLegacy;
 import java.util.HashSet;
@@ -208,8 +213,14 @@
                 userNames = userNames.substring(0, userNames.length() - 1);
             }
 
-            final Map<String, String> subjectOptionMap = generateOptionMap(account, userNames, accountDomain, balance, usage, emailType, false);
-            final Map<String, String> bodyOptionMap = generateOptionMap(account, userNames, accountDomain, balance, usage, emailType, true);
+            String currencySymbol = ObjectUtils.defaultIfNull(_configDao.getValue(QuotaConfig.QuotaCurrencySymbol.key()), QuotaConfig.QuotaCurrencySymbol.defaultValue());
+            NumberFormat localeFormat = getLocaleFormatIfCurrencyLocaleNotNull();
+
+            String balanceStr = String.format("%s %s", currencySymbol, NumbersUtil.formatBigDecimalAccordingToNumberFormat(balance, localeFormat));
+            String usageStr = String.format("%s %s", currencySymbol, NumbersUtil.formatBigDecimalAccordingToNumberFormat(usage, localeFormat));
+
+            final Map<String, String> subjectOptionMap = generateOptionMap(account, userNames, accountDomain, balanceStr, usageStr, emailType, false);
+            final Map<String, String> bodyOptionMap = generateOptionMap(account, userNames, accountDomain, balanceStr, usageStr, emailType, true);
 
             if (s_logger.isDebugEnabled()) {
                 s_logger.debug(String.format("Sending quota alert with values: accountName [%s], accountID [%s], accountUsers [%s], domainName [%s], domainID [%s].",
@@ -223,7 +234,7 @@
             final String body = bodySubstitutor.replace(emailTemplate.getTemplateBody());
 
             try {
-                sendQuotaAlert(account.getUuid(), emailRecipients, subject, body);
+                sendQuotaAlert(account, emailRecipients, subject, body);
                 emailToBeSent.sentSuccessfully(_quotaAcc);
             } catch (Exception e) {
                 s_logger.error(String.format("Unable to send quota alert email (subject=%s; body=%s) to account %s (%s) recipients (%s) due to error (%s)", subject, body, account.getAccountName(),
@@ -237,19 +248,29 @@
         }
     }
 
+    private NumberFormat getLocaleFormatIfCurrencyLocaleNotNull() {
+        String currencyLocale = _configDao.getValue(QuotaConfig.QuotaCurrencyLocale.key());
+        NumberFormat localeFormat = null;
+        if (currencyLocale != null) {
+            Locale locale = Locale.forLanguageTag(currencyLocale);
+            localeFormat = NumberFormat.getNumberInstance(locale);
+        }
+        return localeFormat;
+    }
+
     /*
     *
     *
      */
-    public Map<String, String> generateOptionMap(AccountVO accountVO, String userNames, DomainVO domainVO, final BigDecimal balance, final BigDecimal usage,
+    public Map<String, String> generateOptionMap(AccountVO accountVO, String userNames, DomainVO domainVO, final String balance, final String usage,
                                                  final QuotaConfig.QuotaEmailTemplateTypes emailType, boolean escapeHtml) {
         final Map<String, String> optionMap = new HashMap<>();
         optionMap.put("accountID", accountVO.getUuid());
         optionMap.put("domainID", domainVO.getUuid());
-        optionMap.put("quotaBalance", QuotaConfig.QuotaCurrencySymbol.value() + " " + balance.toString());
+        optionMap.put("quotaBalance", balance);
 
         if (emailType == QuotaEmailTemplateTypes.QUOTA_STATEMENT) {
-            optionMap.put("quotaUsage", QuotaConfig.QuotaCurrencySymbol.value() + " " + usage.toString());
+            optionMap.put("quotaUsage",  usage);
         }
 
         if (escapeHtml) {
@@ -354,17 +375,20 @@
         }
     };
 
-    protected void sendQuotaAlert(String accountUuid, List<String> emails, String subject, String body) {
+    protected void sendQuotaAlert(Account account, List<String> emails, String subject, String body) {
         SMTPMailProperties mailProperties = new SMTPMailProperties();
 
         mailProperties.setSender(new MailAddress(senderAddress));
+
+        body = addHeaderAndFooter(body, QuotaConfig.QuotaEmailHeader.valueIn(account.getDomainId()), QuotaConfig.QuotaEmailFooter.valueIn(account.getDomainId()));
+
         mailProperties.setSubject(subject);
         mailProperties.setContent(body);
         mailProperties.setContentType("text/html; charset=utf-8");
 
         if (CollectionUtils.isEmpty(emails)) {
             s_logger.warn(String.format("Account [%s] does not have users with email registered, "
-                    + "therefore we are unable to send quota alert email with subject [%s] and content [%s].", accountUuid, subject, body));
+                    + "therefore we are unable to send quota alert email with subject [%s] and content [%s].", account.getUuid(), subject, body));
             return;
         }
 
@@ -378,4 +402,16 @@
         mailSender.sendMail(mailProperties);
     }
 
+    protected String addHeaderAndFooter(String body, String header, String footer) {
+
+        if (StringUtils.isNotEmpty(header)) {
+            body = String.format("%s%s", header, body);
+        }
+        if (StringUtils.isNotEmpty(footer)) {
+            body = String.format("%s%s", body, footer);
+        }
+
+        return body;
+    }
+
 }
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
index b17d27b..56a6edf 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
@@ -89,11 +89,11 @@
 
     private TimeZone _usageTimezone;
     private int _aggregationDuration = 0;
-
-    static final BigDecimal s_hoursInMonth = BigDecimal.valueOf(DateUtil.HOURS_IN_A_MONTH);
     static final BigDecimal GiB_DECIMAL = BigDecimal.valueOf(ByteScaleUtils.GiB);
     List<Account.Type> lockablesAccountTypes = Arrays.asList(Account.Type.NORMAL, Account.Type.DOMAIN_ADMIN);
 
+    static BigDecimal hoursInCurrentMonth;
+
     public QuotaManagerImpl() {
         super();
     }
@@ -455,7 +455,7 @@
 
         }
 
-        jsInterpreter.injectVariable("resourceType", presetVariables.getResourceType());
+        jsInterpreter.injectStringVariable("resourceType", presetVariables.getResourceType());
         jsInterpreter.injectVariable("value", presetVariables.getValue().toString());
         jsInterpreter.injectVariable("zone", presetVariables.getZone().toString());
     }
@@ -533,7 +533,7 @@
 
     protected BigDecimal getUsageValueAccordingToUsageUnitType(UsageVO usageRecord, BigDecimal aggregatedQuotaTariffsValue, String quotaUnit) {
         BigDecimal rawUsage = BigDecimal.valueOf(usageRecord.getRawUsage());
-        BigDecimal costPerHour = aggregatedQuotaTariffsValue.divide(s_hoursInMonth, 8, RoundingMode.HALF_EVEN);
+        BigDecimal costPerHour = getCostPerHour(aggregatedQuotaTariffsValue, usageRecord.getStartDate());
 
         switch (UsageUnitTypes.getByDescription(quotaUnit)) {
             case COMPUTE_MONTH:
@@ -558,6 +558,12 @@
         }
     }
 
+    protected BigDecimal getCostPerHour(BigDecimal costPerMonth, Date date) {
+        BigDecimal hoursInCurrentMonth = BigDecimal.valueOf(DateUtil.getHoursInCurrentMonth(date));
+        s_logger.trace(String.format("Dividing tariff cost per month [%s] by [%s] to get the tariffs cost per hour.", costPerMonth, hoursInCurrentMonth));
+        return costPerMonth.divide(hoursInCurrentMonth, 8, RoundingMode.HALF_EVEN);
+    }
+
     @Override
     public boolean isLockable(AccountVO account) {
         return lockablesAccountTypes.contains(account.getType());
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Host.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Host.java
index 3371310..fef3e43 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Host.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Host.java
@@ -22,6 +22,8 @@
 public class Host extends GenericPresetVariable {
     private List<String> tags;
 
+    private Boolean isTagARule;
+
     public List<String> getTags() {
         return tags;
     }
@@ -31,4 +33,12 @@
         fieldNamesToIncludeInToString.add("tags");
     }
 
+    public Boolean getIsTagARule() {
+        return isTagARule;
+    }
+
+    public void setIsTagARule(Boolean isTagARule) {
+        this.isTagARule = isTagARule;
+        fieldNamesToIncludeInToString.add("isTagARule");
+    }
 }
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java
index 083a6fa..9723d3e 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java
@@ -25,8 +25,11 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import com.cloud.host.HostTagVO;
 import javax.inject.Inject;
 
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.StoragePoolTagVO;
 import org.apache.cloudstack.acl.RoleVO;
 import org.apache.cloudstack.acl.dao.RoleDao;
 import org.apache.cloudstack.backup.BackupOfferingVO;
@@ -65,6 +68,7 @@
 import com.cloud.storage.GuestOSVO;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
@@ -318,6 +322,10 @@
 
         value.setTags(getPresetVariableValueResourceTags(vmId, ResourceObjectType.UserVm));
         value.setTemplate(getPresetVariableValueTemplate(vmVo.getTemplateId()));
+        Hypervisor.HypervisorType hypervisorType = vmVo.getHypervisorType();
+        if (hypervisorType != null) {
+            value.setHypervisorType(hypervisorType.name());
+        }
     }
 
     protected void logNotLoadingMessageInTrace(String resource, int usageType) {
@@ -345,7 +353,17 @@
         Host host = new Host();
         host.setId(hostVo.getUuid());
         host.setName(hostVo.getName());
-        host.setTags(hostTagsDao.getHostTags(hostId));
+        List<HostTagVO> hostTagVOList = hostTagsDao.getHostTags(hostId);
+        List<String> hostTags = new ArrayList<>();
+        boolean isTagARule = false;
+        if (CollectionUtils.isNotEmpty(hostTagVOList)) {
+            isTagARule = hostTagVOList.get(0).getIsTagARule();
+            if (!isTagARule) {
+                hostTags = hostTagVOList.parallelStream().map(HostTagVO::getTag).collect(Collectors.toList());
+            }
+        }
+        host.setTags(hostTags);
+        host.setIsTagARule(isTagARule);
 
         return host;
     }
@@ -470,6 +488,11 @@
 
         value.setTags(getPresetVariableValueResourceTags(volumeId, ResourceObjectType.Volume));
         value.setSize(ByteScaleUtils.bytesToMebibytes(volumeVo.getSize()));
+
+        ImageFormat format = volumeVo.getFormat();
+        if (format != null) {
+            value.setVolumeFormat(format.name());
+        }
     }
 
     protected GenericPresetVariable getPresetVariableValueDiskOffering(Long diskOfferingId) {
@@ -497,7 +520,17 @@
         storage.setId(storagePoolVo.getUuid());
         storage.setName(storagePoolVo.getName());
         storage.setScope(storagePoolVo.getScope());
-        storage.setTags(storagePoolTagsDao.getStoragePoolTags(storageId));
+        List<StoragePoolTagVO> storagePoolTagVOList = storagePoolTagsDao.findStoragePoolTags(storageId);
+        List<String> storageTags = new ArrayList<>();
+        boolean isTagARule = false;
+        if (CollectionUtils.isNotEmpty(storagePoolTagVOList)) {
+            isTagARule = storagePoolTagVOList.get(0).isTagARule();
+            if (!isTagARule) {
+                storageTags = storagePoolTagVOList.parallelStream().map(StoragePoolTagVO::getTag).collect(Collectors.toList());
+            }
+        }
+        storage.setTags(storageTags);
+        storage.setIsTagARule(isTagARule);
 
         return storage;
     }
@@ -556,22 +589,37 @@
         value.setName(snapshotVo.getName());
         value.setSize(ByteScaleUtils.bytesToMebibytes(snapshotVo.getSize()));
         value.setSnapshotType(Snapshot.Type.values()[snapshotVo.getSnapshotType()]);
-        value.setStorage(getPresetVariableValueStorage(getSnapshotDataStoreId(snapshotId), usageType));
+        value.setStorage(getPresetVariableValueStorage(getSnapshotDataStoreId(snapshotId, usageRecord.getZoneId()), usageType));
         value.setTags(getPresetVariableValueResourceTags(snapshotId, ResourceObjectType.Snapshot));
+        Hypervisor.HypervisorType hypervisorType = snapshotVo.getHypervisorType();
+        if (hypervisorType != null) {
+            value.setHypervisorType(hypervisorType.name());
+        }
+    }
+
+    protected SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
+        List<SnapshotDataStoreVO> snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
+        for (SnapshotDataStoreVO ref : snaps) {
+            ImageStoreVO store = imageStoreDao.findById(ref.getDataStoreId());
+            if (store != null && zoneId == store.getDataCenterId()) {
+                return ref;
+            }
+        }
+        return null;
     }
 
     /**
      * If {@link SnapshotInfo#BackupSnapshotAfterTakingSnapshot} is enabled, returns the secondary storage's ID where the snapshot is. Otherwise, returns the primary storage's ID
      *  where the snapshot is.
      */
-    protected long getSnapshotDataStoreId(Long snapshotId) {
+    protected long getSnapshotDataStoreId(Long snapshotId, long zoneId) {
         if (backupSnapshotAfterTakingSnapshot) {
-            SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshotId, DataStoreRole.Image);
+            SnapshotDataStoreVO snapshotStore = getSnapshotImageStoreRef(snapshotId, zoneId);
             validateIfObjectIsNull(snapshotStore, snapshotId, "data store for snapshot");
             return snapshotStore.getDataStoreId();
         }
 
-        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
+        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findOneBySnapshotAndDatastoreRole(snapshotId, DataStoreRole.Primary);
         validateIfObjectIsNull(snapshotStore, snapshotId, "data store for snapshot");
         return snapshotStore.getDataStoreId();
     }
@@ -610,6 +658,11 @@
         value.setName(vmSnapshotVo.getName());
         value.setTags(getPresetVariableValueResourceTags(vmSnapshotId, ResourceObjectType.VMSnapshot));
         value.setVmSnapshotType(vmSnapshotVo.getType());
+
+        VMInstanceVO vmVo = vmInstanceDao.findByIdIncludingRemoved(vmSnapshotVo.getVmId());
+        if (vmVo != null && vmVo.getHypervisorType() != null) {
+            value.setHypervisorType(vmVo.getHypervisorType().name());
+        }
     }
 
     protected void loadPresetVariableValueForBackup(UsageVO usageRecord, Value value) {
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Storage.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Storage.java
index 3533c5d..6be1dfb 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Storage.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Storage.java
@@ -23,6 +23,8 @@
 
 public class Storage extends GenericPresetVariable {
     private List<String> tags;
+
+    private Boolean isTagARule;
     private ScopeType scope;
 
     public List<String> getTags() {
@@ -34,6 +36,15 @@
         fieldNamesToIncludeInToString.add("tags");
     }
 
+    public Boolean getIsTagARule() {
+        return isTagARule;
+    }
+
+    public void setIsTagARule(Boolean isTagARule) {
+        this.isTagARule = isTagARule;
+        fieldNamesToIncludeInToString.add("isTagARule");
+    }
+
     public ScopeType getScope() {
         return scope;
     }
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java
index 87fefe3..f3accd8 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java
@@ -41,6 +41,8 @@
     private Storage storage;
     private ComputingResources computingResources;
     private BackupOffering backupOffering;
+    private String hypervisorType;
+    private String volumeFormat;
 
     public Host getHost() {
         return host;
@@ -185,4 +187,22 @@
         this.backupOffering = backupOffering;
         fieldNamesToIncludeInToString.add("backupOffering");
     }
+
+    public void setHypervisorType(String hypervisorType) {
+        this.hypervisorType = hypervisorType;
+        fieldNamesToIncludeInToString.add("hypervisorType");
+    }
+
+    public String getHypervisorType() {
+        return hypervisorType;
+    }
+
+    public void setVolumeFormat(String volumeFormat) {
+        this.volumeFormat = volumeFormat;
+        fieldNamesToIncludeInToString.add("volumeFormat");
+    }
+
+    public String getVolumeFormat() {
+        return volumeFormat;
+    }
 }
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java
index fa9af8f..59aa544 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java
@@ -30,6 +30,9 @@
     public static final ConfigKey<String> QuotaCurrencySymbol = new ConfigKey<String>("Advanced", String.class, "quota.currency.symbol", "$",
             "The symbol for the currency in use to measure usage.", true);
 
+    public static final ConfigKey<String> QuotaCurrencyLocale = new ConfigKey<String>("Advanced", String.class, "quota.currency.locale", null,
+            "The location used for formatting the value (e.g. \"en-US\" for English or \"pt-BR\" for Brazilian Portuguese)", true);
+
     public static final ConfigKey<Integer> QuotaStatementPeriod = new ConfigKey<Integer>("Advanced", Integer.class, "quota.statement.period", "1",
             "This variables define the statement generation interval. Values correspond to bimonthly=0, monthly=1, quarterly=2, half-yearly=3 and yearly=4.", true);
 
@@ -63,6 +66,12 @@
     ConfigKey<Boolean> QuotaAccountEnabled = new ConfigKey<>("Advanced", Boolean.class, "quota.account.enabled", "true", "Indicates whether Quota plugin is enabled or not for " +
             "the account.", true, ConfigKey.Scope.Account);
 
+    ConfigKey<String> QuotaEmailHeader = new ConfigKey<>("Advanced", String.class, "quota.email.header", "",
+            "Text to be added as a header for quota emails. Line breaks are not automatically inserted between this section and the body.", true, ConfigKey.Scope.Domain);
+
+    ConfigKey<String> QuotaEmailFooter = new ConfigKey<>("Advanced", String.class, "quota.email.footer", "",
+            "Text to be added as a footer for quota emails. Line breaks are not automatically inserted between this section and the body.", true, ConfigKey.Scope.Domain);
+
     enum QuotaEmailTemplateTypes {
         QUOTA_LOW, QUOTA_EMPTY, QUOTA_UNLOCK_ACCOUNT, QUOTA_STATEMENT
     }
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java
index 553dc84..6f19fa9 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaTypes.java
@@ -55,6 +55,7 @@
         quotaTypeList.put(VOLUME_SECONDARY, new QuotaTypes(VOLUME_SECONDARY, "VOLUME_SECONDARY", UsageUnitTypes.GB_MONTH.toString(), "Volume secondary storage usage"));
         quotaTypeList.put(VM_SNAPSHOT_ON_PRIMARY, new QuotaTypes(VM_SNAPSHOT_ON_PRIMARY, "VM_SNAPSHOT_ON_PRIMARY", UsageUnitTypes.GB_MONTH.toString(), "VM Snapshot primary storage usage"));
         quotaTypeList.put(BACKUP, new QuotaTypes(BACKUP, "BACKUP", UsageUnitTypes.GB_MONTH.toString(), "Backup storage usage"));
+        quotaTypeList.put(BUCKET, new QuotaTypes(BUCKET, "BUCKET", UsageUnitTypes.GB_MONTH.toString(), "Object Store bucket usage"));
         quotaTypeMap = Collections.unmodifiableMap(quotaTypeList);
     }
 
diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java
index 7a1e7b7..ae2f2e9 100644
--- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java
+++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java
@@ -71,6 +71,21 @@
     @Mock
     private ConfigurationDao configDao;
 
+    @Mock
+    private QuotaAccountVO quotaAccountVOMock;
+
+    @Mock
+    private List<QuotaAlertManagerImpl.DeferredQuotaEmail> deferredQuotaEmailListMock;
+
+    @Mock
+    private QuotaManagerImpl quotaManagerMock;
+
+    @Mock
+    private Date balanceDateMock;
+
+    @Mock
+    private AccountVO accountMock;
+
     @Spy
     @InjectMocks
     private QuotaAlertManagerImpl quotaAlertManager = new QuotaAlertManagerImpl();
@@ -162,11 +177,23 @@
         quotaAlertManager.sendQuotaAlert(email);
         assertTrue(email.getSendDate() != null);
 
-        Mockito.verify(quotaAlertManager, Mockito.times(1)).sendQuotaAlert(Mockito.anyString(), Mockito.anyListOf(String.class), Mockito.anyString(), Mockito.anyString());
+        Mockito.verify(quotaAlertManager, Mockito.times(1)).sendQuotaAlert(Mockito.any(), Mockito.anyListOf(String.class), Mockito.anyString(), Mockito.anyString());
         Mockito.verify(quotaAlertManager.mailSender, Mockito.times(1)).sendMail(Mockito.any(SMTPMailProperties.class));
     }
 
     @Test
+    public void addHeaderAndFooterTestIfHeaderAndFootersAreAdded() {
+        String body = quotaAlertManager.addHeaderAndFooter("body", "Header", "Footer");
+        assertEquals("HeaderbodyFooter", body);
+    }
+
+    @Test
+    public void addHeaderAndFooterTestIfHeaderAndFootersAreNotAddedIfEmpty() {
+        String body = quotaAlertManager.addHeaderAndFooter("body", "", "");
+        assertEquals("body", body);
+    }
+
+    @Test
     public void testGetDifferenceDays() {
         Date now = new Date();
         assertTrue(QuotaAlertManagerImpl.getDifferenceDays(now, now) == 0L);
diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
index 5978dcf..e53051f 100644
--- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
+++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
@@ -142,8 +142,10 @@
     public void getUsageValueAccordingToUsageUnitTypeTestAllTypes() {
         Mockito.doReturn(10.0).when(usageVoMock).getRawUsage();
         Mockito.doReturn(ByteScaleUtils.GiB).when(usageVoMock).getSize();
+        Mockito.doReturn(new Date(0, 8, 10)).when(usageVoMock).getStartDate();
         BigDecimal aggregatedQuotaTariffsValue = new BigDecimal(400);
 
+
         Arrays.asList(UsageUnitTypes.values()).forEach(type -> {
            BigDecimal result = quotaManagerImplSpy.getUsageValueAccordingToUsageUnitType(usageVoMock, aggregatedQuotaTariffsValue, type.toString());
            Double expected = null;
@@ -267,7 +269,7 @@
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("account"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("domain"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock, Mockito.never()).injectVariable(Mockito.eq("project"), Mockito.anyString());
-        Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("resourceType"), Mockito.anyString());
+        Mockito.verify(jsInterpreterMock).injectStringVariable(Mockito.eq("resourceType"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("value"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("zone"), Mockito.anyString());
     }
@@ -288,7 +290,7 @@
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("account"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("domain"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("project"), Mockito.anyString());
-        Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("resourceType"), Mockito.anyString());
+        Mockito.verify(jsInterpreterMock).injectStringVariable(Mockito.eq("resourceType"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("value"), Mockito.anyString());
         Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("zone"), Mockito.anyString());
     }
diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java
index bfc4bd4..b973d11 100644
--- a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java
+++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java
@@ -27,6 +27,9 @@
 import java.util.Map;
 import java.util.Set;
 
+import com.cloud.host.HostTagVO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.StoragePoolTagVO;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.acl.RoleVO;
 import org.apache.cloudstack.acl.dao.RoleDao;
@@ -70,6 +73,7 @@
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage.ProvisioningType;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VolumeVO;
@@ -239,6 +243,14 @@
         return host;
     }
 
+    private List<HostTagVO> getHostTagsForTests() {
+        return Arrays.asList(new HostTagVO(1, "tag1", false), new HostTagVO(1, "tag2", false));
+    }
+
+    private List<HostTagVO> getHostRuleTagsForTests() {
+        return List.of(new HostTagVO(1, "tagrule", true));
+    }
+
     private Storage getStorageForTests() {
         Storage storage = new Storage();
         storage.setId("storage_id");
@@ -248,6 +260,14 @@
         return storage;
     }
 
+    private List<StoragePoolTagVO> getStorageTagsForTests() {
+        return Arrays.asList(new StoragePoolTagVO(1, "tag1", false), new StoragePoolTagVO(1, "tag2", false));
+    }
+
+    private List<StoragePoolTagVO> getStorageRuleTagsForTests() {
+        return List.of(new StoragePoolTagVO(1, "tagrule", true));
+    }
+
     private Set<Map.Entry<Integer, QuotaTypes>> getQuotaTypesForTests(Integer... typesToRemove) {
         Map<Integer, QuotaTypes> quotaTypesMap = new LinkedHashMap<>(QuotaTypes.listQuotaTypes());
 
@@ -485,38 +505,42 @@
 
     @Test
     public void loadPresetVariableValueForRunningAndAllocatedVmTestRecordIsRunningOrAllocatedVmSetFields() {
-        Value expected = getValueForTests();
+        for (Hypervisor.HypervisorType hypervisorType : Hypervisor.HypervisorType.values()) {
+            Value expected = getValueForTests();
 
-        Mockito.doReturn(vmInstanceVoMock).when(vmInstanceDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+            Mockito.doReturn(vmInstanceVoMock).when(vmInstanceDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
 
-        mockMethodValidateIfObjectIsNull();
+            mockMethodValidateIfObjectIsNull();
 
-        Mockito.doNothing().when(presetVariableHelperSpy).setPresetVariableHostInValueIfUsageTypeIsRunningVm(Mockito.any(Value.class), Mockito.anyInt(),
-                Mockito.any(VMInstanceVO.class));
+            Mockito.doNothing().when(presetVariableHelperSpy).setPresetVariableHostInValueIfUsageTypeIsRunningVm(Mockito.any(Value.class), Mockito.anyInt(),
+                    Mockito.any(VMInstanceVO.class));
 
-        Mockito.doReturn(expected.getId()).when(vmInstanceVoMock).getUuid();
-        Mockito.doReturn(expected.getName()).when(vmInstanceVoMock).getHostName();
-        Mockito.doReturn(expected.getOsName()).when(presetVariableHelperSpy).getPresetVariableValueOsName(Mockito.anyLong());
-        Mockito.doNothing().when(presetVariableHelperSpy).setPresetVariableValueServiceOfferingAndComputingResources(Mockito.any(), Mockito.anyInt(), Mockito.any());
-        Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
-        Mockito.doReturn(expected.getTemplate()).when(presetVariableHelperSpy).getPresetVariableValueTemplate(Mockito.anyLong());
+            Mockito.doReturn(expected.getId()).when(vmInstanceVoMock).getUuid();
+            Mockito.doReturn(expected.getName()).when(vmInstanceVoMock).getHostName();
+            Mockito.doReturn(expected.getOsName()).when(presetVariableHelperSpy).getPresetVariableValueOsName(Mockito.anyLong());
+            Mockito.doNothing().when(presetVariableHelperSpy).setPresetVariableValueServiceOfferingAndComputingResources(Mockito.any(), Mockito.anyInt(), Mockito.any());
+            Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
+            Mockito.doReturn(expected.getTemplate()).when(presetVariableHelperSpy).getPresetVariableValueTemplate(Mockito.anyLong());
+            Mockito.doReturn(hypervisorType).when(vmInstanceVoMock).getHypervisorType();
 
-        runningAndAllocatedVmUsageTypes.forEach(type -> {
-            Mockito.doReturn(type).when(usageVoMock).getUsageType();
+            runningAndAllocatedVmUsageTypes.forEach(type -> {
+                Mockito.doReturn(type).when(usageVoMock).getUsageType();
 
-            Value result = new Value();
-            presetVariableHelperSpy.loadPresetVariableValueForRunningAndAllocatedVm(usageVoMock, result);
+                Value result = new Value();
+                presetVariableHelperSpy.loadPresetVariableValueForRunningAndAllocatedVm(usageVoMock, result);
 
-            assertPresetVariableIdAndName(expected, result);
-            Assert.assertEquals(expected.getOsName(), result.getOsName());
-            Assert.assertEquals(expected.getTags(), result.getTags());
-            Assert.assertEquals(expected.getTemplate(), result.getTemplate());
+                assertPresetVariableIdAndName(expected, result);
+                Assert.assertEquals(expected.getOsName(), result.getOsName());
+                Assert.assertEquals(expected.getTags(), result.getTags());
+                Assert.assertEquals(expected.getTemplate(), result.getTemplate());
+                Assert.assertEquals(hypervisorType.name(), result.getHypervisorType());
 
-            validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "osName", "tags", "template"), result);
-        });
+                validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "osName", "tags", "template", "hypervisorType"), result);
+            });
+        }
 
-        Mockito.verify(presetVariableHelperSpy, Mockito.times(runningAndAllocatedVmUsageTypes.size())).getPresetVariableValueResourceTags(Mockito.anyLong(),
-                Mockito.eq(ResourceObjectType.UserVm));
+        Mockito.verify(presetVariableHelperSpy, Mockito.times(runningAndAllocatedVmUsageTypes.size() * Hypervisor.HypervisorType.values().length))
+                .getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.eq(ResourceObjectType.UserVm));
     }
 
     @Test
@@ -533,15 +557,16 @@
     @Test
     public void setPresetVariableHostInValueIfUsageTypeIsRunningVmTestQuotaTypeIsRunningVmSetHost() {
         Value result = new Value();
-        Host expected = getHostForTests();
+        Host expectedHost = getHostForTests();
+        List<HostTagVO> expectedHostTags = getHostTagsForTests();
 
-        Mockito.doReturn(expected).when(presetVariableHelperSpy).getPresetVariableValueHost(Mockito.anyLong());
+        Mockito.doReturn(expectedHost).when(presetVariableHelperSpy).getPresetVariableValueHost(Mockito.anyLong());
         presetVariableHelperSpy.setPresetVariableHostInValueIfUsageTypeIsRunningVm(result, UsageTypes.RUNNING_VM, vmInstanceVoMock);
 
         Assert.assertNotNull(result.getHost());
 
-        assertPresetVariableIdAndName(expected, result.getHost());
-        Assert.assertEquals(expected.getTags(), result.getHost().getTags());
+        assertPresetVariableIdAndName(expectedHost, result.getHost());
+        Assert.assertEquals(expectedHost.getTags(), result.getHost().getTags());
         validateFieldNamesToIncludeInToString(Arrays.asList("host"), result);
     }
 
@@ -549,18 +574,39 @@
     public void getPresetVariableValueHostTestSetFieldsAndReturnObject() {
         Host expected = getHostForTests();
         HostVO hostVoMock = Mockito.mock(HostVO.class);
+        List<HostTagVO> hostTagVOListMock = getHostTagsForTests();
 
         Mockito.doReturn(hostVoMock).when(hostDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
         mockMethodValidateIfObjectIsNull();
         Mockito.doReturn(expected.getId()).when(hostVoMock).getUuid();
         Mockito.doReturn(expected.getName()).when(hostVoMock).getName();
-        Mockito.doReturn(expected.getTags()).when(hostTagsDaoMock).getHostTags(Mockito.anyLong());
+        Mockito.doReturn(hostTagVOListMock).when(hostTagsDaoMock).getHostTags(Mockito.anyLong());
 
         Host result = presetVariableHelperSpy.getPresetVariableValueHost(1l);
 
         assertPresetVariableIdAndName(expected, result);
         Assert.assertEquals(expected.getTags(), result.getTags());
-        validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "tags"), result);
+        validateFieldNamesToIncludeInToString(Arrays.asList("id", "isTagARule", "name", "tags"), result);
+    }
+
+    @Test
+    public void getPresetVariableValueHostTestSetFieldsWithRuleTagAndReturnObject() {
+        Host expected = getHostForTests();
+        HostVO hostVoMock = Mockito.mock(HostVO.class);
+        List<HostTagVO> hostTagVOListMock = getHostRuleTagsForTests();
+
+        Mockito.doReturn(hostVoMock).when(hostDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+        mockMethodValidateIfObjectIsNull();
+        Mockito.doReturn(expected.getId()).when(hostVoMock).getUuid();
+        Mockito.doReturn(expected.getName()).when(hostVoMock).getName();
+        Mockito.doReturn(hostTagVOListMock).when(hostTagsDaoMock).getHostTags(Mockito.anyLong());
+
+        Host result = presetVariableHelperSpy.getPresetVariableValueHost(1l);
+
+        assertPresetVariableIdAndName(expected, result);
+        Assert.assertEquals(new ArrayList<>(), result.getTags());
+        Assert.assertTrue(result.getIsTagARule());
+        validateFieldNamesToIncludeInToString(Arrays.asList("id", "isTagARule", "name", "tags"), result);
     }
 
     @Test
@@ -636,75 +682,85 @@
 
     @Test
     public void loadPresetVariableValueForVolumeTestRecordIsVolumeAndHasStorageSetFields() {
-        Value expected = getValueForTests();
+        for (ImageFormat imageFormat : ImageFormat.values()) {
+            Value expected = getValueForTests();
 
-        VolumeVO volumeVoMock = Mockito.mock(VolumeVO.class);
-        Mockito.doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
-        Mockito.doReturn(1l).when(volumeVoMock).getPoolId();
+            VolumeVO volumeVoMock = Mockito.mock(VolumeVO.class);
+            Mockito.doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+            Mockito.doReturn(1l).when(volumeVoMock).getPoolId();
 
-        mockMethodValidateIfObjectIsNull();
+            mockMethodValidateIfObjectIsNull();
 
-        Mockito.doReturn(expected.getId()).when(volumeVoMock).getUuid();
-        Mockito.doReturn(expected.getName()).when(volumeVoMock).getName();
-        Mockito.doReturn(expected.getDiskOffering()).when(presetVariableHelperSpy).getPresetVariableValueDiskOffering(Mockito.anyLong());
-        Mockito.doReturn(expected.getProvisioningType()).when(volumeVoMock).getProvisioningType();
-        Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt());
-        Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
-        Mockito.doReturn(expected.getSize()).when(volumeVoMock).getSize();
+            Mockito.doReturn(expected.getId()).when(volumeVoMock).getUuid();
+            Mockito.doReturn(expected.getName()).when(volumeVoMock).getName();
+            Mockito.doReturn(expected.getDiskOffering()).when(presetVariableHelperSpy).getPresetVariableValueDiskOffering(Mockito.anyLong());
+            Mockito.doReturn(expected.getProvisioningType()).when(volumeVoMock).getProvisioningType();
+            Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt());
+            Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
+            Mockito.doReturn(expected.getSize()).when(volumeVoMock).getSize();
+            Mockito.doReturn(imageFormat).when(volumeVoMock).getFormat();
 
-        Mockito.doReturn(UsageTypes.VOLUME).when(usageVoMock).getUsageType();
+            Mockito.doReturn(UsageTypes.VOLUME).when(usageVoMock).getUsageType();
 
-        Value result = new Value();
-        presetVariableHelperSpy.loadPresetVariableValueForVolume(usageVoMock, result);
+            Value result = new Value();
+            presetVariableHelperSpy.loadPresetVariableValueForVolume(usageVoMock, result);
 
-        Long expectedSize = ByteScaleUtils.bytesToMebibytes(expected.getSize());
+            Long expectedSize = ByteScaleUtils.bytesToMebibytes(expected.getSize());
 
-        assertPresetVariableIdAndName(expected, result);
-        Assert.assertEquals(expected.getDiskOffering(), result.getDiskOffering());
-        Assert.assertEquals(expected.getProvisioningType(), result.getProvisioningType());
-        Assert.assertEquals(expected.getStorage(), result.getStorage());
-        Assert.assertEquals(expected.getTags(), result.getTags());
-        Assert.assertEquals(expectedSize, result.getSize());
+            assertPresetVariableIdAndName(expected, result);
+            Assert.assertEquals(expected.getDiskOffering(), result.getDiskOffering());
+            Assert.assertEquals(expected.getProvisioningType(), result.getProvisioningType());
+            Assert.assertEquals(expected.getStorage(), result.getStorage());
+            Assert.assertEquals(expected.getTags(), result.getTags());
+            Assert.assertEquals(expectedSize, result.getSize());
+            Assert.assertEquals(imageFormat.name(), result.getVolumeFormat());
 
-        validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "diskOffering", "provisioningType", "storage", "tags", "size"), result);
+            validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "diskOffering", "provisioningType", "storage", "tags", "size", "volumeFormat"), result);
+        }
 
-        Mockito.verify(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.eq(ResourceObjectType.Volume));
+        Mockito.verify(presetVariableHelperSpy, Mockito.times(ImageFormat.values().length)).getPresetVariableValueResourceTags(Mockito.anyLong(),
+                Mockito.eq(ResourceObjectType.Volume));
     }
 
     @Test
     public void loadPresetVariableValueForVolumeTestRecordIsVolumeAndDoesNotHaveStorageSetFields() {
-        Value expected = getValueForTests();
+        for (ImageFormat imageFormat : ImageFormat.values()) {
+            Value expected = getValueForTests();
 
-        VolumeVO volumeVoMock = Mockito.mock(VolumeVO.class);
-        Mockito.doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
-        Mockito.doReturn(null).when(volumeVoMock).getPoolId();
+            VolumeVO volumeVoMock = Mockito.mock(VolumeVO.class);
+            Mockito.doReturn(volumeVoMock).when(volumeDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+            Mockito.doReturn(null).when(volumeVoMock).getPoolId();
 
-        mockMethodValidateIfObjectIsNull();
+            mockMethodValidateIfObjectIsNull();
 
-        Mockito.doReturn(expected.getId()).when(volumeVoMock).getUuid();
-        Mockito.doReturn(expected.getName()).when(volumeVoMock).getName();
-        Mockito.doReturn(expected.getDiskOffering()).when(presetVariableHelperSpy).getPresetVariableValueDiskOffering(Mockito.anyLong());
-        Mockito.doReturn(expected.getProvisioningType()).when(volumeVoMock).getProvisioningType();
-        Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
-        Mockito.doReturn(expected.getSize()).when(volumeVoMock).getSize();
+            Mockito.doReturn(expected.getId()).when(volumeVoMock).getUuid();
+            Mockito.doReturn(expected.getName()).when(volumeVoMock).getName();
+            Mockito.doReturn(expected.getDiskOffering()).when(presetVariableHelperSpy).getPresetVariableValueDiskOffering(Mockito.anyLong());
+            Mockito.doReturn(expected.getProvisioningType()).when(volumeVoMock).getProvisioningType();
+            Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
+            Mockito.doReturn(expected.getSize()).when(volumeVoMock).getSize();
+            Mockito.doReturn(imageFormat).when(volumeVoMock).getFormat();
 
-        Mockito.doReturn(UsageTypes.VOLUME).when(usageVoMock).getUsageType();
+            Mockito.doReturn(UsageTypes.VOLUME).when(usageVoMock).getUsageType();
 
-        Value result = new Value();
-        presetVariableHelperSpy.loadPresetVariableValueForVolume(usageVoMock, result);
+            Value result = new Value();
+            presetVariableHelperSpy.loadPresetVariableValueForVolume(usageVoMock, result);
 
-        Long expectedSize = ByteScaleUtils.bytesToMebibytes(expected.getSize());
+            Long expectedSize = ByteScaleUtils.bytesToMebibytes(expected.getSize());
 
-        assertPresetVariableIdAndName(expected, result);
-        Assert.assertEquals(expected.getDiskOffering(), result.getDiskOffering());
-        Assert.assertEquals(expected.getProvisioningType(), result.getProvisioningType());
-        Assert.assertEquals(null, result.getStorage());
-        Assert.assertEquals(expected.getTags(), result.getTags());
-        Assert.assertEquals(expectedSize, result.getSize());
+            assertPresetVariableIdAndName(expected, result);
+            Assert.assertEquals(expected.getDiskOffering(), result.getDiskOffering());
+            Assert.assertEquals(expected.getProvisioningType(), result.getProvisioningType());
+            Assert.assertNull(result.getStorage());
+            Assert.assertEquals(expected.getTags(), result.getTags());
+            Assert.assertEquals(expectedSize, result.getSize());
+            Assert.assertEquals(imageFormat.name(), result.getVolumeFormat());
 
-        validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "diskOffering", "provisioningType", "tags", "size"), result);
+            validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "diskOffering", "provisioningType", "tags", "size", "volumeFormat"), result);
+        }
 
-        Mockito.verify(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.eq(ResourceObjectType.Volume));
+        Mockito.verify(presetVariableHelperSpy, Mockito.times(ImageFormat.values().length)).getPresetVariableValueResourceTags(Mockito.anyLong(),
+                Mockito.eq(ResourceObjectType.Volume));
     }
 
     @Test
@@ -739,13 +795,15 @@
         Storage expected = getStorageForTests();
         Mockito.doReturn(null).when(presetVariableHelperSpy).getSecondaryStorageForSnapshot(Mockito.anyLong(), Mockito.anyInt());
 
+        List<StoragePoolTagVO> storageTagVOListMock = getStorageTagsForTests();
+
         StoragePoolVO storagePoolVoMock = Mockito.mock(StoragePoolVO.class);
         Mockito.doReturn(storagePoolVoMock).when(primaryStorageDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
 
         Mockito.doReturn(expected.getId()).when(storagePoolVoMock).getUuid();
         Mockito.doReturn(expected.getName()).when(storagePoolVoMock).getName();
         Mockito.doReturn(expected.getScope()).when(storagePoolVoMock).getScope();
-        Mockito.doReturn(expected.getTags()).when(storagePoolTagsDaoMock).getStoragePoolTags(Mockito.anyLong());
+        Mockito.doReturn(storageTagVOListMock).when(storagePoolTagsDaoMock).findStoragePoolTags(Mockito.anyLong());
 
         Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1l, 2);
 
@@ -753,7 +811,32 @@
         Assert.assertEquals(expected.getScope(), result.getScope());
         Assert.assertEquals(expected.getTags(), result.getTags());
 
-        validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "scope", "tags"), result);
+        validateFieldNamesToIncludeInToString(Arrays.asList("id", "isTagARule",  "name", "scope", "tags"), result);
+    }
+
+    @Test
+    public void getPresetVariableValueStorageTestGetSecondaryStorageForSnapshotReturnsNullWithRuleTag() {
+        Storage expected = getStorageForTests();
+        Mockito.doReturn(null).when(presetVariableHelperSpy).getSecondaryStorageForSnapshot(Mockito.anyLong(), Mockito.anyInt());
+
+        List<StoragePoolTagVO> storageTagVOListMock = getStorageRuleTagsForTests();
+
+        StoragePoolVO storagePoolVoMock = Mockito.mock(StoragePoolVO.class);
+        Mockito.doReturn(storagePoolVoMock).when(primaryStorageDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+
+        Mockito.doReturn(expected.getId()).when(storagePoolVoMock).getUuid();
+        Mockito.doReturn(expected.getName()).when(storagePoolVoMock).getName();
+        Mockito.doReturn(expected.getScope()).when(storagePoolVoMock).getScope();
+        Mockito.doReturn(storageTagVOListMock).when(storagePoolTagsDaoMock).findStoragePoolTags(Mockito.anyLong());
+
+        Storage result = presetVariableHelperSpy.getPresetVariableValueStorage(1l, 2);
+
+        assertPresetVariableIdAndName(expected, result);
+        Assert.assertEquals(expected.getScope(), result.getScope());
+        Assert.assertEquals(new ArrayList<>(), result.getTags());
+        Assert.assertTrue(result.getIsTagARule());
+
+        validateFieldNamesToIncludeInToString(Arrays.asList("id", "isTagARule",  "name", "scope", "tags"), result);
     }
 
     @Test
@@ -852,37 +935,42 @@
 
     @Test
     public void loadPresetVariableValueForSnapshotTestRecordIsSnapshotSetFields() {
-        Value expected = getValueForTests();
+        for (Hypervisor.HypervisorType hypervisorType : Hypervisor.HypervisorType.values()) {
+            Value expected = getValueForTests();
 
-        SnapshotVO snapshotVoMock = Mockito.mock(SnapshotVO.class);
-        Mockito.doReturn(snapshotVoMock).when(snapshotDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+            SnapshotVO snapshotVoMock = Mockito.mock(SnapshotVO.class);
+            Mockito.doReturn(snapshotVoMock).when(snapshotDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
 
-        mockMethodValidateIfObjectIsNull();
+            mockMethodValidateIfObjectIsNull();
 
-        Mockito.doReturn(expected.getId()).when(snapshotVoMock).getUuid();
-        Mockito.doReturn(expected.getName()).when(snapshotVoMock).getName();
-        Mockito.doReturn(expected.getSize()).when(snapshotVoMock).getSize();
-        Mockito.doReturn((short) 3).when(snapshotVoMock).getSnapshotType();
-        Mockito.doReturn(1l).when(presetVariableHelperSpy).getSnapshotDataStoreId(Mockito.anyLong());
-        Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt());
-        Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
+            Mockito.doReturn(expected.getId()).when(snapshotVoMock).getUuid();
+            Mockito.doReturn(expected.getName()).when(snapshotVoMock).getName();
+            Mockito.doReturn(expected.getSize()).when(snapshotVoMock).getSize();
+            Mockito.doReturn((short) 3).when(snapshotVoMock).getSnapshotType();
+            Mockito.doReturn(1l).when(presetVariableHelperSpy).getSnapshotDataStoreId(Mockito.anyLong(), Mockito.anyLong());
+            Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt());
+            Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
+            Mockito.doReturn(hypervisorType).when(snapshotVoMock).getHypervisorType();
 
-        Mockito.doReturn(UsageTypes.SNAPSHOT).when(usageVoMock).getUsageType();
+            Mockito.doReturn(UsageTypes.SNAPSHOT).when(usageVoMock).getUsageType();
 
-        Value result = new Value();
-        presetVariableHelperSpy.loadPresetVariableValueForSnapshot(usageVoMock, result);
+            Value result = new Value();
+            presetVariableHelperSpy.loadPresetVariableValueForSnapshot(usageVoMock, result);
 
-        Long expectedSize = ByteScaleUtils.bytesToMebibytes(expected.getSize());
+            Long expectedSize = ByteScaleUtils.bytesToMebibytes(expected.getSize());
 
-        assertPresetVariableIdAndName(expected, result);
-        Assert.assertEquals(expected.getSnapshotType(), result.getSnapshotType());
-        Assert.assertEquals(expected.getStorage(), result.getStorage());
-        Assert.assertEquals(expected.getTags(), result.getTags());
-        Assert.assertEquals(expectedSize, result.getSize());
+            assertPresetVariableIdAndName(expected, result);
+            Assert.assertEquals(expected.getSnapshotType(), result.getSnapshotType());
+            Assert.assertEquals(expected.getStorage(), result.getStorage());
+            Assert.assertEquals(expected.getTags(), result.getTags());
+            Assert.assertEquals(expectedSize, result.getSize());
+            Assert.assertEquals(hypervisorType.name(), result.getHypervisorType());
 
-        validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "snapshotType", "storage", "tags", "size"), result);
+            validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "snapshotType", "storage", "tags", "size", "hypervisorType"), result);
+        }
 
-        Mockito.verify(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.eq(ResourceObjectType.Snapshot));
+        Mockito.verify(presetVariableHelperSpy, Mockito.times(Hypervisor.HypervisorType.values().length)).getPresetVariableValueResourceTags(Mockito.anyLong(),
+                Mockito.eq(ResourceObjectType.Snapshot));
     }
 
 
@@ -891,19 +979,19 @@
         SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class);
 
         Long expected = 1l;
-        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
+        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
         Mockito.doReturn(expected).when(snapshotDataStoreVoMock).getDataStoreId();
         presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = false;
 
-        Long result = presetVariableHelperSpy.getSnapshotDataStoreId(1l);
+        Long result = presetVariableHelperSpy.getSnapshotDataStoreId(1l, 1l);
 
         Assert.assertEquals(expected, result);
 
         Arrays.asList(DataStoreRole.values()).forEach(role -> {
             if (role == DataStoreRole.Primary) {
-                Mockito.verify(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
+                Mockito.verify(snapshotDataStoreDaoMock).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.eq(role));
             } else {
-                Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
+                Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.eq(role));
             }
         });
     }
@@ -913,19 +1001,22 @@
         SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class);
 
         Long expected = 2l;
-        Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
+        ImageStoreVO imageStore = Mockito.mock(ImageStoreVO.class);
+        Mockito.when(imageStoreDaoMock.findById(Mockito.anyLong())).thenReturn(imageStore);
+        Mockito.when(imageStore.getDataCenterId()).thenReturn(1L);
+        Mockito.when(snapshotDataStoreDaoMock.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(snapshotDataStoreVoMock));
         Mockito.doReturn(expected).when(snapshotDataStoreVoMock).getDataStoreId();
         presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = true;
 
-        Long result = presetVariableHelperSpy.getSnapshotDataStoreId(2l);
+        Long result = presetVariableHelperSpy.getSnapshotDataStoreId(2l, 1L);
 
         Assert.assertEquals(expected, result);
 
         Arrays.asList(DataStoreRole.values()).forEach(role -> {
             if (role == DataStoreRole.Image) {
-                Mockito.verify(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
+                Mockito.verify(snapshotDataStoreDaoMock).listReadyBySnapshot(Mockito.anyLong(), Mockito.eq(role));
             } else {
-                Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
+                Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).listReadyBySnapshot(Mockito.anyLong(), Mockito.eq(role));
             }
         });
     }
@@ -1148,4 +1239,26 @@
         Assert.assertEquals(expected.getExternalId(), result.getExternalId());
         validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "externalId"), result);
     }
+
+    @Test
+    public void testGetSnapshotImageStoreRefNull() {
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
+        Mockito.when(snapshotDataStoreDaoMock.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
+        ImageStoreVO store = Mockito.mock(ImageStoreVO.class);
+        Mockito.when(store.getDataCenterId()).thenReturn(2L);
+        Mockito.when(imageStoreDaoMock.findById(1L)).thenReturn(store);
+        Assert.assertNull(presetVariableHelperSpy.getSnapshotImageStoreRef(1L, 1L));
+    }
+
+    @Test
+    public void testGetSnapshotImageStoreRefNotNull() {
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
+        Mockito.when(snapshotDataStoreDaoMock.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
+        ImageStoreVO store = Mockito.mock(ImageStoreVO.class);
+        Mockito.when(store.getDataCenterId()).thenReturn(1L);
+        Mockito.when(imageStoreDaoMock.findById(1L)).thenReturn(store);
+        Assert.assertNotNull(presetVariableHelperSpy.getSnapshotImageStoreRef(1L, 1L));
+    }
 }
diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/ValueTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/ValueTest.java
index c9d1440..9e65de7 100644
--- a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/ValueTest.java
+++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/ValueTest.java
@@ -136,4 +136,18 @@
         variable.setBackupOffering(null);
         Assert.assertTrue(variable.fieldNamesToIncludeInToString.contains("backupOffering"));
     }
+
+    @Test
+    public void setHypervisorTypeTestAddFieldHypervisorTypeToCollection() {
+        Value variable = new Value();
+        variable.setHypervisorType(null);
+        Assert.assertTrue(variable.fieldNamesToIncludeInToString.contains("hypervisorType"));
+    }
+
+    @Test
+    public void setVolumeFormatTestAddFieldVolumeFormatToCollection() {
+        Value variable = new Value();
+        variable.setVolumeFormat(null);
+        Assert.assertTrue(variable.fieldNamesToIncludeInToString.contains("volumeFormat"));
+    }
 }
diff --git a/framework/rest/pom.xml b/framework/rest/pom.xml
index 798e7b1..5666cc8 100644
--- a/framework/rest/pom.xml
+++ b/framework/rest/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>cloud-framework-rest</artifactId>
diff --git a/framework/security/pom.xml b/framework/security/pom.xml
index d5e7567..df084ed 100644
--- a/framework/security/pom.xml
+++ b/framework/security/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-framework</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/spring/lifecycle/pom.xml b/framework/spring/lifecycle/pom.xml
index e028222..f5ed390 100644
--- a/framework/spring/lifecycle/pom.xml
+++ b/framework/spring/lifecycle/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/spring/module/pom.xml b/framework/spring/module/pom.xml
index 74df70b..8edc4fe 100644
--- a/framework/spring/module/pom.xml
+++ b/framework/spring/module/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java
index 5dab5d7..6c03c3c 100644
--- a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java
+++ b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java
@@ -97,16 +97,28 @@
             @Override
             public void with(ModuleDefinition def, Stack<ModuleDefinition> parents) {
                 try {
-                    ApplicationContext context = getApplicationContext(def.getName());
+                    String moduleDefinitionName = def.getName();
+                    log.debug(String.format("Trying to obtain module [%s] context.", moduleDefinitionName));
+                    ApplicationContext context = getApplicationContext(moduleDefinitionName);
                     try {
-                        Runnable runnable = context.getBean("moduleStartup", Runnable.class);
-                        log.info("Starting module [" + def.getName() + "]");
-                        runnable.run();
+                        if (context.containsBean("moduleStartup")) {
+                            Runnable runnable = context.getBean("moduleStartup", Runnable.class);
+                            log.info(String.format("Starting module [%s].", moduleDefinitionName));
+                            runnable.run();
+                        } else {
+                            log.debug(String.format("Could not get module [%s] context bean.", moduleDefinitionName));
+                        }
                     } catch (BeansException e) {
-                        // Ignore
+                        log.warn(String.format("Failed to start module [%s] due to: [%s].", moduleDefinitionName, e.getMessage()));
+                        if (log.isDebugEnabled()) {
+                            log.debug(String.format("module start failure of module [%s] was due to: ", moduleDefinitionName), e);
+                        }
                     }
                 } catch (EmptyStackException e) {
-                    // The root context is already loaded, so ignore the exception
+                    log.warn(String.format("Failed to obtain module context due to [%s]. Using root context instead.", e.getMessage()));
+                    if (log.isDebugEnabled()) {
+                        log.debug("Failed to obtain module context: ", e);
+                    }
                 }
             }
         });
@@ -117,10 +129,25 @@
             @Override
             public void with(ModuleDefinition def, Stack<ModuleDefinition> parents) {
                 try {
+                    String moduleDefinitionName = def.getName();
+                    if (parents.isEmpty()) {
+                        log.debug(String.format("Could not find module [%s] context as they have no parents.", moduleDefinitionName));
+                        return;
+                    }
+                    log.debug(String.format("Trying to obtain module [%s] context.", moduleDefinitionName));
                     ApplicationContext parent = getApplicationContext(parents.peek().getName());
+                    log.debug(String.format("Trying to load module [%s] context.", moduleDefinitionName));
                     loadContext(def, parent);
                 } catch (EmptyStackException e) {
-                    // The root context is already loaded, so ignore the exception
+                    log.warn(String.format("Failed to obtain module context due to [%s]. Using root context instead.", e.getMessage()));
+                    if (log.isDebugEnabled()) {
+                        log.debug("Failed to obtain module context: ", e);
+                    }
+                } catch (BeansException e) {
+                    log.warn(String.format("Failed to start module [%s] due to: [%s].", def.getName(), e.getMessage()));
+                    if (log.isDebugEnabled()) {
+                        log.debug(String.format("module start failure of module [%s] was due to: ", def.getName()), e);
+                    }
                 }
             }
         });
diff --git a/framework/spring/module/src/test/resources/testfiles/all/test2-defaults.properties b/framework/spring/module/src/test/resources/testfiles/all/test2-defaults.properties
index 2456923..13a8339 100644
--- a/framework/spring/module/src/test/resources/testfiles/all/test2-defaults.properties
+++ b/framework/spring/module/src/test/resources/testfiles/all/test2-defaults.properties
@@ -14,4 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
diff --git a/framework/spring/module/src/test/resources/testfiles/missingname/module.properties b/framework/spring/module/src/test/resources/testfiles/missingname/module.properties
index 2456923..13a8339 100644
--- a/framework/spring/module/src/test/resources/testfiles/missingname/module.properties
+++ b/framework/spring/module/src/test/resources/testfiles/missingname/module.properties
@@ -14,4 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
diff --git a/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties b/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties
index 0ea382a..3088f24 100644
--- a/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties
+++ b/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties
@@ -16,4 +16,4 @@
 # under the License.
 
 name=excluded
-parent=base
\ No newline at end of file
+parent=base
diff --git a/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties b/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties
index e3665ee..9c1c16f 100644
--- a/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties
+++ b/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties
@@ -16,4 +16,4 @@
 # under the License.
 
 name=excluded2
-parent=base
\ No newline at end of file
+parent=base
diff --git a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties
index 811d442..bf96fe6 100644
--- a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties
+++ b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties
@@ -17,4 +17,4 @@
 
 modules.include.excluded=false
 modules.include.base=True
-modules.exclude=excluded2,excluded2
\ No newline at end of file
+modules.exclude=excluded2,excluded2
diff --git a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties
index 93b9186..f676150 100644
--- a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties
+++ b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties
@@ -16,4 +16,4 @@
 # under the License.
 
 name=orphan-of-excluded
-parent=excluded
\ No newline at end of file
+parent=excluded
diff --git a/packaging/README.md b/packaging/README.md
index 634b1cf..08e34ba 100644
--- a/packaging/README.md
+++ b/packaging/README.md
@@ -31,7 +31,9 @@
 
 ``docker run -ti -v /tmp:/src ubuntu:14.04 /bin/bash -c "apt-get update && apt-get install -y dpkg-dev python debhelper openjdk-7-jdk genisoimage python-mysql.connector maven lsb-release devscripts && /src/cloudstack/packaging/build-deb.sh"``
 
-The commands above will generate Ubuntu 14.04 and 16.04 packages which you will find in */tmp* on your system after the build succeeds.
+``docker run -ti -v /tmp:/src ubuntu:22.04 /bin/bash -c "apt-get update && apt-get install -y software-properties-common &&apt-add-repository universe --yes && apt-get update && apt-get install -y dpkg-dev debhelper lsb-release devscripts openjdk-11-jdk libws-commons-util-java genisoimage libcommons-codec-java libcommons-httpclient-java liblog4j1.2-java maven python3 python3-mysql.connector python3-setuptools python-setuptools python3-openssl python3-dev libffi-dev build-essential libssl-dev libffi-dev fakeroot python-is-python3 && curl -sL https://deb.nodesource.com/setup_14.x | bash - && apt-get install -y nodejs &&  /src/cloudstack/packaging/build-deb.sh"``
+
+The commands above will generate Ubuntu 14.04, 16.04, and 22.04 packages which you will find in */tmp* on your system after the build succeeds.
 
 ## RPM
 The *package.sh* script can be used to build RPM packages for CloudStack. In the *packaging* script you can run the following command:
diff --git a/packaging/centos7/cloud-ipallocator.rc b/packaging/centos7/cloud-ipallocator.rc
index d3eadec..255725b 100755
--- a/packaging/centos7/cloud-ipallocator.rc
+++ b/packaging/centos7/cloud-ipallocator.rc
@@ -93,4 +93,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec
index cc25977..25eba64 100644
--- a/packaging/centos7/cloud.spec
+++ b/packaging/centos7/cloud.spec
@@ -277,6 +277,9 @@
 install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-baremetal
 install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm
 install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses
+# Bundle cmk in cloudstack-management
+wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.3.0/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk
+chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk
 
 cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
 
@@ -302,7 +305,7 @@
 
 install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/%{name}-external-ipallocator.py
 install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar
-install -D utils/target/cloud-utils-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar
+install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar
 
 install -D packaging/centos7/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator
 install -D packaging/centos7/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud
@@ -609,6 +612,7 @@
 %attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey
 %attr(0755,root,root) %{_bindir}/%{name}-sysvmadm
 %attr(0755,root,root) %{_bindir}/%{name}-setup-encryption
+%attr(0755,root,root) %{_bindir}/cmk
 %{_datadir}/%{name}-management/setup/*.sql
 %{_datadir}/%{name}-management/setup/*.sh
 %{_datadir}/%{name}-management/setup/server-setup.xml
@@ -715,4 +719,3 @@
 
 * Fri Oct 5 2012 Hugo Trippaers <hugo@apache.org> 4.1.0
 - new style spec file
-
diff --git a/packaging/centos8/cloud-ipallocator.rc b/packaging/centos8/cloud-ipallocator.rc
index d3eadec..255725b 100755
--- a/packaging/centos8/cloud-ipallocator.rc
+++ b/packaging/centos8/cloud-ipallocator.rc
@@ -93,4 +93,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/packaging/centos8/cloud.spec b/packaging/centos8/cloud.spec
index 0e70d22..c127782 100644
--- a/packaging/centos8/cloud.spec
+++ b/packaging/centos8/cloud.spec
@@ -259,6 +259,9 @@
 install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-baremetal
 install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm
 install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses
+# Bundle cmk in cloudstack-management
+wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.3.0/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk
+chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk
 
 cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
 
@@ -284,7 +287,7 @@
 
 install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/%{name}-external-ipallocator.py
 install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar
-install -D utils/target/cloud-utils-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar
+install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar
 
 install -D packaging/centos8/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator
 install -D packaging/centos8/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud
@@ -588,6 +591,7 @@
 %attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey
 %attr(0755,root,root) %{_bindir}/%{name}-sysvmadm
 %attr(0755,root,root) %{_bindir}/%{name}-setup-encryption
+%attr(0755,root,root) %{_bindir}/cmk
 %{_datadir}/%{name}-management/setup/*.sql
 %{_datadir}/%{name}-management/setup/*.sh
 %{_datadir}/%{name}-management/setup/server-setup.xml
diff --git a/plugins/acl/dynamic-role-based/pom.xml b/plugins/acl/dynamic-role-based/pom.xml
index 2d1e391..c7646e7 100644
--- a/plugins/acl/dynamic-role-based/pom.xml
+++ b/plugins/acl/dynamic-role-based/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/acl/project-role-based/pom.xml b/plugins/acl/project-role-based/pom.xml
index c5df914..b177cba 100644
--- a/plugins/acl/project-role-based/pom.xml
+++ b/plugins/acl/project-role-based/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java
index 9363ebd..0306a06 100644
--- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java
+++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java
@@ -61,7 +61,9 @@
     @Override
     public boolean isEnabled() {
         if (!roleService.isEnabled()) {
-            LOGGER.trace("RoleService is disabled. We will not use ProjectRoleBasedApiAccessChecker.");
+            if (LOGGER.isTraceEnabled()) {
+                LOGGER.trace("RoleService is disabled. We will not use ProjectRoleBasedApiAccessChecker.");
+            }
         }
         return roleService.isEnabled();
     }
@@ -119,7 +121,9 @@
 
         Account userAccount = accountService.getAccount(user.getAccountId());
         if (accountService.isRootAdmin(userAccount.getId()) || accountService.isDomainAdmin(userAccount.getAccountId())) {
-            LOGGER.info(String.format("Account [%s] is Root Admin or Domain Admin, all APIs are allowed.", userAccount.getAccountName()));
+            if (LOGGER.isTraceEnabled()) {
+                LOGGER.trace(String.format("Account [%s] is Root Admin or Domain Admin, all APIs are allowed.", userAccount.getAccountName()));
+            }
             return true;
         }
 
diff --git a/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties b/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties
index 76064d4..3aa38c8 100644
--- a/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties
+++ b/plugins/acl/project-role-based/src/main/resources/META-INF/cloudstack/acl-project-role-based/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=acl-project-role-based
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/acl/project-role-based/src/main/test/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessCheckerTest.java b/plugins/acl/project-role-based/src/main/test/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessCheckerTest.java
deleted file mode 100644
index 5505975..0000000
--- a/plugins/acl/project-role-based/src/main/test/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessCheckerTest.java
+++ /dev/null
@@ -1,154 +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.acl;
-
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.projects.Project;
-import com.cloud.projects.ProjectAccount;
-import com.cloud.projects.ProjectAccountVO;
-import com.cloud.projects.ProjectVO;
-import com.cloud.projects.dao.ProjectAccountDao;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
-
-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.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import junit.framework.TestCase;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-public class ProjectRoleBasedApiAccessCheckerTest extends TestCase {
-    @Mock
-    ProjectAccountDao projectAccountDaoMock;
-
-    @Mock
-    RoleService roleServiceMock;
-
-    @Mock
-    ProjectAccountVO projectAccountVOMock;
-
-    @Mock
-    CallContext callContextMock;
-
-    @InjectMocks
-    ProjectRoleBasedApiAccessChecker projectRoleBasedApiAccessCheckerSpy = Mockito.spy(ProjectRoleBasedApiAccessChecker.class);
-
-    List<String> apiNames = new ArrayList<>(Arrays.asList("apiName"));
-
-    @Before
-    public void setup() {
-
-        Mockito.doReturn(true).when(roleServiceMock).isEnabled();
-    }
-
-    public Project getTestProject() {
-        return new ProjectVO("Teste", "Teste", 1L, 1L);
-    }
-
-    private User getTestUser() {
-        return new UserVO(12L, "some user", "password", "firstName", "lastName",
-                "email@gmail.com", "GMT", "uuid", User.Source.UNKNOWN);
-    }
-
-    @Test
-    public void getApisAllowedToUserTestRoleServiceIsDisabledShouldReturnUnchangedApiList() {
-        Mockito.doReturn(false).when(roleServiceMock).isEnabled();
-
-        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
-        Assert.assertEquals(1, apisReceived.size());
-    }
-
-    @Test
-    public void getApisAllowedToUserTestProjectIsNullShouldReturnUnchangedApiList() {
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-
-        Mockito.doReturn(null).when(callContextMock).getProject();
-
-        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
-        Assert.assertEquals(1, apisReceived.size());
-    }
-
-    @Test (expected = PermissionDeniedException.class)
-    public void getApisAllowedToUserTestProjectAccountIsNullThrowPermissionDeniedException() {
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-
-        Mockito.when(callContextMock.getProject()).thenReturn(getTestProject());
-        Mockito.when(projectAccountDaoMock.findByProjectIdAccountId(Mockito.anyLong(), Mockito.anyLong())).thenReturn(null);
-        Mockito.when(projectAccountDaoMock.findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(null);
-
-        projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
-    }
-
-    @Test
-    public void getApisAllowedToUserTestProjectAccountHasAdminRoleReturnsUnchangedApiList() {
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-
-        Mockito.doReturn(getTestProject()).when(callContextMock).getProject();
-        Mockito.doReturn(projectAccountVOMock).when(projectAccountDaoMock).findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong());
-        Mockito.doReturn(ProjectAccount.Role.Admin).when(projectAccountVOMock).getAccountRole();
-
-        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
-        Assert.assertEquals(1, apisReceived.size());
-    }
-
-    @Test
-    public void getApisAllowedToUserTestProjectAccountNotPermittedForTheApiListShouldReturnEmptyList() {
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-
-        Mockito.doReturn(getTestProject()).when(callContextMock).getProject();
-        Mockito.doReturn(projectAccountVOMock).when(projectAccountDaoMock).findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong());
-        Mockito.doReturn(ProjectAccount.Role.Regular).when(projectAccountVOMock).getAccountRole();
-        Mockito.doReturn(false).when(projectRoleBasedApiAccessCheckerSpy).isPermitted(Mockito.any(Project.class), Mockito.any(ProjectAccount.class), Mockito.anyString());
-
-
-        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
-        Assert.assertTrue(apisReceived.isEmpty());
-    }
-
-    @Test
-    public void getApisAllowedToUserTestProjectAccountPermittedForTheApiListShouldReturnTheSameList() {
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-
-        Mockito.doReturn(getTestProject()).when(callContextMock).getProject();
-        Mockito.doReturn(projectAccountVOMock).when(projectAccountDaoMock).findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong());
-        Mockito.doReturn(ProjectAccount.Role.Regular).when(projectAccountVOMock).getAccountRole();
-        Mockito.doReturn(true).when(projectRoleBasedApiAccessCheckerSpy).isPermitted(Mockito.any(Project.class), Mockito.any(ProjectAccount.class), Mockito.anyString());
-
-
-        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
-        Assert.assertEquals(1, apisReceived.size());
-    }
-}
diff --git a/plugins/acl/project-role-based/src/test/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessCheckerTest.java b/plugins/acl/project-role-based/src/test/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessCheckerTest.java
new file mode 100644
index 0000000..81061d3
--- /dev/null
+++ b/plugins/acl/project-role-based/src/test/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessCheckerTest.java
@@ -0,0 +1,152 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.PermissionDeniedException;
+import com.cloud.projects.Project;
+import com.cloud.projects.ProjectAccount;
+import com.cloud.projects.ProjectAccountVO;
+import com.cloud.projects.ProjectVO;
+import com.cloud.projects.dao.ProjectAccountDao;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+
+import org.apache.cloudstack.context.CallContext;
+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.MockedStatic;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProjectRoleBasedApiAccessCheckerTest extends TestCase {
+    @Mock
+    ProjectAccountDao projectAccountDaoMock;
+
+    @Mock
+    RoleService roleServiceMock;
+
+    @Mock
+    ProjectAccountVO projectAccountVOMock;
+
+    @Mock
+    CallContext callContextMock;
+
+    @InjectMocks
+    ProjectRoleBasedApiAccessChecker projectRoleBasedApiAccessCheckerSpy = Mockito.spy(ProjectRoleBasedApiAccessChecker.class);
+
+    List<String> apiNames = new ArrayList<>(Arrays.asList("apiName"));
+
+    MockedStatic<CallContext> callContextMocked;
+
+    @Before
+    public void setup() {
+        callContextMocked = Mockito.mockStatic(CallContext.class);
+        callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+        Mockito.doReturn(true).when(roleServiceMock).isEnabled();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        callContextMocked.close();
+    }
+
+    public Project getTestProject() {
+        return new ProjectVO("Teste", "Teste", 1L, 1L);
+    }
+
+    private User getTestUser() {
+        return new UserVO(12L, "some user", "password", "firstName", "lastName",
+                "email@gmail.com", "GMT", "uuid", User.Source.UNKNOWN);
+    }
+
+    @Test
+    public void getApisAllowedToUserTestRoleServiceIsDisabledShouldReturnUnchangedApiList() {
+        Mockito.doReturn(false).when(roleServiceMock).isEnabled();
+
+        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
+        Assert.assertEquals(1, apisReceived.size());
+    }
+
+    @Test
+    public void getApisAllowedToUserTestProjectIsNullShouldReturnUnchangedApiList() {
+
+        Mockito.doReturn(null).when(callContextMock).getProject();
+
+        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
+        Assert.assertEquals(1, apisReceived.size());
+    }
+
+    @Test (expected = PermissionDeniedException.class)
+    public void getApisAllowedToUserTestProjectAccountIsNullThrowPermissionDeniedException() {
+
+        Mockito.when(callContextMock.getProject()).thenReturn(getTestProject());
+        Mockito.when(projectAccountDaoMock.findByProjectIdAccountId(Mockito.anyLong(), Mockito.anyLong())).thenReturn(null);
+        Mockito.when(projectAccountDaoMock.findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(null);
+
+        projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
+    }
+
+    @Test
+    public void getApisAllowedToUserTestProjectAccountHasAdminRoleReturnsUnchangedApiList() {
+
+        Mockito.doReturn(getTestProject()).when(callContextMock).getProject();
+        Mockito.doReturn(projectAccountVOMock).when(projectAccountDaoMock).findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong());
+        Mockito.doReturn(ProjectAccount.Role.Admin).when(projectAccountVOMock).getAccountRole();
+
+        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
+        Assert.assertEquals(1, apisReceived.size());
+    }
+
+    @Test
+    public void getApisAllowedToUserTestProjectAccountNotPermittedForTheApiListShouldReturnEmptyList() {
+
+        Mockito.doReturn(getTestProject()).when(callContextMock).getProject();
+        Mockito.doReturn(projectAccountVOMock).when(projectAccountDaoMock).findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong());
+        Mockito.doReturn(ProjectAccount.Role.Regular).when(projectAccountVOMock).getAccountRole();
+        Mockito.doReturn(false).when(projectRoleBasedApiAccessCheckerSpy).isPermitted(Mockito.any(Project.class), Mockito.any(ProjectAccount.class), Mockito.anyString());
+
+
+        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
+        Assert.assertTrue(apisReceived.isEmpty());
+    }
+
+    @Test
+    public void getApisAllowedToUserTestProjectAccountPermittedForTheApiListShouldReturnTheSameList() {
+
+        Mockito.doReturn(getTestProject()).when(callContextMock).getProject();
+        Mockito.doReturn(projectAccountVOMock).when(projectAccountDaoMock).findByProjectIdUserId(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong());
+        Mockito.doReturn(ProjectAccount.Role.Regular).when(projectAccountVOMock).getAccountRole();
+        Mockito.doReturn(true).when(projectRoleBasedApiAccessCheckerSpy).isPermitted(Mockito.any(Project.class), Mockito.any(ProjectAccount.class), Mockito.anyString());
+
+
+        List<String> apisReceived = projectRoleBasedApiAccessCheckerSpy.getApisAllowedToUser(null, getTestUser(), apiNames);
+        Assert.assertEquals(1, apisReceived.size());
+    }
+}
diff --git a/plugins/acl/project-role-based/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/acl/project-role-based/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/acl/project-role-based/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/acl/static-role-based/pom.xml b/plugins/acl/static-role-based/pom.xml
index 534fc28..bb86a08 100644
--- a/plugins/acl/static-role-based/pom.xml
+++ b/plugins/acl/static-role-based/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties b/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties
index 06fc721..93ef1fa 100644
--- a/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties
+++ b/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=acl-static-role-based
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/affinity-group-processors/explicit-dedication/pom.xml b/plugins/affinity-group-processors/explicit-dedication/pom.xml
index f5f7b21..ae8fa82 100644
--- a/plugins/affinity-group-processors/explicit-dedication/pom.xml
+++ b/plugins/affinity-group-processors/explicit-dedication/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java b/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java
index 8070a74..9528302 100644
--- a/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java
+++ b/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java
@@ -86,7 +86,7 @@
      * This IncludeList is then used to update the avoid list for a given data center.
      */
     @Override
-    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
+    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {
         VirtualMachine vm = vmProfile.getVirtualMachine();
         List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
         DataCenter dc = _dcDao.findById(vm.getDataCenterId());
diff --git a/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties b/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties
index e204fe7..7c1a555 100644
--- a/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties
+++ b/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=explicit-dedication
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/plugins/affinity-group-processors/host-affinity/pom.xml b/plugins/affinity-group-processors/host-affinity/pom.xml
index 014baf0..94719fc 100644
--- a/plugins/affinity-group-processors/host-affinity/pom.xml
+++ b/plugins/affinity-group-processors/host-affinity/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java
index 0e7c536..07c1dd5 100644
--- a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java
+++ b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java
@@ -16,14 +16,16 @@
 // under the License.
 package org.apache.cloudstack.affinity;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.HashSet;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
 
-import com.cloud.vm.VMInstanceVO;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
@@ -50,37 +52,56 @@
     protected AffinityGroupVMMapDao _affinityGroupVMMapDao;
 
     @Override
-    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
+    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {
         VirtualMachine vm = vmProfile.getVirtualMachine();
         List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
         if (CollectionUtils.isNotEmpty(vmGroupMappings)) {
             for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
-                processAffinityGroup(vmGroupMapping, plan, vm);
+                processAffinityGroup(vmGroupMapping, plan, vm, vmList);
             }
         }
     }
 
+
     /**
      * Process Affinity Group for VM deployment
      */
-    protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, DeploymentPlan plan, VirtualMachine vm) {
+    protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, DeploymentPlan plan, VirtualMachine vm, List<VirtualMachine> vmList) {
         AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId());
         s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId());
 
         List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId());
         groupVMIds.remove(vm.getId());
 
-        List<Long> preferredHosts = getPreferredHostsFromGroupVMIds(groupVMIds);
+        List<Long> preferredHosts = getPreferredHostsFromGroupVMIds(groupVMIds, vmList);
         plan.setPreferredHosts(preferredHosts);
     }
 
+
+    /**
+     * Process Affinity Group for VM deployment
+     */
+    protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, DeploymentPlan plan, VirtualMachine vm) {
+        processAffinityGroup(vmGroupMapping, plan, vm, Collections.emptyList());
+    }
+
     /**
      * Get host ids set from vm ids list
      */
+
     protected Set<Long> getHostIdSet(List<Long> vmIds) {
+        return getHostIdSet(vmIds, Collections.emptyList());
+    }
+
+    protected Set<Long> getHostIdSet(List<Long> vmIds, List<VirtualMachine> vmList) {
         Set<Long> hostIds = new HashSet<>();
+        Map<Long, VirtualMachine> vmIdVmMap = getVmIdVmMap(vmList);
         for (Long groupVMId : vmIds) {
-            VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId);
+            VirtualMachine groupVM = vmIdVmMap.get(groupVMId);
+            if (groupVM == null) {
+                groupVM = _vmInstanceDao.findById(groupVMId);
+            }
+
             if (groupVM != null && groupVM.getHostId() != null) {
                 hostIds.add(groupVM.getHostId());
             }
@@ -88,11 +109,19 @@
         return hostIds;
     }
 
+    protected Map<Long, VirtualMachine> getVmIdVmMap(List<VirtualMachine> vmList) {
+        Map<Long, VirtualMachine> vmIdVmMap = new HashMap<>();
+        for (VirtualMachine vm : vmList) {
+            vmIdVmMap.put(vm.getId(), vm);
+        }
+        return vmIdVmMap;
+    }
+
     /**
      * Get preferred host ids list from the affinity group VMs
      */
-    protected List<Long> getPreferredHostsFromGroupVMIds(List<Long> vmIds) {
-        return new ArrayList<>(getHostIdSet(vmIds));
+    protected List<Long> getPreferredHostsFromGroupVMIds(List<Long> vmIds, List<VirtualMachine> vmList) {
+        return new ArrayList<>(getHostIdSet(vmIds, vmList));
     }
 
     @Override
diff --git a/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/module.properties b/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/module.properties
index fe0d91b..d915002 100644
--- a/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/module.properties
+++ b/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=host-affinity
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/spring-host-affinity-context.xml b/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/spring-host-affinity-context.xml
index 3d42e80..483da79 100644
--- a/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/spring-host-affinity-context.xml
+++ b/plugins/affinity-group-processors/host-affinity/src/main/resources/META-INF/cloudstack/host-affinity/spring-host-affinity-context.xml
@@ -32,4 +32,4 @@
         <property name="name" value="HostAffinityProcessor" />
         <property name="type" value="host affinity" />
     </bean>
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/affinity-group-processors/host-affinity/src/test/java/org/apache/cloudstack/affinity/HostAffinityProcessorTest.java b/plugins/affinity-group-processors/host-affinity/src/test/java/org/apache/cloudstack/affinity/HostAffinityProcessorTest.java
index 5dc9270..66f3a37 100644
--- a/plugins/affinity-group-processors/host-affinity/src/test/java/org/apache/cloudstack/affinity/HostAffinityProcessorTest.java
+++ b/plugins/affinity-group-processors/host-affinity/src/test/java/org/apache/cloudstack/affinity/HostAffinityProcessorTest.java
@@ -36,6 +36,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
@@ -139,7 +140,7 @@
     @Test
     public void testGetPreferredHostsFromGroupVMIdsMultipleVMs() {
         List<Long> list = new ArrayList<>(Arrays.asList(GROUP_VM_1_ID, GROUP_VM_2_ID));
-        List<Long> preferredHosts = processor.getPreferredHostsFromGroupVMIds(list);
+        List<Long> preferredHosts = processor.getPreferredHostsFromGroupVMIds(list, Collections.emptyList());
         assertNotNull(preferredHosts);
         assertEquals(1, preferredHosts.size());
         assertEquals(HOST_ID, preferredHosts.get(0));
@@ -148,7 +149,7 @@
     @Test
     public void testGetPreferredHostsFromGroupVMIdsEmptyVMsList() {
         List<Long> list = new ArrayList<>();
-        List<Long> preferredHosts = processor.getPreferredHostsFromGroupVMIds(list);
+        List<Long> preferredHosts = processor.getPreferredHostsFromGroupVMIds(list, Collections.emptyList());
         assertNotNull(preferredHosts);
         assertTrue(preferredHosts.isEmpty());
     }
diff --git a/plugins/affinity-group-processors/host-anti-affinity/pom.xml b/plugins/affinity-group-processors/host-anti-affinity/pom.xml
index 408d7a0..8ffd50c 100644
--- a/plugins/affinity-group-processors/host-anti-affinity/pom.xml
+++ b/plugins/affinity-group-processors/host-anti-affinity/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java
index 21ac1ac..2a3c579 100644
--- a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java
+++ b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java
@@ -63,7 +63,7 @@
     protected VMReservationDao _reservationDao;
 
     @Override
-    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
+    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {
         VirtualMachine vm = vmProfile.getVirtualMachine();
         List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
 
diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties b/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties
index 1ea1e84..359359d 100644
--- a/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties
+++ b/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=host-anti-affinity
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml b/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml
index 5b2671e..f41d30f 100644
--- a/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml
+++ b/plugins/affinity-group-processors/non-strict-host-affinity/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java
index a511021..cdb3447 100644
--- a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java
+++ b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java
@@ -63,7 +63,7 @@
     private int vmCapacityReleaseInterval;
 
     @Override
-    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
+    public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {
         VirtualMachine vm = vmProfile.getVirtualMachine();
         List<AffinityGroupVMMapVO> vmGroupMappings = affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
 
diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml b/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml
index 63ac5b6..ed826e7 100644
--- a/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml
+++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/pom.xml
@@ -25,14 +25,14 @@
         <dependency>
             <groupId>org.apache.cloudstack</groupId>
             <artifactId>cloud-plugin-non-strict-host-affinity</artifactId>
-            <version>4.18.3.0-SNAPSHOT</version>
+            <version>${project.version}</version>
             <scope>compile</scope>
         </dependency>
     </dependencies>
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java
index 5ea423b..7239594 100644
--- a/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java
+++ b/plugins/affinity-group-processors/non-strict-host-anti-affinity/src/test/java/org/apache/cloudstack/affinity/NonStrictHostAntiAffinityProcessorTest.java
@@ -35,7 +35,7 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
@@ -47,7 +47,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.when;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class NonStrictHostAntiAffinityProcessorTest {
 
     @Spy
diff --git a/plugins/alert-handlers/snmp-alerts/pom.xml b/plugins/alert-handlers/snmp-alerts/pom.xml
index 97cf6c0..53ba655 100644
--- a/plugins/alert-handlers/snmp-alerts/pom.xml
+++ b/plugins/alert-handlers/snmp-alerts/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>cloudstack-plugins</artifactId>
         <groupId>org.apache.cloudstack</groupId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/alert-handlers/syslog-alerts/pom.xml b/plugins/alert-handlers/syslog-alerts/pom.xml
index 7300891..691faf7 100644
--- a/plugins/alert-handlers/syslog-alerts/pom.xml
+++ b/plugins/alert-handlers/syslog-alerts/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <artifactId>cloudstack-plugins</artifactId>
         <groupId>org.apache.cloudstack</groupId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/api/discovery/pom.xml b/plugins/api/discovery/pom.xml
index 4b929d8..b4ff341 100644
--- a/plugins/api/discovery/pom.xml
+++ b/plugins/api/discovery/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/api/rate-limit/pom.xml b/plugins/api/rate-limit/pom.xml
index 5757882..35c9663 100644
--- a/plugins/api/rate-limit/pom.xml
+++ b/plugins/api/rate-limit/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <build>
diff --git a/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties b/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties
index c998a87..86aa179 100644
--- a/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties
+++ b/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=rate-limit
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/api/solidfire-intg-test/pom.xml b/plugins/api/solidfire-intg-test/pom.xml
index dbc69f3..35b73f0 100644
--- a/plugins/api/solidfire-intg-test/pom.xml
+++ b/plugins/api/solidfire-intg-test/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties b/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties
index a6460b9..3b3f2a8 100644
--- a/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties
+++ b/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=solidfire-intg-test
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/api/vmware-sioc/pom.xml b/plugins/api/vmware-sioc/pom.xml
index d841a06..583396a 100644
--- a/plugins/api/vmware-sioc/pom.xml
+++ b/plugins/api/vmware-sioc/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java
index 966c837..f012dbf 100644
--- a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java
+++ b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java
@@ -35,9 +35,9 @@
 
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 import com.cloud.hypervisor.vmware.mo.VirtualMachineDiskInfoBuilder;
 import com.cloud.storage.Storage.StoragePoolType;
diff --git a/plugins/api/vmware-sioc/src/main/resources/META-INF/cloudstack/vmware-sioc/module.properties b/plugins/api/vmware-sioc/src/main/resources/META-INF/cloudstack/vmware-sioc/module.properties
index 826e644..c000d56 100644
--- a/plugins/api/vmware-sioc/src/main/resources/META-INF/cloudstack/vmware-sioc/module.properties
+++ b/plugins/api/vmware-sioc/src/main/resources/META-INF/cloudstack/vmware-sioc/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vmware-sioc
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/backup/dummy/pom.xml b/plugins/backup/dummy/pom.xml
index 16fad25..9c4771a 100644
--- a/plugins/backup/dummy/pom.xml
+++ b/plugins/backup/dummy/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>cloudstack-plugins</artifactId>
         <groupId>org.apache.cloudstack</groupId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java
index 0297ce8..fabc982 100644
--- a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java
+++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java
@@ -117,7 +117,7 @@
         backup.setVmId(vm.getId());
         backup.setExternalId("dummy-external-id");
         backup.setType("FULL");
-        backup.setDate(new Date().toString());
+        backup.setDate(new Date());
         backup.setSize(1024L);
         backup.setProtectedSize(1024000L);
         backup.setStatus(Backup.Status.BackedUp);
diff --git a/plugins/backup/networker/pom.xml b/plugins/backup/networker/pom.xml
index 8419568..0f0aa43 100644
--- a/plugins/backup/networker/pom.xml
+++ b/plugins/backup/networker/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <artifactId>cloudstack-plugins</artifactId>
         <groupId>org.apache.cloudstack</groupId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java
index 37051f9..9703203 100644
--- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java
+++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java
@@ -51,6 +51,8 @@
 import java.net.URISyntaxException;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -605,7 +607,14 @@
                         strayBackup.setVmId(vm.getId());
                         strayBackup.setExternalId(strayNetworkerBackup.getId());
                         strayBackup.setType(strayNetworkerBackup.getType());
-                        strayBackup.setDate(strayNetworkerBackup.getSaveTime());
+                        SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+                        try {
+                            strayBackup.setDate(formatterDateTime.parse(strayNetworkerBackup.getSaveTime()));
+                        } catch (ParseException e) {
+                            String msg = String.format("Unable to parse date [%s].", strayNetworkerBackup.getSaveTime());
+                            LOG.error(msg, e);
+                            throw new CloudRuntimeException(msg, e);
+                        }
                         strayBackup.setStatus(Backup.Status.BackedUp);
                         for ( Backup.VolumeInfo thisVMVol : vm.getBackupVolumeList()) {
                             vmBackupSize += (thisVMVol.getSize() / 1024L /1024L);
diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java
index 939543d..8bb89b6 100644
--- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java
+++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java
@@ -211,7 +211,7 @@
 
         SimpleDateFormat formatterDate = new SimpleDateFormat("yyyy-MM-dd");
         SimpleDateFormat formatterTime = new SimpleDateFormat("HH:mm:ss");
-        SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
+        SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
 
         String startDate = formatterDate.format(backupJobStart);
         String startTime = formatterTime.format(backupJobStart);
@@ -252,7 +252,13 @@
             backup.setVmId(vm.getId());
             backup.setExternalId(networkerLatestBackup.getId());
             backup.setType(networkerLatestBackup.getType());
-            backup.setDate(networkerLatestBackup.getCreationTime());
+            try {
+                backup.setDate(formatterDateTime.parse(networkerLatestBackup.getCreationTime()));
+            } catch (ParseException e) {
+                String msg = String.format("Unable to parse date [%s].", networkerLatestBackup.getCreationTime());
+                LOG.error(msg, e);
+                throw new CloudRuntimeException(msg, e);
+            }
             backup.setSize(networkerLatestBackup.getSize().getValue());
             backup.setProtectedSize(networkerLatestBackup.getSize().getValue());
             backup.setStatus(org.apache.cloudstack.backup.Backup.Status.BackedUp);
diff --git a/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/module.properties b/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/module.properties
index 816ba25..72abf02 100644
--- a/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/module.properties
+++ b/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=networker
-parent=backup
\ No newline at end of file
+parent=backup
diff --git a/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/spring-backup-networker-context.xml b/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/spring-backup-networker-context.xml
index 271fcc7..bfe265e 100644
--- a/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/spring-backup-networker-context.xml
+++ b/plugins/backup/networker/src/main/resources/META-INF/cloudstack/networker/spring-backup-networker-context.xml
@@ -24,4 +24,3 @@
         <property name="name" value="networker"/>
     </bean>
 </beans>
-
diff --git a/plugins/backup/networker/src/test/java/org/apache/backup/networker/NetworkerClientTest.java b/plugins/backup/networker/src/test/java/org/apache/backup/networker/NetworkerClientTest.java
index 82284f9..96f8a7a 100644
--- a/plugins/backup/networker/src/test/java/org/apache/backup/networker/NetworkerClientTest.java
+++ b/plugins/backup/networker/src/test/java/org/apache/backup/networker/NetworkerClientTest.java
@@ -46,7 +46,7 @@
 public class NetworkerClientTest {
     private final String adminUsername = "administrator";
     private final String adminPassword = "password";
-    private final int port = 9399;
+    private final int port = 9400;
     private final String url =  "http://localhost:" + port + "/nwrestapi/v3";
     private NetworkerClient client;
     @Rule
@@ -87,7 +87,7 @@
                                 "            \"comment\": \"-CSBKP-\",\n" +
                                 "            \"links\": [\n" +
                                 "                {\n" +
-                                "                    \"href\": \"http://localhost:9399/nwrestapi/v3/global/protectionpolicies/CSBRONZE\",\n" +
+                                "                    \"href\": \"http://localhost:9400/nwrestapi/v3/global/protectionpolicies/CSBRONZE\",\n" +
                                 "                    \"rel\": \"item\"\n" +
                                 "                }\n" +
                                 "            ],\n" +
@@ -108,7 +108,7 @@
                                 "            \"comment\": \"-CSBKP-\",\n" +
                                 "            \"links\": [\n" +
                                 "                {\n" +
-                                "                    \"href\": \"http://localhost:9399/nwrestapi/v3/global/protectionpolicies/CSGOLD\",\n" +
+                                "                    \"href\": \"http://localhost:9400/nwrestapi/v3/global/protectionpolicies/CSGOLD\",\n" +
                                 "                    \"rel\": \"item\"\n" +
                                 "                }\n" +
                                 "            ],\n" +
@@ -129,7 +129,7 @@
                                 "            \"comment\": \"-CSBKP-\",\n" +
                                 "            \"links\": [\n" +
                                 "                {\n" +
-                                "                    \"href\": \"http://localhost:9399/nwrestapi/v3/global/protectionpolicies/CSSILVER\",\n" +
+                                "                    \"href\": \"http://localhost:9400/nwrestapi/v3/global/protectionpolicies/CSSILVER\",\n" +
                                 "                    \"rel\": \"item\"\n" +
                                 "                }\n" +
                                 "            ],\n" +
@@ -210,7 +210,7 @@
                                 "            \"level\": \"Manual\",\n" +
                                 "            \"links\": [\n" +
                                 "                {\n" +
-                                "                    \"href\": \"http://localhost:9399/nwrestapi/v3/global/backups/6034732f-00000006-7acd14e3-62cd14e3-00871500-5a80015d\",\n" +
+                                "                    \"href\": \"http://localhost:9400/nwrestapi/v3/global/backups/6034732f-00000006-7acd14e3-62cd14e3-00871500-5a80015d\",\n" +
                                 "                    \"rel\": \"item\"\n" +
                                 "                }\n" +
                                 "            ],\n" +
@@ -271,7 +271,7 @@
                                 "            \"level\": \"Manual\",\n" +
                                 "            \"links\": [\n" +
                                 "                {\n" +
-                                "                    \"href\": \"http://localhost:9399/nwrestapi/v3/global/backups/98d29c5e-00000006-81ccda87-62ccda87-00801500-5a80015d\",\n" +
+                                "                    \"href\": \"http://localhost:9400/nwrestapi/v3/global/backups/98d29c5e-00000006-81ccda87-62ccda87-00801500-5a80015d\",\n" +
                                 "                    \"rel\": \"item\"\n" +
                                 "                }\n" +
                                 "            ],\n" +
@@ -332,7 +332,7 @@
                                 "            \"level\": \"Manual\",\n" +
                                 "            \"links\": [\n" +
                                 "                {\n" +
-                                "                    \"href\": \"http://localhost:9399/nwrestapi/v3/global/backups/d371d629-00000006-84ccd61b-62ccd61b-007d1500-5a80015d\",\n" +
+                                "                    \"href\": \"http://localhost:9400/nwrestapi/v3/global/backups/d371d629-00000006-84ccd61b-62ccd61b-007d1500-5a80015d\",\n" +
                                 "                    \"rel\": \"item\"\n" +
                                 "                }\n" +
                                 "            ],\n" +
diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml
index 316dc8d..bff016e 100644
--- a/plugins/backup/veeam/pom.xml
+++ b/plugins/backup/veeam/pom.xml
@@ -23,7 +23,7 @@
   <parent>
     <artifactId>cloudstack-plugins</artifactId>
     <groupId>org.apache.cloudstack</groupId>
-    <version>4.18.3.0-SNAPSHOT</version>
+    <version>4.19.1.0-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
 
diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
index 5c96e4b..e20f679 100644
--- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
+++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java
@@ -47,9 +47,9 @@
 import com.cloud.event.EventTypes;
 import com.cloud.event.EventVO;
 import com.cloud.hypervisor.Hypervisor;
-import com.cloud.hypervisor.vmware.VmwareDatacenter;
+import com.cloud.dc.VmwareDatacenter;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 import com.cloud.user.User;
 import com.cloud.utils.Pair;
diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
index bdc5723..701c45f 100644
--- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
+++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
@@ -37,6 +37,8 @@
 import java.util.Map;
 import java.util.StringJoiner;
 import java.util.UUID;
+import java.util.Date;
+import java.util.Calendar;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.X509TrustManager;
@@ -105,7 +107,6 @@
     private static final String RESTORE_POINT_REFERENCE = "RestorePointReference";
     private static final String BACKUP_FILE_REFERENCE = "BackupFileReference";
     private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
-    private static final SimpleDateFormat newDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
 
     private String veeamServerIp;
@@ -802,7 +803,7 @@
     private Backup.RestorePoint getRestorePointFromBlock(String[] parts) {
         LOG.debug(String.format("Processing block of restore points: [%s].", StringUtils.join(parts, ", ")));
         String id = null;
-        String created = null;
+        Date created = null;
         String type = null;
         for (String part : parts) {
             if (part.matches("Id(\\s)+:(.)*")) {
@@ -810,7 +811,11 @@
                 id = split[1].trim();
             } else if (part.matches("CreationTime(\\s)+:(.)*")) {
                 String [] split = part.split(":", 2);
-                created = split[1].trim();
+                split[1] = StringUtils.trim(split[1]);
+                String [] time = split[1].split("[:/ ]");
+                Calendar cal = Calendar.getInstance();
+                cal.set(Integer.parseInt(time[2]), Integer.parseInt(time[0]) - 1, Integer.parseInt(time[1]), Integer.parseInt(time[3]), Integer.parseInt(time[4]), Integer.parseInt(time[5]));
+                created = cal.getTime();
             } else if (part.matches("Type(\\s)+:(.)*")) {
                 String [] split = part.split(":");
                 type = split[1].trim();
@@ -891,7 +896,7 @@
                     continue;
                 }
                 String vmRestorePointId = vmRestorePoint.getUid().substring(vmRestorePoint.getUid().lastIndexOf(':') + 1);
-                String created = formatDate(vmRestorePoint.getCreationTimeUtc());
+                Date created = formatDate(vmRestorePoint.getCreationTimeUtc());
                 String type = vmRestorePoint.getPointType();
                 LOG.debug(String.format("Adding restore point %s, %s, %s", vmRestorePointId, created, type));
                 vmRestorePointList.add(new Backup.RestorePoint(vmRestorePointId, created, type));
@@ -903,8 +908,8 @@
         return vmRestorePointList;
     }
 
-    private String formatDate(String date) throws ParseException {
-        return newDateFormat.format(dateFormat.parse(StringUtils.substring(date, 0, 19)));
+    private Date formatDate(String date) throws ParseException {
+        return dateFormat.parse(StringUtils.substring(date, 0, 19));
     }
 
     public Pair<Boolean, String> restoreVMToDifferentLocation(String restorePointId, String hostIp, String dataStoreUuid) {
diff --git a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
index 48d1f88..06804d6 100644
--- a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
+++ b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java
@@ -30,6 +30,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.text.SimpleDateFormat;
 import java.util.List;
 import java.util.Map;
 
@@ -55,6 +56,7 @@
     private String adminPassword = "password";
     private VeeamClient client;
     private VeeamClient mockClient;
+    private static final SimpleDateFormat newDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
     @Rule
     public WireMockRule wireMockRule = new WireMockRule(9399);
@@ -463,7 +465,7 @@
 
         Assert.assertEquals(1, vmRestorePointList.size());
         Assert.assertEquals("f6d504cf-eafe-4cd2-8dfc-e9cfe2f1e977", vmRestorePointList.get(0).getId());
-        Assert.assertEquals("2023-11-03 16:26:12", vmRestorePointList.get(0).getCreated());
+        Assert.assertEquals("2023-11-03 16:26:12", newDateFormat.format(vmRestorePointList.get(0).getCreated()));
         Assert.assertEquals("Full", vmRestorePointList.get(0).getType());
     }
 
diff --git a/plugins/ca/root-ca/pom.xml b/plugins/ca/root-ca/pom.xml
index 9167f0d..0fd467b 100644
--- a/plugins/ca/root-ca/pom.xml
+++ b/plugins/ca/root-ca/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
index f71274b..69df700 100644
--- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
+++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
@@ -21,6 +21,8 @@
 import java.io.StringReader;
 import java.math.BigInteger;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.security.InvalidKeyException;
 import java.security.KeyManagementException;
 import java.security.KeyPair;
@@ -37,6 +39,8 @@
 import java.security.spec.InvalidKeySpecException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -49,6 +53,7 @@
 import javax.net.ssl.TrustManagerFactory;
 import javax.xml.bind.DatatypeConverter;
 
+import com.cloud.configuration.Config;
 import org.apache.cloudstack.ca.CAManager;
 import org.apache.cloudstack.framework.ca.CAProvider;
 import org.apache.cloudstack.framework.ca.Certificate;
@@ -365,8 +370,12 @@
         if (managementKeyStore != null) {
             return true;
         }
-        final Certificate serverCertificate = issueCertificate(Collections.singletonList(NetUtils.getHostName()),
-                NetUtils.getAllDefaultNicIps(), getCaValidityDays());
+        List<String> nicIps = NetUtils.getAllDefaultNicIps();
+        addConfiguredManagementIp(nicIps);
+        nicIps = new ArrayList<>(new HashSet<>(nicIps));
+
+        final Certificate serverCertificate = issueCertificate(Collections.singletonList(NetUtils.getHostName()), nicIps, getCaValidityDays());
+
         if (serverCertificate == null || serverCertificate.getPrivateKey() == null) {
             throw new CloudRuntimeException("Failed to generate management server certificate and load management server keystore");
         }
@@ -384,6 +393,28 @@
         return managementKeyStore != null;
     }
 
+    protected void addConfiguredManagementIp(List<String> ipList) {
+        String msNetworkCidr = configDao.getValue(Config.ManagementNetwork.key());
+        try {
+            LOG.debug(String.format("Trying to find management IP in CIDR range [%s].", msNetworkCidr));
+            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+
+            networkInterfaces.asIterator().forEachRemaining(networkInterface -> {
+                networkInterface.getInetAddresses().asIterator().forEachRemaining(inetAddress -> {
+                    if (NetUtils.isIpWithInCidrRange(inetAddress.getHostAddress(), msNetworkCidr)) {
+                        ipList.add(inetAddress.getHostAddress());
+                        LOG.debug(String.format("Added IP [%s] to the list of IPs in the management server's certificate.", inetAddress.getHostAddress()));
+                    }
+                });
+            });
+        } catch (SocketException e) {
+            String msg = "Exception while trying to gather the management server's network interfaces.";
+            LOG.error(msg, e);
+            throw new CloudRuntimeException(msg, e);
+        }
+    }
+
+
     private boolean setupCA() {
         if (!loadRootCAKeyPair() && !saveNewRootCAKeypair()) {
             LOG.error("Failed to save and load root CA keypair");
diff --git a/plugins/database/mysql-ha/pom.xml b/plugins/database/mysql-ha/pom.xml
index afe8703..b518ba8 100644
--- a/plugins/database/mysql-ha/pom.xml
+++ b/plugins/database/mysql-ha/pom.xml
@@ -24,13 +24,13 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
     </dependencies>
diff --git a/plugins/database/quota/pom.xml b/plugins/database/quota/pom.xml
index daad3c1..458425c 100644
--- a/plugins/database/quota/pom.xml
+++ b/plugins/database/quota/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -49,8 +49,8 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
index afcfe3e..9179691 100644
--- a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
@@ -140,8 +140,9 @@
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {QuotaPluginEnabled, QuotaEnableEnforcement, QuotaCurrencySymbol, QuotaStatementPeriod, QuotaSmtpHost, QuotaSmtpPort, QuotaSmtpTimeout,
-                QuotaSmtpUser, QuotaSmtpPassword, QuotaSmtpAuthType, QuotaSmtpSender, QuotaSmtpEnabledSecurityProtocols, QuotaSmtpUseStartTLS, QuotaActivationRuleTimeout, QuotaAccountEnabled};
+        return new ConfigKey<?>[] {QuotaPluginEnabled, QuotaEnableEnforcement, QuotaCurrencySymbol, QuotaCurrencyLocale, QuotaStatementPeriod, QuotaSmtpHost, QuotaSmtpPort, QuotaSmtpTimeout,
+                QuotaSmtpUser, QuotaSmtpPassword, QuotaSmtpAuthType, QuotaSmtpSender, QuotaSmtpEnabledSecurityProtocols, QuotaSmtpUseStartTLS, QuotaActivationRuleTimeout, QuotaAccountEnabled,
+                QuotaEmailHeader, QuotaEmailFooter};
     }
 
     @Override
diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
index a02afda..b960a1b 100644
--- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
+++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
@@ -52,10 +52,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.user.Account;
@@ -64,8 +62,9 @@
 import com.cloud.user.dao.UserDao;
 
 import junit.framework.TestCase;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class QuotaResponseBuilderImplTest extends TestCase {
 
     @Mock
@@ -322,12 +321,13 @@
     }
 
     @Test
-    @PrepareForTest(QuotaResponseBuilderImpl.class)
     public void getNewQuotaTariffObjectTestCreateFromCurrentQuotaTariff() throws Exception {
-        PowerMockito.whenNew(QuotaTariffVO.class).withArguments(Mockito.any(QuotaTariffVO.class)).thenReturn(quotaTariffVoMock);
-
-        quotaResponseBuilderSpy.getNewQuotaTariffObject(quotaTariffVoMock, "", 0);
-        PowerMockito.verifyNew(QuotaTariffVO.class).withArguments(Mockito.any(QuotaTariffVO.class));
+        try (MockedConstruction<QuotaTariffVO> quotaTariffVOMockedConstruction = Mockito.mockConstruction(QuotaTariffVO.class, (mock,
+                                                                                                        context) -> {
+        })) {
+            QuotaTariffVO result = quotaResponseBuilderSpy.getNewQuotaTariffObject(quotaTariffVoMock, "", 0);
+            Assert.assertEquals(quotaTariffVOMockedConstruction.constructed().get(0), result);
+        }
     }
 
     @Test (expected = InvalidParameterValueException.class)
@@ -360,7 +360,6 @@
 
     @Test (expected = ServerApiException.class)
     public void deleteQuotaTariffTestQuotaDoesNotExistThrowsServerApiException() {
-        Mockito.doReturn(null).when(quotaTariffDaoMock).findById(Mockito.anyLong());
         quotaResponseBuilderSpy.deleteQuotaTariff("");
     }
 
@@ -380,7 +379,6 @@
         Calendar[] period = createPeriodForQuotaSummary();
         overrideDefaultQuotaEnabledConfigValue("false");
 
-        Mockito.doReturn(accountMock).when(accountDaoMock).findActiveAccount(Mockito.anyString(), Mockito.anyLong());
         Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime();
         Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong());
         Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class));
@@ -396,7 +394,6 @@
         Calendar[] period = createPeriodForQuotaSummary();
         overrideDefaultQuotaEnabledConfigValue("true");
 
-        Mockito.doReturn(accountMock).when(accountDaoMock).findActiveAccount(Mockito.anyString(), Mockito.anyLong());
         Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime();
         Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong());
         Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class));
diff --git a/plugins/database/quota/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/database/quota/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/database/quota/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/dedicated-resources/pom.xml b/plugins/dedicated-resources/pom.xml
index 11fdf16..f590bea 100644
--- a/plugins/dedicated-resources/pom.xml
+++ b/plugins/dedicated-resources/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/deployment-planners/implicit-dedication/pom.xml b/plugins/deployment-planners/implicit-dedication/pom.xml
index 7cecefa..90bcd12 100644
--- a/plugins/deployment-planners/implicit-dedication/pom.xml
+++ b/plugins/deployment-planners/implicit-dedication/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/deployment-planners/implicit-dedication/src/main/resources/META-INF/cloudstack/implicit-dedication/module.properties b/plugins/deployment-planners/implicit-dedication/src/main/resources/META-INF/cloudstack/implicit-dedication/module.properties
index 6cda904..85ba7d5 100644
--- a/plugins/deployment-planners/implicit-dedication/src/main/resources/META-INF/cloudstack/implicit-dedication/module.properties
+++ b/plugins/deployment-planners/implicit-dedication/src/main/resources/META-INF/cloudstack/implicit-dedication/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=implicit-dedication
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/plugins/deployment-planners/user-concentrated-pod/pom.xml b/plugins/deployment-planners/user-concentrated-pod/pom.xml
index 540d2c3..12f6429 100644
--- a/plugins/deployment-planners/user-concentrated-pod/pom.xml
+++ b/plugins/deployment-planners/user-concentrated-pod/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties b/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties
index 7a430b2..1ac3e14 100644
--- a/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties
+++ b/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=user-concentrated-pod
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/plugins/deployment-planners/user-dispersing/pom.xml b/plugins/deployment-planners/user-dispersing/pom.xml
index e116b2e..7d683fa 100644
--- a/plugins/deployment-planners/user-dispersing/pom.xml
+++ b/plugins/deployment-planners/user-dispersing/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/drs/cluster/balanced/pom.xml b/plugins/drs/cluster/balanced/pom.xml
new file mode 100644
index 0000000..d4bf70a
--- /dev/null
+++ b/plugins/drs/cluster/balanced/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <name>Apache CloudStack Plugin - Cluster DRS Algorithm - Balanced</name>
+    <artifactId>cloud-plugin-cluster-drs-balanced</artifactId>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+</project>
diff --git a/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java b/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java
new file mode 100644
index 0000000..ea234c2
--- /dev/null
+++ b/plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.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.cluster;
+
+import com.cloud.host.Host;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.vm.VirtualMachine;
+import org.apache.log4j.Logger;
+
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
+
+public class Balanced extends AdapterBase implements ClusterDrsAlgorithm {
+
+    private static final Logger logger = Logger.getLogger(Balanced.class);
+
+    @Override
+    public boolean needsDrs(long clusterId, List<Ternary<Long, Long, Long>> cpuList,
+            List<Ternary<Long, Long, Long>> memoryList) throws ConfigurationException {
+        double threshold = getThreshold(clusterId);
+        Double imbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, cpuList, memoryList, null);
+        String drsMetric = ClusterDrsAlgorithm.getClusterDrsMetric(clusterId);
+        String metricType = ClusterDrsAlgorithm.getDrsMetricType(clusterId);
+        Boolean useRatio = ClusterDrsAlgorithm.getDrsMetricUseRatio(clusterId);
+        if (imbalance > threshold) {
+            logger.debug(String.format("Cluster %d needs DRS. Imbalance: %s Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use ratio: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, metricType, useRatio));
+            return true;
+        } else {
+            logger.debug(String.format("Cluster %d does not need DRS. Imbalance: %s Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use ratio: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, metricType, useRatio));
+            return false;
+        }
+    }
+
+    private double getThreshold(long clusterId) {
+        return 1.0 - ClusterDrsImbalanceThreshold.valueIn(clusterId);
+    }
+
+    @Override
+    public String getName() {
+        return "balanced";
+    }
+
+    @Override
+    public Ternary<Double, Double, Double> getMetrics(long clusterId, VirtualMachine vm,
+            ServiceOffering serviceOffering, Host destHost,
+            Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
+            Boolean requiresStorageMotion) throws ConfigurationException {
+        Double preImbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, new ArrayList<>(hostCpuMap.values()), new ArrayList<>(hostMemoryMap.values()), null);
+        Double postImbalance = getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuMap, hostMemoryMap);
+
+        logger.debug(String.format("Cluster %d pre-imbalance: %s post-imbalance: %s Algorithm: %s VM: %s srcHost: %d destHost: %s",
+                clusterId, preImbalance, postImbalance, getName(), vm.getUuid(), vm.getHostId(), destHost.getUuid()));
+
+        // This needs more research to determine the cost and benefit of a migration
+        // TODO: Cost should be a factor of the VM size and the host capacity
+        // TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
+        final double improvement = preImbalance - postImbalance;
+        final double cost = 0.0;
+        final double benefit = 1.0;
+        return new Ternary<>(improvement, cost, benefit);
+    }
+}
diff --git a/plugins/drs/cluster/balanced/src/main/resources/META-INF/cloudstack/balanced/module.properties b/plugins/drs/cluster/balanced/src/main/resources/META-INF/cloudstack/balanced/module.properties
new file mode 100644
index 0000000..77d8d20
--- /dev/null
+++ b/plugins/drs/cluster/balanced/src/main/resources/META-INF/cloudstack/balanced/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=balanced
+parent=cluster
diff --git a/plugins/drs/cluster/balanced/src/main/resources/META-INF/cloudstack/balanced/spring-balanced-context.xml b/plugins/drs/cluster/balanced/src/main/resources/META-INF/cloudstack/balanced/spring-balanced-context.xml
new file mode 100644
index 0000000..8c58f62
--- /dev/null
+++ b/plugins/drs/cluster/balanced/src/main/resources/META-INF/cloudstack/balanced/spring-balanced-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+
+  <bean id="balanced" class="org.apache.cloudstack.cluster.Balanced">
+      <property name="name" value="balanced" />
+  </bean>
+</beans>
diff --git a/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java b/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java
new file mode 100644
index 0000000..a1562b5
--- /dev/null
+++ b/plugins/drs/cluster/balanced/src/test/java/org/apache/cloudstack/cluster/BalancedTest.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster;
+
+import com.cloud.host.Host;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.utils.Ternary;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import javax.naming.ConfigurationException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BalancedTest {
+
+    @InjectMocks
+    Balanced balanced;
+
+    VirtualMachine vm1, vm2, vm3;
+
+    Host destHost;
+
+    ServiceOfferingVO serviceOffering;
+
+    long clusterId = 1L;
+
+    Map<Long, List<VirtualMachine>> hostVmMap;
+
+    Map<Long, Ternary<Long, Long, Long>> hostCpuFreeMap, hostMemoryFreeMap;
+
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws NoSuchFieldException, IllegalAccessException {
+        closeable = MockitoAnnotations.openMocks(this);
+
+
+        vm1 = Mockito.mock(VirtualMachine.class);
+        vm2 = Mockito.mock(VirtualMachine.class);
+        vm3 = Mockito.mock(VirtualMachine.class); // vm to migrate
+
+        destHost = Mockito.mock(Host.class);
+        hostVmMap = new HashMap<>();
+        hostVmMap.put(1L, Collections.singletonList(vm1));
+        hostVmMap.put(2L, Arrays.asList(vm2, vm3));
+
+        serviceOffering = Mockito.mock(ServiceOfferingVO.class);
+        Mockito.when(vm3.getHostId()).thenReturn(2L);
+
+        Mockito.when(destHost.getId()).thenReturn(1L);
+
+        Mockito.when(serviceOffering.getCpu()).thenReturn(1);
+        Mockito.when(serviceOffering.getSpeed()).thenReturn(1000);
+        Mockito.when(serviceOffering.getRamSize()).thenReturn(1024);
+
+        overrideDefaultConfigValue(ClusterDrsImbalanceThreshold, "_defaultValue", "0.5");
+
+        hostCpuFreeMap = new HashMap<>();
+        hostCpuFreeMap.put(1L, new Ternary<>(1000L, 0L, 10000L));
+        hostCpuFreeMap.put(2L, new Ternary<>(2000L, 0L, 10000L));
+
+        hostMemoryFreeMap = new HashMap<>();
+        hostMemoryFreeMap.put(1L, new Ternary<>(512L * 1024L * 1024L, 0L, 8192L * 1024L * 1024L));
+        hostMemoryFreeMap.put(2L, new Ternary<>(2048L * 1024L * 1024L, 0L, 8192L * 1024L * 1024L));
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name,
+            final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    /**
+     * <b>needsDrs tests</b>
+     * <p>Scenarios to test for needsDrs
+     * <p>1. cluster with cpu metric
+     * <p>2. cluster with memory metric
+     * <p>3. cluster with "unknown" metric
+     * <p>
+     * <p>CPU imbalance = 0.333
+     * <p>Memory imbalance = 0.6
+     */
+
+    /*
+     1. cluster with cpu metric
+     0.3333 > 0.5 -> False
+    */
+    @Test
+    public void needsDrsWithCpu() throws ConfigurationException, NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
+        assertFalse(balanced.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new ArrayList<>(hostMemoryFreeMap.values())));
+    }
+
+    /*
+     2. cluster with memory metric
+     0.6 > 0.5 -> True
+    */
+    @Test
+    public void needsDrsWithMemory() throws ConfigurationException, NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "memory");
+        assertTrue(balanced.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new ArrayList<>(hostMemoryFreeMap.values())));
+    }
+
+    /* 3. cluster with "unknown" metric */
+    @Test
+    public void needsDrsWithUnknown() throws NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "unknown");
+        assertThrows(ConfigurationException.class, () -> balanced.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new ArrayList<>(hostMemoryFreeMap.values())));
+    }
+
+    /**
+     * getMetrics tests
+     * <p>Scenarios to test for getMetrics
+     * <p>1. cluster with cpu metric
+     * <p>2. cluster with memory metric
+     * <p>3. cluster with default metric
+     * <p>
+     * <p>Pre
+     * <p>CPU imbalance = 0.333333
+     * <p>Memory imbalance = 0.6
+     * <p>
+     * <p>Post
+     * <p>CPU imbalance = 0.3333
+     * <p>Memory imbalance = 0.2
+     * <p>
+     * <p>Cost 512.0
+     * <p>Benefit  (0.6-0.2) * 8192 = 3276.8
+     */
+
+    /*
+     1. cluster with cpu metric
+     improvement = 0.3333 - 0.3333  = 0.0
+    */
+    @Test
+    public void getMetricsWithCpu() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
+        Ternary<Double, Double, Double> result = balanced.getMetrics(clusterId, vm3, serviceOffering, destHost,
+                hostCpuFreeMap, hostMemoryFreeMap, false);
+        assertEquals(0.0, result.first(), 0.01);
+        assertEquals(0.0, result.second(), 0.0);
+        assertEquals(1.0, result.third(), 0.0);
+    }
+
+    /*
+     2. cluster with memory metric
+     improvement = 0.6 - 0.2 = 0.4
+    */
+    @Test
+    public void getMetricsWithMemory() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "memory");
+        Ternary<Double, Double, Double> result = balanced.getMetrics(clusterId, vm3, serviceOffering, destHost,
+                hostCpuFreeMap, hostMemoryFreeMap, false);
+        assertEquals(0.4, result.first(), 0.01);
+        assertEquals(0, result.second(), 0.0);
+        assertEquals(1, result.third(), 0.0);
+    }
+}
diff --git a/plugins/drs/cluster/condensed/pom.xml b/plugins/drs/cluster/condensed/pom.xml
new file mode 100644
index 0000000..cb62477
--- /dev/null
+++ b/plugins/drs/cluster/condensed/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <name>Apache CloudStack Plugin - Cluster DRS Algorithm - Condensed</name>
+    <artifactId>cloud-plugin-cluster-drs-condensed</artifactId>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+</project>
diff --git a/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.java b/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.java
new file mode 100644
index 0000000..dc1546f
--- /dev/null
+++ b/plugins/drs/cluster/condensed/src/main/java/org/apache/cloudstack/cluster/Condensed.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.cluster;
+
+import com.cloud.host.Host;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.vm.VirtualMachine;
+import org.apache.log4j.Logger;
+
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceSkipThreshold;
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
+
+public class Condensed extends AdapterBase implements ClusterDrsAlgorithm {
+
+    private static final Logger logger = Logger.getLogger(Condensed.class);
+
+    @Override
+    public boolean needsDrs(long clusterId, List<Ternary<Long, Long, Long>> cpuList,
+            List<Ternary<Long, Long, Long>> memoryList) throws ConfigurationException {
+        double threshold = getThreshold(clusterId);
+        Float skipThreshold = ClusterDrsImbalanceSkipThreshold.valueIn(clusterId);
+        Double imbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, cpuList, memoryList, skipThreshold);
+        String drsMetric = ClusterDrsAlgorithm.getClusterDrsMetric(clusterId);
+        String metricType = ClusterDrsAlgorithm.getDrsMetricType(clusterId);
+        Boolean useRatio = ClusterDrsAlgorithm.getDrsMetricUseRatio(clusterId);
+        if (imbalance < threshold) {
+
+            logger.debug(String.format("Cluster %d needs DRS. Imbalance: %s Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use ratio: %s SkipThreshold: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, metricType, useRatio, skipThreshold));
+            return true;
+        } else {
+            logger.debug(String.format("Cluster %d does not need DRS. Imbalance: %s Threshold: %s Algorithm: %s DRS metric: %s Metric Type: %s Use ratio: %s SkipThreshold: %s",
+                    clusterId, imbalance, threshold, getName(), drsMetric, metricType, useRatio, skipThreshold));
+            return false;
+        }
+    }
+
+    private double getThreshold(long clusterId) {
+        return ClusterDrsImbalanceThreshold.valueIn(clusterId);
+    }
+
+    @Override
+    public String getName() {
+        return "condensed";
+    }
+
+    @Override
+    public Ternary<Double, Double, Double> getMetrics(long clusterId, VirtualMachine vm,
+            ServiceOffering serviceOffering, Host destHost,
+            Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
+            Boolean requiresStorageMotion) throws ConfigurationException {
+        Double preImbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, new ArrayList<>(hostCpuMap.values()),
+                new ArrayList<>(hostMemoryMap.values()), null);
+        Double postImbalance = getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuMap, hostMemoryMap);
+
+        logger.debug(String.format("Cluster %d pre-imbalance: %s post-imbalance: %s Algorithm: %s VM: %s srcHost: %d destHost: %s",
+                clusterId, preImbalance, postImbalance, getName(), vm.getUuid(), vm.getHostId(), destHost.getUuid()));
+
+        // This needs more research to determine the cost and benefit of a migration
+        // TODO: Cost should be a factor of the VM size and the host capacity
+        // TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
+        final double improvement = postImbalance - preImbalance;
+        final double cost = 0;
+        final double benefit = 1;
+        return new Ternary<>(improvement, cost, benefit);
+    }
+}
diff --git a/plugins/drs/cluster/condensed/src/main/resources/META-INF/cloudstack/condensed/module.properties b/plugins/drs/cluster/condensed/src/main/resources/META-INF/cloudstack/condensed/module.properties
new file mode 100644
index 0000000..0581736
--- /dev/null
+++ b/plugins/drs/cluster/condensed/src/main/resources/META-INF/cloudstack/condensed/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=condensed
+parent=cluster
diff --git a/plugins/drs/cluster/condensed/src/main/resources/META-INF/cloudstack/condensed/spring-condensed-context.xml b/plugins/drs/cluster/condensed/src/main/resources/META-INF/cloudstack/condensed/spring-condensed-context.xml
new file mode 100644
index 0000000..dffa7d8
--- /dev/null
+++ b/plugins/drs/cluster/condensed/src/main/resources/META-INF/cloudstack/condensed/spring-condensed-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+
+  <bean id="condensed" class="org.apache.cloudstack.cluster.Condensed">
+      <property name="name" value="condensed" />
+  </bean>
+</beans>
diff --git a/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java b/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java
new file mode 100644
index 0000000..d507277
--- /dev/null
+++ b/plugins/drs/cluster/condensed/src/test/java/org/apache/cloudstack/cluster/CondensedTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster;
+
+import com.cloud.host.Host;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.utils.Ternary;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import javax.naming.ConfigurationException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
+import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CondensedTest {
+
+    @InjectMocks
+    Condensed condensed;
+
+    VirtualMachine vm1, vm2, vm3;
+
+    Host destHost;
+
+    ServiceOfferingVO serviceOffering;
+
+    long clusterId = 1L;
+
+    Map<Long, List<VirtualMachine>> hostVmMap;
+
+    Map<Long, Ternary<Long, Long, Long>> hostCpuFreeMap, hostMemoryFreeMap;
+
+
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws NoSuchFieldException, IllegalAccessException {
+        closeable = MockitoAnnotations.openMocks(this);
+
+        vm1 = Mockito.mock(VirtualMachine.class);
+        vm2 = Mockito.mock(VirtualMachine.class);
+        vm3 = Mockito.mock(VirtualMachine.class); // vm to migrate
+
+        destHost = Mockito.mock(Host.class);
+        hostVmMap = new HashMap<>();
+        hostVmMap.put(1L, Collections.singletonList(vm1));
+        hostVmMap.put(2L, Arrays.asList(vm2, vm3));
+
+        serviceOffering = Mockito.mock(ServiceOfferingVO.class);
+        Mockito.when(vm3.getHostId()).thenReturn(2L);
+
+        Mockito.when(destHost.getId()).thenReturn(1L);
+
+        Mockito.when(serviceOffering.getCpu()).thenReturn(1);
+        Mockito.when(serviceOffering.getSpeed()).thenReturn(1000);
+        Mockito.when(serviceOffering.getRamSize()).thenReturn(512);
+
+        overrideDefaultConfigValue(ClusterDrsImbalanceThreshold, "_defaultValue", "0.5");
+
+        hostCpuFreeMap = new HashMap<>();
+        hostCpuFreeMap.put(1L, new Ternary<>(1000L, 0L, 10000L));
+        hostCpuFreeMap.put(2L, new Ternary<>(2000L, 0L, 10000L));
+
+        hostMemoryFreeMap = new HashMap<>();
+        hostMemoryFreeMap.put(1L, new Ternary<>(512L * 1024L * 1024L, 0L, 8192L * 1024L * 1024L));
+        hostMemoryFreeMap.put(2L, new Ternary<>(2048L * 1024L * 1024L, 0L, 8192L * 1024L * 1024L));
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey,
+            final String name,
+            final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    /**
+     * <p>needsDrs tests
+     * <p>Scenarios to test for needsDrs
+     * <p>1. cluster with cpu metric
+     * <p>2. cluster with memory metric
+     * <p>3. cluster with "unknown" metric
+     * <p>
+     * <p>CPU imbalance = 0.333
+     * <p>Memory imbalance = 0.6
+     */
+
+    /*
+     1. cluster with cpu metric
+     0.3333 < 0.5 -> True
+    */
+    @Test
+    public void needsDrsWithCpu() throws ConfigurationException, NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
+        assertTrue(condensed.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new ArrayList<>(hostMemoryFreeMap.values())));
+    }
+
+    /*
+     2. cluster with memory metric
+     0.6 < 0.5 -> False
+    */
+    @Test
+    public void needsDrsWithMemory() throws ConfigurationException, NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "memory");
+        assertFalse(condensed.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new ArrayList<>(hostMemoryFreeMap.values())));
+    }
+
+    /* 3. cluster with "unknown" metric */
+    @Test
+    public void needsDrsWithUnknown() throws NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "unknown");
+        assertThrows(ConfigurationException.class, () -> condensed.needsDrs(clusterId, new ArrayList<>(hostCpuFreeMap.values()), new ArrayList<>(hostMemoryFreeMap.values())));
+    }
+
+    /**
+     * getMetrics tests
+     * <p>Scenarios to test for getMetrics
+     * <p>1. cluster with cpu metric
+     * <p>2. cluster with memory metric
+     * <p>3. cluster with default metric
+     * <p>
+     * <p>Pre
+     * <p>CPU imbalance = 0.333333
+     * <p>Memory imbalance = 0.6
+     * <p>
+     * <p>Post
+     * <p>CPU imbalance = 0.3333
+     * <p>Memory imbalance = 0.2
+     * <p>
+     * <p>Cost 512.0
+     * <p>Benefit  (0.2-0.6) * 8192 = -3276.8
+     */
+
+    /*
+     1. cluster with cpu metric
+     improvement = 0.3333 - 0.3333 = 0.0
+    */
+    @Test
+    public void getMetricsWithCpu() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
+        Ternary<Double, Double, Double> result = condensed.getMetrics(clusterId, vm3, serviceOffering, destHost,
+                hostCpuFreeMap, hostMemoryFreeMap, false);
+        assertEquals(0.0, result.first(), 0.0);
+        assertEquals(0, result.second(), 0.0);
+        assertEquals(1, result.third(), 0.0);
+    }
+
+    /*
+     2. cluster with memory metric
+     improvement = 0.2 - 0.6 = -0.4
+    */
+    @Test
+    public void getMetricsWithMemory() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
+        overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "memory");
+        Ternary<Double, Double, Double> result = condensed.getMetrics(clusterId, vm3, serviceOffering, destHost,
+                hostCpuFreeMap, hostMemoryFreeMap, false);
+        assertEquals(-0.4, result.first(), 0.01);
+        assertEquals(0, result.second(), 0.0);
+        assertEquals(1, result.third(), 0.0);
+    }
+}
diff --git a/plugins/event-bus/inmemory/pom.xml b/plugins/event-bus/inmemory/pom.xml
index dc3828c..2729b2d 100644
--- a/plugins/event-bus/inmemory/pom.xml
+++ b/plugins/event-bus/inmemory/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/event-bus/kafka/pom.xml b/plugins/event-bus/kafka/pom.xml
index 7f246db..b34d37b 100644
--- a/plugins/event-bus/kafka/pom.xml
+++ b/plugins/event-bus/kafka/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/event-bus/rabbitmq/pom.xml b/plugins/event-bus/rabbitmq/pom.xml
index dfe7303..ceb0d58 100644
--- a/plugins/event-bus/rabbitmq/pom.xml
+++ b/plugins/event-bus/rabbitmq/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/ha-planners/skip-heurestics/pom.xml b/plugins/ha-planners/skip-heurestics/pom.xml
index c6d065c..3dcb9f9 100644
--- a/plugins/ha-planners/skip-heurestics/pom.xml
+++ b/plugins/ha-planners/skip-heurestics/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml b/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml
index 21a5064..3cb5a55 100644
--- a/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml
+++ b/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml
@@ -23,4 +23,3 @@
 
 
 </beans>
-
diff --git a/plugins/host-allocators/random/pom.xml b/plugins/host-allocators/random/pom.xml
index 3c7a80b..71dcb69 100644
--- a/plugins/host-allocators/random/pom.xml
+++ b/plugins/host-allocators/random/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
index 8a46d10..70920df 100644
--- a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
+++ b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
@@ -22,7 +22,9 @@
 
 import javax.inject.Inject;
 
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.ListUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -72,7 +74,7 @@
         }
         String hostTag = offering.getHostTag();
         if (hostTag != null) {
-            s_logger.debug("Looking for hosts in dc: " + dcId + "  pod:" + podId + "  cluster:" + clusterId + " having host tag:" + hostTag);
+            s_logger.debug(String.format("Looking for hosts in dc [%s], pod [%s], cluster [%s] and complying with host tag [%s].", dcId, podId, clusterId, hostTag));
         } else {
             s_logger.debug("Looking for hosts in dc: " + dcId + "  pod:" + podId + "  cluster:" + clusterId);
         }
@@ -82,7 +84,7 @@
             if (hostTag != null) {
                 hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag));
             } else {
-                hostsCopy.retainAll(_resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId));
+                hostsCopy.retainAll(_hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId));
             }
         } else {
             // list all computing hosts, regardless of whether they support routing...it's random after all
@@ -90,9 +92,16 @@
             if (hostTag != null) {
                 hostsCopy = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag);
             } else {
-                hostsCopy = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId);
+                hostsCopy = _hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId);
             }
         }
+        hostsCopy = ListUtils.union(hostsCopy, _hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTag));
+
+        if (hostsCopy.isEmpty()) {
+            s_logger.error(String.format("No suitable host found for vm [%s] with tags [%s].", vmProfile, hostTag));
+            throw new CloudRuntimeException(String.format("No suitable host found for vm [%s].", vmProfile));
+        }
+
         s_logger.debug("Random Allocator found " + hostsCopy.size() + "  hosts");
         if (hostsCopy.size() == 0) {
             return suitableHosts;
diff --git a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties b/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties
index 9a04174..dcfe8d3 100644
--- a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties
+++ b/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=host-allocator-random
-parent=allocator
\ No newline at end of file
+parent=allocator
diff --git a/plugins/hypervisors/baremetal/pom.xml b/plugins/hypervisors/baremetal/pom.xml
index f8d15bb..4b568d1 100755
--- a/plugins/hypervisors/baremetal/pom.xml
+++ b/plugins/hypervisors/baremetal/pom.xml
@@ -22,7 +22,7 @@
     <parent>

         <groupId>org.apache.cloudstack</groupId>

         <artifactId>cloudstack-plugins</artifactId>

-        <version>4.18.3.0-SNAPSHOT</version>

+        <version>4.19.1.0-SNAPSHOT</version>

         <relativePath>../../pom.xml</relativePath>

     </parent>

     <artifactId>cloud-plugin-hypervisor-baremetal</artifactId>

diff --git a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-compute/module.properties b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-compute/module.properties
index 654b0d8..9fb9f43 100644
--- a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-compute/module.properties
+++ b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=baremetal-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-discoverer/module.properties b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-discoverer/module.properties
index 3307c8c..93c622e 100644
--- a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-discoverer/module.properties
+++ b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=baremetal-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-network/module.properties b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-network/module.properties
index acfe594..993c464 100644
--- a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-network/module.properties
+++ b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-network/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=baremetal-network
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-planner/module.properties b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-planner/module.properties
index c6c4e74..2c1ec6b 100644
--- a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-planner/module.properties
+++ b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-planner/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=baremetal-planner
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-storage/module.properties b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-storage/module.properties
index b4269a8..5583b2b 100644
--- a/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-storage/module.properties
+++ b/plugins/hypervisors/baremetal/src/main/resources/META-INF/cloudstack/baremetal-storage/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=baremetal-storage
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/.gitignore b/plugins/hypervisors/hyperv/DotNet/ServerResource/.gitignore
index a2f30c8..ca0119f 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/.gitignore
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/.gitignore
@@ -23,4 +23,3 @@
 ServerResource*/bin/*
 *.user
 !.nuget/
-
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.Config b/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.Config
index 3bdbf2e..6efc7f7 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.Config
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.Config
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<configuration>

-  <solution>

-    <add key="disableSourceControlIntegration" value="true" />

-  </solution>

-</configuration>

+<configuration>
+  <solution>
+    <add key="disableSourceControlIntegration" value="true" />
+  </solution>
+</configuration>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.targets b/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.targets
index b7cd8dd..40eaf45 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.targets
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/.nuget/NuGet.targets
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>

+<?xml version="1.0" encoding="utf-8"?>
 <!--
 
     Licensed to the Apache Software Foundation (ASF) under one
@@ -19,134 +19,134 @@
     under the License.
 
 -->
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

-    <PropertyGroup>

-        <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>

+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
 
-        <!-- Enable the restore command to run before builds -->

-        <RestorePackages Condition="  '$(RestorePackages)' == '' ">false</RestorePackages>

-

-        <!-- Property that enables building a package from a project -->

-        <BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>

-

-        <!-- Determines if package restore consent is required to restore packages -->

-        <RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>

+        <!-- Enable the restore command to run before builds -->
+        <RestorePackages Condition="  '$(RestorePackages)' == '' ">false</RestorePackages>
 
-        <!-- Download NuGet.exe if it does not already exist -->

-        <DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>

-    </PropertyGroup>

-    <ItemGroup Condition=" '$(PackageSources)' == '' ">

-        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->

-        <!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->

-        <!--

-            <PackageSource Include="https://www.nuget.org/api/v2/" />

-            <PackageSource Include="https://my-nuget-source/nuget/" />

-        -->

-    </ItemGroup>

-

+        <!-- Property that enables building a package from a project -->
+        <BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
+
+        <!-- Determines if package restore consent is required to restore packages -->
+        <RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
+
+        <!-- Download NuGet.exe if it does not already exist -->
+        <DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
+    </PropertyGroup>
+    <ItemGroup Condition=" '$(PackageSources)' == '' ">
+        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
+        <!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
+        <!--
+            <PackageSource Include="https://www.nuget.org/api/v2/" />
+            <PackageSource Include="https://my-nuget-source/nuget/" />
+        -->
+    </ItemGroup>
+
     <PropertyGroup Condition=" '$(OS)' == 'Windows_NT' And '$(BuildWithMono)' != 'true'">
-        <!-- Windows specific commands -->

-        <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>

-        <PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>

-    </PropertyGroup>

+        <!-- Windows specific commands -->
+        <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
+        <PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
+    </PropertyGroup>
 
     <PropertyGroup Condition=" '$(OS)' != 'Windows_NT' Or '$(BuildWithMono)' == 'true'">
-        <!-- We need to launch nuget.exe with the mono command if we're not on windows -->

-        <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>

+        <!-- We need to launch nuget.exe with the mono command if we're not on windows -->
+        <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
         <PackagesConfig>$(ProjectDir)packages.config</PackagesConfig>
-    </PropertyGroup>

+    </PropertyGroup>
 
-    <PropertyGroup>

-        <!-- NuGet command -->

-        <NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>

-        <PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>

+    <PropertyGroup>
+        <!-- NuGet command -->
+        <NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
+        <PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
 
         <NuGetCommand Condition=" '$(OS)' == 'Windows_NT' And '$(BuildWithMono)' != 'true' ">"$(NuGetExePath)"</NuGetCommand>
         <NuGetCommand Condition=" '$(OS)' != 'Windows_NT' Or '$(BuildWithMono)' == 'true' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>
-

-        <PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>

 
-        <RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>

-        <NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>

-

-        <!-- Commands -->

+        <PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
+
+        <RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
+        <NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
+
+        <!-- Commands -->
         <RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)"  $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir "$(SolutionDir)\" </RestoreCommand>
         <BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties Configuration=$(Configuration) $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
-

-        <!-- We need to ensure packages are restored prior to assembly resolve -->

-        <BuildDependsOn Condition="$(RestorePackages) == 'true'">

-            RestorePackages;

-            $(BuildDependsOn);

-        </BuildDependsOn>

-

-        <!-- Make the build depend on restore packages -->

-        <BuildDependsOn Condition="$(BuildPackage) == 'true'">

-            $(BuildDependsOn);

-            BuildPackage;

-        </BuildDependsOn>

-    </PropertyGroup>

-

-    <Target Name="CheckPrerequisites">

-        <!-- Raise an error if we're unable to locate nuget.exe  -->

-        <Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />

-        <!--

-        Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.

-        This effectively acts as a lock that makes sure that the download operation will only happen once and all

-        parallel builds will have to wait for it to complete.

-        -->

-        <MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />

-    </Target>

-

-    <Target Name="_DownloadNuGet">

-        <DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />

-    </Target>

-

-    <Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">

-        <Exec Command="$(RestoreCommand)"

+
+        <!-- We need to ensure packages are restored prior to assembly resolve -->
+        <BuildDependsOn Condition="$(RestorePackages) == 'true'">
+            RestorePackages;
+            $(BuildDependsOn);
+        </BuildDependsOn>
+
+        <!-- Make the build depend on restore packages -->
+        <BuildDependsOn Condition="$(BuildPackage) == 'true'">
+            $(BuildDependsOn);
+            BuildPackage;
+        </BuildDependsOn>
+    </PropertyGroup>
+
+    <Target Name="CheckPrerequisites">
+        <!-- Raise an error if we're unable to locate nuget.exe  -->
+        <Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
+        <!--
+        Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
+        This effectively acts as a lock that makes sure that the download operation will only happen once and all
+        parallel builds will have to wait for it to complete.
+        -->
+        <MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
+    </Target>
+
+    <Target Name="_DownloadNuGet">
+        <DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
+    </Target>
+
+    <Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
+        <Exec Command="$(RestoreCommand)"
               Condition="( '$(OS)' != 'Windows_NT' Or '$(BuildWithMono)' == 'true' ) And Exists('$(PackagesConfig)')" />
 
-        <Exec Command="$(RestoreCommand)"

-              LogStandardErrorAsError="true"

+        <Exec Command="$(RestoreCommand)"
+              LogStandardErrorAsError="true"
         Condition="'$(OS)' == 'Windows_NT' And '$(BuildWithMono)' != 'true' And Exists('$(PackagesConfig)')" />
-    </Target>

-

-    <Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">

+    </Target>
+
+    <Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
         <Exec Command="$(BuildCommand)"
               Condition=" '$(OS)' != 'Windows_NT' Or '$(BuildWithMono)' " />
 
-        <Exec Command="$(BuildCommand)"

-              LogStandardErrorAsError="true"

+        <Exec Command="$(BuildCommand)"
+              LogStandardErrorAsError="true"
         Condition=" '$(OS)' == 'Windows_NT' And '$(BuildWithMono)' != 'true' " />
-    </Target>

+    </Target>
 
-    <UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">

-        <ParameterGroup>

-            <OutputFilename ParameterType="System.String" Required="true" />

-        </ParameterGroup>

-        <Task>

-            <Reference Include="System.Core" />

-            <Using Namespace="System" />

-            <Using Namespace="System.IO" />

-            <Using Namespace="System.Net" />

-            <Using Namespace="Microsoft.Build.Framework" />

-            <Using Namespace="Microsoft.Build.Utilities" />

-            <Code Type="Fragment" Language="cs">

-                <![CDATA[

-                try {

-                    OutputFilename = Path.GetFullPath(OutputFilename);

-

-                    Log.LogMessage("Downloading latest version of NuGet.exe...");

-                    WebClient webClient = new WebClient();

-                    webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);

-

-                    return true;

-                }

-                catch (Exception ex) {

-                    Log.LogErrorFromException(ex);

-                    return false;

-                }

-            ]]>

-            </Code>

-        </Task>

-    </UsingTask>

+    <UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
+        <ParameterGroup>
+            <OutputFilename ParameterType="System.String" Required="true" />
+        </ParameterGroup>
+        <Task>
+            <Reference Include="System.Core" />
+            <Using Namespace="System" />
+            <Using Namespace="System.IO" />
+            <Using Namespace="System.Net" />
+            <Using Namespace="Microsoft.Build.Framework" />
+            <Using Namespace="Microsoft.Build.Utilities" />
+            <Code Type="Fragment" Language="cs">
+                <![CDATA[
+                try {
+                    OutputFilename = Path.GetFullPath(OutputFilename);
+
+                    Log.LogMessage("Downloading latest version of NuGet.exe...");
+                    WebClient webClient = new WebClient();
+                    webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
+
+                    return true;
+                }
+                catch (Exception ex) {
+                    Log.LogErrorFromException(ex);
+                    return false;
+                }
+            ]]>
+            </Code>
+        </Task>
+    </UsingTask>
 </Project>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/AgentShell.csproj b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/AgentShell.csproj
index f804ef6..262d2a8 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/AgentShell.csproj
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/AgentShell.csproj
@@ -148,4 +148,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
\ No newline at end of file
+</Project>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config
index 3ec08bd..f7acb95 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config
@@ -143,4 +143,4 @@
       </setting>
     </CloudStack.Plugin.AgentShell.AgentSettings>
   </userSettings>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/packages.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/packages.config
index fb1c846..2f2cd01 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/packages.config
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/packages.config
@@ -10,4 +10,4 @@
   <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
   <package id="NSubstitute" version="1.6.1.0" targetFramework="net45" />
   <package id="xunit" version="1.9.2" targetFramework="net45" />
-</packages>
\ No newline at end of file
+</packages>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs
index 6e1df24..306bc96 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs
@@ -24,7 +24,7 @@
 using System.Threading.Tasks;
 
 // C# versions of certain CloudStack types to simplify JSON serialisation.
-// Limit to the number of types, becasue they are written and maintained manually.
+// Limit to the number of types, because they are written and maintained manually.
 // JsonProperty used to identify property name when serialised, which allows
 // later adoption of C# naming conventions if requried. 
 namespace HypervResource
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResource.csproj b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResource.csproj
index 8bebfff..6adba77 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResource.csproj
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResource.csproj
@@ -1,113 +1,113 @@
-<?xml version="1.0" encoding="utf-8"?>

-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

-  <PropertyGroup>

-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

-    <ProjectGuid>{C963DFFF-65BA-4E71-ADA5-526A4DA4E0B2}</ProjectGuid>

-    <OutputType>Library</OutputType>

-    <AppDesignerFolder>Properties</AppDesignerFolder>

-    <RootNamespace>HypervResource</RootNamespace>

-    <AssemblyName>HypervResource</AssemblyName>

-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

-    <FileAlignment>512</FileAlignment>

-    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>

-    <RestorePackages>true</RestorePackages>

-  </PropertyGroup>

-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

-    <DebugSymbols>true</DebugSymbols>

-    <DebugType>full</DebugType>

-    <Optimize>false</Optimize>

-    <OutputPath>bin\Debug\</OutputPath>

-    <DefineConstants>DEBUG;TRACE</DefineConstants>

-    <ErrorReport>prompt</ErrorReport>

-    <WarningLevel>4</WarningLevel>

-  </PropertyGroup>

-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

-    <DebugType>pdbonly</DebugType>

-    <Optimize>true</Optimize>

-    <OutputPath>bin\Release\</OutputPath>

-    <DefineConstants>TRACE</DefineConstants>

-    <ErrorReport>prompt</ErrorReport>

-    <WarningLevel>4</WarningLevel>

-  </PropertyGroup>

-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTestsDebug|AnyCPU'">

-    <DebugSymbols>true</DebugSymbols>

-    <OutputPath>bin\NoUnitTestsDebug\</OutputPath>

-    <DefineConstants>DEBUG;TRACE</DefineConstants>

-    <DebugType>full</DebugType>

-    <PlatformTarget>AnyCPU</PlatformTarget>

-    <ErrorReport>prompt</ErrorReport>

-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>

-  </PropertyGroup>

-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTests|AnyCPU'">

-    <OutputPath>bin\NoUnitTests\</OutputPath>

-    <DefineConstants>TRACE</DefineConstants>

-    <Optimize>true</Optimize>

-    <DebugType>pdbonly</DebugType>

-    <PlatformTarget>AnyCPU</PlatformTarget>

-    <ErrorReport>prompt</ErrorReport>

-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>

-  </PropertyGroup>

-  <ItemGroup>

-    <Reference Include="AWSSDK">

-      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>

-    </Reference>

-    <Reference Include="Ionic.Zip">

-      <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>

-    </Reference>

-    <Reference Include="log4net">

-      <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath>

-    </Reference>

-    <Reference Include="Newtonsoft.Json">

-      <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>

-    </Reference>

-    <Reference Include="System" />

-    <Reference Include="System.Configuration" />

-    <Reference Include="System.Core" />

-    <Reference Include="System.Management" />

-    <Reference Include="System.Net.Http" />

-    <Reference Include="System.Web" />

-    <Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />

-    <Reference Include="System.Xml.Linq" />

-    <Reference Include="System.Data.DataSetExtensions" />

-    <Reference Include="Microsoft.CSharp" />

-    <Reference Include="System.Data" />

-    <Reference Include="System.Xml" />

-  </ItemGroup>

-  <ItemGroup>

-    <Compile Include="CloudStackTypes.cs" />

-    <Compile Include="IWmiCallsV2.cs" />

-    <Compile Include="WmiCallsV2.cs" />

-    <Compile Include="Properties\AssemblyInfo.cs" />

-    <Compile Include="HypervResourceController.cs" />

-    <Compile Include="Utils.cs" />

-  </ItemGroup>

-  <ItemGroup>

-    <None Include="packages.config" />

-  </ItemGroup>

-  <ItemGroup>

+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{C963DFFF-65BA-4E71-ADA5-526A4DA4E0B2}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HypervResource</RootNamespace>
+    <AssemblyName>HypervResource</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTestsDebug|AnyCPU'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\NoUnitTestsDebug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTests|AnyCPU'">
+    <OutputPath>bin\NoUnitTests\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="AWSSDK">
+      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>
+    </Reference>
+    <Reference Include="Ionic.Zip">
+      <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
+    </Reference>
+    <Reference Include="log4net">
+      <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="CloudStackTypes.cs" />
+    <Compile Include="IWmiCallsV2.cs" />
+    <Compile Include="WmiCallsV2.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="HypervResourceController.cs" />
+    <Compile Include="Utils.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
     <None Include="heartbeat.bat">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\WmiWrappers\WmiWrappers.csproj">

-      <Project>{db824727-bdc3-437c-a364-7a811d8a160f}</Project>

-      <Name>WmiWrappers</Name>

-    </ProjectReference>

-  </ItemGroup>

-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />

+    <ProjectReference Include="..\WmiWrappers\WmiWrappers.csproj">
+      <Project>{db824727-bdc3-437c-a364-7a811d8a160f}</Project>
+      <Name>WmiWrappers</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
   <Target Name="BeforeBuild" Condition="'$(BuildWithMono)' == 'true' ">
     <RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" Condition="Exists('$(ProjectDir)$(BaseIntermediateOutputPath)')"/>
     <RemoveDir Directories="$(ProjectDir)$(OutputPath)" Condition="Exists('$(ProjectDir)$(OutputPath)')"/>
   </Target>
-  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

-       Other similar extension points exist, see Microsoft.Common.targets.

-  <Target Name="BeforeBuild">

-  </Target>

-  <Target Name="AfterBuild">

-  </Target>

-  -->

-</Project>
\ No newline at end of file
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs
index 6272c4e..d385a8e 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs
@@ -529,7 +529,7 @@
         {
             // Get the virtual switch
             VirtualEthernetSwitch vSwitch = GetExternalVirtSwitch(vSwitchName);
-            //check the the recevied vSwitch is the same as vSwitchName.
+            //check the recevied vSwitch is the same as vSwitchName.
             if (!vSwitchName.Equals("")  && !vSwitch.ElementName.Equals(vSwitchName))
             {
                var errMsg = string.Format("Internal error, coudl not find Virtual Switch with the name : " +vSwitchName);
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/heartbeat.bat b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/heartbeat.bat
index 85f6e7b..472f68f 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/heartbeat.bat
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/heartbeat.bat
@@ -20,4 +20,4 @@
 @REM http://stackoverflow.com/questions/9871499/how-to-get-utc-time-with-windows-batch-file
 
 for /f %%x in ('wmic path win32_utctime get /format:list ^| findstr "="') do set %%x
-echo %Year%-%Month%-%Day%@%Hour%:%Minute%:%Second%>%1
\ No newline at end of file
+echo %Year%-%Month%-%Day%@%Hour%:%Minute%:%Second%>%1
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config
index 4c538e4..efa54b2 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config
@@ -6,4 +6,4 @@
   <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
   <package id="NSubstitute" version="1.6.1.0" targetFramework="net45" />
   <package id="xunit" version="1.9.2" targetFramework="net45" />
-</packages>
\ No newline at end of file
+</packages>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config
index c959ccf..6b941dc 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config
@@ -136,4 +136,4 @@
       </providers>
     </roleManager>
   </system.web>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceController1Test.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceController1Test.cs
index 87a12c9..91d41e6 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceController1Test.cs
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceController1Test.cs
@@ -217,7 +217,7 @@
         {

             testSampleVolumeTempURIJSON = "\"storagepool\"";

             // Arrange

-            String destoryCmd = //"{\"volume\":" + getSampleVolumeObjectTO() + "}";

+            String destroyCmd = //"{\"volume\":" + getSampleVolumeObjectTO() + "}";

                             "{\"volume\":{\"name\":\"" + testSampleVolumeTempUUIDNoExt

                                     + "\",\"storagePoolType\":\"Filesystem\","

                                     + "\"mountPoint\":"

@@ -233,15 +233,15 @@
             HypervResourceController rsrcServer = new HypervResourceController();

             HypervResourceController.wmiCallsV2 = wmiCallsV2;

 

-            dynamic jsonDestoryCmd = JsonConvert.DeserializeObject(destoryCmd);

+            dynamic jsonDestroyCmd = JsonConvert.DeserializeObject(destroyCmd);

 

             // Act

-            dynamic destoryAns = rsrcServer.DestroyCommand(jsonDestoryCmd);

+            dynamic destroyAns = rsrcServer.DestroyCommand(jsonDestroyCmd);

 

             // Assert

-            JObject ansAsProperty2 = destoryAns[0];

+            JObject ansAsProperty2 = destroyAns[0];

             dynamic ans = ansAsProperty2.GetValue(CloudStackTypes.Answer);

-            String path = jsonDestoryCmd.volume.path;

+            String path = jsonDestroyCmd.volume.path;

             Assert.True((bool)ans.result, "DestroyCommand did not succeed " + ans.details);

             Assert.True(!File.Exists(path), "Failed to delete file " + path);

         }

diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceControllerTest.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceControllerTest.cs
index fab1b82..7f03e15 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceControllerTest.cs
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/HypervResourceControllerTest.cs
@@ -232,7 +232,7 @@
         {

             // Arrange

             String sampleVolume = getSampleVolumeObjectTO();

-            String destoryCmd = //"{\"volume\":" + getSampleVolumeObjectTO() + "}";

+            String destroyCmd = //"{\"volume\":" + getSampleVolumeObjectTO() + "}";

                             "{\"volume\":{\"name\":\"" + testSampleVolumeTempUUIDNoExt

                                     + "\",\"storagePoolType\":\"Filesystem\","

                                     + "\"mountPoint\":"

@@ -243,15 +243,15 @@
                                     + "\"type\":\"ROOT\",\"id\":9,\"size\":0}}";

 

             HypervResourceController rsrcServer = new HypervResourceController();

-            dynamic jsonDestoryCmd = JsonConvert.DeserializeObject(destoryCmd);

+            dynamic jsonDestroyCmd = JsonConvert.DeserializeObject(destroyCmd);

 

             // Act

-            dynamic destoryAns = rsrcServer.DestroyCommand(jsonDestoryCmd);

+            dynamic destroyAns = rsrcServer.DestroyCommand(jsonDestroyCmd);

 

             // Assert

-            JObject ansAsProperty2 = destoryAns[0];

+            JObject ansAsProperty2 = destroyAns[0];

             dynamic ans = ansAsProperty2.GetValue(CloudStackTypes.Answer);

-            String path = jsonDestoryCmd.volume.path;

+            String path = jsonDestroyCmd.volume.path;

             Assert.True((bool)ans.result, "DestroyCommand did not succeed " + ans.details);

             Assert.True(!File.Exists(path), "Failed to delete file " + path);

         }

diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/ServerResource.Tests.csproj b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/ServerResource.Tests.csproj
index d2fb47e..b7c3194 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/ServerResource.Tests.csproj
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/ServerResource.Tests.csproj
@@ -1,126 +1,126 @@
-<?xml version="1.0" encoding="utf-8"?>

-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

-  <PropertyGroup>

-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

-    <ProductVersion>

-    </ProductVersion>

-    <SchemaVersion>2.0</SchemaVersion>

-    <ProjectGuid>{925FD1DE-6211-4E10-9949-3751B8ABDF59}</ProjectGuid>

-    <OutputType>Library</OutputType>

-    <AppDesignerFolder>Properties</AppDesignerFolder>

-    <RootNamespace>ServerResource.Tests</RootNamespace>

-    <AssemblyName>ServerResource.Tests</AssemblyName>

-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

-    <FileAlignment>512</FileAlignment>

-    <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

-    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>

-    <RestorePackages>true</RestorePackages>

-  </PropertyGroup>

-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

-    <DebugSymbols>true</DebugSymbols>

-    <DebugType>full</DebugType>

-    <Optimize>false</Optimize>

-    <OutputPath>bin\Debug\</OutputPath>

-    <DefineConstants>DEBUG;TRACE</DefineConstants>

-    <ErrorReport>prompt</ErrorReport>

-    <WarningLevel>4</WarningLevel>

-  </PropertyGroup>

-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

-    <DebugType>pdbonly</DebugType>

-    <Optimize>true</Optimize>

-    <OutputPath>bin\Release\</OutputPath>

-    <DefineConstants>TRACE</DefineConstants>

-    <ErrorReport>prompt</ErrorReport>

-    <WarningLevel>4</WarningLevel>

-  </PropertyGroup>

-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTestsDebug|AnyCPU'">

-    <DebugSymbols>true</DebugSymbols>

-    <OutputPath>bin\NoUnitTestsDebug\</OutputPath>

-    <DefineConstants>DEBUG;TRACE</DefineConstants>

-    <DebugType>full</DebugType>

-    <PlatformTarget>AnyCPU</PlatformTarget>

-    <ErrorReport>prompt</ErrorReport>

-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>

-  </PropertyGroup>

-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTests|AnyCPU'">

-    <OutputPath>bin\NoUnitTests\</OutputPath>

-    <DefineConstants>TRACE</DefineConstants>

-    <Optimize>true</Optimize>

-    <DebugType>pdbonly</DebugType>

-    <PlatformTarget>AnyCPU</PlatformTarget>

-    <ErrorReport>prompt</ErrorReport>

-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>

-  </PropertyGroup>

-  <ItemGroup>

-    <Reference Include="AWSSDK">

-      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>

-    </Reference>

-    <Reference Include="Ionic.Zip">

-      <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>

-    </Reference>

-    <Reference Include="log4net">

-      <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath>

-    </Reference>

-    <Reference Include="Microsoft.CSharp" />

-    <Reference Include="Newtonsoft.Json">

-      <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>

-    </Reference>

-    <Reference Include="NSubstitute">

-      <HintPath>..\packages\NSubstitute.1.6.1.0\lib\NET40\NSubstitute.dll</HintPath>

-    </Reference>

-    <Reference Include="System" />

-    <Reference Include="System.ComponentModel.DataAnnotations" />

-    <Reference Include="System.Configuration" />

-    <Reference Include="System.Core" />

-    <Reference Include="System.Data" />

-    <Reference Include="System.Management" />

-    <Reference Include="System.ServiceProcess" />

-    <Reference Include="System.Web" />

-    <Reference Include="System.Web.ApplicationServices" />

-    <Reference Include="System.Web.Extensions" />

-    <Reference Include="System.Web.Abstractions" />

-    <Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />

-    <Reference Include="System.Web.Routing" />

-    <Reference Include="System.Xml" />

-    <Reference Include="System.Xml.Linq" />

-    <Reference Include="System.Net.Http">

-    </Reference>

-    <Reference Include="xunit">

-      <HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>

-    </Reference>

-  </ItemGroup>

-  <ItemGroup>

-    <Compile Include="HypervResourceController1Test.cs" />

-    <Compile Include="Properties\AssemblyInfo.cs" />

-    <!--<Compile Include="HypervResourceControllerTest.cs" /> -->

-  </ItemGroup>

-  <ItemGroup>

-    <Content Include="App.config">

-      <SubType>Designer</SubType>

-    </Content>

-  </ItemGroup>

-  <ItemGroup>

-    <None Include="packages.config">

-      <SubType>Designer</SubType>

-    </None>

-  </ItemGroup>

-  <ItemGroup>

-    <ProjectReference Include="..\AgentShell\AgentShell.csproj">

-      <Project>{9060b539-62d0-4e71-a6c6-5944828774e9}</Project>

-      <Name>AgentShell</Name>

-    </ProjectReference>

-    <ProjectReference Include="..\HypervResource\HypervResource.csproj">

-      <Project>{c963dfff-65ba-4e71-ada5-526a4da4e0b2}</Project>

-      <Name>HypervResource</Name>

-    </ProjectReference>

-    <ProjectReference Include="..\WmiWrappers\WmiWrappers.csproj">

-      <Project>{db824727-bdc3-437c-a364-7a811d8a160f}</Project>

-      <Name>WmiWrappers</Name>

-    </ProjectReference>

-  </ItemGroup>

-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />

+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>
+    </ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{925FD1DE-6211-4E10-9949-3751B8ABDF59}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ServerResource.Tests</RootNamespace>
+    <AssemblyName>ServerResource.Tests</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTestsDebug|AnyCPU'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\NoUnitTestsDebug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTests|AnyCPU'">
+    <OutputPath>bin\NoUnitTests\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="AWSSDK">
+      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>
+    </Reference>
+    <Reference Include="Ionic.Zip">
+      <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
+    </Reference>
+    <Reference Include="log4net">
+      <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="NSubstitute">
+      <HintPath>..\packages\NSubstitute.1.6.1.0\lib\NET40\NSubstitute.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.ComponentModel.DataAnnotations" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.ServiceProcess" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Web.ApplicationServices" />
+    <Reference Include="System.Web.Extensions" />
+    <Reference Include="System.Web.Abstractions" />
+    <Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
+    <Reference Include="System.Web.Routing" />
+    <Reference Include="System.Xml" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Net.Http">
+    </Reference>
+    <Reference Include="xunit">
+      <HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="HypervResourceController1Test.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <!--<Compile Include="HypervResourceControllerTest.cs" /> -->
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="App.config">
+      <SubType>Designer</SubType>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config">
+      <SubType>Designer</SubType>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\AgentShell\AgentShell.csproj">
+      <Project>{9060b539-62d0-4e71-a6c6-5944828774e9}</Project>
+      <Name>AgentShell</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\HypervResource\HypervResource.csproj">
+      <Project>{c963dfff-65ba-4e71-ada5-526a4da4e0b2}</Project>
+      <Name>HypervResource</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\WmiWrappers\WmiWrappers.csproj">
+      <Project>{db824727-bdc3-437c-a364-7a811d8a160f}</Project>
+      <Name>WmiWrappers</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
   <Target Name="BeforeBuild" Condition="'$(BuildWithMono)' == 'true' ">
     <RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" Condition="Exists('$(ProjectDir)$(BaseIntermediateOutputPath)')"/>
     <RemoveDir Directories="$(ProjectDir)$(OutputPath)" Condition="Exists('$(ProjectDir)$(OutputPath)')"/>
@@ -131,9 +131,9 @@
   </Target>
   <Target Name="AfterBuild">
   </Target>
-  -->

-  <UsingTask AssemblyFile="..\packages\xunit.1.9.2\lib\net20\xunit.runner.msbuild.dll" TaskName="Xunit.Runner.MSBuild.xunit" />

-  <Target Name="AfterBuild">

-    <Xunit.Runner.MSBuild.xunit Assembly="..\ServerResource.Tests\bin\Debug\ServerResource.Tests.dll" />

-  </Target>

+  -->
+  <UsingTask AssemblyFile="..\packages\xunit.1.9.2\lib\net20\xunit.runner.msbuild.dll" TaskName="Xunit.Runner.MSBuild.xunit" />
+  <Target Name="AfterBuild">
+    <Xunit.Runner.MSBuild.xunit Assembly="..\ServerResource.Tests\bin\Debug\ServerResource.Tests.dll" />
+  </Target>
 </Project>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/packages.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/packages.config
index 4c538e4..efa54b2 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/packages.config
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/packages.config
@@ -6,4 +6,4 @@
   <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
   <package id="NSubstitute" version="1.6.1.0" targetFramework="net45" />
   <package id="xunit" version="1.9.2" targetFramework="net45" />
-</packages>
\ No newline at end of file
+</packages>
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/WmiWrappers.csproj b/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/WmiWrappers.csproj
index 5404736..ef0a926 100644
--- a/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/WmiWrappers.csproj
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/WmiWrappers.csproj
@@ -1,197 +1,197 @@
-<?xml version="1.0" encoding="utf-8"?>

-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

-  <PropertyGroup>

-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

-    <ProjectGuid>{DB824727-BDC3-437C-A364-7A811D8A160F}</ProjectGuid>

-    <OutputType>Library</OutputType>

-    <AppDesignerFolder>Properties</AppDesignerFolder>

-    <RootNamespace>CloudStack.Plugin.WmiWrappers</RootNamespace>

-    <AssemblyName>WmiWrappers</AssemblyName>

-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

-    <FileAlignment>512</FileAlignment>

-    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>

-    <RestorePackages>true</RestorePackages>

-  </PropertyGroup>

-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

-    <DebugSymbols>true</DebugSymbols>

-    <DebugType>full</DebugType>

-    <Optimize>false</Optimize>

-    <OutputPath>bin\Debug\</OutputPath>

-    <DefineConstants>DEBUG;TRACE</DefineConstants>

-    <ErrorReport>prompt</ErrorReport>

-    <WarningLevel>4</WarningLevel>

-  </PropertyGroup>

-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

-    <DebugType>pdbonly</DebugType>

-    <Optimize>true</Optimize>

-    <OutputPath>bin\Release\</OutputPath>

-    <DefineConstants>TRACE</DefineConstants>

-    <ErrorReport>prompt</ErrorReport>

-    <WarningLevel>4</WarningLevel>

-  </PropertyGroup>

-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTestsDebug|AnyCPU'">

-    <DebugSymbols>true</DebugSymbols>

-    <OutputPath>bin\NoUnitTestsDebug\</OutputPath>

-    <DefineConstants>DEBUG;TRACE</DefineConstants>

-    <DebugType>full</DebugType>

-    <PlatformTarget>AnyCPU</PlatformTarget>

-    <ErrorReport>prompt</ErrorReport>

-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>

-  </PropertyGroup>

-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTests|AnyCPU'">

-    <OutputPath>bin\NoUnitTests\</OutputPath>

-    <DefineConstants>TRACE</DefineConstants>

-    <Optimize>true</Optimize>

-    <DebugType>pdbonly</DebugType>

-    <PlatformTarget>AnyCPU</PlatformTarget>

-    <ErrorReport>prompt</ErrorReport>

-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>

-  </PropertyGroup>

-  <ItemGroup>

-    <Reference Include="AWSSDK">

-      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>

-    </Reference>

-    <Reference Include="Ionic.Zip">

-      <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>

-    </Reference>

-    <Reference Include="log4net">

-      <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath>

-    </Reference>

-    <Reference Include="Newtonsoft.Json">

-      <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>

-    </Reference>

-    <Reference Include="NSubstitute">

-      <HintPath>..\packages\NSubstitute.1.6.1.0\lib\NET40\NSubstitute.dll</HintPath>

-    </Reference>

-    <Reference Include="System" />

-    <Reference Include="System.Core" />

-    <Reference Include="System.Management" />

-    <Reference Include="System.Xml.Linq" />

-    <Reference Include="System.Data.DataSetExtensions" />

-    <Reference Include="Microsoft.CSharp" />

-    <Reference Include="System.Data" />

-    <Reference Include="System.Xml" />

-    <Reference Include="xunit">

-      <HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>

-    </Reference>

-  </ItemGroup>

-  <ItemGroup>

-    <Compile Include="ROOT.CIMV2.Win32_OperatingSystem.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="root.CIMV2.Win32_PerfFormattedData_Counters_ProcessorInformation.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.CIMV2.Win32_Processor.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_ComputerSystem.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_ConcreteJob.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_EthernetPortAllocationSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_EthernetSwitchPort.cs">

-      <SubType>Component</SubType>

-    </Compile>

+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{DB824727-BDC3-437C-A364-7A811D8A160F}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>CloudStack.Plugin.WmiWrappers</RootNamespace>
+    <AssemblyName>WmiWrappers</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTestsDebug|AnyCPU'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\NoUnitTestsDebug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NoUnitTests|AnyCPU'">
+    <OutputPath>bin\NoUnitTests\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="AWSSDK">
+      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>
+    </Reference>
+    <Reference Include="Ionic.Zip">
+      <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
+    </Reference>
+    <Reference Include="log4net">
+      <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="NSubstitute">
+      <HintPath>..\packages\NSubstitute.1.6.1.0\lib\NET40\NSubstitute.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+    <Reference Include="xunit">
+      <HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ROOT.CIMV2.Win32_OperatingSystem.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="root.CIMV2.Win32_PerfFormattedData_Counters_ProcessorInformation.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.CIMV2.Win32_Processor.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_ComputerSystem.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_ConcreteJob.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_EthernetPortAllocationSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_EthernetSwitchPort.cs">
+      <SubType>Component</SubType>
+    </Compile>
     <Compile Include="ROOT.virtualization.v2.Msvm_EthernetSwitchPortBandwidthSettingData.cs">
       <SubType>Component</SubType>
     </Compile>
-    <Compile Include="ROOT.virtualization.v2.Msvm_EthernetSwitchPortVlanSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_ExternalEthernetPort.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_ImageManagementService.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_KvpExchangeComponent.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_KvpExchangeComponentSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_KvpExchangeDataItem.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_LANEndpoint.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_MemorySettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_MigrationJob.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_ProcessorSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_ResourceAllocationSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

+    <Compile Include="ROOT.virtualization.v2.Msvm_EthernetSwitchPortVlanSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_ExternalEthernetPort.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_ImageManagementService.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_KvpExchangeComponent.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_KvpExchangeComponentSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_KvpExchangeDataItem.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_LANEndpoint.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_MemorySettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_MigrationJob.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_ProcessorSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_ResourceAllocationSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
     <Compile Include="ROOT.virtualization.v2.Msvm_ShutdownComponent.cs">
       <SubType>Component</SubType>
     </Compile>
-    <Compile Include="ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_StorageJob.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_SummaryInformation.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_SyntheticEthernetPortSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualEthernetSwitch.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualEthernetSwitchManagementService.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualHardDiskSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemManagementService.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemManagementServiceSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemMigrationService.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemMigrationSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemSettingData.cs">

-      <SubType>Component</SubType>

-    </Compile>

-  </ItemGroup>

-  <ItemGroup>

-    <Content Include="Readme.txt" />

-  </ItemGroup>

-  <ItemGroup>

-    <Folder Include="Properties\" />

-  </ItemGroup>

-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />

+    <Compile Include="ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_StorageJob.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_SummaryInformation.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_SyntheticEthernetPortSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualEthernetSwitch.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualEthernetSwitchManagementService.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualHardDiskSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemManagementService.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemManagementServiceSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemMigrationService.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemMigrationSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="ROOT.virtualization.v2.Msvm_VirtualSystemSettingData.cs">
+      <SubType>Component</SubType>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Readme.txt" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Properties\" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
   <Target Name="BeforeBuild" Condition="'$(BuildWithMono)' == 'true' ">
     <RemoveDir Directories="$(ProjectDir)$(BaseIntermediateOutputPath)" Condition="Exists('$(ProjectDir)$(BaseIntermediateOutputPath)')" />
     <RemoveDir Directories="$(ProjectDir)$(OutputPath)" Condition="Exists('$(ProjectDir)$(OutputPath)')" />
   </Target>
-  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

-       Other similar extension points exist, see Microsoft.Common.targets.

-  <Target Name="BeforeBuild">

-  </Target>

-  <Target Name="AfterBuild">

-  </Target>

-  -->

-</Project>
\ No newline at end of file
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
diff --git a/plugins/hypervisors/hyperv/pom.xml b/plugins/hypervisors/hyperv/pom.xml
index e85fe43..396cc4d 100644
--- a/plugins/hypervisors/hyperv/pom.xml
+++ b/plugins/hypervisors/hyperv/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <properties>
diff --git a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java
index 4538201..6bc1b98 100644
--- a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java
+++ b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java
@@ -166,34 +166,34 @@
     private static final Logger s_logger = Logger.getLogger(HypervDirectConnectResource.class.getName());
 
     private static final Gson s_gson = GsonHelper.getGson();
-    private String _zoneId;
-    private String _podId;
-    private String _clusterId;
-    private String _guid;
-    private String _agentIp;
-    private final int _port = DEFAULT_AGENT_PORT;
-    protected final long _opsTimeout = 900000;  // 15 minutes time out to time
+    private String zoneId;
+    private String podId;
+    private String clusterId;
+    private String guid;
+    private String agentIp;
+    private final int port = DEFAULT_AGENT_PORT;
+    protected final long opsTimeout = 900000;  // 15 minutes time out to time
 
-    protected final int _retry = 24;
-    protected final int _sleep = 10000;
+    protected final int retry = 24;
+    protected final int sleep = 10000;
     protected static final int DEFAULT_DOMR_SSHPORT = 3922;
-    private String _clusterGuid;
+    private String clusterGuid;
 
     // Used by initialize to assert object configured before
     // initialize called.
-    private boolean _configureCalled = false;
+    private boolean configureCalled = false;
 
-    private String _username;
-    private String _password;
+    private String username;
+    private String password;
 
     private static HypervManager s_hypervMgr;
     @Inject
-    HypervManager _hypervMgr;
-    protected VirtualRoutingResource _vrResource;
+    HypervManager hypervManager;
+    protected VirtualRoutingResource vrResource;
 
     @PostConstruct
     void init() {
-        s_hypervMgr = _hypervMgr;
+        s_hypervMgr = hypervManager;
     }
 
     @Override
@@ -204,7 +204,7 @@
     @Override
     public final StartupCommand[] initialize() {
         // assert
-        if (!_configureCalled) {
+        if (!configureCalled) {
             final String errMsg = this.getClass().getName() + " requires configure() be called before" + " initialize()";
             s_logger.error(errMsg);
         }
@@ -215,16 +215,16 @@
 
         // Identity within the data centre is decided by CloudStack kernel,
         // and passed via ServerResource.configure()
-        defaultStartRoutCmd.setDataCenter(_zoneId);
-        defaultStartRoutCmd.setPod(_podId);
-        defaultStartRoutCmd.setCluster(_clusterId);
-        defaultStartRoutCmd.setGuid(_guid);
-        defaultStartRoutCmd.setName(_name);
-        defaultStartRoutCmd.setPrivateIpAddress(_agentIp);
-        defaultStartRoutCmd.setStorageIpAddress(_agentIp);
-        defaultStartRoutCmd.setPool(_clusterGuid);
+        defaultStartRoutCmd.setDataCenter(zoneId);
+        defaultStartRoutCmd.setPod(podId);
+        defaultStartRoutCmd.setCluster(clusterId);
+        defaultStartRoutCmd.setGuid(guid);
+        defaultStartRoutCmd.setName(name);
+        defaultStartRoutCmd.setPrivateIpAddress(agentIp);
+        defaultStartRoutCmd.setStorageIpAddress(agentIp);
+        defaultStartRoutCmd.setPool(clusterGuid);
 
-        s_logger.debug("Generated StartupRoutingCommand for _agentIp \"" + _agentIp + "\"");
+        s_logger.debug("Generated StartupRoutingCommand for agentIp \"" + agentIp + "\"");
 
         defaultStartRoutCmd.setVersion(this.getClass().getPackage().getImplementationVersion());
 
@@ -239,48 +239,48 @@
 
         // Assert that host identity is consistent with existing values.
         if (startCmd == null) {
-            final String errMsg = String.format("Host %s (IP %s)" + "did not return a StartupRoutingCommand", _name, _agentIp);
+            final String errMsg = String.format("Host %s (IP %s)" + "did not return a StartupRoutingCommand", name, agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
         }
         if (!startCmd.getDataCenter().equals(defaultStartRoutCmd.getDataCenter())) {
             final String errMsg =
-                    String.format("Host %s (IP %s) changed zone/data center.  Was " + defaultStartRoutCmd.getDataCenter() + " NOW its " + startCmd.getDataCenter(), _name,
-                            _agentIp);
+                    String.format("Host %s (IP %s) changed zone/data center.  Was " + defaultStartRoutCmd.getDataCenter() + " NOW its " + startCmd.getDataCenter(), name,
+                            agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
         }
         if (!startCmd.getPod().equals(defaultStartRoutCmd.getPod())) {
-            final String errMsg = String.format("Host %s (IP %s) changed pod.  Was " + defaultStartRoutCmd.getPod() + " NOW its " + startCmd.getPod(), _name, _agentIp);
+            final String errMsg = String.format("Host %s (IP %s) changed pod.  Was " + defaultStartRoutCmd.getPod() + " NOW its " + startCmd.getPod(), name, agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
         }
         if (!startCmd.getCluster().equals(defaultStartRoutCmd.getCluster())) {
             final String errMsg =
-                    String.format("Host %s (IP %s) changed cluster.  Was " + defaultStartRoutCmd.getCluster() + " NOW its " + startCmd.getCluster(), _name, _agentIp);
+                    String.format("Host %s (IP %s) changed cluster.  Was " + defaultStartRoutCmd.getCluster() + " NOW its " + startCmd.getCluster(), name, agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
         }
         if (!startCmd.getGuid().equals(defaultStartRoutCmd.getGuid())) {
-            final String errMsg = String.format("Host %s (IP %s) changed guid.  Was " + defaultStartRoutCmd.getGuid() + " NOW its " + startCmd.getGuid(), _name, _agentIp);
+            final String errMsg = String.format("Host %s (IP %s) changed guid.  Was " + defaultStartRoutCmd.getGuid() + " NOW its " + startCmd.getGuid(), name, agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
         }
         if (!startCmd.getPrivateIpAddress().equals(defaultStartRoutCmd.getPrivateIpAddress())) {
             final String errMsg =
-                    String.format("Host %s (IP %s) IP address.  Was " + defaultStartRoutCmd.getPrivateIpAddress() + " NOW its " + startCmd.getPrivateIpAddress(), _name,
-                            _agentIp);
+                    String.format("Host %s (IP %s) IP address.  Was " + defaultStartRoutCmd.getPrivateIpAddress() + " NOW its " + startCmd.getPrivateIpAddress(), name,
+                            agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
         }
         if (!startCmd.getName().equals(defaultStartRoutCmd.getName())) {
-            final String errMsg = String.format("Host %s (IP %s) name.  Was " + startCmd.getName() + " NOW its " + defaultStartRoutCmd.getName(), _name, _agentIp);
+            final String errMsg = String.format("Host %s (IP %s) name.  Was " + startCmd.getName() + " NOW its " + defaultStartRoutCmd.getName(), name, agentIp);
             s_logger.error(errMsg);
             // TODO: valid to return null, or should we throw?
             return null;
@@ -300,14 +300,14 @@
             // TODO: is this assertion required?
             if (storePoolCmd == null) {
                 final String frmtStr = "Host %s (IP %s) sent incorrect Command, " + "second parameter should be a " + "StartupStorageCommand";
-                final String errMsg = String.format(frmtStr, _name, _agentIp);
+                final String errMsg = String.format(frmtStr, name, agentIp);
                 s_logger.error(errMsg);
                 // TODO: valid to return null, or should we throw?
                 return null;
             }
-            s_logger.info("Host " + _name + " (IP " + _agentIp + ") already configured with a storeage pool, details " + s_gson.toJson(startCmds[1]));
+            s_logger.info("Host " + name + " (IP " + agentIp + ") already configured with a storeage pool, details " + s_gson.toJson(startCmds[1]));
         } else {
-            s_logger.info("Host " + _name + " (IP " + _agentIp + ") already configured with a storeage pool, details ");
+            s_logger.info("Host " + name + " (IP " + agentIp + ") already configured with a storeage pool, details ");
         }
         return new StartupCommand[] {startCmd, storePoolCmd};
     }
@@ -317,13 +317,13 @@
         final PingCommand pingCmd = new PingRoutingCommand(getType(), id, getHostVmStateReport());
 
         if (s_logger.isDebugEnabled()) {
-            s_logger.debug("Ping host " + _name + " (IP " + _agentIp + ")");
+            s_logger.debug("Ping host " + name + " (IP " + agentIp + ")");
         }
 
         final Answer pingAns = executeRequest(pingCmd);
 
         if (pingAns == null || !pingAns.getResult()) {
-            s_logger.info("Cannot ping host " + _name + " (IP " + _agentIp + "), pingAns (blank means null) is:" + pingAns);
+            s_logger.info("Cannot ping host " + name + " (IP " + agentIp + "), pingAns (blank means null) is:" + pingAns);
             return null;
         }
         return pingCmd;
@@ -332,7 +332,7 @@
     public final ArrayList<Map<String, String>> requestHostVmStateReport() {
         URI agentUri = null;
         try {
-            agentUri = new URI("https", null, _agentIp, _port, "/api/HypervResource/" + HOST_VM_STATE_REPORT_COMMAND, null, null);
+            agentUri = new URI("https", null, agentIp, port, "/api/HypervResource/" + HOST_VM_STATE_REPORT_COMMAND, null, null);
         } catch (final URISyntaxException e) {
             final String errMsg = "Could not generate URI for Hyper-V agent";
             s_logger.error(errMsg, e);
@@ -372,7 +372,7 @@
 
         for (final Map<String, String> vmMap : vmList) {
             final String name = (String)vmMap.keySet().toArray()[0];
-            vmStates.put(name, new HostVmStateReportEntry(PowerState.valueOf(vmMap.get(name)), _guid));
+            vmStates.put(name, new HostVmStateReportEntry(PowerState.valueOf(vmMap.get(name)), guid));
         }
         return vmStates;
     }
@@ -388,7 +388,7 @@
         try {
             final String cmdName = StartupCommand.class.getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             // TODO add proper logging
@@ -427,7 +427,7 @@
         try {
             final String cmdName = cmd.getClass().getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             // TODO add proper logging
@@ -436,7 +436,7 @@
             return null;
         }
         if (cmd instanceof NetworkElementCommand) {
-            return _vrResource.executeRequest((NetworkElementCommand)cmd);
+            return vrResource.executeRequest((NetworkElementCommand)cmd);
         }if (clazz == CheckSshCommand.class) {
             answer = execute((CheckSshCommand)cmd);
         } else if (cmd instanceof NetworkUsageCommand) {
@@ -455,7 +455,7 @@
                 final VirtualMachineTO vmSpec = ((StartCommand)cmd).getVirtualMachine();
                 if (vmSpec.getType() != VirtualMachine.Type.User) {
                     if (s_hypervMgr != null) {
-                        final String secondary = s_hypervMgr.prepareSecondaryStorageStore(Long.parseLong(_zoneId));
+                        final String secondary = s_hypervMgr.prepareSecondaryStorageStore(Long.parseLong(zoneId));
                         if (secondary != null) {
                             ((StartCommand)cmd).setSecondaryStorage(secondary);
                         }
@@ -487,7 +487,7 @@
         try {
             final String cmdName = cmd.getClass().getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             final String errMsg = "Could not generate URI for Hyper-V agent";
@@ -1803,7 +1803,7 @@
         try {
             final String cmdName = GetVmConfigCommand.class.getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             final String errMsg = "Could not generate URI for Hyper-V agent";
@@ -1836,7 +1836,7 @@
         try {
             final String cmdName = GetVmConfigCommand.class.getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             final String errMsg = "Could not generate URI for Hyper-V agent";
@@ -1865,7 +1865,7 @@
         try {
             final String cmdName = ModifyVmNicConfigCommand.class.getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             final String errMsg = "Could not generate URI for Hyper-V agent";
@@ -1886,7 +1886,7 @@
         try {
             final String cmdName = ModifyVmNicConfigCommand.class.getName();
             agentUri =
-                    new URI("https", null, _agentIp, _port,
+                    new URI("https", null, agentIp, port,
                             "/api/HypervResource/" + cmdName, null, null);
         } catch (final URISyntaxException e) {
             final String errMsg = "Could not generate URI for Hyper-V agent";
@@ -2298,22 +2298,22 @@
         /* todo: update, make consistent with the xen server equivalent. */
 
         if (params != null) {
-            _guid = (String)params.get("guid");
-            _zoneId = (String)params.get("zone");
-            _podId = (String)params.get("pod");
-            _clusterId = (String)params.get("cluster");
-            _agentIp = (String)params.get("ipaddress"); // was agentIp
-            _name = name;
+            guid = (String)params.get("guid");
+            zoneId = (String)params.get("zone");
+            podId = (String)params.get("pod");
+            clusterId = (String)params.get("cluster");
+            agentIp = (String)params.get("ipaddress"); // was agentIp
+            this.name = name;
 
-            _clusterGuid = (String)params.get("cluster.guid");
-            _username = (String)params.get("url");
-            _password = (String)params.get("password");
-            _username = (String)params.get("username");
-            _configureCalled = true;
+            clusterGuid = (String)params.get("cluster.guid");
+            username = (String)params.get("url");
+            password = (String)params.get("password");
+            username = (String)params.get("username");
+            configureCalled = true;
         }
 
-        _vrResource = new VirtualRoutingResource(this);
-        if (!_vrResource.configure(name, new HashMap<String, Object>())) {
+        vrResource = new VirtualRoutingResource(this);
+        if (!vrResource.configure(name, new HashMap<String, Object>())) {
             throw new ConfigurationException("Unable to configure VirtualRoutingResource");
         }
         return true;
@@ -2349,11 +2349,11 @@
     protected String connect(final String vmName, final String ipAddress, final int port) {
         final long startTick = System.currentTimeMillis();
 
-        // wait until we have at least been waiting for _ops_timeout time or
-        // at least have tried _retry times, this is to coordinate with system
+        // wait until we have at least been waiting for ops_timeout time or
+        // at least have tried retry times, this is to coordinate with system
         // VM patching/rebooting time that may need
-        int retry = _retry;
-        while (System.currentTimeMillis() - startTick <= _opsTimeout || --retry > 0) {
+        int retry = this.retry;
+        while (System.currentTimeMillis() - startTick <= opsTimeout || --retry > 0) {
             s_logger.info("Trying to connect to " + ipAddress);
             try (SocketChannel sch = SocketChannel.open();) {
                 sch.configureBlocking(true);
diff --git a/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-compute/module.properties b/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-compute/module.properties
index 439b7d5..abf8789 100644
--- a/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-compute/module.properties
+++ b/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=hyperv-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-discoverer/module.properties b/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-discoverer/module.properties
index be51dd6..ea8c023 100644
--- a/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-discoverer/module.properties
+++ b/plugins/hypervisors/hyperv/src/main/resources/META-INF/cloudstack/hyperv-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=hyperv-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml
index 6082a76..4d7db7f 100644
--- a/plugins/hypervisors/kvm/pom.xml
+++ b/plugins/hypervisors/kvm/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java
index a76b56a..0225015 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java
@@ -27,8 +27,12 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.resource.ResourceManager;
-import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.utils.component.AdapterBase;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+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.PrimaryDataStoreDriver;
 import org.apache.cloudstack.ha.HAManager;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@@ -49,6 +53,8 @@
     private PrimaryDataStoreDao _storagePoolDao;
     @Inject
     private HAManager haManager;
+    @Inject
+    private DataStoreProviderManager dataStoreProviderMgr;
 
     @Override
     public boolean isVmAlive(com.cloud.vm.VirtualMachine vm, Host host) throws UnknownVM {
@@ -78,23 +84,12 @@
         }
 
         List<StoragePoolVO> clusterPools = _storagePoolDao.listPoolsByCluster(agent.getClusterId());
-        boolean hasNfs = false;
-        for (StoragePoolVO pool : clusterPools) {
-            if (pool.getPoolType() == StoragePoolType.NetworkFilesystem) {
-                hasNfs = true;
-                break;
-            }
-        }
-        if (!hasNfs) {
+        boolean storageSupportHA = storageSupportHa(clusterPools);
+        if (!storageSupportHA) {
             List<StoragePoolVO> zonePools = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(agent.getDataCenterId(), agent.getHypervisorType());
-            for (StoragePoolVO pool : zonePools) {
-                if (pool.getPoolType() == StoragePoolType.NetworkFilesystem) {
-                    hasNfs = true;
-                    break;
-                }
-            }
+            storageSupportHA = storageSupportHa(zonePools);
         }
-        if (!hasNfs) {
+        if (!storageSupportHA) {
             s_logger.warn(
                     "Agent investigation was requested on host " + agent + ", but host does not support investigation because it has no NFS storage. Skipping investigation.");
             return Status.Disconnected;
@@ -102,7 +97,8 @@
 
         Status hostStatus = null;
         Status neighbourStatus = null;
-        CheckOnHostCommand cmd = new CheckOnHostCommand(agent);
+        boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value();
+        CheckOnHostCommand cmd = new CheckOnHostCommand(agent, reportFailureIfOneStorageIsDown);
 
         try {
             Answer answer = _agentMgr.easySend(agent.getId(), cmd);
@@ -145,4 +141,20 @@
         s_logger.debug("HA: HOST is ineligible legacy state " + hostStatus + " for host " + agent.getId());
         return hostStatus;
     }
+
+    private boolean storageSupportHa(List<StoragePoolVO> pools) {
+        boolean storageSupportHA = false;
+        for (StoragePoolVO pool : pools) {
+            DataStoreProvider storeProvider = dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName());
+            DataStoreDriver storeDriver = storeProvider.getDataStoreDriver();
+            if (storeDriver instanceof PrimaryDataStoreDriver) {
+                PrimaryDataStoreDriver primaryStoreDriver = (PrimaryDataStoreDriver)storeDriver;
+                if (primaryStoreDriver.isStorageSupportHA(pool.getPoolType())) {
+                    storageSupportHA = true;
+                    break;
+                }
+            }
+        }
+        return storageSupportHA;
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMGuestOsMapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMGuestOsMapper.java
index 11d1e16..1cf7450 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMGuestOsMapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMGuestOsMapper.java
@@ -107,7 +107,7 @@
         s_mapper.put("Windows Server 2003 Standard Edition(32-bit)", "Windows Server 2003");
         s_mapper.put("Windows Server 2003 Standard Edition(64-bit)", "Windows Server 2003");
         s_mapper.put("Windows Server 2003 Web Edition", "Windows Server 2003");
-        s_mapper.put("Microsoft Small Bussiness Server 2003", "Windows Server 2003");
+        s_mapper.put("Microsoft Small Business Server 2003", "Windows Server 2003");
         s_mapper.put("Windows Server 2008 (32-bit)", "Windows Server 2008");
         s_mapper.put("Windows Server 2008 (64-bit)", "Windows Server 2008");
         s_mapper.put("Windows Server 2008 R2 (64-bit)", "Windows Server 2008");
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
index fd1122f..b9abea4 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
@@ -24,43 +24,96 @@
 import org.libvirt.StoragePoolInfo;
 import org.libvirt.StoragePoolInfo.StoragePoolState;
 
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
 import com.cloud.utils.script.OutputInterpreter;
 import com.cloud.utils.script.OutputInterpreter.AllLinesParser;
 import com.cloud.utils.script.Script;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 
 public class KVMHABase {
     private static final Logger s_logger = Logger.getLogger(KVMHABase.class);
     private long _timeout = 60000; /* 1 minutes */
     protected static String s_heartBeatPath;
-    protected long _heartBeatUpdateTimeout = 60000;
-    protected long _heartBeatUpdateFreq = 60000;
-    protected long _heartBeatUpdateMaxTries = 5;
-    protected long _heartBeatUpdateRetrySleep = 10000;
+    protected long _heartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
+    protected long _heartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
+    protected long _heartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES);
+    protected long _heartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP);
 
     public static enum PoolType {
         PrimaryStorage, SecondaryStorage
     }
 
-    public static class NfsStoragePool {
-        String _poolUUID;
-        String _poolIp;
-        String _poolMountSourcePath;
-        String _mountDestPath;
-        PoolType _type;
+    public static class HAStoragePool {
+        String poolUuid;
+        String poolIp;
+        String poolMountSourcePath;
+        String mountDestPath;
+        PoolType poolType;
+        KVMStoragePool pool;
 
-        public NfsStoragePool(String poolUUID, String poolIp, String poolSourcePath, String mountDestPath, PoolType type) {
-            _poolUUID = poolUUID;
-            _poolIp = poolIp;
-            _poolMountSourcePath = poolSourcePath;
-            _mountDestPath = mountDestPath;
-            _type = type;
+        public HAStoragePool(KVMStoragePool pool, String host, String path, PoolType type) {
+            this.pool = pool;
+            this.poolUuid = pool.getUuid();
+            this.mountDestPath = pool.getLocalPath();
+            this.poolIp = host;
+            this.poolMountSourcePath = path;
+            this.poolType = type;
+        }
+
+        public String getPoolUUID() {
+            return poolUuid;
+        }
+
+        public void setPoolUUID(String poolUuid) {
+            this.poolUuid = poolUuid;
+        }
+
+        public String getPoolIp() {
+            return poolIp;
+        }
+
+        public void setPoolIp(String poolIp) {
+            this.poolIp = poolIp;
+        }
+
+        public String getPoolMountSourcePath() {
+            return poolMountSourcePath;
+        }
+
+        public void setPoolMountSourcePath(String poolMountSourcePath) {
+            this.poolMountSourcePath = poolMountSourcePath;
+        }
+
+        public String getMountDestPath() {
+            return mountDestPath;
+        }
+
+        public void setMountDestPath(String mountDestPath) {
+            this.mountDestPath = mountDestPath;
+        }
+
+        public PoolType getType() {
+            return poolType;
+        }
+
+        public void setType(PoolType type) {
+            this.poolType = type;
+        }
+
+        public KVMStoragePool getPool() {
+            return pool;
+        }
+
+        public void setPool(KVMStoragePool pool) {
+            this.pool = pool;
         }
     }
 
-    protected String checkingMountPoint(NfsStoragePool pool, String poolName) {
-        String mountSource = pool._poolIp + ":" + pool._poolMountSourcePath;
+    protected String checkingMountPoint(HAStoragePool pool, String poolName) {
+        String mountSource = pool.getPoolIp() + ":" + pool.getPoolMountSourcePath();
         String mountPaths = Script.runSimpleBashScript("cat /proc/mounts | grep " + mountSource);
-        String destPath = pool._mountDestPath;
+        String destPath = pool.getMountDestPath();
 
         if (mountPaths != null) {
             String token[] = mountPaths.split(" ");
@@ -100,12 +153,12 @@
         return destPath;
     }
 
-    protected String getMountPoint(NfsStoragePool storagePool) {
+    protected String getMountPoint(HAStoragePool storagePool) {
 
         StoragePool pool = null;
         String poolName = null;
         try {
-            pool = LibvirtConnection.getConnection().storagePoolLookupByUUIDString(storagePool._poolUUID);
+            pool = LibvirtConnection.getConnection().storagePoolLookupByUUIDString(storagePool.getPoolUUID());
             if (pool != null) {
                 StoragePoolInfo spi = pool.getInfo();
                 if (spi.state != StoragePoolState.VIR_STORAGE_POOL_RUNNING) {
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java
index 5ceaef2..2df7037 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java
@@ -22,18 +22,18 @@
 
 import org.apache.log4j.Logger;
 
-import com.cloud.utils.script.OutputInterpreter;
-import com.cloud.utils.script.Script;
+import com.cloud.agent.api.to.HostTO;
 
 public class KVMHAChecker extends KVMHABase implements Callable<Boolean> {
     private static final Logger s_logger = Logger.getLogger(KVMHAChecker.class);
-    private List<NfsStoragePool> nfsStoragePools;
-    private String hostIp;
-    private long heartBeatCheckerTimeout = 360000; // 6 minutes
+    private List<HAStoragePool> storagePools;
+    private HostTO host;
+    private boolean reportFailureIfOneStorageIsDown;
 
-    public KVMHAChecker(List<NfsStoragePool> pools, String host) {
-        this.nfsStoragePools = pools;
-        this.hostIp = host;
+    public KVMHAChecker(List<HAStoragePool> pools, HostTO host, boolean reportFailureIfOneStorageIsDown) {
+        this.storagePools = pools;
+        this.host = host;
+        this.reportFailureIfOneStorageIsDown = reportFailureIfOneStorageIsDown;
     }
 
     /*
@@ -44,30 +44,14 @@
     public Boolean checkingHeartBeat() {
         boolean validResult = false;
 
-        String hostAndPools = String.format("host IP [%s] in pools [%s]", hostIp, nfsStoragePools.stream().map(pool -> pool._poolIp).collect(Collectors.joining(", ")));
+        String hostAndPools = String.format("host IP [%s] in pools [%s]", host.getPrivateNetwork().getIp(), storagePools.stream().map(pool -> pool.getPoolUUID()).collect(Collectors.joining(", ")));
 
         s_logger.debug(String.format("Checking heart beat with KVMHAChecker for %s", hostAndPools));
 
-        for (NfsStoragePool pool : nfsStoragePools) {
-            Script cmd = new Script(s_heartBeatPath, heartBeatCheckerTimeout, s_logger);
-            cmd.add("-i", pool._poolIp);
-            cmd.add("-p", pool._poolMountSourcePath);
-            cmd.add("-m", pool._mountDestPath);
-            cmd.add("-h", hostIp);
-            cmd.add("-r");
-            cmd.add("-t", String.valueOf(_heartBeatUpdateFreq / 1000));
-            OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
-            String result = cmd.execute(parser);
-            String parsedLine = parser.getLine();
-
-            s_logger.debug(String.format("Checking heart beat with KVMHAChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine,
-                    pool._poolIp));
-
-            if (result == null && parsedLine.contains("DEAD")) {
-                s_logger.warn(String.format("Checking heart beat with KVMHAChecker command [%s] returned [%s]. [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(),
-                        result, parsedLine, hostIp));
-            } else {
-                validResult = true;
+        for (HAStoragePool pool : storagePools) {
+            validResult = pool.getPool().checkingHeartBeat(pool, host);
+            if (reportFailureIfOneStorageIsDown && !validResult) {
+                break;
             }
         }
 
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
index 022b48c..eb09408 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
@@ -18,6 +18,7 @@
 
 import com.cloud.agent.properties.AgentProperties;
 import com.cloud.agent.properties.AgentPropertiesFileHandler;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.utils.script.Script;
 import org.apache.log4j.Logger;
 import org.libvirt.Connect;
@@ -35,19 +36,18 @@
 public class KVMHAMonitor extends KVMHABase implements Runnable {
 
     private static final Logger s_logger = Logger.getLogger(KVMHAMonitor.class);
-    private final Map<String, NfsStoragePool> storagePool = new ConcurrentHashMap<>();
+    private final Map<String, HAStoragePool> storagePool = new ConcurrentHashMap<>();
     private final boolean rebootHostAndAlertManagementOnHeartbeatTimeout;
 
     private final String hostPrivateIp;
 
-    public KVMHAMonitor(NfsStoragePool pool, String host, String scriptPath) {
+    public KVMHAMonitor(HAStoragePool pool, String host, String scriptPath) {
         if (pool != null) {
-            storagePool.put(pool._poolUUID, pool);
+            storagePool.put(pool.getPoolUUID(), pool);
         }
         hostPrivateIp = host;
         configureHeartBeatPath(scriptPath);
 
-        _heartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
         rebootHostAndAlertManagementOnHeartbeatTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.REBOOT_HOST_AND_ALERT_MANAGEMENT_ON_HEARTBEAT_TIMEOUT);
     }
 
@@ -55,29 +55,29 @@
         KVMHABase.s_heartBeatPath = scriptPath;
     }
 
-    public void addStoragePool(NfsStoragePool pool) {
+    public void addStoragePool(HAStoragePool pool) {
         synchronized (storagePool) {
-            storagePool.put(pool._poolUUID, pool);
+            storagePool.put(pool.getPoolUUID(), pool);
         }
     }
 
     public void removeStoragePool(String uuid) {
         synchronized (storagePool) {
-            NfsStoragePool pool = storagePool.get(uuid);
+            HAStoragePool pool = storagePool.get(uuid);
             if (pool != null) {
-                Script.runSimpleBashScript("umount " + pool._mountDestPath);
+                Script.runSimpleBashScript("umount " + pool.getMountDestPath());
                 storagePool.remove(uuid);
             }
         }
     }
 
-    public List<NfsStoragePool> getStoragePools() {
+    public List<HAStoragePool> getStoragePools() {
         synchronized (storagePool) {
             return new ArrayList<>(storagePool.values());
         }
     }
 
-    public NfsStoragePool getStoragePool(String uuid) {
+    public HAStoragePool getStoragePool(String uuid) {
         synchronized (storagePool) {
             return storagePool.get(uuid);
         }
@@ -87,86 +87,72 @@
         synchronized (storagePool) {
             Set<String> removedPools = new HashSet<>();
             for (String uuid : storagePool.keySet()) {
-                NfsStoragePool primaryStoragePool = storagePool.get(uuid);
-                StoragePool storage;
-                try {
-                    Connect conn = LibvirtConnection.getConnection();
-                    storage = conn.storagePoolLookupByUUIDString(uuid);
-                    if (storage == null || storage.getInfo().state != StoragePoolState.VIR_STORAGE_POOL_RUNNING) {
-                        if (storage == null) {
-                            s_logger.debug(String.format("Libvirt storage pool [%s] not found, removing from HA list.", uuid));
-                        } else {
-                            s_logger.debug(String.format("Libvirt storage pool [%s] found, but not running, removing from HA list.", uuid));
-                        }
-
-                        removedPools.add(uuid);
+                HAStoragePool primaryStoragePool = storagePool.get(uuid);
+                if (primaryStoragePool.getPool().getType() == StoragePoolType.NetworkFilesystem) {
+                    checkForNotExistingPools(removedPools, uuid);
+                    if (removedPools.contains(uuid)) {
                         continue;
                     }
-
-                    s_logger.debug(String.format("Found NFS storage pool [%s] in libvirt, continuing.", uuid));
-
-                } catch (LibvirtException e) {
-                    s_logger.debug(String.format("Failed to lookup libvirt storage pool [%s].", uuid), e);
-
-                    if (e.toString().contains("pool not found")) {
-                        s_logger.debug(String.format("Removing pool [%s] from HA monitor since it was deleted.", uuid));
-                        removedPools.add(uuid);
-                        continue;
-                    }
-
                 }
-
                 String result = null;
-                for (int i = 1; i <= _heartBeatUpdateMaxTries; i++) {
-                    Script cmd = createHeartBeatCommand(primaryStoragePool, hostPrivateIp, true);
-                    result = cmd.execute();
-
-                    s_logger.debug(String.format("The command (%s), to the pool [%s], has the result [%s].", cmd.toString(), uuid, result));
-
-                    if (result != null) {
-                        s_logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; try: %s of %s.", uuid, result, i, _heartBeatUpdateMaxTries));
-                        try {
-                            Thread.sleep(_heartBeatUpdateRetrySleep);
-                        } catch (InterruptedException e) {
-                            s_logger.debug("[IGNORED] Interrupted between heartbeat retries.", e);
-                        }
-                    } else {
-                        break;
-                    }
-
-                }
+                result = executePoolHeartBeatCommand(uuid, primaryStoragePool, result);
 
                 if (result != null && rebootHostAndAlertManagementOnHeartbeatTimeout) {
                     s_logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; stopping cloudstack-agent.", uuid, result));
-                    Script cmd = createHeartBeatCommand(primaryStoragePool, null, false);
-                    result = cmd.execute();
+                    primaryStoragePool.getPool().createHeartBeatCommand(primaryStoragePool, null, false);;
                 }
             }
-
             if (!removedPools.isEmpty()) {
                 for (String uuid : removedPools) {
                     removeStoragePool(uuid);
                 }
             }
         }
-
     }
 
-    private Script createHeartBeatCommand(NfsStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) {
-        Script cmd = new Script(s_heartBeatPath, _heartBeatUpdateTimeout, s_logger);
-        cmd.add("-i", primaryStoragePool._poolIp);
-        cmd.add("-p", primaryStoragePool._poolMountSourcePath);
-        cmd.add("-m", primaryStoragePool._mountDestPath);
+    private String executePoolHeartBeatCommand(String uuid, HAStoragePool primaryStoragePool, String result) {
+        for (int i = 1; i <= _heartBeatUpdateMaxTries; i++) {
+            result = primaryStoragePool.getPool().createHeartBeatCommand(primaryStoragePool, hostPrivateIp, true);
 
-        if (hostValidation) {
-            cmd.add("-h", hostPrivateIp);
+            if (result != null) {
+                s_logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; try: %s of %s.", uuid, result, i, _heartBeatUpdateMaxTries));
+                try {
+                    Thread.sleep(_heartBeatUpdateRetrySleep);
+                } catch (InterruptedException e) {
+                    s_logger.debug("[IGNORED] Interrupted between heartbeat retries.", e);
+                }
+            } else {
+                break;
+            }
+
         }
+        return result;
+    }
 
-        if (!hostValidation) {
-            cmd.add("-c");
+    private void checkForNotExistingPools(Set<String> removedPools, String uuid) {
+        try {
+            Connect conn = LibvirtConnection.getConnection();
+            StoragePool storage = conn.storagePoolLookupByUUIDString(uuid);
+            if (storage == null || storage.getInfo().state != StoragePoolState.VIR_STORAGE_POOL_RUNNING) {
+                if (storage == null) {
+                    s_logger.debug(String.format("Libvirt storage pool [%s] not found, removing from HA list.", uuid));
+                } else {
+                    s_logger.debug(String.format("Libvirt storage pool [%s] found, but not running, removing from HA list.", uuid));
+                }
+
+                removedPools.add(uuid);
+            }
+
+            s_logger.debug(String.format("Found NFS storage pool [%s] in libvirt, continuing.", uuid));
+
+        } catch (LibvirtException e) {
+            s_logger.debug(String.format("Failed to lookup libvirt storage pool [%s].", uuid), e);
+
+            if (e.toString().contains("pool not found")) {
+                s_logger.debug(String.format("Removing pool [%s] from HA monitor since it was deleted.", uuid));
+                removedPools.add(uuid);
+            }
         }
-
-        return cmd;
     }
 
     @Override
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java
index 758edd2..e6937b5 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java
@@ -16,54 +16,31 @@
 // under the License.
 package com.cloud.hypervisor.kvm.resource;
 
-import com.cloud.utils.script.OutputInterpreter;
-import com.cloud.utils.script.Script;
-import org.apache.log4j.Logger;
+import com.cloud.agent.api.to.HostTO;
 import org.joda.time.Duration;
 
 import java.util.concurrent.Callable;
 
 public class KVMHAVMActivityChecker extends KVMHABase implements Callable<Boolean> {
-    private static final Logger LOG = Logger.getLogger(KVMHAVMActivityChecker.class);
 
-    final private NfsStoragePool nfsStoragePool;
-    final private String hostIP;
-    final private String volumeUuidList;
-    final private String vmActivityCheckPath;
-    final private Duration activityScriptTimeout = Duration.standardSeconds(3600L);
-    final private long suspectTimeInSeconds;
+    private final HAStoragePool storagePool;
+    private final String volumeUuidList;
+    private final String vmActivityCheckPath;
+    private final Duration activityScriptTimeout = Duration.standardSeconds(3600L);
+    private final long suspectTimeInSeconds;
+    private final HostTO host;
 
-    public KVMHAVMActivityChecker(final NfsStoragePool pool, final String host, final String volumeUUIDListString, String vmActivityCheckPath, final long suspectTime) {
-        this.nfsStoragePool = pool;
-        this.hostIP = host;
+    public KVMHAVMActivityChecker(final HAStoragePool pool, final HostTO host, final String volumeUUIDListString, String vmActivityCheckPath, final long suspectTime) {
+        this.storagePool = pool;
         this.volumeUuidList = volumeUUIDListString;
         this.vmActivityCheckPath = vmActivityCheckPath;
         this.suspectTimeInSeconds = suspectTime;
+        this.host = host;
     }
 
     @Override
     public Boolean checkingHeartBeat() {
-        Script cmd = new Script(vmActivityCheckPath, activityScriptTimeout.getStandardSeconds(), LOG);
-        cmd.add("-i", nfsStoragePool._poolIp);
-        cmd.add("-p", nfsStoragePool._poolMountSourcePath);
-        cmd.add("-m", nfsStoragePool._mountDestPath);
-        cmd.add("-h", hostIP);
-        cmd.add("-u", volumeUuidList);
-        cmd.add("-t", String.valueOf(String.valueOf(System.currentTimeMillis() / 1000)));
-        cmd.add("-d", String.valueOf(suspectTimeInSeconds));
-        OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
-
-        String result = cmd.execute(parser);
-        String parsedLine = parser.getLine();
-
-        LOG.debug(String.format("Checking heart beat with KVMHAVMActivityChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine, nfsStoragePool._poolIp));
-
-        if (result == null && parsedLine.contains("DEAD")) {
-            LOG.warn(String.format("Checking heart beat with KVMHAVMActivityChecker command [%s] returned [%s]. It is [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(), result, parsedLine, hostIP));
-            return false;
-        } else {
-            return true;
-        }
+        return this.storagePool.getPool().vmActivityCheck(storagePool, host, activityScriptTimeout, volumeUuidList, vmActivityCheckPath, suspectTimeInSeconds);
     }
 
     @Override
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index a3bee2f..37aba35 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -50,6 +50,7 @@
 
 import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
 import org.apache.cloudstack.storage.configdrive.ConfigDrive;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
@@ -98,6 +99,7 @@
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
+
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.HostVmStateReportEntry;
@@ -176,6 +178,8 @@
 import com.cloud.network.Networks.IsolationType;
 import com.cloud.network.Networks.RouterPrivateIpStrategy;
 import com.cloud.network.Networks.TrafficType;
+import com.cloud.resource.AgentStatusUpdater;
+import com.cloud.resource.ResourceStatusUpdater;
 import com.cloud.resource.RequestWrapper;
 import com.cloud.resource.ServerResource;
 import com.cloud.resource.ServerResourceBase;
@@ -225,11 +229,12 @@
  *         private mac addresses for domrs | mac address | start + 126 || ||
  *         pool | the parent of the storage pool hierarchy * }
  **/
-public class LibvirtComputingResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer {
+public class LibvirtComputingResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer, ResourceStatusUpdater {
     protected static Logger s_logger = Logger.getLogger(LibvirtComputingResource.class);
 
     private static final String CONFIG_VALUES_SEPARATOR = ",";
 
+
     private static final String LEGACY = "legacy";
     private static final String SECURE = "secure";
 
@@ -300,32 +305,33 @@
 
     public static final String TUNGSTEN_PATH = "scripts/vm/network/tungsten";
 
-    private String _modifyVlanPath;
-    private String _versionstringpath;
-    private String _patchScriptPath;
-    private String _createvmPath;
-    private String _manageSnapshotPath;
-    private String _resizeVolumePath;
-    private String _createTmplPath;
-    private String _heartBeatPath;
-    private String _vmActivityCheckPath;
-    private String _securityGroupPath;
-    private String _ovsPvlanDhcpHostPath;
-    private String _ovsPvlanVmPath;
-    private String _routerProxyPath;
-    private String _ovsTunnelPath;
+    private String modifyVlanPath;
+    private String versionStringPath;
+    private String patchScriptPath;
+    private String createVmPath;
+    private String manageSnapshotPath;
+    private String resizeVolumePath;
+    private String createTmplPath;
+    private String heartBeatPath;
+    private String vmActivityCheckPath;
+    private String securityGroupPath;
+    private String ovsPvlanDhcpHostPath;
+    private String ovsPvlanVmPath;
+    private String routerProxyPath;
+    private String ovsTunnelPath;
 
     private String setupTungstenVrouterPath;
     private String updateTungstenLoadbalancerStatsPath;
     private String updateTungstenLoadbalancerSslPath;
-    private String _host;
+    private String host;
 
-    private String _dcId;
-    private String _clusterId;
-    private final Properties _uefiProperties = new Properties();
+    private String dcId;
+    private String clusterId;
+    private final Properties uefiProperties = new Properties();
+    private String hostHealthCheckScriptPath;
 
-    private long _hvVersion;
-    private Duration _timeout;
+    private long hvVersion;
+    private Duration timeout;
     /**
      * Since the memoryStats method returns an array that isn't ordered, we pass a big number to get all the array and then search for the information we want.
      * */
@@ -337,7 +343,7 @@
     private static final int UNUSEDMEMORY = 4;
 
 
-    private KVMHAMonitor _monitor;
+    private KVMHAMonitor kvmhaMonitor;
     public static final String SSHPUBKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.pub.cloud";
     public static final String DEFAULTDOMRSSHPORT = "3922";
 
@@ -346,12 +352,12 @@
 
     public static final String BASH_SCRIPT_PATH = "/bin/bash";
 
-    private StorageLayer _storage;
-    private KVMStoragePoolManager _storagePoolMgr;
+    private StorageLayer storageLayer;
+    private KVMStoragePoolManager storagePoolManager;
 
-    private VifDriver _defaultVifDriver;
+    private VifDriver defaultVifDriver;
     private VifDriver tungstenVifDriver;
-    private Map<TrafficType, VifDriver> _trafficTypeVifDrivers;
+    private Map<TrafficType, VifDriver> trafficTypeVifDriverMap;
 
     protected static final String DEFAULT_OVS_VIF_DRIVER_CLASS_NAME = "com.cloud.hypervisor.kvm.resource.OvsVifDriver";
     protected static final String DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME = "com.cloud.hypervisor.kvm.resource.BridgeVifDriver";
@@ -359,57 +365,57 @@
     private final static long HYPERVISOR_LIBVIRT_VERSION_SUPPORTS_IO_URING = 6003000;
     private final static long HYPERVISOR_QEMU_VERSION_SUPPORTS_IO_URING = 5000000;
 
-    protected HypervisorType _hypervisorType;
-    protected String _hypervisorURI;
-    protected long _hypervisorLibvirtVersion;
-    protected long _hypervisorQemuVersion;
-    protected String _hypervisorPath;
-    protected String _hostDistro;
-    protected String _networkDirectSourceMode;
-    protected String _networkDirectDevice;
-    protected String _sysvmISOPath;
-    protected String _privNwName;
-    protected String _privBridgeName;
-    protected String _linkLocalBridgeName;
-    protected String _publicBridgeName;
-    protected String _guestBridgeName;
-    protected String _privateIp;
-    protected String _pool;
-    protected String _localGateway;
-    private boolean _canBridgeFirewall;
-    protected boolean _noMemBalloon = false;
-    protected String _guestCpuArch;
-    protected String _guestCpuMode;
-    protected String _guestCpuModel;
-    protected boolean _noKvmClock;
-    protected String _videoHw;
-    protected int _videoRam;
+    protected HypervisorType hypervisorType;
+    protected String hypervisorURI;
+    protected long hypervisorLibvirtVersion;
+    protected long hypervisorQemuVersion;
+    protected String hypervisorPath;
+    protected String hostDistro;
+    protected String networkDirectSourceMode;
+    protected String networkDirectDevice;
+    protected String sysvmISOPath;
+    protected String privNwName;
+    protected String privBridgeName;
+    protected String linkLocalBridgeName;
+    protected String publicBridgeName;
+    protected String guestBridgeName;
+    protected String privateIp;
+    protected String pool;
+    protected String localGateway;
+    private boolean canBridgeFirewall;
+    protected boolean noMemBalloon = false;
+    protected String guestCpuArch;
+    protected String guestCpuMode;
+    protected String guestCpuModel;
+    protected boolean noKvmClock;
+    protected String videoHw;
+    protected int videoRam;
     protected Pair<Integer,Integer> hostOsVersion;
-    protected int _migrateSpeed;
-    protected int _migrateDowntime;
-    protected int _migratePauseAfter;
-    protected int _migrateWait;
-    protected boolean _diskActivityCheckEnabled;
+    protected int migrateSpeed;
+    protected int migrateDowntime;
+    protected int migratePauseAfter;
+    protected int migrateWait;
+    protected boolean diskActivityCheckEnabled;
     protected RollingMaintenanceExecutor rollingMaintenanceExecutor;
-    protected long _diskActivityCheckFileSizeMin = 10485760; // 10MB
-    protected int _diskActivityCheckTimeoutSeconds = 120; // 120s
-    protected long _diskActivityInactiveThresholdMilliseconds = 30000; // 30s
-    protected boolean _rngEnable = false;
-    protected RngBackendModel _rngBackendModel = RngBackendModel.RANDOM;
-    protected String _rngPath = "/dev/random";
-    protected int _rngRatePeriod = 1000;
-    protected int _rngRateBytes = 2048;
-    protected int _manualCpuSpeed = 0;
-    protected String _agentHooksBasedir = "/etc/cloudstack/agent/hooks";
+    protected long diskActivityCheckFileSizeMin = 10485760; // 10MB
+    protected int diskActivityCheckTimeoutSeconds = 120; // 120s
+    protected long diskActivityInactiveThresholdMilliseconds = 30000; // 30s
+    protected boolean rngEnable = false;
+    protected RngBackendModel rngBackendModel = RngBackendModel.RANDOM;
+    protected String rngPath = "/dev/random";
+    protected int rngRatePeriod = 1000;
+    protected int rngRateBytes = 2048;
+    protected int manualCpuSpeed = 0;
+    protected String agentHooksBasedir = "/etc/cloudstack/agent/hooks";
 
-    protected String _agentHooksLibvirtXmlScript = "libvirt-vm-xml-transformer.groovy";
-    protected String _agentHooksLibvirtXmlMethod = "transform";
+    protected String agentHooksLibvirtXmlScript = "libvirt-vm-xml-transformer.groovy";
+    protected String agentHooksLibvirtXmlMethod = "transform";
 
-    protected String _agentHooksVmOnStartScript = "libvirt-vm-state-change.groovy";
-    protected String _agentHooksVmOnStartMethod = "onStart";
+    protected String agentHooksVmOnStartScript = "libvirt-vm-state-change.groovy";
+    protected String agentHooksVmOnStartMethod = "onStart";
 
-    protected String _agentHooksVmOnStopScript = "libvirt-vm-state-change.groovy";
-    protected String _agentHooksVmOnStopMethod = "onStop";
+    protected String agentHooksVmOnStopScript = "libvirt-vm-state-change.groovy";
+    protected String agentHooksVmOnStopMethod = "onStop";
 
     protected static final String LOCAL_STORAGE_PATH = "local.storage.path";
     protected static final String LOCAL_STORAGE_UUID = "local.storage.uuid";
@@ -421,42 +427,45 @@
     private static final String CONFIG_DRIVE_ISO_DISK_LABEL = "hdd";
     private static final int CONFIG_DRIVE_ISO_DEVICE_ID = 4;
 
-    protected File _qemuSocketsPath;
-    private final String _qemuGuestAgentSocketName = "org.qemu.guest_agent.0";
-    protected WatchDogAction _watchDogAction = WatchDogAction.NONE;
-    protected WatchDogModel _watchDogModel = WatchDogModel.I6300ESB;
+    protected File qemuSocketsPath;
+    private final String qemuGuestAgentSocketName = "org.qemu.guest_agent.0";
+    protected WatchDogAction watchDogAction = WatchDogAction.NONE;
+    protected WatchDogModel watchDogModel = WatchDogModel.I6300ESB;
 
-    private final Map <String, String> _pifs = new HashMap<String, String>();
-    private final Map<String, VmStats> _vmStats = new ConcurrentHashMap<String, VmStats>();
+    private final Map <String, String> pifs = new HashMap<String, String>();
+    private final Map<String, VmStats> vmStats = new ConcurrentHashMap<String, VmStats>();
 
     private final Map<String, DomainBlockStats> vmDiskStats = new ConcurrentHashMap<>();
 
-    protected static final HashMap<DomainState, PowerState> s_powerStatesTable;
+    protected static final HashMap<DomainState, PowerState> POWER_STATES_TABLE;
     static {
-        s_powerStatesTable = new HashMap<DomainState, PowerState>();
-        s_powerStatesTable.put(DomainState.VIR_DOMAIN_SHUTOFF, PowerState.PowerOff);
-        s_powerStatesTable.put(DomainState.VIR_DOMAIN_PAUSED, PowerState.PowerOn);
-        s_powerStatesTable.put(DomainState.VIR_DOMAIN_RUNNING, PowerState.PowerOn);
-        s_powerStatesTable.put(DomainState.VIR_DOMAIN_BLOCKED, PowerState.PowerOn);
-        s_powerStatesTable.put(DomainState.VIR_DOMAIN_NOSTATE, PowerState.PowerUnknown);
-        s_powerStatesTable.put(DomainState.VIR_DOMAIN_SHUTDOWN, PowerState.PowerOff);
+        POWER_STATES_TABLE = new HashMap<DomainState, PowerState>();
+        POWER_STATES_TABLE.put(DomainState.VIR_DOMAIN_SHUTOFF, PowerState.PowerOff);
+        POWER_STATES_TABLE.put(DomainState.VIR_DOMAIN_PAUSED, PowerState.PowerOn);
+        POWER_STATES_TABLE.put(DomainState.VIR_DOMAIN_RUNNING, PowerState.PowerOn);
+        POWER_STATES_TABLE.put(DomainState.VIR_DOMAIN_BLOCKED, PowerState.PowerOn);
+        POWER_STATES_TABLE.put(DomainState.VIR_DOMAIN_NOSTATE, PowerState.PowerUnknown);
+        POWER_STATES_TABLE.put(DomainState.VIR_DOMAIN_SHUTDOWN, PowerState.PowerOff);
     }
 
-    public VirtualRoutingResource _virtRouterResource;
+    public VirtualRoutingResource virtRouterResource;
 
-    private String _pingTestPath;
+    private String pingTestPath;
 
-    private String _updateHostPasswdPath;
+    private String updateHostPasswdPath;
 
-    private long _dom0MinMem;
+    private long dom0MinMem;
 
-    private long _dom0OvercommitMem;
+    private long dom0OvercommitMem;
 
-    protected int _cmdsTimeout;
-    protected int _stopTimeout;
-    protected CPUStat _cpuStat = new CPUStat();
-    protected MemStat _memStat = new MemStat(_dom0MinMem, _dom0OvercommitMem);
+    private int dom0MinCpuCores;
+
+    protected int cmdsTimeout;
+    protected int stopTimeout;
+    protected CPUStat cpuStat = new CPUStat();
+    protected MemStat memStat = new MemStat(dom0MinMem, dom0OvercommitMem);
     private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new LibvirtUtilitiesHelper();
+    private LibvirtDomainListener libvirtDomainListener;
 
     protected Boolean enableManuallySettingCpuTopologyOnKvmVm = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM);
 
@@ -482,21 +491,41 @@
     public static final String CGROUP_V2 = "cgroup2fs";
 
     protected long getHypervisorLibvirtVersion() {
-        return _hypervisorLibvirtVersion;
+        return hypervisorLibvirtVersion;
     }
 
     protected long getHypervisorQemuVersion() {
-        return _hypervisorQemuVersion;
+        return hypervisorQemuVersion;
+    }
+
+    @Override
+    public synchronized void registerStatusUpdater(AgentStatusUpdater updater) {
+        if (AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_EVENTS_ENABLED)) {
+            try {
+                Connect conn = LibvirtConnection.getConnection();
+                if (libvirtDomainListener != null) {
+                    s_logger.debug("Clearing old domain listener");
+                    conn.removeLifecycleListener(libvirtDomainListener);
+                }
+                libvirtDomainListener = new LibvirtDomainListener(updater);
+                conn.addLifecycleListener(libvirtDomainListener);
+                s_logger.debug("Set up the libvirt domain event lifecycle listener");
+            } catch (LibvirtException e) {
+                s_logger.error("Failed to get libvirt connection for domain event lifecycle", e);
+            }
+        } else {
+            s_logger.debug("Libvirt event listening is disabled, not registering status updater");
+        }
     }
 
     @Override
     public ExecutionResult executeInVR(final String routerIp, final String script, final String args) {
-        return executeInVR(routerIp, script, args, _timeout);
+        return executeInVR(routerIp, script, args, timeout);
     }
 
     @Override
     public ExecutionResult executeInVR(final String routerIp, final String script, final String args, final Duration timeout) {
-        final Script command = new Script(_routerProxyPath, timeout, s_logger);
+        final Script command = new Script(routerProxyPath, timeout, s_logger);
         final AllLinesParser parser = new AllLinesParser();
         command.add(script);
         command.add(routerIp);
@@ -569,15 +598,15 @@
     }
 
     public LibvirtKvmAgentHook getTransformer() throws IOException {
-        return new LibvirtKvmAgentHook(_agentHooksBasedir, _agentHooksLibvirtXmlScript, _agentHooksLibvirtXmlMethod);
+        return new LibvirtKvmAgentHook(agentHooksBasedir, agentHooksLibvirtXmlScript, agentHooksLibvirtXmlMethod);
     }
 
     public LibvirtKvmAgentHook getStartHook() throws IOException {
-        return new LibvirtKvmAgentHook(_agentHooksBasedir, _agentHooksVmOnStartScript, _agentHooksVmOnStartMethod);
+        return new LibvirtKvmAgentHook(agentHooksBasedir, agentHooksVmOnStartScript, agentHooksVmOnStartMethod);
     }
 
     public LibvirtKvmAgentHook getStopHook() throws IOException {
-        return new LibvirtKvmAgentHook(_agentHooksBasedir, _agentHooksVmOnStopScript, _agentHooksVmOnStopMethod);
+        return new LibvirtKvmAgentHook(agentHooksBasedir, agentHooksVmOnStopScript, agentHooksVmOnStopMethod);
     }
 
     public LibvirtUtilitiesHelper getLibvirtUtilitiesHelper() {
@@ -585,44 +614,44 @@
     }
 
     public CPUStat getCPUStat() {
-        return _cpuStat;
+        return cpuStat;
     }
 
     public MemStat getMemStat() {
-        _memStat.refresh();
-        return _memStat;
+        memStat.refresh();
+        return memStat;
     }
 
     public VirtualRoutingResource getVirtRouterResource() {
-        return _virtRouterResource;
+        return virtRouterResource;
     }
 
     public String getPublicBridgeName() {
-        return _publicBridgeName;
+        return publicBridgeName;
     }
 
     public KVMStoragePoolManager getStoragePoolMgr() {
-        return _storagePoolMgr;
+        return storagePoolManager;
     }
 
     public String getPrivateIp() {
-        return _privateIp;
+        return privateIp;
     }
 
     public int getMigrateDowntime() {
-        return _migrateDowntime;
+        return migrateDowntime;
     }
 
     public int getMigratePauseAfter() {
-        return _migratePauseAfter;
+        return migratePauseAfter;
     }
 
     public int getMigrateWait() {
-        return _migrateWait;
+        return migrateWait;
     }
 
     public int getMigrateSpeed() {
-        return _migrateSpeed;
+        return migrateSpeed;
     }
 
     public RollingMaintenanceExecutor getRollingMaintenanceExecutor() {
@@ -630,55 +659,55 @@
     }
 
     public String getPingTestPath() {
-        return _pingTestPath;
+        return pingTestPath;
     }
 
     public String getUpdateHostPasswdPath() {
-        return _updateHostPasswdPath;
+        return updateHostPasswdPath;
     }
 
     public Duration getTimeout() {
-        return _timeout;
+        return timeout;
     }
 
     public String getOvsTunnelPath() {
-        return _ovsTunnelPath;
+        return ovsTunnelPath;
     }
 
     public KVMHAMonitor getMonitor() {
-        return _monitor;
+        return kvmhaMonitor;
     }
 
     public StorageLayer getStorage() {
-        return _storage;
+        return storageLayer;
     }
 
     public String createTmplPath() {
-        return _createTmplPath;
+        return createTmplPath;
     }
 
     public int getCmdsTimeout() {
-        return _cmdsTimeout;
+        return cmdsTimeout;
     }
 
     public String manageSnapshotPath() {
-        return _manageSnapshotPath;
+        return manageSnapshotPath;
     }
 
     public String getGuestBridgeName() {
-        return _guestBridgeName;
+        return guestBridgeName;
     }
 
     public String getVmActivityCheckPath() {
-        return _vmActivityCheckPath;
+        return vmActivityCheckPath;
     }
 
     public String getOvsPvlanDhcpHostPath() {
-        return _ovsPvlanDhcpHostPath;
+        return ovsPvlanDhcpHostPath;
     }
 
     public String getOvsPvlanVmPath() {
-        return _ovsPvlanVmPath;
+        return ovsPvlanVmPath;
     }
 
     public String getDirectDownloadTemporaryDownloadPath() {
@@ -694,7 +723,7 @@
     }
 
     public String getResizeVolumePath() {
-        return _resizeVolumePath;
+        return resizeVolumePath;
     }
 
     public StorageSubsystemCommandHandler getStorageHandler() {
@@ -732,16 +761,21 @@
         return null;
     }
 
-    protected List<String> _cpuFeatures;
+    protected List<String> cpuFeatures;
 
     protected enum BridgeType {
         NATIVE, OPENVSWITCH, TUNGSTEN
     }
 
-    protected BridgeType _bridgeType;
+    protected enum HealthCheckResult {
+        SUCCESS, FAILURE, IGNORE
+    }
+
+    protected BridgeType bridgeType;
 
     protected StorageSubsystemCommandHandler storageHandler;
 
+    private boolean convertInstanceVerboseMode = false;
     protected boolean dpdkSupport = false;
     protected String dpdkOvsPath;
     protected String directDownloadTemporaryDownloadPath;
@@ -795,11 +829,15 @@
     }
 
     protected String getNetworkDirectSourceMode() {
-        return _networkDirectSourceMode;
+        return networkDirectSourceMode;
     }
 
     protected String getNetworkDirectDevice() {
-        return _networkDirectDevice;
+        return networkDirectDevice;
+    }
+
+    public boolean isConvertInstanceVerboseModeEnabled() {
+        return convertInstanceVerboseMode;
     }
 
     /**
@@ -807,19 +845,19 @@
      */
     @Override
     protected void defineResourceNetworkInterfaces(Map<String, Object> params) {
-        _privBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PRIVATE_NETWORK_DEVICE);
-        _publicBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PUBLIC_NETWORK_DEVICE);
+        privBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PRIVATE_NETWORK_DEVICE);
+        publicBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PUBLIC_NETWORK_DEVICE);
 
-        _privateNic = NetUtils.getNetworkInterface(_privBridgeName);
-        _publicNic = NetUtils.getNetworkInterface(_publicBridgeName);
+        privateNic = NetUtils.getNetworkInterface(privBridgeName);
+        publicNic = NetUtils.getNetworkInterface(publicBridgeName);
     }
 
     public NetworkInterface getPrivateNic() {
-        return _privateNic;
+        return privateNic;
     }
 
     public NetworkInterface getPublicNic() {
-        return _publicNic;
+        return publicNic;
     }
 
     protected String getDefaultTungstenScriptsDir() {
@@ -838,8 +876,8 @@
             s_logger.error("uefi properties file not found due to: " + e.getLocalizedMessage());
         }
 
-        _storage = new JavaStorageLayer();
-        _storage.configure("StorageLayer", params);
+        storageLayer = new JavaStorageLayer();
+        storageLayer.configure("StorageLayer", params);
 
         String domrScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.DOMR_SCRIPTS_DIR);
 
@@ -858,13 +896,13 @@
 
         final String bridgeType = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_BRIDGE_TYPE);
         if (bridgeType == null) {
-            _bridgeType = BridgeType.NATIVE;
+            this.bridgeType = BridgeType.NATIVE;
         } else {
-            _bridgeType = BridgeType.valueOf(bridgeType.toUpperCase());
+            this.bridgeType = BridgeType.valueOf(bridgeType.toUpperCase());
         }
 
         Boolean dpdk = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.OPENVSWITCH_DPDK_ENABLED);
-        if (_bridgeType == BridgeType.OPENVSWITCH && BooleanUtils.isTrue(dpdk)) {
+        if (this.bridgeType == BridgeType.OPENVSWITCH && BooleanUtils.isTrue(dpdk)) {
             dpdkSupport = true;
             dpdkOvsPath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.OPENVSWITCH_DPDK_OVS_PATH);
             if (dpdkOvsPath != null && !dpdkOvsPath.endsWith("/")) {
@@ -878,92 +916,98 @@
 
         params.put("domr.scripts.dir", domrScriptsDir);
 
-        _virtRouterResource = new VirtualRoutingResource(this);
-        success = _virtRouterResource.configure(name, params);
+        virtRouterResource = new VirtualRoutingResource(this);
+        success = virtRouterResource.configure(name, params);
 
         if (!success) {
             return false;
         }
 
-        _dcId = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ZONE);
+        dcId = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ZONE);
 
-        _clusterId = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CLUSTER);
+        clusterId = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CLUSTER);
 
-        _updateHostPasswdPath = Script.findScript(hypervisorScriptsDir, VRScripts.UPDATE_HOST_PASSWD);
-        if (_updateHostPasswdPath == null) {
+        updateHostPasswdPath = Script.findScript(hypervisorScriptsDir, VRScripts.UPDATE_HOST_PASSWD);
+        if (updateHostPasswdPath == null) {
             throw new ConfigurationException("Unable to find update_host_passwd.sh");
         }
 
-        _modifyVlanPath = Script.findScript(networkScriptsDir, "modifyvlan.sh");
-        if (_modifyVlanPath == null) {
+        modifyVlanPath = Script.findScript(networkScriptsDir, "modifyvlan.sh");
+        if (modifyVlanPath == null) {
             throw new ConfigurationException("Unable to find modifyvlan.sh");
         }
 
-        _versionstringpath = Script.findScript(kvmScriptsDir, "versions.sh");
-        if (_versionstringpath == null) {
+        versionStringPath = Script.findScript(kvmScriptsDir, "versions.sh");
+        if (versionStringPath == null) {
             throw new ConfigurationException("Unable to find versions.sh");
         }
 
-        _patchScriptPath = Script.findScript(kvmScriptsDir, "patch.sh");
-        if (_patchScriptPath == null) {
+        patchScriptPath = Script.findScript(kvmScriptsDir, "patch.sh");
+        if (patchScriptPath == null) {
             throw new ConfigurationException("Unable to find patch.sh");
         }
 
-        _heartBeatPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh");
-        if (_heartBeatPath == null) {
+        heartBeatPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh");
+        if (heartBeatPath == null) {
             throw new ConfigurationException("Unable to find kvmheartbeat.sh");
         }
 
-        _createvmPath = Script.findScript(storageScriptsDir, "createvm.sh");
-        if (_createvmPath == null) {
+        createVmPath = Script.findScript(storageScriptsDir, "createvm.sh");
+        if (createVmPath == null) {
             throw new ConfigurationException("Unable to find the createvm.sh");
         }
 
-        _manageSnapshotPath = Script.findScript(storageScriptsDir, "managesnapshot.sh");
-        if (_manageSnapshotPath == null) {
+        manageSnapshotPath = Script.findScript(storageScriptsDir, "managesnapshot.sh");
+        if (manageSnapshotPath == null) {
             throw new ConfigurationException("Unable to find the managesnapshot.sh");
         }
 
-        _resizeVolumePath = Script.findScript(storageScriptsDir, "resizevolume.sh");
-        if (_resizeVolumePath == null) {
+        resizeVolumePath = Script.findScript(storageScriptsDir, "resizevolume.sh");
+        if (resizeVolumePath == null) {
             throw new ConfigurationException("Unable to find the resizevolume.sh");
         }
 
-        _vmActivityCheckPath = Script.findScript(kvmScriptsDir, "kvmvmactivity.sh");
-        if (_vmActivityCheckPath == null) {
+        vmActivityCheckPath = Script.findScript(kvmScriptsDir, "kvmvmactivity.sh");
+        if (vmActivityCheckPath == null) {
             throw new ConfigurationException("Unable to find kvmvmactivity.sh");
         }
 
-        _createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh");
-        if (_createTmplPath == null) {
+        createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh");
+        if (createTmplPath == null) {
             throw new ConfigurationException("Unable to find the createtmplt.sh");
         }
 
-        _securityGroupPath = Script.findScript(networkScriptsDir, "security_group.py");
-        if (_securityGroupPath == null) {
+        securityGroupPath = Script.findScript(networkScriptsDir, "security_group.py");
+        if (securityGroupPath == null) {
             throw new ConfigurationException("Unable to find the security_group.py");
         }
 
-        _ovsTunnelPath = Script.findScript(networkScriptsDir, "ovstunnel.py");
-        if (_ovsTunnelPath == null) {
+        ovsTunnelPath = Script.findScript(networkScriptsDir, "ovstunnel.py");
+        if (ovsTunnelPath == null) {
             throw new ConfigurationException("Unable to find the ovstunnel.py");
         }
 
-        _routerProxyPath = Script.findScript("scripts/network/domr/", "router_proxy.sh");
-        if (_routerProxyPath == null) {
+        routerProxyPath = Script.findScript("scripts/network/domr/", "router_proxy.sh");
+        if (routerProxyPath == null) {
             throw new ConfigurationException("Unable to find the router_proxy.sh");
         }
 
-        _ovsPvlanDhcpHostPath = Script.findScript(networkScriptsDir, "ovs-pvlan-kvm-dhcp-host.sh");
-        if (_ovsPvlanDhcpHostPath == null) {
+        ovsPvlanDhcpHostPath = Script.findScript(networkScriptsDir, "ovs-pvlan-kvm-dhcp-host.sh");
+        if (ovsPvlanDhcpHostPath == null) {
             throw new ConfigurationException("Unable to find the ovs-pvlan-kvm-dhcp-host.sh");
         }
 
-        _ovsPvlanVmPath = Script.findScript(networkScriptsDir, "ovs-pvlan-kvm-vm.sh");
-        if (_ovsPvlanVmPath == null) {
+        ovsPvlanVmPath = Script.findScript(networkScriptsDir, "ovs-pvlan-kvm-vm.sh");
+        if (ovsPvlanVmPath == null) {
             throw new ConfigurationException("Unable to find the ovs-pvlan-kvm-vm.sh");
         }
 
+        hostHealthCheckScriptPath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEALTH_CHECK_SCRIPT_PATH);
+        if (StringUtils.isNotBlank(hostHealthCheckScriptPath) && !new File(hostHealthCheckScriptPath).exists()) {
+            s_logger.info(String.format("Unable to find the host health check script at: %s, " +
+                    "discarding it", hostHealthCheckScriptPath));
+        }
+
         setupTungstenVrouterPath = Script.findScript(tungstenScriptsDir, "setup_tungsten_vrouter.sh");
         if (setupTungstenVrouterPath == null) {
             throw new ConfigurationException("Unable to find the setup_tungsten_vrouter.sh");
@@ -984,52 +1028,51 @@
             params.putAll(getDeveloperProperties());
         }
 
-        _pool = (String)params.get("pool");
-        if (_pool == null) {
-            _pool = "/root";
+        convertInstanceVerboseMode = BooleanUtils.isTrue(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VIRTV2V_VERBOSE_ENABLED));
+
+        pool = (String)params.get("pool");
+        if (pool == null) {
+            pool = "/root";
         }
 
         final String instance = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.INSTANCE);
 
-        _hypervisorType = HypervisorType.getType(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HYPERVISOR_TYPE));
+        hypervisorType = HypervisorType.getType(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HYPERVISOR_TYPE));
 
         String hooksDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ROLLING_MAINTENANCE_HOOKS_DIR);
         rollingMaintenanceExecutor = BooleanUtils.isTrue(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ROLLING_MAINTENANCE_SERVICE_EXECUTOR_DISABLED)) ? new RollingMaintenanceAgentExecutor(hooksDir) :
                 new RollingMaintenanceServiceExecutor(hooksDir);
 
-        _hypervisorURI = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HYPERVISOR_URI);
-        if (_hypervisorURI == null) {
-            _hypervisorURI = LibvirtConnection.getHypervisorURI(_hypervisorType.toString());
-        }
+        hypervisorURI = LibvirtConnection.getHypervisorURI(hypervisorType.toString());
 
-        _networkDirectSourceMode = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_DIRECT_SOURCE_MODE);
-        _networkDirectDevice = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_DIRECT_DEVICE);
+        networkDirectSourceMode = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_DIRECT_SOURCE_MODE);
+        networkDirectDevice = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_DIRECT_DEVICE);
 
-        _pingTestPath = Script.findScript(kvmScriptsDir, "pingtest.sh");
-        if (_pingTestPath == null) {
+        pingTestPath = Script.findScript(kvmScriptsDir, "pingtest.sh");
+        if (pingTestPath == null) {
             throw new ConfigurationException("Unable to find the pingtest.sh");
         }
 
-        _linkLocalBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PRIVATE_BRIDGE_NAME);
-        if (_linkLocalBridgeName == null) {
+        linkLocalBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PRIVATE_BRIDGE_NAME);
+        if (linkLocalBridgeName == null) {
             if (isDeveloper) {
-                _linkLocalBridgeName = "cloud-" + instance + "-0";
+                linkLocalBridgeName = "cloud-" + instance + "-0";
             } else {
-                _linkLocalBridgeName = "cloud0";
+                linkLocalBridgeName = "cloud0";
             }
         }
 
-        _guestBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_NETWORK_DEVICE);
-        if (_guestBridgeName == null) {
-            _guestBridgeName = _privBridgeName;
+        guestBridgeName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_NETWORK_DEVICE);
+        if (guestBridgeName == null) {
+            guestBridgeName = privBridgeName;
         }
 
-        _privNwName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PRIVATE_NETWORK_NAME);
-        if (_privNwName == null) {
+        privNwName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.PRIVATE_NETWORK_NAME);
+        if (privNwName == null) {
             if (isDeveloper) {
-                _privNwName = "cloud-" + instance + "-private";
+                privNwName = "cloud-" + instance + "-private";
             } else {
-                _privNwName = "cloud-private";
+                privNwName = "cloud-private";
             }
         }
 
@@ -1038,57 +1081,59 @@
 
         /* Directory to use for Qemu sockets like for the Qemu Guest Agent */
         String qemuSocketsPathVar = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.QEMU_SOCKETS_PATH);
-        _qemuSocketsPath = new File(qemuSocketsPathVar);
+        qemuSocketsPath = new File(qemuSocketsPathVar);
 
         // This value is never set. Default value is always used.
         String value = (String)params.get("scripts.timeout");
-        _timeout = Duration.standardSeconds(NumbersUtil.parseInt(value, 30 * 60));
+        timeout = Duration.standardSeconds(NumbersUtil.parseInt(value, 30 * 60));
 
-        _stopTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.STOP_SCRIPT_TIMEOUT) * 1000;
+        stopTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.STOP_SCRIPT_TIMEOUT) * 1000;
 
-        _cmdsTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CMDS_TIMEOUT) * 1000;
+        cmdsTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CMDS_TIMEOUT) * 1000;
 
-        _noMemBalloon = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_DISABLE);
+        noMemBalloon = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_DISABLE);
 
-        _manualCpuSpeed = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_CPU_MANUAL_SPEED_MHZ);
+        manualCpuSpeed = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_CPU_MANUAL_SPEED_MHZ);
 
-        _videoHw = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_VIDEO_HARDWARE);
+        videoHw = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_VIDEO_HARDWARE);
 
-        _videoRam = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_VIDEO_RAM);
+        videoRam = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_VIDEO_RAM);
 
         // Reserve 1GB unless admin overrides
-        _dom0MinMem = ByteScaleUtils.mebibytesToBytes(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_RESERVED_MEM_MB));
+        dom0MinMem = ByteScaleUtils.mebibytesToBytes(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_RESERVED_MEM_MB));
+
+        dom0MinCpuCores = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_RESERVED_CPU_CORE_COUNT);
 
         // Support overcommit memory for host if host uses ZSWAP, KSM and other memory
         // compressing technologies
-        _dom0OvercommitMem = ByteScaleUtils.mebibytesToBytes(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_OVERCOMMIT_MEM_MB));
+        dom0OvercommitMem = ByteScaleUtils.mebibytesToBytes(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_OVERCOMMIT_MEM_MB));
 
         if (BooleanUtils.isTrue(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVMCLOCK_DISABLE))) {
-            _noKvmClock = true;
+            noKvmClock = true;
         }
 
         if (BooleanUtils.isTrue(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_ENABLE))) {
-            _rngEnable = true;
+            rngEnable = true;
 
-            _rngBackendModel = RngBackendModel.valueOf(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_MODEL).toUpperCase());
+            rngBackendModel = RngBackendModel.valueOf(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_MODEL).toUpperCase());
 
-            _rngPath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_PATH);
+            rngPath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_PATH);
 
-            _rngRateBytes = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_RATE_BYTES);
+            rngRateBytes = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_RATE_BYTES);
 
-            _rngRatePeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_RATE_PERIOD);
+            rngRatePeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_RNG_RATE_PERIOD);
         }
 
-        _watchDogModel = WatchDogModel.valueOf(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_WATCHDOG_MODEL).toUpperCase());
+        watchDogModel = WatchDogModel.valueOf(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_WATCHDOG_MODEL).toUpperCase());
 
-        _watchDogAction = WatchDogAction.valueOf(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_WATCHDOG_ACTION).toUpperCase());
+        watchDogAction = WatchDogAction.valueOf(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_WATCHDOG_ACTION).toUpperCase());
 
-        LibvirtConnection.initialize(_hypervisorURI);
+        LibvirtConnection.initialize(hypervisorURI);
         Connect conn = null;
         try {
             conn = LibvirtConnection.getConnection();
 
-            if (_bridgeType == BridgeType.OPENVSWITCH) {
+            if (this.bridgeType == BridgeType.OPENVSWITCH) {
                 if (conn.getLibVirVersion() < 10 * 1000 + 0) {
                     throw new ConfigurationException("Libvirt version 0.10.0 required for openvswitch support, but version " + conn.getLibVirVersion() + " detected");
                 }
@@ -1111,7 +1156,7 @@
             s_logger.warn("Ignoring libvirt error.", e);
         }
 
-        if (HypervisorType.KVM == _hypervisorType) {
+        if (HypervisorType.KVM == hypervisorType) {
             /* Does node support HVM guest? If not, exit */
             if (!IsHVMEnabled(conn)) {
                 throw new ConfigurationException("NO HVM support on this machine, please make sure: " + "1. VT/SVM is supported by your CPU, or is enabled in BIOS. "
@@ -1119,12 +1164,12 @@
             }
         }
 
-        _hypervisorPath = getHypervisorPath(conn);
+        hypervisorPath = getHypervisorPath(conn);
         try {
-            _hvVersion = conn.getVersion();
-            _hvVersion = _hvVersion % 1000000 / 1000;
-            _hypervisorLibvirtVersion = conn.getLibVirVersion();
-            _hypervisorQemuVersion = conn.getVersion();
+            hvVersion = conn.getVersion();
+            hvVersion = hvVersion % 1000000 / 1000;
+            hypervisorLibvirtVersion = conn.getLibVirVersion();
+            hypervisorQemuVersion = conn.getVersion();
         } catch (final LibvirtException e) {
             s_logger.trace("Ignoring libvirt error.", e);
         }
@@ -1135,49 +1180,49 @@
 
         final String cpuArchOverride = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_CPU_ARCH);
         if (StringUtils.isNotEmpty(cpuArchOverride)) {
-            _guestCpuArch = cpuArchOverride;
-            s_logger.info("Using guest CPU architecture: " + _guestCpuArch);
+            guestCpuArch = cpuArchOverride;
+            s_logger.info("Using guest CPU architecture: " + guestCpuArch);
         }
 
-        _guestCpuMode = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_CPU_MODE);
-        if (_guestCpuMode != null) {
-            _guestCpuModel = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_CPU_MODEL);
+        guestCpuMode = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_CPU_MODE);
+        if (guestCpuMode != null) {
+            guestCpuModel = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_CPU_MODEL);
 
-            if (_hypervisorLibvirtVersion < 9 * 1000 + 10) {
-                s_logger.warn("Libvirt version 0.9.10 required for guest cpu mode, but version " + prettyVersion(_hypervisorLibvirtVersion) +
+            if (hypervisorLibvirtVersion < 9 * 1000 + 10) {
+                s_logger.warn("Libvirt version 0.9.10 required for guest cpu mode, but version " + prettyVersion(hypervisorLibvirtVersion) +
                         " detected, so it will be disabled");
-                _guestCpuMode = "";
-                _guestCpuModel = "";
+                guestCpuMode = "";
+                guestCpuModel = "";
             }
-            params.put("guest.cpu.mode", _guestCpuMode);
-            params.put("guest.cpu.model", _guestCpuModel);
+            params.put("guest.cpu.mode", guestCpuMode);
+            params.put("guest.cpu.model", guestCpuModel);
         }
 
         final String cpuFeatures = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.GUEST_CPU_FEATURES);
         if (cpuFeatures != null) {
-            _cpuFeatures = new ArrayList<String>();
+            this.cpuFeatures = new ArrayList<String>();
             for (final String feature: cpuFeatures.split(" ")) {
                 if (!feature.isEmpty()) {
-                    _cpuFeatures.add(feature);
+                    this.cpuFeatures.add(feature);
                 }
             }
         }
 
-        final String[] info = NetUtils.getNetworkParams(_privateNic);
+        final String[] info = NetUtils.getNetworkParams(privateNic);
 
-        _monitor = new KVMHAMonitor(null, info[0], _heartBeatPath);
-        final Thread ha = new Thread(_monitor);
+        kvmhaMonitor = new KVMHAMonitor(null, info[0], heartBeatPath);
+        final Thread ha = new Thread(kvmhaMonitor);
         ha.start();
 
-        _storagePoolMgr = new KVMStoragePoolManager(_storage, _monitor);
+        storagePoolManager = new KVMStoragePoolManager(storageLayer, kvmhaMonitor);
 
         final Map<String, String> bridges = new HashMap<String, String>();
 
         params.put("libvirt.host.bridges", bridges);
-        params.put("libvirt.host.pifs", _pifs);
+        params.put("libvirt.host.pifs", pifs);
 
         params.put("libvirt.computing.resource", this);
-        params.put("libvirtVersion", _hypervisorLibvirtVersion);
+        params.put("libvirtVersion", hypervisorLibvirtVersion);
 
 
         configureVifDrivers(params);
@@ -1194,61 +1239,61 @@
         }
         */
 
-        if (_pifs.get("private") == null) {
+        if (pifs.get("private") == null) {
             s_logger.error("Failed to get private nic name");
             throw new ConfigurationException("Failed to get private nic name");
         }
 
-        if (_pifs.get("public") == null) {
+        if (pifs.get("public") == null) {
             s_logger.error("Failed to get public nic name");
             throw new ConfigurationException("Failed to get public nic name");
         }
-        s_logger.debug("Found pif: " + _pifs.get("private") + " on " + _privBridgeName + ", pif: " + _pifs.get("public") + " on " + _publicBridgeName);
+        s_logger.debug("Found pif: " + pifs.get("private") + " on " + privBridgeName + ", pif: " + pifs.get("public") + " on " + publicBridgeName);
 
-        _canBridgeFirewall = canBridgeFirewall(_pifs.get("public"));
+        canBridgeFirewall = canBridgeFirewall(pifs.get("public"));
 
-        _localGateway = Script.runSimpleBashScript("ip route show default 0.0.0.0/0|head -1|awk '{print $3}'");
-        if (_localGateway == null) {
+        localGateway = Script.runSimpleBashScript("ip route show default 0.0.0.0/0|head -1|awk '{print $3}'");
+        if (localGateway == null) {
             s_logger.warn("No default IPv4 gateway found");
         }
 
-        _migrateDowntime = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_DOWNTIME);
+        migrateDowntime = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_DOWNTIME);
 
-        _migratePauseAfter = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_PAUSEAFTER);
+        migratePauseAfter = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_PAUSEAFTER);
 
-        _migrateWait = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_WAIT);
+        migrateWait = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_WAIT);
 
         configureAgentHooks();
 
-        _migrateSpeed = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_SPEED);
-        if (_migrateSpeed == -1) {
+        migrateSpeed = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MIGRATE_SPEED);
+        if (migrateSpeed == -1) {
             //get guest network device speed
-            _migrateSpeed = 0;
-            final String speed = Script.runSimpleBashScript("ethtool " + _pifs.get("public") + " |grep Speed | cut -d \\  -f 2");
+            migrateSpeed = 0;
+            final String speed = Script.runSimpleBashScript("ethtool " + pifs.get("public") + " |grep Speed | cut -d \\  -f 2");
             if (speed != null) {
                 final String[] tokens = speed.split("M");
                 if (tokens.length == 2) {
                     try {
-                        _migrateSpeed = Integer.parseInt(tokens[0]);
+                        migrateSpeed = Integer.parseInt(tokens[0]);
                     } catch (final NumberFormatException e) {
                         s_logger.trace("Ignoring migrateSpeed extraction error.", e);
                     }
-                    s_logger.debug("device " + _pifs.get("public") + " has speed: " + String.valueOf(_migrateSpeed));
+                    s_logger.debug("device " + pifs.get("public") + " has speed: " + String.valueOf(migrateSpeed));
                 }
             }
-            params.put("vm.migrate.speed", String.valueOf(_migrateSpeed));
+            params.put("vm.migrate.speed", String.valueOf(migrateSpeed));
         }
 
-        bridges.put("linklocal", _linkLocalBridgeName);
-        bridges.put("public", _publicBridgeName);
-        bridges.put("private", _privBridgeName);
-        bridges.put("guest", _guestBridgeName);
+        bridges.put("linklocal", linkLocalBridgeName);
+        bridges.put("public", publicBridgeName);
+        bridges.put("private", privBridgeName);
+        bridges.put("guest", guestBridgeName);
 
-        getVifDriver(TrafficType.Control).createControlNetwork(_linkLocalBridgeName);
+        getVifDriver(TrafficType.Control).createControlNetwork(linkLocalBridgeName);
 
         configureDiskActivityChecks();
 
-        final KVMStorageProcessor storageProcessor = new KVMStorageProcessor(_storagePoolMgr, this);
+        final KVMStorageProcessor storageProcessor = new KVMStorageProcessor(storagePoolManager, this);
         storageProcessor.configure(name, params);
         storageHandler = new StorageSubsystemCommandHandlerBase(storageProcessor);
 
@@ -1415,7 +1460,7 @@
             String value = (String)params.get(Config.MigrateWait.toString());
             Integer intValue = NumbersUtil.parseInt(value, -1);
             storage.persist("vm.migrate.wait", String.valueOf(intValue));
-            _migrateWait = intValue;
+            migrateWait = intValue;
         }
 
         if (params.get(NetworkOrchestrationService.TUNGSTEN_ENABLED.key()) != null) {
@@ -1426,30 +1471,30 @@
     }
 
     private void configureAgentHooks() {
-        _agentHooksBasedir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_BASEDIR);
-        s_logger.debug("agent.hooks.basedir is " + _agentHooksBasedir);
+        agentHooksBasedir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_BASEDIR);
+        s_logger.debug("agent.hooks.basedir is " + agentHooksBasedir);
 
-        _agentHooksLibvirtXmlScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_XML_TRANSFORMER_SCRIPT);
-        s_logger.debug("agent.hooks.libvirt_vm_xml_transformer.script is " + _agentHooksLibvirtXmlScript);
+        agentHooksLibvirtXmlScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_XML_TRANSFORMER_SCRIPT);
+        s_logger.debug("agent.hooks.libvirt_vm_xml_transformer.script is " + agentHooksLibvirtXmlScript);
 
-        _agentHooksLibvirtXmlMethod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_XML_TRANSFORMER_METHOD);
-        s_logger.debug("agent.hooks.libvirt_vm_xml_transformer.method is " + _agentHooksLibvirtXmlMethod);
+        agentHooksLibvirtXmlMethod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_XML_TRANSFORMER_METHOD);
+        s_logger.debug("agent.hooks.libvirt_vm_xml_transformer.method is " + agentHooksLibvirtXmlMethod);
 
-        _agentHooksVmOnStartScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_START_SCRIPT);
-        s_logger.debug("agent.hooks.libvirt_vm_on_start.script is " + _agentHooksVmOnStartScript);
+        agentHooksVmOnStartScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_START_SCRIPT);
+        s_logger.debug("agent.hooks.libvirt_vm_on_start.script is " + agentHooksVmOnStartScript);
 
-        _agentHooksVmOnStartMethod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_START_METHOD);
-        s_logger.debug("agent.hooks.libvirt_vm_on_start.method is " + _agentHooksVmOnStartMethod);
+        agentHooksVmOnStartMethod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_START_METHOD);
+        s_logger.debug("agent.hooks.libvirt_vm_on_start.method is " + agentHooksVmOnStartMethod);
 
-        _agentHooksVmOnStopScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_STOP_SCRIPT);
-        s_logger.debug("agent.hooks.libvirt_vm_on_stop.script is " + _agentHooksVmOnStopScript);
+        agentHooksVmOnStopScript = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_STOP_SCRIPT);
+        s_logger.debug("agent.hooks.libvirt_vm_on_stop.script is " + agentHooksVmOnStopScript);
 
-        _agentHooksVmOnStopMethod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_STOP_METHOD);
-        s_logger.debug("agent.hooks.libvirt_vm_on_stop.method is " + _agentHooksVmOnStopMethod);
+        agentHooksVmOnStopMethod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_HOOKS_LIBVIRT_VM_ON_STOP_METHOD);
+        s_logger.debug("agent.hooks.libvirt_vm_on_stop.method is " + agentHooksVmOnStopMethod);
     }
 
     public boolean isUefiPropertiesFileLoaded() {
-        return !_uefiProperties.isEmpty();
+        return !uefiProperties.isEmpty();
     }
 
     private void loadUefiProperties() throws FileNotFoundException {
@@ -1464,12 +1509,12 @@
 
         s_logger.info("uefi.properties file found at " + file.getAbsolutePath());
         try {
-            PropertiesUtil.loadFromFile(_uefiProperties, file);
-            s_logger.info("guest.nvram.template.legacy = " + _uefiProperties.getProperty("guest.nvram.template.legacy"));
-            s_logger.info("guest.loader.legacy = " + _uefiProperties.getProperty("guest.loader.legacy"));
-            s_logger.info("guest.nvram.template.secure = " + _uefiProperties.getProperty("guest.nvram.template.secure"));
-            s_logger.info("guest.loader.secure =" + _uefiProperties.getProperty("guest.loader.secure"));
-            s_logger.info("guest.nvram.path = " + _uefiProperties.getProperty("guest.nvram.path"));
+            PropertiesUtil.loadFromFile(uefiProperties, file);
+            s_logger.info("guest.nvram.template.legacy = " + uefiProperties.getProperty("guest.nvram.template.legacy"));
+            s_logger.info("guest.loader.legacy = " + uefiProperties.getProperty("guest.loader.legacy"));
+            s_logger.info("guest.nvram.template.secure = " + uefiProperties.getProperty("guest.nvram.template.secure"));
+            s_logger.info("guest.loader.secure =" + uefiProperties.getProperty("guest.loader.secure"));
+            s_logger.info("guest.nvram.path = " + uefiProperties.getProperty("guest.nvram.path"));
         } catch (final FileNotFoundException ex) {
             throw new CloudRuntimeException("Cannot find the file: " + file.getAbsolutePath(), ex);
         } catch (final IOException ex) {
@@ -1478,15 +1523,15 @@
     }
 
     protected void configureDiskActivityChecks() {
-        _diskActivityCheckEnabled = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_DISKACTIVITY_CHECKENABLED);
-        if (_diskActivityCheckEnabled) {
+        diskActivityCheckEnabled = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_DISKACTIVITY_CHECKENABLED);
+        if (diskActivityCheckEnabled) {
             final int timeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_DISKACTIVITY_CHECKTIMEOUT_S);
             if (timeout > 0) {
-                _diskActivityCheckTimeoutSeconds = timeout;
+                diskActivityCheckTimeoutSeconds = timeout;
             }
             final long inactiveTime = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_DISKACTIVITY_INACTIVETIME_MS);
             if (inactiveTime > 0) {
-                _diskActivityInactiveThresholdMilliseconds = inactiveTime;
+                diskActivityInactiveThresholdMilliseconds = inactiveTime;
             }
         }
     }
@@ -1494,12 +1539,12 @@
     protected void configureVifDrivers(final Map<String, Object> params) throws ConfigurationException {
         final String LIBVIRT_VIF_DRIVER = "libvirt.vif.driver";
 
-        _trafficTypeVifDrivers = new HashMap<TrafficType, VifDriver>();
+        trafficTypeVifDriverMap = new HashMap<TrafficType, VifDriver>();
 
         // Load the default vif driver
         String defaultVifDriverName = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_VIF_DRIVER);
         if (defaultVifDriverName == null) {
-            if (_bridgeType == BridgeType.OPENVSWITCH) {
+            if (bridgeType == BridgeType.OPENVSWITCH) {
                 s_logger.info("No libvirt.vif.driver specified. Defaults to OvsVifDriver.");
                 defaultVifDriverName = DEFAULT_OVS_VIF_DRIVER_CLASS_NAME;
             } else {
@@ -1507,7 +1552,7 @@
                 defaultVifDriverName = DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME;
             }
         }
-        _defaultVifDriver = getVifDriverClass(defaultVifDriverName, params);
+        defaultVifDriver = getVifDriverClass(defaultVifDriverName, params);
         tungstenVifDriver = getVifDriverClass(DEFAULT_TUNGSTEN_VIF_DRIVER_CLASS_NAME, params);
 
         // Load any per-traffic-type vif drivers
@@ -1527,7 +1572,7 @@
                     // if value is null, ignore
                     if (vifDriverClassName != null) {
                         // add traffic type to vif driver mapping to Map
-                        _trafficTypeVifDrivers.put(trafficType, getVifDriverClass(vifDriverClassName, params));
+                        trafficTypeVifDriverMap.put(trafficType, getVifDriverClass(vifDriverClassName, params));
                     }
                 }
             }
@@ -1552,10 +1597,10 @@
     }
 
     public VifDriver getVifDriver(final TrafficType trafficType) {
-        VifDriver vifDriver = _trafficTypeVifDrivers.get(trafficType);
+        VifDriver vifDriver = trafficTypeVifDriverMap.get(trafficType);
 
         if (vifDriver == null) {
-            vifDriver = _defaultVifDriver;
+            vifDriver = defaultVifDriver;
         }
 
         return vifDriver;
@@ -1581,11 +1626,11 @@
     public List<VifDriver> getAllVifDrivers() {
         final Set<VifDriver> vifDrivers = new HashSet<VifDriver>();
 
-        vifDrivers.add(_defaultVifDriver);
+        vifDrivers.add(defaultVifDriver);
         if (isTungstenEnabled) {
             vifDrivers.add(tungstenVifDriver);
         }
-        vifDrivers.addAll(_trafficTypeVifDrivers.values());
+        vifDrivers.addAll(trafficTypeVifDriverMap.values());
 
         final ArrayList<VifDriver> vifDriverList = new ArrayList<VifDriver>(vifDrivers);
 
@@ -1610,33 +1655,33 @@
             s_logger.debug("looking for pif for bridge " + bridge);
             final String pif = getPif(bridge);
             if (isPublicBridge(bridge)) {
-                _pifs.put("public", pif);
+                pifs.put("public", pif);
             }
             if (isGuestBridge(bridge)) {
-                _pifs.put("private", pif);
+                pifs.put("private", pif);
             }
-            _pifs.put(bridge, pif);
+            pifs.put(bridge, pif);
         }
 
         // guest(private) creates bridges on a pif, if private bridge not found try pif direct
         // This addresses the unnecessary requirement of someone to create an unused bridge just for traffic label
-        if (_pifs.get("private") == null) {
-            s_logger.debug("guest(private) traffic label '" + _guestBridgeName + "' not found as bridge, looking for physical interface");
-            final File dev = new File("/sys/class/net/" + _guestBridgeName);
+        if (pifs.get("private") == null) {
+            s_logger.debug("guest(private) traffic label '" + guestBridgeName + "' not found as bridge, looking for physical interface");
+            final File dev = new File("/sys/class/net/" + guestBridgeName);
             if (dev.exists()) {
-                s_logger.debug("guest(private) traffic label '" + _guestBridgeName + "' found as a physical device");
-                _pifs.put("private", _guestBridgeName);
+                s_logger.debug("guest(private) traffic label '" + guestBridgeName + "' found as a physical device");
+                pifs.put("private", guestBridgeName);
             }
         }
 
         // public creates bridges on a pif, if private bridge not found try pif direct
         // This addresses the unnecessary requirement of someone to create an unused bridge just for traffic label
-        if (_pifs.get("public") == null) {
-            s_logger.debug("public traffic label '" + _publicBridgeName+ "' not found as bridge, looking for physical interface");
-            final File dev = new File("/sys/class/net/" + _publicBridgeName);
+        if (pifs.get("public") == null) {
+            s_logger.debug("public traffic label '" + publicBridgeName + "' not found as bridge, looking for physical interface");
+            final File dev = new File("/sys/class/net/" + publicBridgeName);
             if (dev.exists()) {
-                s_logger.debug("public traffic label '" + _publicBridgeName + "' found as a physical device");
-                _pifs.put("public", _publicBridgeName);
+                s_logger.debug("public traffic label '" + publicBridgeName + "' found as a physical device");
+                pifs.put("public", publicBridgeName);
             }
         }
 
@@ -1644,7 +1689,7 @@
     }
 
     boolean isGuestBridge(String bridge) {
-        return _guestBridgeName != null && bridge.equals(_guestBridgeName);
+        return guestBridgeName != null && bridge.equals(guestBridgeName);
     }
 
     private void getOvsPifs() {
@@ -1658,18 +1703,18 @@
             // bridges
             final String pif = bridge;
             if (isPublicBridge(bridge)) {
-                _pifs.put("public", pif);
+                pifs.put("public", pif);
             }
             if (isGuestBridge(bridge)) {
-                _pifs.put("private", pif);
+                pifs.put("private", pif);
             }
-            _pifs.put(bridge, pif);
+            pifs.put(bridge, pif);
         }
         s_logger.debug("done looking for pifs, no more bridges");
     }
 
     public boolean isPublicBridge(String bridge) {
-        return _publicBridgeName != null && bridge.equals(_publicBridgeName);
+        return publicBridgeName != null && bridge.equals(publicBridgeName);
     }
 
     private String getPif(final String bridge) {
@@ -1774,14 +1819,14 @@
             return true;
         }
 
-        final Script command = new Script("/bin/sh", _timeout);
+        final Script command = new Script("/bin/sh", timeout);
         command.add("-c");
         command.add("ovs-vsctl br-exists " + networkName);
         return "0".equals(command.execute(null));
     }
 
     public boolean passCmdLine(final String vmName, final String cmdLine) throws InternalErrorException {
-        final Script command = new Script(_patchScriptPath, 300000, s_logger);
+        final Script command = new Script(patchScriptPath, 300000, s_logger);
         String result;
         command.add("-n", vmName);
         command.add("-c", cmdLine);
@@ -1845,6 +1890,10 @@
     public boolean stop() {
         try {
             final Connect conn = LibvirtConnection.getConnection();
+            if (AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_EVENTS_ENABLED) && libvirtDomainListener != null) {
+                s_logger.debug("Clearing old domain listener");
+                conn.removeLifecycleListener(libvirtDomainListener);
+            }
             conn.close();
         } catch (final LibvirtException e) {
             s_logger.trace("Ignoring libvirt error.", e);
@@ -1875,7 +1924,7 @@
     public synchronized boolean destroyTunnelNetwork(final String bridge) {
         findOrCreateTunnelNetwork(bridge);
 
-        final Script cmd = new Script(_ovsTunnelPath, _timeout, s_logger);
+        final Script cmd = new Script(ovsTunnelPath, timeout, s_logger);
         cmd.add("destroy_ovs_bridge");
         cmd.add("--bridge", bridge);
 
@@ -1929,7 +1978,7 @@
                 }
             }
             if (!configured) {
-                final Script cmd = new Script(_ovsTunnelPath, _timeout, s_logger);
+                final Script cmd = new Script(ovsTunnelPath, timeout, s_logger);
                 cmd.add("setup_ovs_bridge");
                 cmd.add("--key", nwName);
                 cmd.add("--cs_host_id", ((Long)hostId).toString());
@@ -1964,7 +2013,7 @@
         KVMPhysicalDisk templateVol = null;
         KVMStoragePool secondaryPool = null;
         try {
-            secondaryPool = _storagePoolMgr.getStoragePoolByURI(mountpoint);
+            secondaryPool = storagePoolManager.getStoragePoolByURI(mountpoint);
             /* Get template vol */
             if (templateName == null) {
                 secondaryPool.refresh();
@@ -1989,14 +2038,14 @@
 
             /* Copy volume to primary storage */
 
-            final KVMPhysicalDisk primaryVol = _storagePoolMgr.copyPhysicalDisk(templateVol, volUuid, primaryPool, 0);
+            final KVMPhysicalDisk primaryVol = storagePoolManager.copyPhysicalDisk(templateVol, volUuid, primaryPool, 0);
             return primaryVol;
         } catch (final CloudRuntimeException e) {
             s_logger.error("Failed to download template to primary storage", e);
             return null;
         } finally {
             if (secondaryPool != null) {
-                _storagePoolMgr.deleteStoragePool(secondaryPool.getType(), secondaryPool.getUuid());
+                storagePoolManager.deleteStoragePool(secondaryPool.getType(), secondaryPool.getUuid());
             }
         }
     }
@@ -2124,9 +2173,9 @@
             for (final InterfaceDef pluggedNic : pluggedNics) {
                 final String pluggedVlanBr = pluggedNic.getBrName();
                 final String pluggedVlanId = getBroadcastUriFromBridge(pluggedVlanBr);
-                if (pubVlan.equalsIgnoreCase(Vlan.UNTAGGED) && pluggedVlanBr.equalsIgnoreCase(_publicBridgeName)) {
+                if (pubVlan.equalsIgnoreCase(Vlan.UNTAGGED) && pluggedVlanBr.equalsIgnoreCase(publicBridgeName)) {
                     break;
-                } else if (pluggedVlanBr.equalsIgnoreCase(_linkLocalBridgeName)) {
+                } else if (pluggedVlanBr.equalsIgnoreCase(linkLocalBridgeName)) {
                     /*skip over, no physical bridge device exists*/
                 } else if (pluggedVlanId == null) {
                     /*this should only be true in the case of link local bridge*/
@@ -2261,8 +2310,8 @@
         return new Pair<Map<String, Integer>, Integer>(macAddressToNicNum, devNum);
     }
 
-    protected PowerState convertToPowerState(final DomainState ps) {
-        final PowerState state = s_powerStatesTable.get(ps);
+    public PowerState convertToPowerState(final DomainState ps) {
+        final PowerState state = POWER_STATES_TABLE.get(ps);
         return state == null ? PowerState.PowerUnknown : state;
     }
 
@@ -2294,7 +2343,7 @@
     }
 
     public String networkUsage(final String privateIpAddress, final String option, final String vif, String publicIp) {
-        final Script getUsage = new Script(_routerProxyPath, s_logger);
+        final Script getUsage = new Script(routerProxyPath, s_logger);
         getUsage.add("netusage.sh");
         getUsage.add(privateIpAddress);
         if (option.equals("get")) {
@@ -2340,7 +2389,7 @@
     }
 
     public String getHaproxyStats(final String privateIP, final String publicIp, final Integer port) {
-        final Script getHaproxyStatsScript = new Script(_routerProxyPath, s_logger);
+        final Script getHaproxyStatsScript = new Script(routerProxyPath, s_logger);
         getHaproxyStatsScript.add("get_haproxy_stats.sh");
         getHaproxyStatsScript.add(privateIP);
         getHaproxyStatsScript.add(publicIp);
@@ -2366,7 +2415,7 @@
     }
 
     public String configureVPCNetworkUsage(final String privateIpAddress, final String publicIp, final String option, final String vpcCIDR) {
-        final Script getUsage = new Script(_routerProxyPath, s_logger);
+        final Script getUsage = new Script(routerProxyPath, s_logger);
         getUsage.add("vpc_netusage.sh");
         getUsage.add(privateIpAddress);
         getUsage.add("-l", publicIp);
@@ -2527,7 +2576,7 @@
             vm.addComp(createCpuModeDef(vmTO, vcpus));
         }
 
-        if (_hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE) {
+        if (hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE) {
             vm.addComp(createCpuTuneDef(vmTO));
         }
 
@@ -2557,11 +2606,11 @@
      */
     protected DevicesDef createDevicesDef(VirtualMachineTO vmTO, GuestDef guest, int vcpus, boolean isUefiEnabled) {
         DevicesDef devices = new DevicesDef();
-        devices.setEmulatorPath(_hypervisorPath);
+        devices.setEmulatorPath(hypervisorPath);
         devices.setGuestType(guest.getGuestType());
         devices.addDevice(createSerialDef());
 
-        if (_rngEnable) {
+        if (rngEnable) {
             devices.addDevice(createRngDef());
         }
 
@@ -2591,7 +2640,7 @@
     }
 
     protected WatchDogDef createWatchDogDef() {
-        return new WatchDogDef(_watchDogAction, _watchDogModel);
+        return new WatchDogDef(watchDogAction, watchDogModel);
     }
 
     protected void createArm64UsbDef(DevicesDef devices) {
@@ -2615,8 +2664,8 @@
      * Adds a Virtio channel for the Qemu Guest Agent tools.
      */
     protected ChannelDef createChannelDef(VirtualMachineTO vmTO) {
-        File virtIoChannel = Paths.get(_qemuSocketsPath.getPath(), vmTO.getName() + "." + _qemuGuestAgentSocketName).toFile();
-        return new ChannelDef(_qemuGuestAgentSocketName, ChannelDef.ChannelType.UNIX, virtIoChannel);
+        File virtIoChannel = Paths.get(qemuSocketsPath.getPath(), vmTO.getName() + "." + qemuGuestAgentSocketName).toFile();
+        return new ChannelDef(qemuGuestAgentSocketName, ChannelDef.ChannelType.UNIX, virtIoChannel);
     }
 
     /**
@@ -2633,8 +2682,8 @@
 
     protected VideoDef createVideoDef(VirtualMachineTO vmTO) {
         Map<String, String> details = vmTO.getDetails();
-        String videoHw = _videoHw;
-        int videoRam = _videoRam;
+        String videoHw = this.videoHw;
+        int videoRam = this.videoRam;
         if (details != null) {
             if (details.containsKey(VmDetailConstants.VIDEO_HARDWARE)) {
                 videoHw = details.get(VmDetailConstants.VIDEO_HARDWARE);
@@ -2648,7 +2697,7 @@
     }
 
     protected RngDef createRngDef() {
-        return new RngDef(_rngPath, _rngBackendModel, _rngRateBytes, _rngRatePeriod);
+        return new RngDef(rngPath, rngBackendModel, rngRateBytes, rngRatePeriod);
     }
 
     protected SerialDef createSerialDef() {
@@ -2660,8 +2709,8 @@
         if (StringUtils.startsWith(vmTO.getOs(), WINDOWS)) {
             clock.setClockOffset(ClockDef.ClockOffset.LOCALTIME);
             clock.setTimer(HYPERVCLOCK, null, null);
-        } else if ((vmTO.getType() != VirtualMachine.Type.User || isGuestPVEnabled(vmTO.getOs())) && _hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_MODE) {
-            clock.setTimer(KVMCLOCK, null, null, _noKvmClock);
+        } else if ((vmTO.getType() != VirtualMachine.Type.User || isGuestPVEnabled(vmTO.getOs())) && hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_MODE) {
+            clock.setTimer(KVMCLOCK, null, null, noKvmClock);
         }
         return clock;
     }
@@ -2731,10 +2780,10 @@
 
     private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) {
         final CpuModeDef cmd = new CpuModeDef();
-        cmd.setMode(_guestCpuMode);
-        cmd.setModel(_guestCpuModel);
+        cmd.setMode(guestCpuMode);
+        cmd.setModel(guestCpuModel);
         if (VirtualMachine.Type.User.equals(vmTO.getType())) {
-            cmd.setFeatures(_cpuFeatures);
+            cmd.setFeatures(cpuFeatures);
         }
         int vCpusInDef = vmTO.getVcpuMaxLimit() == null ? vcpus : vmTO.getVcpuMaxLimit();
         setCpuTopology(cmd, vCpusInDef, vmTO.getDetails());
@@ -2746,28 +2795,28 @@
         setGuestLoader(bootMode, LEGACY, guest, GuestDef.GUEST_LOADER_LEGACY);
 
         if (isUefiPropertieNotNull(GuestDef.GUEST_NVRAM_PATH)) {
-            guest.setNvram(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_PATH));
+            guest.setNvram(uefiProperties.getProperty(GuestDef.GUEST_NVRAM_PATH));
         }
 
         if (isSecureBoot && isUefiPropertieNotNull(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE) && SECURE.equalsIgnoreCase(bootMode)) {
-            guest.setNvramTemplate(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE));
+            guest.setNvramTemplate(uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_SECURE));
         } else if (isUefiPropertieNotNull(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY)) {
-            guest.setNvramTemplate(_uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY));
+            guest.setNvramTemplate(uefiProperties.getProperty(GuestDef.GUEST_NVRAM_TEMPLATE_LEGACY));
         }
     }
 
     private void setGuestLoader(String bootMode, String mode, GuestDef guest, String propertie) {
         if (isUefiPropertieNotNull(propertie) && mode.equalsIgnoreCase(bootMode)) {
-            guest.setLoader(_uefiProperties.getProperty(propertie));
+            guest.setLoader(uefiProperties.getProperty(propertie));
         }
     }
 
     private boolean isUefiPropertieNotNull(String propertie) {
-        return _uefiProperties.getProperty(propertie) != null;
+        return uefiProperties.getProperty(propertie) != null;
     }
 
     private boolean isGuestAarch64() {
-        return AARCH64.equals(_guestCpuArch);
+        return AARCH64.equals(guestCpuArch);
     }
 
     /**
@@ -2777,7 +2826,7 @@
         GuestDef guest = new GuestDef();
 
         configureGuestAndVMHypervisorType(vmTO, vm, guest);
-        guest.setGuestArch(_guestCpuArch != null ? _guestCpuArch : vmTO.getArch());
+        guest.setGuestArch(guestCpuArch != null ? guestCpuArch : vmTO.getArch());
         guest.setMachineType(isGuestAarch64() ? VIRT : PC);
         guest.setBootType(GuestDef.BootType.BIOS);
         if (MapUtils.isNotEmpty(customParams)) {
@@ -2798,7 +2847,7 @@
     }
 
     protected void configureGuestAndVMHypervisorType(VirtualMachineTO vmTO, LibvirtVMDef vm, GuestDef guest) {
-        if (HypervisorType.LXC == _hypervisorType && VirtualMachine.Type.User.equals(vmTO.getType())) {
+        if (HypervisorType.LXC == hypervisorType && VirtualMachine.Type.User.equals(vmTO.getType())) {
             configureGuestAndUserVMToUseLXC(vm, guest);
         } else {
             configureGuestAndSystemVMToUseKVM(vm, guest);
@@ -2811,8 +2860,8 @@
     private void configureGuestAndSystemVMToUseKVM(LibvirtVMDef vm, GuestDef guest) {
         guest.setGuestType(GuestDef.GuestType.KVM);
         vm.setHvsType(HypervisorType.KVM.toString().toLowerCase());
-        vm.setLibvirtVersion(_hypervisorLibvirtVersion);
-        vm.setQemuVersion(_hypervisorQemuVersion);
+        vm.setLibvirtVersion(hypervisorLibvirtVersion);
+        vm.setQemuVersion(hypervisorQemuVersion);
     }
 
     /**
@@ -2829,7 +2878,7 @@
     protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO){
         GuestResourceDef grd = new GuestResourceDef();
 
-        grd.setMemBalloning(!_noMemBalloon);
+        grd.setMemBalloning(!noMemBalloon);
 
         Long maxRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam());
 
@@ -2847,7 +2896,7 @@
 
     protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long maxRam) {
         long retVal = maxRam;
-        if (_noMemBalloon) {
+        if (noMemBalloon) {
             s_logger.warn(String.format("Setting VM's [%s] current memory as max memory [%s] due to memory ballooning is disabled. If you are using a custom service offering, verify if memory ballooning really should be disabled.", vmTO.toString(), maxRam));
         } else if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) {
             s_logger.warn(String.format("Setting System VM's [%s] current memory as max memory [%s].", vmTO.toString(), maxRam));
@@ -2888,7 +2937,7 @@
         for (int i = 0; i < nics.length; i++) {
             for (final NicTO nic : vmSpec.getNics()) {
                 if (nic.getDeviceId() == i) {
-                    createVif(vm, nic, nicAdapter, extraConfig);
+                    createVif(vm, vmSpec, nic, nicAdapter, extraConfig);
                 }
             }
         }
@@ -2913,7 +2962,7 @@
                 final int index = isoPath.lastIndexOf("/");
                 final String path = isoPath.substring(0, index);
                 final String name = isoPath.substring(index + 1);
-                final KVMStoragePool secondaryPool = _storagePoolMgr.getStoragePoolByURI(path);
+                final KVMStoragePool secondaryPool = storagePoolManager.getStoragePoolByURI(path);
                 final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name);
                 return isoVol.getPath();
             }
@@ -2973,7 +3022,7 @@
                 }
             } else if (volume.getType() != Volume.Type.ISO) {
                 final PrimaryDataStoreTO store = (PrimaryDataStoreTO)data.getDataStore();
-                physicalDisk = _storagePoolMgr.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
+                physicalDisk = storagePoolManager.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
                 pool = physicalDisk.getPool();
             }
 
@@ -2993,10 +3042,10 @@
             }
 
             // check for disk activity, if detected we should exit because vm is running elsewhere
-            if (_diskActivityCheckEnabled && physicalDisk != null && physicalDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
+            if (diskActivityCheckEnabled && physicalDisk != null && physicalDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
                 s_logger.debug("Checking physical disk file at path " + volPath + " for disk activity to ensure vm is not running elsewhere");
                 try {
-                    HypervisorUtils.checkVolumeFileForActivity(volPath, _diskActivityCheckTimeoutSeconds, _diskActivityInactiveThresholdMilliseconds, _diskActivityCheckFileSizeMin);
+                    HypervisorUtils.checkVolumeFileForActivity(volPath, diskActivityCheckTimeoutSeconds, diskActivityInactiveThresholdMilliseconds, diskActivityCheckFileSizeMin);
                 } catch (final IOException ex) {
                     throw new CloudRuntimeException("Unable to check physical disk file for activity", ex);
                 }
@@ -3020,7 +3069,7 @@
 
                 disk.defISODisk(volPath, devId, isUefiEnabled);
 
-                if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) {
+                if (guestCpuArch != null && guestCpuArch.equals("aarch64")) {
                     disk.setBusType(DiskDef.DiskBus.SCSI);
                 }
             } else {
@@ -3107,8 +3156,8 @@
 
         if (vmSpec.getType() != VirtualMachine.Type.User) {
             final DiskDef iso = new DiskDef();
-            iso.defISODisk(_sysvmISOPath);
-            if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) {
+            iso.defISODisk(sysvmISOPath);
+            if (guestCpuArch != null && guestCpuArch.equals("aarch64")) {
                 iso.setBusType(DiskDef.DiskBus.SCSI);
             }
             vm.getDevices().addDevice(iso);
@@ -3120,11 +3169,11 @@
                 final DataTO data = volume.getData();
                 final PrimaryDataStoreTO store = (PrimaryDataStoreTO)data.getDataStore();
                 if (volume.getType() == Volume.Type.ROOT) {
-                    final KVMPhysicalDisk physicalDisk = _storagePoolMgr.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
+                    final KVMPhysicalDisk physicalDisk = storagePoolManager.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
                     final FilesystemDef rootFs = new FilesystemDef(physicalDisk.getPath(), "/");
                     vm.getDevices().addDevice(rootFs);
                 } else if (volume.getType() == Volume.Type.DATADISK) {
-                    final KVMPhysicalDisk physicalDisk = _storagePoolMgr.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
+                    final KVMPhysicalDisk physicalDisk = storagePoolManager.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
                     final KVMStoragePool pool = physicalDisk.getPool();
                     if(StoragePoolType.RBD.equals(pool.getType())) {
                         final int devId = volume.getDiskSeq().intValue();
@@ -3145,7 +3194,7 @@
     }
 
     private KVMPhysicalDisk getPhysicalDiskPrimaryStore(PrimaryDataStoreTO primaryDataStoreTO, DataTO data) {
-        KVMStoragePool storagePool = _storagePoolMgr.getStoragePool(primaryDataStoreTO.getPoolType(), primaryDataStoreTO.getUuid());
+        KVMStoragePool storagePool = storagePoolManager.getStoragePool(primaryDataStoreTO.getPoolType(), primaryDataStoreTO.getUuid());
         return storagePool.getPhysicalDisk(data.getPath());
     }
 
@@ -3226,7 +3275,7 @@
         final int index = volPath.lastIndexOf("/");
         final String volDir = volPath.substring(0, index);
         final String volName = volPath.substring(index + 1);
-        final KVMStoragePool storage = _storagePoolMgr.getStoragePoolByURI(volDir);
+        final KVMStoragePool storage = storagePoolManager.getStoragePoolByURI(volDir);
         return storage.getPhysicalDisk(volName);
     }
 
@@ -3269,16 +3318,20 @@
         }
     }
 
-    private void createVif(final LibvirtVMDef vm, final NicTO nic, final String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException {
+    private void createVif(final LibvirtVMDef vm, final VirtualMachineTO vmSpec, final NicTO nic, final String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException {
         if (vm.getDevices() == null) {
             s_logger.error("LibvirtVMDef object get devices with null result");
             throw new InternalErrorException("LibvirtVMDef object get devices with null result");
         }
-        vm.getDevices().addDevice(getVifDriver(nic.getType(), nic.getName()).plug(nic, vm.getPlatformEmulator(), nicAdapter, extraConfig));
+        final InterfaceDef interfaceDef = getVifDriver(nic.getType(), nic.getName()).plug(nic, vm.getPlatformEmulator(), nicAdapter, extraConfig);
+        if (vmSpec.getDetails() != null) {
+            setInterfaceDefQueueSettings(vmSpec.getDetails(), vmSpec.getCpus(), interfaceDef);
+        }
+        vm.getDevices().addDevice(interfaceDef);
     }
 
     public boolean cleanupDisk(Map<String, String> volumeToDisconnect) {
-        return _storagePoolMgr.disconnectPhysicalDisk(volumeToDisconnect);
+        return storagePoolManager.disconnectPhysicalDisk(volumeToDisconnect);
     }
 
     public boolean cleanupDisk(final DiskDef disk) {
@@ -3294,11 +3347,11 @@
             return true;
         }
 
-        return _storagePoolMgr.disconnectPhysicalDiskByPath(path);
+        return storagePoolManager.disconnectPhysicalDiskByPath(path);
     }
 
     protected KVMStoragePoolManager getPoolManager() {
-        return _storagePoolMgr;
+        return storagePoolManager;
     }
 
     public void detachAndAttachConfigDriveISO(final Connect conn, final String vmName) {
@@ -3334,7 +3387,7 @@
             final int index = isoPath.lastIndexOf("/");
             final String path = isoPath.substring(0, index);
             final String name = isoPath.substring(index + 1);
-            final KVMStoragePool secondaryPool = _storagePoolMgr.getStoragePoolByURI(path);
+            final KVMStoragePool secondaryPool = storagePoolManager.getStoragePoolByURI(path);
             final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name);
             isoPath = isoVol.getPath();
 
@@ -3492,13 +3545,54 @@
 
     @Override
     public PingCommand getCurrentStatus(final long id) {
-
-        if (!_canBridgeFirewall) {
-            return new PingRoutingCommand(com.cloud.host.Host.Type.Routing, id, this.getHostVmStateReport());
+        PingRoutingCommand pingRoutingCommand;
+        if (!canBridgeFirewall) {
+            pingRoutingCommand = new PingRoutingCommand(com.cloud.host.Host.Type.Routing, id, this.getHostVmStateReport());
         } else {
             final HashMap<String, Pair<Long, Long>> nwGrpStates = syncNetworkGroups(id);
-            return new PingRoutingWithNwGroupsCommand(getType(), id, this.getHostVmStateReport(), nwGrpStates);
+            pingRoutingCommand = new PingRoutingWithNwGroupsCommand(getType(), id, this.getHostVmStateReport(), nwGrpStates);
         }
+        HealthCheckResult healthCheckResult = getHostHealthCheckResult();
+        if (healthCheckResult != HealthCheckResult.IGNORE) {
+            pingRoutingCommand.setHostHealthCheckResult(healthCheckResult == HealthCheckResult.SUCCESS);
+        }
+        return pingRoutingCommand;
+    }
+
+    /**
+     * The health check result is true, if the script is executed successfully and the exit code is 0
+     * The health check result is false, if the script is executed successfully and the exit code is 1
+     * The health check result is null, if
+     * - Script file is not specified, or
+     * - Script file does not exist, or
+     * - Script file is not accessible by the user of the cloudstack-agent process, or
+     * - Script file is not executable
+     * - There are errors when the script is executed (exit codes other than 0 or 1)
+     */
+    private HealthCheckResult getHostHealthCheckResult() {
+        if (StringUtils.isBlank(hostHealthCheckScriptPath)) {
+            s_logger.debug("Host health check script path is not specified");
+            return HealthCheckResult.IGNORE;
+        }
+        File script = new File(hostHealthCheckScriptPath);
+        if (!script.exists() || !script.isFile() || !script.canExecute()) {
+            s_logger.warn(String.format("The host health check script file set at: %s cannot be executed, " +
+                            "reason: %s", hostHealthCheckScriptPath,
+                    !script.exists() ? "file does not exist" : "please check file permissions to execute this file"));
+            return HealthCheckResult.IGNORE;
+        }
+        int exitCode = executeBashScriptAndRetrieveExitValue(hostHealthCheckScriptPath);
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("Host health check script exit code: %s", exitCode));
+        }
+        return retrieveHealthCheckResultFromExitCode(exitCode);
+    }
+
+    private HealthCheckResult retrieveHealthCheckResultFromExitCode(int exitCode) {
+        if (exitCode != 0 && exitCode != 1) {
+            return HealthCheckResult.IGNORE;
+        }
+        return exitCode == 0 ? HealthCheckResult.SUCCESS : HealthCheckResult.FAILURE;
     }
 
     @Override
@@ -3507,7 +3601,7 @@
     }
 
     private Map<String, String> getVersionStrings() {
-        final Script command = new Script(_versionstringpath, _timeout, s_logger);
+        final Script command = new Script(versionStringPath, timeout, s_logger);
         final KeyValueInterpreter kvi = new KeyValueInterpreter();
         final String result = command.execute(kvi);
         if (result == null) {
@@ -3519,8 +3613,8 @@
 
     @Override
     public StartupCommand[] initialize() {
-        final KVMHostInfo info = new KVMHostInfo(_dom0MinMem, _dom0OvercommitMem, _manualCpuSpeed);
-        calculateHostCpuMaxCapacity(info.getCpus(), info.getCpuSpeed());
+        final KVMHostInfo info = new KVMHostInfo(dom0MinMem, dom0OvercommitMem, manualCpuSpeed, dom0MinCpuCores);
+        calculateHostCpuMaxCapacity(info.getAllocatableCpus(), info.getCpuSpeed());
 
         String capabilities = String.join(",", info.getCapabilities());
         if (dpdkSupport) {
@@ -3528,21 +3622,25 @@
         }
 
         final StartupRoutingCommand cmd =
-                new StartupRoutingCommand(info.getCpus(), info.getCpuSpeed(), info.getTotalMemory(), info.getReservedMemory(), capabilities, _hypervisorType,
+                new StartupRoutingCommand(info.getAllocatableCpus(), info.getCpuSpeed(), info.getTotalMemory(), info.getReservedMemory(), capabilities, hypervisorType,
                         RouterPrivateIpStrategy.HostLocal);
         cmd.setCpuSockets(info.getCpuSockets());
         fillNetworkInformation(cmd);
-        _privateIp = cmd.getPrivateIpAddress();
+        privateIp = cmd.getPrivateIpAddress();
         cmd.getHostDetails().putAll(getVersionStrings());
         cmd.getHostDetails().put(KeyStoreUtils.SECURED, String.valueOf(isHostSecured()).toLowerCase());
-        cmd.setPool(_pool);
-        cmd.setCluster(_clusterId);
-        cmd.setGatewayIpAddress(_localGateway);
+        cmd.setPool(pool);
+        cmd.setCluster(clusterId);
+        cmd.setGatewayIpAddress(localGateway);
         cmd.setIqn(getIqn());
         cmd.getHostDetails().put(HOST_VOLUME_ENCRYPTION, String.valueOf(hostSupportsVolumeEncryption()));
+        HealthCheckResult healthCheckResult = getHostHealthCheckResult();
+        if (healthCheckResult != HealthCheckResult.IGNORE) {
+            cmd.setHostHealthCheckResult(healthCheckResult == HealthCheckResult.SUCCESS);
+        }
 
         if (cmd.getHostDetails().containsKey("Host.OS")) {
-            _hostDistro = cmd.getHostDetails().get("Host.OS");
+            hostDistro = cmd.getHostDetails().get("Host.OS");
         }
 
         List<StartupCommand> startupCommands = new ArrayList<>();
@@ -3593,7 +3691,7 @@
     private StartupStorageCommand createLocalStoragePool(String localStoragePath, String localStorageUUID, StartupRoutingCommand cmd) {
         StartupStorageCommand sscmd = null;
         try {
-            final KVMStoragePool localStoragePool = _storagePoolMgr.createStoragePool(localStorageUUID, "localhost", -1, localStoragePath, "", StoragePoolType.Filesystem);
+            final KVMStoragePool localStoragePool = storagePoolManager.createStoragePool(localStorageUUID, "localhost", -1, localStoragePath, "", StoragePoolType.Filesystem);
             final com.cloud.agent.api.StoragePoolInfo pi =
                     new com.cloud.agent.api.StoragePoolInfo(localStoragePool.getUuid(), cmd.getPrivateIpAddress(), localStoragePath, localStoragePath,
                             StoragePoolType.Filesystem, localStoragePool.getCapacity(), localStoragePool.getAvailable());
@@ -3601,7 +3699,7 @@
             sscmd = new StartupStorageCommand();
             sscmd.setPoolInfo(pi);
             sscmd.setGuid(pi.getUuid());
-            sscmd.setDataCenter(_dcId);
+            sscmd.setDataCenter(dcId);
             sscmd.setResourceType(Storage.StorageResourceType.STORAGE_POOL);
         } catch (final CloudRuntimeException e) {
             s_logger.debug("Unable to initialize local storage pool: " + e);
@@ -3640,7 +3738,39 @@
         }
     }
 
-    protected List<String> getAllVmNames(final Connect conn) {
+    /**
+     * Given a disk path on KVM host, attempts to find source host and path using mount command
+     * @param diskPath KVM host path for virtual disk
+     * @return Pair with IP of host and path
+     */
+    public Pair<String, String> getSourceHostPath(String diskPath) {
+        String sourceHostIp = null;
+        String sourcePath = null;
+        try {
+            String mountResult = Script.runSimpleBashScript("mount | grep \"" + diskPath + "\"");
+            s_logger.debug("Got mount result for " + diskPath + "\n\n" + mountResult);
+            if (StringUtils.isNotEmpty(mountResult)) {
+                String[] res = mountResult.strip().split(" ");
+                if (res[0].contains(":")) {
+                    res = res[0].split(":");
+                    sourceHostIp = res[0].strip();
+                    sourcePath = res[1].strip();
+                } else {
+                    // Assume local storage
+                    sourceHostIp = getPrivateIp();
+                    sourcePath = diskPath;
+                }
+            }
+            if (StringUtils.isNotEmpty(sourceHostIp) && StringUtils.isNotEmpty(sourcePath)) {
+                return new Pair<>(sourceHostIp, sourcePath);
+            }
+        } catch (Exception ex) {
+            s_logger.warn("Failed to list source host and IP for " + diskPath + ex.toString());
+        }
+        return null;
+    }
+
+    public List<String> getAllVmNames(final Connect conn) {
         final ArrayList<String> la = new ArrayList<String>();
         try {
             final String names[] = conn.listDefinedDomains();
@@ -3684,7 +3814,7 @@
         final HashMap<String, HostVmStateReportEntry> vmStates = new HashMap<String, HostVmStateReportEntry>();
         Connect conn = null;
 
-        if (_hypervisorType == HypervisorType.LXC) {
+        if (hypervisorType == HypervisorType.LXC) {
             try {
                 conn = LibvirtConnection.getConnectionByType(HypervisorType.LXC.toString());
                 vmStates.putAll(getHostVmStateReport(conn));
@@ -3695,7 +3825,7 @@
             }
         }
 
-        if (_hypervisorType == HypervisorType.KVM) {
+        if (hypervisorType == HypervisorType.KVM) {
             try {
                 conn = LibvirtConnection.getConnectionByType(HypervisorType.KVM.toString());
                 vmStates.putAll(getHostVmStateReport(conn));
@@ -3904,7 +4034,7 @@
                     return null;
                 }
                 dm.shutdown();
-                int retry = _stopTimeout / 2000;
+                int retry = stopTimeout / 2000;
                 /* Wait for the domain gets into shutoff state. When it does
                    the dm object will no longer work, so we need to catch it. */
                 try {
@@ -4003,7 +4133,7 @@
     }
 
     public boolean isCentosHost() {
-        if (_hvVersion <= 9) {
+        if (hvVersion <= 9) {
             return true;
         } else {
             return false;
@@ -4060,7 +4190,7 @@
             return DiskDef.DiskBus.VIRTIO;
         } else if (isUefiEnabled && StringUtils.startsWithAny(platformEmulator, "Windows", "Other")) {
             return DiskDef.DiskBus.SATA;
-        } else if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) {
+        } else if (guestCpuArch != null && guestCpuArch.equals("aarch64")) {
             return DiskDef.DiskBus.SCSI;
         } else {
             return DiskDef.DiskBus.IDE;
@@ -4130,7 +4260,7 @@
     }
 
     private Script createScript(final String script) {
-        final Script command = new Script("/bin/bash", _timeout, s_logger);
+        final Script command = new Script("/bin/bash", timeout, s_logger);
         command.add("-c");
         command.add(script);
         return command;
@@ -4233,14 +4363,14 @@
     }
 
     private class VmStats {
-        long _usedTime;
-        long _tx;
-        long _rx;
-        long _ioRead;
-        long _ioWrote;
-        long _bytesRead;
-        long _bytesWrote;
-        Calendar _timestamp;
+        long usedTime;
+        long tx;
+        long rx;
+        long ioRead;
+        long ioWrote;
+        long bytesRead;
+        long bytesWrote;
+        Calendar timestamp;
     }
 
     public VmStatsEntry getVmStat(final Connect conn, final String vmName) throws LibvirtException {
@@ -4265,12 +4395,12 @@
 
             final Calendar now = Calendar.getInstance();
 
-            oldStats = _vmStats.get(vmName);
+            oldStats = vmStats.get(vmName);
 
             long elapsedTime = 0;
             if (oldStats != null) {
-                elapsedTime = now.getTimeInMillis() - oldStats._timestamp.getTimeInMillis();
-                double utilization = (info.cpuTime - oldStats._usedTime) / ((double)elapsedTime * 1000000);
+                elapsedTime = now.getTimeInMillis() - oldStats.timestamp.getTimeInMillis();
+                double utilization = (info.cpuTime - oldStats.usedTime) / ((double)elapsedTime * 1000000);
 
                 utilization = utilization / info.nrVirtCpu;
                 if (utilization > 0) {
@@ -4290,11 +4420,11 @@
             }
 
             if (oldStats != null) {
-                final double deltarx = rx - oldStats._rx;
+                final double deltarx = rx - oldStats.rx;
                 if (deltarx > 0) {
                     stats.setNetworkReadKBs(deltarx / 1024);
                 }
-                final double deltatx = tx - oldStats._tx;
+                final double deltatx = tx - oldStats.tx;
                 if (deltatx > 0) {
                     stats.setNetworkWriteKBs(deltatx / 1024);
                 }
@@ -4318,19 +4448,19 @@
             }
 
             if (oldStats != null) {
-                final long deltaiord = io_rd - oldStats._ioRead;
+                final long deltaiord = io_rd - oldStats.ioRead;
                 if (deltaiord > 0) {
                     stats.setDiskReadIOs(deltaiord);
                 }
-                final long deltaiowr = io_wr - oldStats._ioWrote;
+                final long deltaiowr = io_wr - oldStats.ioWrote;
                 if (deltaiowr > 0) {
                     stats.setDiskWriteIOs(deltaiowr);
                 }
-                final double deltabytesrd = bytes_rd - oldStats._bytesRead;
+                final double deltabytesrd = bytes_rd - oldStats.bytesRead;
                 if (deltabytesrd > 0) {
                     stats.setDiskReadKBs(deltabytesrd / 1024);
                 }
-                final double deltabyteswr = bytes_wr - oldStats._bytesWrote;
+                final double deltabyteswr = bytes_wr - oldStats.bytesWrote;
                 if (deltabyteswr > 0) {
                     stats.setDiskWriteKBs(deltabyteswr / 1024);
                 }
@@ -4338,15 +4468,15 @@
 
             /* save to Hashmap */
             final VmStats newStat = new VmStats();
-            newStat._usedTime = info.cpuTime;
-            newStat._rx = rx;
-            newStat._tx = tx;
-            newStat._ioRead = io_rd;
-            newStat._ioWrote = io_wr;
-            newStat._bytesRead = bytes_rd;
-            newStat._bytesWrote = bytes_wr;
-            newStat._timestamp = now;
-            _vmStats.put(vmName, newStat);
+            newStat.usedTime = info.cpuTime;
+            newStat.rx = rx;
+            newStat.tx = tx;
+            newStat.ioRead = io_rd;
+            newStat.ioWrote = io_wr;
+            newStat.bytesRead = bytes_rd;
+            newStat.bytesWrote = bytes_wr;
+            newStat.timestamp = now;
+            vmStats.put(vmName, newStat);
             return stats;
         } finally {
             if (dm != null) {
@@ -4389,7 +4519,7 @@
     }
 
     private boolean canBridgeFirewall(final String prvNic) {
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("can_bridge_firewall");
         cmd.add("--privnic", prvNic);
         final String result = cmd.execute();
@@ -4400,7 +4530,7 @@
     }
 
     public boolean destroyNetworkRulesForVM(final Connect conn, final String vmName) {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
         String vif = null;
@@ -4409,7 +4539,7 @@
             final InterfaceDef intf = intfs.get(0);
             vif = intf.getDevName();
         }
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("destroy_network_rules_for_vm");
         cmd.add("--vmname", vmName);
         if (vif != null) {
@@ -4432,7 +4562,7 @@
      *      false  : If failure
      */
     public boolean destroyNetworkRulesForNic(final Connect conn, final String vmName, final NicTO nic) {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
         final List<String> nicSecIps = nic.getNicSecIps();
@@ -4455,7 +4585,7 @@
         final String brname = intf.getBrName();
         final String vif = intf.getDevName();
 
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("destroy_network_rules_for_vm");
         cmd.add("--vmname", vmName);
         if (nic.getIp() != null) {
@@ -4533,7 +4663,7 @@
     }
 
     public boolean defaultNetworkRules(final Connect conn, final String vmName, final NicTO nic, final Long vmId, final String secIpStr, final boolean isFirstNic, final boolean checkBeforeApply) {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
 
@@ -4546,7 +4676,7 @@
         final String brname = intf.getBrName();
         final String vif = intf.getDevName();
 
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("default_network_rules");
         cmd.add("--vmname", vmName);
         cmd.add("--vmid", vmId.toString());
@@ -4574,7 +4704,7 @@
     }
 
     protected boolean post_default_network_rules(final Connect conn, final String vmName, final NicTO nic, final Long vmId, final InetAddress dhcpServerIp, final String hostIp, final String hostMacAddr) {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
 
@@ -4587,7 +4717,7 @@
         final String brname = intf.getBrName();
         final String vif = intf.getDevName();
 
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("post_default_network_rules");
         cmd.add("--vmname", vmName);
         cmd.add("--vmid", vmId.toString());
@@ -4609,14 +4739,14 @@
     }
 
     public boolean configureDefaultNetworkRulesForSystemVm(final Connect conn, final String vmName) {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
 
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("default_network_rules_systemvm");
         cmd.add("--vmname", vmName);
-        cmd.add("--localbrname", _linkLocalBridgeName);
+        cmd.add("--localbrname", linkLocalBridgeName);
         final String result = cmd.execute();
         if (result != null) {
             return false;
@@ -4624,14 +4754,20 @@
         return true;
     }
 
+    public Answer listFilesAtPath(ListDataStoreObjectsCommand command) {
+        DataStoreTO store = command.getStore();
+        KVMStoragePool storagePool = storagePoolManager.getStoragePool(StoragePoolType.NetworkFilesystem, store.getUuid());
+        return listFilesAtPath(storagePool.getLocalPath(), command.getPath(), command.getStartIndex(), command.getPageSize());
+    }
+
     public boolean addNetworkRules(final String vmName, final String vmId, final String guestIP, final String guestIP6, final String sig, final String seq, final String mac, final String rules, final String vif, final String brname,
                                    final String secIps) {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
 
         final String newRules = rules.replace(" ", ";");
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("add_network_rules");
         cmd.add("--vmname", vmName);
         cmd.add("--vmid", vmId);
@@ -4657,11 +4793,11 @@
 
     public boolean configureNetworkRulesVMSecondaryIP(final Connect conn, final String vmName, final String vmMac, final String secIp, final String action) {
 
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
 
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("network_rules_vmSecondaryIp");
         cmd.add("--vmname", vmName);
         cmd.add("--vmmac", vmMac);
@@ -4677,7 +4813,7 @@
 
     public boolean setupTungstenVRouter(final String oper, final String inf, final String subnet, final String route,
         final String vrf) {
-        final Script cmd = new Script(setupTungstenVrouterPath, _timeout, s_logger);
+        final Script cmd = new Script(setupTungstenVrouterPath, timeout, s_logger);
         cmd.add(oper);
         cmd.add(inf);
         cmd.add(subnet);
@@ -4690,7 +4826,7 @@
 
     public boolean updateTungstenLoadbalancerStats(final String lbUuid, final String lbStatsPort,
         final String lbStatsUri, final String lbStatsAuth) {
-        final Script cmd = new Script(updateTungstenLoadbalancerStatsPath, _timeout, s_logger);
+        final Script cmd = new Script(updateTungstenLoadbalancerStatsPath, timeout, s_logger);
         cmd.add(lbUuid);
         cmd.add(lbStatsPort);
         cmd.add(lbStatsUri);
@@ -4702,7 +4838,7 @@
 
     public boolean updateTungstenLoadbalancerSsl(final String lbUuid, final String sslCertName,
         final String certificateKey, final String privateKey, final String privateIp, final String port) {
-        final Script cmd = new Script(updateTungstenLoadbalancerSslPath, _timeout, s_logger);
+        final Script cmd = new Script(updateTungstenLoadbalancerSslPath, timeout, s_logger);
         cmd.add(lbUuid);
         cmd.add(sslCertName);
         cmd.add(certificateKey);
@@ -4715,7 +4851,7 @@
     }
 
     public boolean setupTfRoute(final String privateIpAddress, final String fromNetwork, final String toNetwork) {
-        final Script setupTfRouteScript = new Script(_routerProxyPath, _timeout, s_logger);
+        final Script setupTfRouteScript = new Script(routerProxyPath, timeout, s_logger);
         setupTfRouteScript.add("setup_tf_route.py");
         setupTfRouteScript.add(privateIpAddress);
         setupTfRouteScript.add(fromNetwork);
@@ -4731,10 +4867,10 @@
     }
 
     public boolean cleanupRules() {
-        if (!_canBridgeFirewall) {
+        if (!canBridgeFirewall) {
             return false;
         }
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("cleanup_rules");
         final String result = cmd.execute();
         if (result != null) {
@@ -4744,7 +4880,7 @@
     }
 
     public String getRuleLogsForVms() {
-        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
+        final Script cmd = new Script(securityGroupPath, timeout, s_logger);
         cmd.add("get_rule_logs_for_vms");
         final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
         final String result = cmd.execute(parser);
@@ -4833,7 +4969,7 @@
     }
 
     public HypervisorType getHypervisorType(){
-        return _hypervisorType;
+        return hypervisorType;
     }
 
     public String mapRbdDevice(final KVMPhysicalDisk disk){
@@ -4948,7 +5084,7 @@
     }
 
     public String getHostDistro() {
-        return _hostDistro;
+        return hostDistro;
     }
 
     public boolean isHostSecured() {
@@ -4960,7 +5096,7 @@
 
         // Test for libvirt TLS configuration
         try {
-            new Connect(String.format("qemu+tls://%s/system", _privateIp));
+            new Connect(String.format("qemu+tls://%s/system", privateIp));
         } catch (final LibvirtException ignored) {
             return false;
         }
@@ -5170,4 +5306,51 @@
     public static String generateSecretUUIDFromString(String seed) {
         return UUID.nameUUIDFromBytes(seed.getBytes()).toString();
     }
+
+    public void setInterfaceDefQueueSettings(Map<String, String> details, Integer cpus, InterfaceDef interfaceDef) {
+        String nicMultiqueueNumber = details.get(VmDetailConstants.NIC_MULTIQUEUE_NUMBER);
+        if (nicMultiqueueNumber != null) {
+            try {
+                Integer nicMultiqueueNumberInteger = Integer.valueOf(nicMultiqueueNumber);
+                if (nicMultiqueueNumberInteger == InterfaceDef.MULTI_QUEUE_NUMBER_MEANS_CPU_CORES) {
+                    if (cpus != null) {
+                        interfaceDef.setMultiQueueNumber(cpus);
+                    }
+                } else {
+                    interfaceDef.setMultiQueueNumber(nicMultiqueueNumberInteger);
+                }
+            } catch (NumberFormatException ex) {
+                s_logger.warn(String.format("VM details %s is not a valid integer value %s", VmDetailConstants.NIC_MULTIQUEUE_NUMBER, nicMultiqueueNumber));
+            }
+        }
+        String nicPackedEnabled = details.get(VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED);
+        if (nicPackedEnabled != null) {
+            try {
+                interfaceDef.setPackedVirtQueues(Boolean.valueOf(nicPackedEnabled));
+            } catch (NumberFormatException ex) {
+                s_logger.warn(String.format("VM details %s is not a valid Boolean value %s", VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, nicPackedEnabled));
+            }
+        }
+    }
+
+    /*
+    Scp volume from remote host to local directory
+     */
+    public String copyVolume(String srcIp, String username, String password, String localDir, String remoteFile, String tmpPath) {
+        try {
+            String outputFile = UUID.randomUUID().toString();
+            StringBuilder command = new StringBuilder("qemu-img convert -O qcow2 ");
+            command.append(remoteFile);
+            command.append(" "+tmpPath);
+            command.append(outputFile);
+            s_logger.debug("Converting remoteFile: "+remoteFile);
+            SshHelper.sshExecute(srcIp, 22, username, null, password, command.toString());
+            s_logger.debug("Copying remoteFile to: "+localDir);
+            SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, tmpPath+outputFile);
+            s_logger.debug("Successfully copyied remoteFile to: "+localDir+"/"+outputFile);
+            return outputFile;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java
index c70a72f..0fa012b 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java
@@ -19,8 +19,11 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 import org.apache.log4j.Logger;
 import org.libvirt.Connect;
+import org.libvirt.Library;
 import org.libvirt.LibvirtException;
 
 import com.cloud.hypervisor.Hypervisor;
@@ -32,17 +35,20 @@
 
     static private Connect s_connection;
     static private String s_hypervisorURI;
+    static private Thread libvirtEventThread;
 
     static public Connect getConnection() throws LibvirtException {
         return getConnection(s_hypervisorURI);
     }
 
-    static public Connect getConnection(String hypervisorURI) throws LibvirtException {
+    static synchronized public Connect getConnection(String hypervisorURI) throws LibvirtException {
         s_logger.debug("Looking for libvirtd connection at: " + hypervisorURI);
         Connect conn = s_connections.get(hypervisorURI);
 
         if (conn == null) {
             s_logger.info("No existing libvirtd connection found. Opening a new one");
+
+            setupEventListener();
             conn = new Connect(hypervisorURI, false);
             s_logger.debug("Successfully connected to libvirt at: " + hypervisorURI);
             s_connections.put(hypervisorURI, conn);
@@ -51,7 +57,15 @@
                 conn.getVersion();
             } catch (LibvirtException e) {
                 s_logger.error("Connection with libvirtd is broken: " + e.getMessage());
+
+                try {
+                    conn.close();
+                } catch (LibvirtException closeEx) {
+                    s_logger.debug("Ignoring error while trying to close broken connection:" + closeEx.getMessage());
+                }
+
                 s_logger.debug("Opening a new libvirtd connection to: " + hypervisorURI);
+                setupEventListener();
                 conn = new Connect(hypervisorURI, false);
                 s_connections.put(hypervisorURI, conn);
             }
@@ -88,10 +102,49 @@
     }
 
     static String getHypervisorURI(String hypervisorType) {
+        String uri = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HYPERVISOR_URI);
+        if (uri != null) {
+            return uri;
+        }
+
         if ("LXC".equalsIgnoreCase(hypervisorType)) {
             return "lxc:///";
-        } else {
-            return "qemu:///system";
+        }
+
+        return "qemu:///system";
+    }
+
+    /**
+     * Set up Libvirt event handling and polling. This is not specific to a connection object instance, but needs
+     * to be done prior to creating connections. See the Libvirt documentation for virEventRegisterDefaultImpl and
+     * virEventRunDefaultImpl or the libvirt-java Library Javadoc for more information.
+     * @throws LibvirtException
+     */
+    private static synchronized void setupEventListener() throws LibvirtException {
+        if (!AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_EVENTS_ENABLED)) {
+            s_logger.debug("Libvirt event listening is disabled, not setting up event loop");
+            return;
+        }
+
+        if (libvirtEventThread == null || !libvirtEventThread.isAlive()) {
+            // Registers a default event loop, must be called before connecting to hypervisor
+            Library.initEventLoop();
+            libvirtEventThread = new Thread(() -> {
+                while (true) {
+                    try {
+                        // This blocking call contains a loop of its own that will process events until the event loop is stopped or exception is thrown.
+                        Library.runEventLoop();
+                    } catch (LibvirtException e) {
+                        s_logger.error("LibvirtException was thrown in event loop: ", e);
+                    } catch (InterruptedException e) {
+                        s_logger.error("Libvirt event loop was interrupted: ", e);
+                    }
+                }
+            });
+
+            // Process events in separate thread. Failure to run event loop regularly will cause connections to close due to keepalive timeout.
+            libvirtEventThread.setDaemon(true);
+            libvirtEventThread.start();
         }
     }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainListener.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainListener.java
new file mode 100644
index 0000000..281de01
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainListener.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.cloud.hypervisor.kvm.resource;
+
+import com.cloud.resource.AgentStatusUpdater;
+import org.apache.log4j.Logger;
+import org.libvirt.Domain;
+import org.libvirt.LibvirtException;
+import org.libvirt.event.DomainEvent;
+import org.libvirt.event.DomainEventDetail;
+import org.libvirt.event.LifecycleListener;
+import org.libvirt.event.StoppedDetail;
+
+public class LibvirtDomainListener implements LifecycleListener {
+    private static final Logger LOGGER = Logger.getLogger(LibvirtDomainListener.class);
+
+    private final AgentStatusUpdater agentStatusUpdater;
+
+    public LibvirtDomainListener(AgentStatusUpdater updater) {
+        agentStatusUpdater = updater;
+    }
+
+    public int onLifecycleChange(Domain domain, DomainEvent domainEvent) {
+        try {
+            LOGGER.debug(String.format("Got event lifecycle change on Domain %s, event %s", domain.getName(), domainEvent));
+            if (domainEvent != null) {
+                switch (domainEvent.getType()) {
+                    case STOPPED:
+                        /* libvirt-destroyed VMs have detail StoppedDetail.DESTROYED, self shutdown guests are StoppedDetail.SHUTDOWN
+                         * Checking for this helps us differentiate between events where cloudstack or admin stopped the VM vs guest
+                         * initiated, and avoid pushing extra updates for actions we are initiating without a need for extra tracking */
+                        DomainEventDetail detail = domainEvent.getDetail();
+                        if (StoppedDetail.SHUTDOWN.equals(detail) || StoppedDetail.CRASHED.equals(detail) || StoppedDetail.FAILED.equals(detail)) {
+                            if (agentStatusUpdater != null) {
+                                LOGGER.info("Triggering out of band status update due to completed self-shutdown or crash of VM");
+                                agentStatusUpdater.triggerUpdate();
+                            }
+                        } else {
+                            LOGGER.debug("Event detail: " + detail);
+                        }
+                        break;
+                    default:
+                        LOGGER.debug(String.format("No handling for event %s", domainEvent));
+                }
+            }
+        } catch (LibvirtException e) {
+            LOGGER.error("Libvirt exception while processing lifecycle event", e);
+        } catch (Throwable e) {
+            LOGGER.error("Error during lifecycle", e);
+        }
+        return 0;
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
index 80a35a0..a0dd270 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
@@ -57,8 +57,10 @@
     private final List<ChannelDef> channels = new ArrayList<ChannelDef>();
     private final List<WatchDogDef> watchDogDefs = new ArrayList<WatchDogDef>();
     private Integer vncPort;
+    private  String vncPasswd;
     private String desc;
-
+    private LibvirtVMDef.CpuTuneDef cpuTuneDef;
+    private LibvirtVMDef.CpuModeDef cpuModeDef;
     private String name;
 
     public boolean parseDomainXML(String domXML) {
@@ -252,6 +254,15 @@
                     def.setDpdkOvsPath(ovsPath);
                     def.setInterfaceMode(mode);
                 }
+                String multiQueueNumber = getAttrValue("driver", "queues", nic);
+                if (StringUtils.isNotBlank(multiQueueNumber)) {
+                    def.setMultiQueueNumber(Integer.valueOf(multiQueueNumber));
+                }
+
+                String packedOn = getAttrValue("driver", "packed", nic);
+                if (StringUtils.isNotBlank(packedOn)) {
+                    def.setPackedVirtQueues("on".equalsIgnoreCase(packedOn));
+                }
 
                 if (StringUtils.isNotBlank(slot)) {
                     def.setSlot(Integer.parseInt(slot, 16));
@@ -269,6 +280,14 @@
                 String name = getAttrValue("target", "name", channel);
                 String state = getAttrValue("target", "state", channel);
 
+                if (ChannelDef.ChannelType.valueOf(type.toUpperCase()).equals(ChannelDef.ChannelType.SPICEVMC)) {
+                    continue;
+                }
+
+                if (path == null) {
+                    path = "";
+                }
+
                 ChannelDef def = null;
                 if (StringUtils.isBlank(state)) {
                     def = new ChannelDef(name, ChannelDef.ChannelType.valueOf(type.toUpperCase()), new File(path));
@@ -296,6 +315,12 @@
                         vncPort = null;
                     }
                 }
+
+                String passwd = graphic.getAttribute("passwd");
+                if (passwd != null) {
+                    vncPasswd = passwd;
+                }
+
             }
 
             NodeList rngs = devices.getElementsByTagName("rng");
@@ -308,6 +333,26 @@
                 String period = getAttrValue("rate", "period", rng);
                 if (StringUtils.isAnyEmpty(bytes, period)) {
                     s_logger.debug(String.format("Bytes and period in the rng section should not be null, please check the VM %s", name));
+                }
+
+                if (bytes == null) {
+                    bytes = "0";
+                }
+
+                if (period == null) {
+                    period = "0";
+                }
+
+                if (bytes == null) {
+                    bytes = "0";
+                }
+
+                if (period == null) {
+                    period = "0";
+                }
+
+                if (StringUtils.isEmpty(backendModel)) {
+                    def = new RngDef(path, Integer.parseInt(bytes), Integer.parseInt(period));
                 } else {
                     if (StringUtils.isEmpty(backendModel)) {
                         def = new RngDef(path, Integer.parseInt(bytes), Integer.parseInt(period));
@@ -341,7 +386,8 @@
 
                 watchDogDefs.add(def);
             }
-
+            extractCpuTuneDef(rootElement);
+            extractCpuModeDef(rootElement);
             return true;
         } catch (ParserConfigurationException e) {
             s_logger.debug(e.toString());
@@ -373,6 +419,9 @@
     }
 
     private static String getTagValue(String tag, Element eElement) {
+        if (eElement == null) {
+            return null;
+        }
         NodeList tagNodeList = eElement.getElementsByTagName(tag);
         if (tagNodeList == null || tagNodeList.getLength() == 0) {
             return null;
@@ -380,14 +429,20 @@
 
         NodeList nlList = tagNodeList.item(0).getChildNodes();
 
+        if (nlList == null || nlList.getLength() == 0) {
+            return null;
+        }
         Node nValue = nlList.item(0);
 
         return nValue.getNodeValue();
     }
 
     private static String getAttrValue(String tag, String attr, Element eElement) {
+        if (eElement == null) {
+            return null;
+        }
         NodeList tagNode = eElement.getElementsByTagName(tag);
-        if (tagNode.getLength() == 0) {
+        if (tag == null || tagNode.getLength() == 0) {
             return null;
         }
         Element node = (Element)tagNode.item(0);
@@ -402,6 +457,10 @@
         return interfaces;
     }
 
+    public String getVncPasswd() {
+        return vncPasswd;
+    }
+
     public MemBalloonDef getMemBalloon() {
         return memBalloonDef;
     }
@@ -429,4 +488,65 @@
     public String getName() {
         return name;
     }
+
+    public LibvirtVMDef.CpuTuneDef getCpuTuneDef() {
+        return cpuTuneDef;
+    }
+
+    public LibvirtVMDef.CpuModeDef getCpuModeDef() {
+        return cpuModeDef;
+    }
+
+    private void extractCpuTuneDef(final Element rootElement) {
+        NodeList cpuTunesList = rootElement.getElementsByTagName("cputune");
+        if (cpuTunesList.getLength() > 0) {
+            cpuTuneDef = new LibvirtVMDef.CpuTuneDef();
+            final Element cpuTuneDefElement = (Element) cpuTunesList.item(0);
+            final String cpuShares = getTagValue("shares", cpuTuneDefElement);
+            if (StringUtils.isNotBlank(cpuShares)) {
+                cpuTuneDef.setShares((Integer.parseInt(cpuShares)));
+            }
+
+            final String quota = getTagValue("quota", cpuTuneDefElement);
+            if (StringUtils.isNotBlank(quota)) {
+                cpuTuneDef.setQuota((Integer.parseInt(quota)));
+            }
+
+            final String period = getTagValue("period", cpuTuneDefElement);
+            if (StringUtils.isNotBlank(period)) {
+                cpuTuneDef.setPeriod((Integer.parseInt(period)));
+            }
+        }
+    }
+
+    private void extractCpuModeDef(final Element rootElement){
+        NodeList cpuModeList = rootElement.getElementsByTagName("cpu");
+        if (cpuModeList.getLength() > 0){
+            cpuModeDef = new LibvirtVMDef.CpuModeDef();
+            final Element cpuModeDefElement = (Element) cpuModeList.item(0);
+            final String cpuModel = getTagValue("model", cpuModeDefElement);
+            if (StringUtils.isNotBlank(cpuModel)){
+                cpuModeDef.setModel(cpuModel);
+            }
+            NodeList cpuFeatures = cpuModeDefElement.getElementsByTagName("features");
+            if (cpuFeatures.getLength() > 0) {
+                final ArrayList<String> features = new ArrayList<>(cpuFeatures.getLength());
+                for (int i = 0; i < cpuFeatures.getLength(); i++) {
+                    final Element feature = (Element)cpuFeatures.item(i);
+                    final String policy = feature.getAttribute("policy");
+                    String featureName = feature.getAttribute("name");
+                    if ("disable".equals(policy)) {
+                        featureName = "-" + featureName;
+                    }
+                    features.add(featureName);
+                }
+                cpuModeDef.setFeatures(features);
+            }
+            final String sockets = getAttrValue("topology", "sockets", cpuModeDefElement);
+            final String cores = getAttrValue("topology", "cores", cpuModeDefElement);
+            if (StringUtils.isNotBlank(sockets) && StringUtils.isNotBlank(cores)) {
+                cpuModeDef.setTopology(Integer.parseInt(cores), Integer.parseInt(sockets));
+            }
+        }
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
index db179f9..1165556 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
@@ -609,7 +609,7 @@
             }
         }
 
-        enum DiskType {
+        public enum DiskType {
             FILE("file"), BLOCK("block"), DIRECTROY("dir"), NETWORK("network");
             String _diskType;
 
@@ -1072,6 +1072,18 @@
 
         public LibvirtDiskEncryptDetails getLibvirtDiskEncryptDetails() { return this.encryptDetails; }
 
+        public String getSourceHost() {
+            return _sourceHost;
+        }
+
+        public int getSourceHostPort() {
+            return _sourcePort;
+        }
+
+        public String getSourcePath() {
+            return _sourcePath;
+        }
+
         @Override
         public String toString() {
             StringBuilder diskBuilder = new StringBuilder();
@@ -1280,6 +1292,8 @@
             DIRECT_ATTACHED_WITHOUT_DHCP, DIRECT_ATTACHED_WITH_DHCP, VNET, VLAN;
         }
 
+        public static final int MULTI_QUEUE_NUMBER_MEANS_CPU_CORES = -1;
+
         private GuestNetType _netType; /*
          * bridge, ethernet, network, user,
          * internal, vhostuser
@@ -1305,6 +1319,8 @@
         private String _interfaceMode;
         private String _userIp4Network;
         private Integer _userIp4Prefix;
+        private Integer _multiQueueNumber;
+        private Boolean _packedVirtQueues;
 
         public void defBridgeNet(String brName, String targetBrName, String macAddr, NicModel model) {
             defBridgeNet(brName, targetBrName, macAddr, model, 0);
@@ -1493,6 +1509,14 @@
             _interfaceMode = mode;
         }
 
+        public void setMultiQueueNumber(Integer multiQueueNumber) {
+            this._multiQueueNumber = multiQueueNumber;
+        }
+
+        public void setPackedVirtQueues(Boolean packedVirtQueues) {
+            this._packedVirtQueues = packedVirtQueues;
+        }
+
         public String getContent() {
             StringBuilder netBuilder = new StringBuilder();
             if (_netType == GuestNetType.BRIDGE) {
@@ -1515,6 +1539,21 @@
             if (_model != null) {
                 netBuilder.append("<model type='" + _model + "'/>\n");
             }
+            if (NicModel.VIRTIO.equals(_model)) {
+                boolean isMultiQueueNumberSpecified = _multiQueueNumber != null;
+                boolean isPackedVirtQueuesEnabled = _packedVirtQueues != null && _packedVirtQueues
+                        && s_qemuVersion >= 4200000 && s_libvirtVersion >= 6300000;
+                if (isMultiQueueNumberSpecified || isPackedVirtQueuesEnabled) {
+                    netBuilder.append("<driver");
+                    if (isMultiQueueNumberSpecified) {
+                        netBuilder.append(" queues='" + _multiQueueNumber + "'");
+                    }
+                    if (isPackedVirtQueuesEnabled) {
+                        netBuilder.append(" packed='on'");
+                    }
+                    netBuilder.append("/>\n");
+                }
+            }
             if ((s_libvirtVersion >= 9004) && (_networkRateKBps > 0)) { // supported from libvirt 0.9.4
                 netBuilder.append("<bandwidth>\n");
                 netBuilder.append("<inbound average='" + _networkRateKBps + "' peak='" + _networkRateKBps + "'/>\n");
@@ -1710,6 +1749,10 @@
             modeBuilder.append("</cpu>");
             return modeBuilder.toString();
         }
+
+        public int getCoresPerSocket() {
+            return _coresPerSocket;
+        }
     }
 
     public static class SerialDef {
@@ -1766,7 +1809,7 @@
 
     public final static class ChannelDef {
         enum ChannelType {
-            UNIX("unix"), SERIAL("serial");
+            UNIX("unix"), SERIAL("serial"), SPICEVMC("spicevmc");
             String type;
 
             ChannelType(String type) {
@@ -2086,14 +2129,14 @@
         private String path = "/dev/random";
         private RngModel rngModel = RngModel.VIRTIO;
         private RngBackendModel rngBackendModel = RngBackendModel.RANDOM;
-        private int rngRateBytes = 2048;
-        private int rngRatePeriod = 1000;
+        private Integer rngRateBytes = 2048;
+        private Integer rngRatePeriod = 1000;
 
         public RngDef(String path) {
             this.path = path;
         }
 
-        public RngDef(String path, int rngRateBytes, int rngRatePeriod) {
+        public RngDef(String path, Integer rngRateBytes, Integer rngRatePeriod) {
             this.path = path;
             this.rngRateBytes = rngRateBytes;
             this.rngRatePeriod = rngRatePeriod;
@@ -2112,7 +2155,7 @@
             this.rngBackendModel = rngBackendModel;
         }
 
-        public RngDef(String path, RngBackendModel rngBackendModel, int rngRateBytes, int rngRatePeriod) {
+        public RngDef(String path, RngBackendModel rngBackendModel, Integer rngRateBytes, Integer rngRatePeriod) {
             this.path = path;
             this.rngBackendModel = rngBackendModel;
             this.rngRateBytes = rngRateBytes;
@@ -2137,11 +2180,11 @@
         }
 
         public int getRngRateBytes() {
-            return rngRateBytes;
+            return rngRateBytes != null ? rngRateBytes : 0;
         }
 
         public int getRngRatePeriod() {
-            return rngRatePeriod;
+            return rngRatePeriod != null ? rngRatePeriod : 0;
         }
 
         @Override
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckAndRepairVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckAndRepairVolumeCommandWrapper.java
new file mode 100644
index 0000000..cd81a2f
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckAndRepairVolumeCommandWrapper.java
@@ -0,0 +1,192 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
+import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.cloudstack.utils.cryptsetup.KeyFile;
+import org.apache.cloudstack.utils.qemu.QemuImageOptions;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.cloudstack.utils.qemu.QemuObject;
+import org.apache.cloudstack.utils.qemu.QemuObject.EncryptFormat;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@ResourceWrapper(handles =  CheckAndRepairVolumeCommand.class)
+public class LibvirtCheckAndRepairVolumeCommandWrapper extends CommandWrapper<CheckAndRepairVolumeCommand, Answer, LibvirtComputingResource> {
+
+    private static final Logger s_logger = Logger.getLogger(LibvirtCheckAndRepairVolumeCommandWrapper.class);
+
+    @Override
+    public Answer execute(CheckAndRepairVolumeCommand command, LibvirtComputingResource serverResource) {
+        final String volumeId = command.getPath();
+        final String repair = command.getRepair();
+        final StorageFilerTO spool = command.getPool();
+
+        final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
+        KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
+        final KVMPhysicalDisk vol = pool.getPhysicalDisk(volumeId);
+        byte[] passphrase = command.getPassphrase();
+
+        try {
+            CheckAndRepairVolumeAnswer answer = null;
+            String checkVolumeResult = null;
+            if (QemuImg.PhysicalDiskFormat.RAW.equals(vol.getFormat())) {
+                checkVolumeResult = "Volume format RAW is not supported to check and repair";
+                String jsonStringFormat = String.format("{ \"message\": \"%s\" }", checkVolumeResult);
+                answer = new CheckAndRepairVolumeAnswer(command, true, checkVolumeResult);
+                answer.setVolumeCheckExecutionResult(jsonStringFormat);
+
+                return answer;
+            } else {
+                answer = checkVolume(vol, command, serverResource);
+                checkVolumeResult =  answer.getVolumeCheckExecutionResult();
+            }
+
+            CheckAndRepairVolumeAnswer resultAnswer = checkIfRepairLeaksIsRequired(command, checkVolumeResult, vol.getName());
+            // resultAnswer is not null when repair is not required, so return from here
+            if (resultAnswer != null) {
+                return resultAnswer;
+            }
+
+            if (StringUtils.isNotEmpty(repair)) {
+                answer = repairVolume(vol, command, serverResource, checkVolumeResult);
+            }
+
+            return answer;
+        } catch (Exception e) {
+            return new CheckAndRepairVolumeAnswer(command, false, e.toString());
+        } finally {
+            if (passphrase != null) {
+                Arrays.fill(passphrase, (byte) 0);
+            }
+        }
+    }
+
+    private CheckAndRepairVolumeAnswer checkVolume(KVMPhysicalDisk vol, CheckAndRepairVolumeCommand command, LibvirtComputingResource serverResource) {
+        EncryptFormat encryptFormat = EncryptFormat.enumValue(command.getEncryptFormat());
+        byte[] passphrase = command.getPassphrase();
+        String checkVolumeResult = checkAndRepairVolume(vol, null, encryptFormat, passphrase, serverResource);
+        s_logger.info(String.format("Check Volume result for the volume %s is %s", vol.getName(), checkVolumeResult));
+        CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, checkVolumeResult);
+        answer.setVolumeCheckExecutionResult(checkVolumeResult);
+
+        return answer;
+    }
+
+    private CheckAndRepairVolumeAnswer repairVolume(KVMPhysicalDisk vol, CheckAndRepairVolumeCommand command, LibvirtComputingResource serverResource, String checkVolumeResult) {
+        EncryptFormat encryptFormat = EncryptFormat.enumValue(command.getEncryptFormat());
+        byte[] passphrase = command.getPassphrase();
+        final String repair = command.getRepair();
+
+        String repairVolumeResult = checkAndRepairVolume(vol, repair, encryptFormat, passphrase, serverResource);
+        String finalResult = (checkVolumeResult != null ? checkVolumeResult.concat(",") : "") + repairVolumeResult;
+        s_logger.info(String.format("Repair Volume result for the volume %s is %s", vol.getName(), repairVolumeResult));
+
+        CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, finalResult);
+        answer.setVolumeRepairExecutionResult(repairVolumeResult);
+        answer.setVolumeCheckExecutionResult(checkVolumeResult);
+
+        return answer;
+    }
+
+    private CheckAndRepairVolumeAnswer checkIfRepairLeaksIsRequired(CheckAndRepairVolumeCommand command, String checkVolumeResult, String volumeName) {
+        final String repair = command.getRepair();
+        int leaks = 0;
+        if (StringUtils.isNotEmpty(checkVolumeResult) && StringUtils.isNotEmpty(repair) && repair.equals("leaks")) {
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode jsonNode = null;
+            try {
+                jsonNode = objectMapper.readTree(checkVolumeResult);
+            } catch (JsonProcessingException e) {
+                String msg = String.format("Error processing response %s during check volume %s", checkVolumeResult, e.getMessage());
+                s_logger.info(msg);
+
+                return skipRepairVolumeCommand(command, checkVolumeResult, msg);
+            }
+            JsonNode leaksNode = jsonNode.get("leaks");
+            if (leaksNode != null) {
+                leaks = leaksNode.asInt();
+            }
+
+            if (leaks == 0) {
+                String msg = String.format("No leaks found while checking for the volume %s, so skipping repair", volumeName);
+                return skipRepairVolumeCommand(command, checkVolumeResult, msg);
+            }
+        }
+
+        return null;
+    }
+
+    private CheckAndRepairVolumeAnswer skipRepairVolumeCommand(CheckAndRepairVolumeCommand command, String checkVolumeResult, String msg) {
+        s_logger.info(msg);
+        String jsonStringFormat = String.format("{ \"message\": \"%s\" }", msg);
+        String finalResult = (checkVolumeResult != null ? checkVolumeResult.concat(",") : "") + jsonStringFormat;
+        CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, finalResult);
+        answer.setVolumeRepairExecutionResult(jsonStringFormat);
+        answer.setVolumeCheckExecutionResult(checkVolumeResult);
+
+        return answer;
+    }
+
+    protected String checkAndRepairVolume(final KVMPhysicalDisk vol, final String repair, final EncryptFormat encryptFormat, byte[] passphrase, final LibvirtComputingResource libvirtComputingResource) throws CloudRuntimeException {
+        List<QemuObject> passphraseObjects = new ArrayList<>();
+        QemuImageOptions imgOptions = null;
+        if (ArrayUtils.isEmpty(passphrase)) {
+            passphrase = null;
+        }
+        try (KeyFile keyFile = new KeyFile(passphrase)) {
+            if (passphrase != null) {
+                passphraseObjects.add(
+                        QemuObject.prepareSecretForQemuImg(vol.getFormat(), encryptFormat, keyFile.toString(), "sec0", null)
+                );
+                imgOptions = new QemuImageOptions(vol.getFormat(), vol.getPath(),"sec0");
+            }
+            QemuImg q = new QemuImg(libvirtComputingResource.getCmdsTimeout());
+            QemuImgFile file = new QemuImgFile(vol.getPath());
+            return q.checkAndRepair(file, imgOptions, passphraseObjects, repair);
+        } catch (QemuImgException | LibvirtException ex) {
+            throw new CloudRuntimeException("Failed to run qemu-img for check volume", ex);
+        } catch (IOException ex) {
+            throw new CloudRuntimeException("Failed to create keyfile for encrypted volume for check volume operation", ex);
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java
index ff72436..48996a7 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java
@@ -28,8 +28,7 @@
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckOnHostCommand;
 import com.cloud.agent.api.to.HostTO;
-import com.cloud.agent.api.to.NetworkTO;
-import com.cloud.hypervisor.kvm.resource.KVMHABase.NfsStoragePool;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.hypervisor.kvm.resource.KVMHAChecker;
 import com.cloud.hypervisor.kvm.resource.KVMHAMonitor;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
@@ -44,10 +43,9 @@
         final ExecutorService executors = Executors.newSingleThreadExecutor();
         final KVMHAMonitor monitor = libvirtComputingResource.getMonitor();
 
-        final List<NfsStoragePool> pools = monitor.getStoragePools();
+        final List<HAStoragePool> pools = monitor.getStoragePools();
         final HostTO host = command.getHost();
-        final NetworkTO privateNetwork = host.getPrivateNetwork();
-        final KVMHAChecker ha = new KVMHAChecker(pools, privateNetwork.getIp());
+        final KVMHAChecker ha = new KVMHAChecker(pools, host, command.isCheckFailedOnOneStorage());
 
         final Future<Boolean> future = executors.submit(ha);
         try {
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckUrlCommand.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckUrlCommand.java
index 9d003eb..0e1bf57 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckUrlCommand.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckUrlCommand.java
@@ -38,14 +38,13 @@
         final Integer connectTimeout = cmd.getConnectTimeout();
         final Integer connectionRequestTimeout = cmd.getConnectionRequestTimeout();
         final Integer socketTimeout = cmd.getSocketTimeout();
-        final boolean followRedirects = cmd.isFollowRedirects();
 
         s_logger.info(String.format("Checking URL: %s, with connect timeout: %d, connect request timeout: %d, socket timeout: %d", url, connectTimeout, connectionRequestTimeout, socketTimeout));
         Long remoteSize = null;
 
-        boolean checkResult = DirectDownloadHelper.checkUrlExistence(url, connectTimeout, connectionRequestTimeout, socketTimeout, followRedirects);
+        boolean checkResult = DirectDownloadHelper.checkUrlExistence(url, connectTimeout, connectionRequestTimeout, socketTimeout, cmd.isFollowRedirects());
         if (checkResult) {
-            remoteSize = DirectDownloadHelper.getFileSize(url, cmd.getFormat(), connectTimeout, connectionRequestTimeout, socketTimeout, followRedirects);
+            remoteSize = DirectDownloadHelper.getFileSize(url, cmd.getFormat(), connectTimeout, connectionRequestTimeout, socketTimeout, cmd.isFollowRedirects());
             if (remoteSize == null || remoteSize < 0) {
                 s_logger.error(String.format("Couldn't properly retrieve the remote size of the template on " +
                         "url %s, obtained size = %s", url, remoteSize));
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
index 9c899a0..a708d44 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
@@ -22,13 +22,14 @@
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckVMActivityOnStoragePoolCommand;
 import com.cloud.agent.api.to.StorageFilerTO;
-import com.cloud.hypervisor.kvm.resource.KVMHABase.NfsStoragePool;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
 import com.cloud.hypervisor.kvm.resource.KVMHAMonitor;
 import com.cloud.hypervisor.kvm.resource.KVMHAVMActivityChecker;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
-import com.cloud.storage.Storage;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -43,9 +44,13 @@
         final ExecutorService executors = Executors.newSingleThreadExecutor();
         final KVMHAMonitor monitor = libvirtComputingResource.getMonitor();
         final StorageFilerTO pool = command.getPool();
-        if (Storage.StoragePoolType.NetworkFilesystem == pool.getType()){
-            final NfsStoragePool nfspool = monitor.getStoragePool(pool.getUuid());
-            final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(nfspool, command.getHost().getPrivateNetwork().getIp(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds());
+        final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
+
+        KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid());
+
+        if (primaryPool.isPoolSupportHA()){
+            final HAStoragePool nfspool = monitor.getStoragePool(pool.getUuid());
+            final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(nfspool, command.getHost(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds());
             final Future<Boolean> future = executors.submit(ha);
             try {
                 final Boolean result = future.get();
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVolumeCommandWrapper.java
new file mode 100644
index 0000000..8b0a5aa
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVolumeCommandWrapper.java
@@ -0,0 +1,86 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckVolumeAnswer;
+import com.cloud.agent.api.CheckVolumeCommand;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+import java.util.Map;
+
+@ResourceWrapper(handles = CheckVolumeCommand.class)
+public final class LibvirtCheckVolumeCommandWrapper extends CommandWrapper<CheckVolumeCommand, Answer, LibvirtComputingResource> {
+
+    private static final Logger s_logger = Logger.getLogger(LibvirtCheckVolumeCommandWrapper.class);
+
+    @Override
+    public Answer execute(final CheckVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
+        String result = null;
+        String srcFile = command.getSrcFile();
+        StorageFilerTO storageFilerTO = command.getStorageFilerTO();
+        KVMStoragePoolManager poolMgr = libvirtComputingResource.getStoragePoolMgr();
+        KVMStoragePool pool = poolMgr.getStoragePool(storageFilerTO.getType(), storageFilerTO.getUuid());
+
+        try {
+            if (storageFilerTO.getType() == Storage.StoragePoolType.Filesystem ||
+                    storageFilerTO.getType() == Storage.StoragePoolType.NetworkFilesystem) {
+                final KVMPhysicalDisk vol = pool.getPhysicalDisk(srcFile);
+                final String path = vol.getPath();
+                long size = getVirtualSizeFromFile(path);
+                return  new CheckVolumeAnswer(command, "", size);
+            } else {
+                return new Answer(command, false, "Unsupported Storage Pool");
+            }
+
+        } catch (final Exception e) {
+            s_logger.error("Error while locating disk: "+ e.getMessage());
+            return new Answer(command, false, result);
+        }
+    }
+
+    private long getVirtualSizeFromFile(String path) {
+        try {
+            QemuImg qemu = new QemuImg(0);
+            QemuImgFile qemuFile = new QemuImgFile(path);
+            Map<String, String> info = qemu.info(qemuFile);
+            if (info.containsKey(QemuImg.VIRTUAL_SIZE)) {
+                return Long.parseLong(info.get(QemuImg.VIRTUAL_SIZE));
+            } else {
+                throw new CloudRuntimeException("Unable to determine virtual size of volume at path " + path);
+            }
+        } catch (QemuImgException | LibvirtException ex) {
+            throw new CloudRuntimeException("Error when inspecting volume at path " + path, ex);
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
new file mode 100644
index 0000000..a263118
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
@@ -0,0 +1,400 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.ConvertInstanceAnswer;
+import com.cloud.agent.api.ConvertInstanceCommand;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.NfsTO;
+import com.cloud.agent.api.to.RemoteInstanceTO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.storage.Storage;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.OutputInterpreter;
+import com.cloud.utils.script.Script;
+import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@ResourceWrapper(handles =  ConvertInstanceCommand.class)
+public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<ConvertInstanceCommand, Answer, LibvirtComputingResource> {
+
+    private static final Logger s_logger = Logger.getLogger(LibvirtConvertInstanceCommandWrapper.class);
+
+    private static final List<Hypervisor.HypervisorType> supportedInstanceConvertSourceHypervisors =
+            List.of(Hypervisor.HypervisorType.VMware);
+
+    protected static final String checkIfConversionIsSupportedCommand = "which virt-v2v";
+
+    @Override
+    public Answer execute(ConvertInstanceCommand cmd, LibvirtComputingResource serverResource) {
+        RemoteInstanceTO sourceInstance = cmd.getSourceInstance();
+        Hypervisor.HypervisorType sourceHypervisorType = sourceInstance.getHypervisorType();
+        String sourceInstanceName = sourceInstance.getInstanceName();
+        Hypervisor.HypervisorType destinationHypervisorType = cmd.getDestinationHypervisorType();
+        List<String> destinationStoragePools = cmd.getDestinationStoragePools();
+        DataStoreTO conversionTemporaryLocation = cmd.getConversionTemporaryLocation();
+        long timeout = (long) cmd.getWait() * 1000;
+
+        if (!isInstanceConversionSupportedOnHost()) {
+            String msg = String.format("Cannot convert the instance %s from VMware as the virt-v2v binary is not found. " +
+                    "Please install virt-v2v on the host before attempting the instance conversion", sourceInstanceName);
+            s_logger.info(msg);
+            return new ConvertInstanceAnswer(cmd, false, msg);
+        }
+
+        if (!areSourceAndDestinationHypervisorsSupported(sourceHypervisorType, destinationHypervisorType)) {
+            String err = destinationHypervisorType != Hypervisor.HypervisorType.KVM ?
+                    String.format("The destination hypervisor type is %s, KVM was expected, cannot handle it", destinationHypervisorType) :
+                    String.format("The source hypervisor type %s is not supported for KVM conversion", sourceHypervisorType);
+            s_logger.error(err);
+            return new ConvertInstanceAnswer(cmd, false, err);
+        }
+
+        final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
+        KVMStoragePool temporaryStoragePool = getTemporaryStoragePool(conversionTemporaryLocation, storagePoolMgr);
+
+        s_logger.info(String.format("Attempting to convert the instance %s from %s to KVM",
+                sourceInstanceName, sourceHypervisorType));
+        final String convertInstanceUrl = getConvertInstanceUrl(sourceInstance);
+        final String temporaryConvertUuid = UUID.randomUUID().toString();
+        final String temporaryPasswordFilePath = createTemporaryPasswordFileAndRetrievePath(sourceInstance);
+        final String temporaryConvertPath = temporaryStoragePool.getLocalPath();
+        boolean verboseModeEnabled = serverResource.isConvertInstanceVerboseModeEnabled();
+
+        try {
+            boolean result = performInstanceConversion(convertInstanceUrl, sourceInstanceName, temporaryPasswordFilePath,
+                    temporaryConvertPath, temporaryConvertUuid, timeout, verboseModeEnabled);
+            if (!result) {
+                String err = String.format("The virt-v2v conversion of the instance %s failed. " +
+                                "Please check the agent logs for the virt-v2v output", sourceInstanceName);
+                s_logger.error(err);
+                return new ConvertInstanceAnswer(cmd, false, err);
+            }
+            String convertedBasePath = String.format("%s/%s", temporaryConvertPath, temporaryConvertUuid);
+            LibvirtDomainXMLParser xmlParser = parseMigratedVMXmlDomain(convertedBasePath);
+
+            List<KVMPhysicalDisk> temporaryDisks = xmlParser == null ?
+                    getTemporaryDisksWithPrefixFromTemporaryPool(temporaryStoragePool, temporaryConvertPath, temporaryConvertUuid) :
+                    getTemporaryDisksFromParsedXml(temporaryStoragePool, xmlParser, convertedBasePath);
+
+            List<KVMPhysicalDisk> destinationDisks = moveTemporaryDisksToDestination(temporaryDisks,
+                    destinationStoragePools, storagePoolMgr);
+
+            cleanupDisksAndDomainFromTemporaryLocation(temporaryDisks, temporaryStoragePool, temporaryConvertUuid);
+
+            UnmanagedInstanceTO convertedInstanceTO = getConvertedUnmanagedInstance(temporaryConvertUuid,
+                    destinationDisks, xmlParser);
+            return new ConvertInstanceAnswer(cmd, convertedInstanceTO);
+        } catch (Exception e) {
+            String error = String.format("Error converting instance %s from %s, due to: %s",
+                    sourceInstanceName, sourceHypervisorType, e.getMessage());
+            s_logger.error(error, e);
+            return new ConvertInstanceAnswer(cmd, false, error);
+        } finally {
+            s_logger.debug("Cleaning up instance conversion temporary password file");
+            Script.runSimpleBashScript(String.format("rm -rf %s", temporaryPasswordFilePath));
+            if (conversionTemporaryLocation instanceof NfsTO) {
+                s_logger.debug("Cleaning up secondary storage temporary location");
+                storagePoolMgr.deleteStoragePool(temporaryStoragePool.getType(), temporaryStoragePool.getUuid());
+            }
+        }
+    }
+
+    protected KVMStoragePool getTemporaryStoragePool(DataStoreTO conversionTemporaryLocation, KVMStoragePoolManager storagePoolMgr) {
+        if (conversionTemporaryLocation instanceof NfsTO) {
+            NfsTO nfsTO = (NfsTO) conversionTemporaryLocation;
+            return storagePoolMgr.getStoragePoolByURI(nfsTO.getUrl());
+        } else {
+            PrimaryDataStoreTO primaryDataStoreTO = (PrimaryDataStoreTO) conversionTemporaryLocation;
+            return storagePoolMgr.getStoragePool(primaryDataStoreTO.getPoolType(), primaryDataStoreTO.getUuid());
+        }
+    }
+
+    protected boolean areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType sourceHypervisorType,
+                                                                  Hypervisor.HypervisorType destinationHypervisorType) {
+        return destinationHypervisorType == Hypervisor.HypervisorType.KVM &&
+                supportedInstanceConvertSourceHypervisors.contains(sourceHypervisorType);
+    }
+
+    protected List<KVMPhysicalDisk> getTemporaryDisksFromParsedXml(KVMStoragePool pool, LibvirtDomainXMLParser xmlParser, String convertedBasePath) {
+        List<LibvirtVMDef.DiskDef> disksDefs = xmlParser.getDisks();
+        disksDefs = disksDefs.stream().filter(x -> x.getDiskType() == LibvirtVMDef.DiskDef.DiskType.FILE &&
+                x.getDeviceType() == LibvirtVMDef.DiskDef.DeviceType.DISK).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(disksDefs)) {
+            String err = String.format("Cannot find any disk defined on the converted XML domain %s.xml", convertedBasePath);
+            s_logger.error(err);
+            throw new CloudRuntimeException(err);
+        }
+        sanitizeDisksPath(disksDefs);
+        return getPhysicalDisksFromDefPaths(disksDefs, pool);
+    }
+
+    private List<KVMPhysicalDisk> getPhysicalDisksFromDefPaths(List<LibvirtVMDef.DiskDef> disksDefs, KVMStoragePool pool) {
+        List<KVMPhysicalDisk> disks = new ArrayList<>();
+        for (LibvirtVMDef.DiskDef diskDef : disksDefs) {
+            KVMPhysicalDisk physicalDisk = pool.getPhysicalDisk(diskDef.getDiskPath());
+            disks.add(physicalDisk);
+        }
+        return disks;
+    }
+
+    protected List<KVMPhysicalDisk> getTemporaryDisksWithPrefixFromTemporaryPool(KVMStoragePool pool, String path, String prefix) {
+        String msg = String.format("Could not parse correctly the converted XML domain, checking for disks on %s with prefix %s", path, prefix);
+        s_logger.info(msg);
+        pool.refresh();
+        List<KVMPhysicalDisk> disksWithPrefix = pool.listPhysicalDisks()
+                .stream()
+                .filter(x -> x.getName().startsWith(prefix) && !x.getName().endsWith(".xml"))
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(disksWithPrefix)) {
+            msg = String.format("Could not find any converted disk with prefix %s on temporary location %s", prefix, path);
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+        return disksWithPrefix;
+    }
+
+    private void cleanupDisksAndDomainFromTemporaryLocation(List<KVMPhysicalDisk> disks,
+                                                            KVMStoragePool temporaryStoragePool,
+                                                            String temporaryConvertUuid) {
+        for (KVMPhysicalDisk disk : disks) {
+            s_logger.info(String.format("Cleaning up temporary disk %s after conversion from temporary location", disk.getName()));
+            temporaryStoragePool.deletePhysicalDisk(disk.getName(), Storage.ImageFormat.QCOW2);
+        }
+        s_logger.info(String.format("Cleaning up temporary domain %s after conversion from temporary location", temporaryConvertUuid));
+        Script.runSimpleBashScript(String.format("rm -f %s/%s*.xml", temporaryStoragePool.getLocalPath(), temporaryConvertUuid));
+    }
+
+    protected boolean isInstanceConversionSupportedOnHost() {
+        int exitValue = Script.runSimpleBashScriptForExitValue(checkIfConversionIsSupportedCommand);
+        return exitValue == 0;
+    }
+
+    protected void sanitizeDisksPath(List<LibvirtVMDef.DiskDef> disks) {
+        for (LibvirtVMDef.DiskDef disk : disks) {
+            String[] diskPathParts = disk.getDiskPath().split("/");
+            String relativePath = diskPathParts[diskPathParts.length - 1];
+            disk.setDiskPath(relativePath);
+        }
+    }
+
+    protected List<KVMPhysicalDisk> moveTemporaryDisksToDestination(List<KVMPhysicalDisk> temporaryDisks,
+                                                                  List<String> destinationStoragePools,
+                                                                  KVMStoragePoolManager storagePoolMgr) {
+        List<KVMPhysicalDisk> targetDisks = new ArrayList<>();
+        if (temporaryDisks.size() != destinationStoragePools.size()) {
+            String warn = String.format("Discrepancy between the converted instance disks (%s) " +
+                    "and the expected number of disks (%s)", temporaryDisks.size(), destinationStoragePools.size());
+            s_logger.warn(warn);
+        }
+        for (int i = 0; i < temporaryDisks.size(); i++) {
+            String poolPath = destinationStoragePools.get(i);
+            KVMStoragePool destinationPool = storagePoolMgr.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, poolPath);
+            if (destinationPool == null) {
+                String err = String.format("Could not find a storage pool by URI: %s", poolPath);
+                s_logger.error(err);
+                continue;
+            }
+            KVMPhysicalDisk sourceDisk = temporaryDisks.get(i);
+            if (s_logger.isDebugEnabled()) {
+                String msg = String.format("Trying to copy converted instance disk number %s from the temporary location %s" +
+                        " to destination storage pool %s", i, sourceDisk.getPool().getLocalPath(), destinationPool.getUuid());
+                s_logger.debug(msg);
+            }
+
+            String destinationName = UUID.randomUUID().toString();
+
+            KVMPhysicalDisk destinationDisk = storagePoolMgr.copyPhysicalDisk(sourceDisk, destinationName, destinationPool, 7200 * 1000);
+            targetDisks.add(destinationDisk);
+        }
+        return targetDisks;
+    }
+
+    private UnmanagedInstanceTO getConvertedUnmanagedInstance(String baseName,
+                                                              List<KVMPhysicalDisk> vmDisks,
+                                                              LibvirtDomainXMLParser xmlParser) {
+        UnmanagedInstanceTO instanceTO = new UnmanagedInstanceTO();
+        instanceTO.setName(baseName);
+        instanceTO.setDisks(getUnmanagedInstanceDisks(vmDisks, xmlParser));
+        instanceTO.setNics(getUnmanagedInstanceNics(xmlParser));
+        return instanceTO;
+    }
+
+    private List<UnmanagedInstanceTO.Nic> getUnmanagedInstanceNics(LibvirtDomainXMLParser xmlParser) {
+        List<UnmanagedInstanceTO.Nic> nics = new ArrayList<>();
+        if (xmlParser != null) {
+            List<LibvirtVMDef.InterfaceDef> interfaces = xmlParser.getInterfaces();
+            for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) {
+                UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic();
+                nic.setMacAddress(interfaceDef.getMacAddress());
+                nic.setNicId(interfaceDef.getBrName());
+                nic.setAdapterType(interfaceDef.getModel().toString());
+                nics.add(nic);
+            }
+        }
+        return nics;
+    }
+
+    protected List<UnmanagedInstanceTO.Disk> getUnmanagedInstanceDisks(List<KVMPhysicalDisk> vmDisks, LibvirtDomainXMLParser xmlParser) {
+        List<UnmanagedInstanceTO.Disk> instanceDisks = new ArrayList<>();
+        List<LibvirtVMDef.DiskDef> diskDefs = xmlParser != null ? xmlParser.getDisks() : null;
+        for (int i = 0; i< vmDisks.size(); i++) {
+            KVMPhysicalDisk physicalDisk = vmDisks.get(i);
+            KVMStoragePool storagePool = physicalDisk.getPool();
+            UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk();
+            disk.setPosition(i);
+            Pair<String, String> storagePoolHostAndPath = getNfsStoragePoolHostAndPath(storagePool);
+            disk.setDatastoreHost(storagePoolHostAndPath.first());
+            disk.setDatastorePath(storagePoolHostAndPath.second());
+            disk.setDatastoreName(storagePool.getUuid());
+            disk.setDatastoreType(storagePool.getType().name());
+            disk.setCapacity(physicalDisk.getVirtualSize());
+            disk.setFileBaseName(physicalDisk.getName());
+            if (CollectionUtils.isNotEmpty(diskDefs)) {
+                LibvirtVMDef.DiskDef diskDef = diskDefs.get(i);
+                disk.setController(diskDef.getBusType() != null ? diskDef.getBusType().toString() : LibvirtVMDef.DiskDef.DiskBus.VIRTIO.toString());
+            } else {
+                // If the job is finished but we cannot parse the XML, the guest VM can use the virtio driver
+                disk.setController(LibvirtVMDef.DiskDef.DiskBus.VIRTIO.toString());
+            }
+            instanceDisks.add(disk);
+        }
+        return instanceDisks;
+    }
+
+    protected Pair<String, String> getNfsStoragePoolHostAndPath(KVMStoragePool storagePool) {
+        String sourceHostIp = null;
+        String sourcePath = null;
+        String storagePoolMountPoint = Script.runSimpleBashScript(String.format("mount | grep %s", storagePool.getLocalPath()));
+        if (StringUtils.isNotEmpty(storagePoolMountPoint)) {
+            String[] res = storagePoolMountPoint.strip().split(" ");
+            res = res[0].split(":");
+            sourceHostIp = res[0].strip();
+            sourcePath = res[1].strip();
+        }
+        return new Pair<>(sourceHostIp, sourcePath);
+    }
+
+    protected boolean performInstanceConversion(String convertInstanceUrl, String sourceInstanceName,
+                                              String temporaryPasswordFilePath,
+                                              String temporaryConvertFolder,
+                                              String temporaryConvertUuid,
+                                              long timeout, boolean verboseModeEnabled) {
+        Script script = new Script("virt-v2v", timeout, s_logger);
+        script.add("--root", "first");
+        script.add("-ic", convertInstanceUrl);
+        script.add(sourceInstanceName);
+        script.add("--password-file", temporaryPasswordFilePath);
+        script.add("-o", "local");
+        script.add("-os", temporaryConvertFolder);
+        script.add("-of", "qcow2");
+        script.add("-on", temporaryConvertUuid);
+        if (verboseModeEnabled) {
+            script.add("-v");
+        }
+
+        String logPrefix = String.format("virt-v2v source: %s %s progress", convertInstanceUrl, sourceInstanceName);
+        OutputInterpreter.LineByLineOutputLogger outputLogger = new OutputInterpreter.LineByLineOutputLogger(s_logger, logPrefix);
+        script.execute(outputLogger);
+        int exitValue = script.getExitValue();
+        return exitValue == 0;
+    }
+
+    private String createTemporaryPasswordFileAndRetrievePath(RemoteInstanceTO sourceInstance) {
+        String password = null;
+        if (sourceInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
+            password = sourceInstance.getVcenterPassword();
+        }
+        String passwordFile = String.format("/tmp/vmw-%s", UUID.randomUUID());
+        String msg = String.format("Creating a temporary password file for VMware instance %s conversion on: %s", sourceInstance.getInstanceName(), passwordFile);
+        s_logger.debug(msg);
+        Script.runSimpleBashScriptForExitValueAvoidLogging(String.format("echo \"%s\" > %s", password, passwordFile));
+        return passwordFile;
+    }
+
+    private String getConvertInstanceUrl(RemoteInstanceTO sourceInstance) {
+        String url = null;
+        if (sourceInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
+            url = getConvertInstanceUrlFromVmware(sourceInstance);
+        }
+        return url;
+    }
+
+    private String getConvertInstanceUrlFromVmware(RemoteInstanceTO vmwareInstance) {
+        String vcenter = vmwareInstance.getVcenterHost();
+        String datacenter = vmwareInstance.getDatacenterName();
+        String username = vmwareInstance.getVcenterUsername();
+        String host = vmwareInstance.getHostName();
+        String cluster = vmwareInstance.getClusterName();
+
+        String encodedUsername = encodeUsername(username);
+        return String.format("vpx://%s@%s/%s/%s/%s?no_verify=1",
+                encodedUsername, vcenter, datacenter, cluster, host);
+    }
+    protected LibvirtDomainXMLParser parseMigratedVMXmlDomain(String installPath) throws IOException {
+        String xmlPath = String.format("%s.xml", installPath);
+        if (!new File(xmlPath).exists()) {
+            String err = String.format("Conversion failed. Unable to find the converted XML domain, expected %s", xmlPath);
+            s_logger.error(err);
+            throw new CloudRuntimeException(err);
+        }
+        InputStream is = new BufferedInputStream(new FileInputStream(xmlPath));
+        String xml = IOUtils.toString(is, Charset.defaultCharset());
+        final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
+        try {
+            parser.parseDomainXML(xml);
+            return parser;
+        } catch (RuntimeException e) {
+            String err = String.format("Error parsing the converted instance XML domain at %s: %s", xmlPath, e.getMessage());
+            s_logger.error(err, e);
+            s_logger.debug(xml);
+            return null;
+        }
+    }
+
+    protected String encodeUsername(String username) {
+        return URLEncoder.encode(username, Charset.defaultCharset());
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.java
new file mode 100644
index 0000000..e48edd8
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyRemoteVolumeCommandWrapper.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 com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CopyRemoteVolumeAnswer;
+import com.cloud.agent.api.CopyRemoteVolumeCommand;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+import java.util.Map;
+
+@ResourceWrapper(handles = CopyRemoteVolumeCommand.class)
+public final class LibvirtCopyRemoteVolumeCommandWrapper extends CommandWrapper<CopyRemoteVolumeCommand, Answer, LibvirtComputingResource> {
+
+    private static final Logger s_logger = Logger.getLogger(LibvirtCopyRemoteVolumeCommandWrapper.class);
+
+    @Override
+    public Answer execute(final CopyRemoteVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
+        String result = null;
+        String srcIp = command.getRemoteIp();
+        String username = command.getUsername();
+        String password = command.getPassword();
+        String srcFile = command.getSrcFile();
+        StorageFilerTO storageFilerTO = command.getStorageFilerTO();
+        String tmpPath = command.getTmpPath();
+        KVMStoragePoolManager poolMgr = libvirtComputingResource.getStoragePoolMgr();
+        KVMStoragePool pool = poolMgr.getStoragePool(storageFilerTO.getType(), storageFilerTO.getUuid());
+        String dstPath = pool.getLocalPath();
+
+        try {
+            if (storageFilerTO.getType() == Storage.StoragePoolType.Filesystem ||
+                    storageFilerTO.getType() == Storage.StoragePoolType.NetworkFilesystem) {
+                String filename = libvirtComputingResource.copyVolume(srcIp, username, password, dstPath, srcFile, tmpPath);
+                s_logger.debug("Volume Copy Successful");
+                final KVMPhysicalDisk vol = pool.getPhysicalDisk(filename);
+                final String path = vol.getPath();
+                long size = getVirtualSizeFromFile(path);
+                return  new CopyRemoteVolumeAnswer(command, "", filename, size);
+            } else {
+                return new Answer(command, false, "Unsupported Storage Pool");
+            }
+
+        } catch (final Exception e) {
+            s_logger.error("Error while copying file from remote host: "+ e.getMessage());
+            return new Answer(command, false, result);
+        }
+    }
+
+    private long getVirtualSizeFromFile(String path) {
+        try {
+            QemuImg qemu = new QemuImg(0);
+            QemuImgFile qemuFile = new QemuImgFile(path);
+            Map<String, String> info = qemu.info(qemuFile);
+            if (info.containsKey(QemuImg.VIRTUAL_SIZE)) {
+                return Long.parseLong(info.get(QemuImg.VIRTUAL_SIZE));
+            } else {
+                throw new CloudRuntimeException("Unable to determine virtual size of volume at path " + path);
+            }
+        } catch (QemuImgException | LibvirtException ex) {
+            throw new CloudRuntimeException("Error when inspecting volume at path " + path, ex);
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFenceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFenceCommandWrapper.java
index e41a896..9a6ee7a 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFenceCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFenceCommandWrapper.java
@@ -30,7 +30,7 @@
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.FenceAnswer;
 import com.cloud.agent.api.FenceCommand;
-import com.cloud.hypervisor.kvm.resource.KVMHABase.NfsStoragePool;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.hypervisor.kvm.resource.KVMHAChecker;
 import com.cloud.hypervisor.kvm.resource.KVMHAMonitor;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
@@ -47,7 +47,7 @@
         final ExecutorService executors = Executors.newSingleThreadExecutor();
         final KVMHAMonitor monitor = libvirtComputingResource.getMonitor();
 
-        final List<NfsStoragePool> pools = monitor.getStoragePools();
+        final List<HAStoragePool> pools = monitor.getStoragePools();
 
         /**
          * We can only safely fence off hosts when we use NFS
@@ -60,7 +60,7 @@
             return new FenceAnswer(command, false, logline);
         }
 
-        final KVMHAChecker ha = new KVMHAChecker(pools, command.getHostIp());
+        final KVMHAChecker ha = new KVMHAChecker(pools, command.getHost(), command.isReportCheckFailureIfOneStorageIsDown());
 
         final Future<Boolean> future = executors.submit(ha);
         try {
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java
new file mode 100644
index 0000000..700f058
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java
@@ -0,0 +1,194 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.GetRemoteVmsAnswer;
+import com.cloud.agent.api.GetRemoteVmsCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
+import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.apache.log4j.Logger;
+import org.libvirt.Connect;
+import org.libvirt.Domain;
+import org.libvirt.DomainBlockInfo;
+import org.libvirt.DomainInfo;
+import org.libvirt.LibvirtException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@ResourceWrapper(handles = GetRemoteVmsCommand.class)
+public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper<GetRemoteVmsCommand, Answer, LibvirtComputingResource> {
+
+    private static final Logger s_logger = Logger.getLogger(LibvirtGetRemoteVmsCommandWrapper.class);
+
+    @Override
+    public Answer execute(final GetRemoteVmsCommand command, final LibvirtComputingResource libvirtComputingResource) {
+        String result = null;
+        String hypervisorURI = "qemu+tcp://" + command.getRemoteIp() +
+                "/system";
+        HashMap<String, UnmanagedInstanceTO> unmanagedInstances = new HashMap<>();
+        try {
+            Connect conn = LibvirtConnection.getConnection(hypervisorURI);
+            final List<String> allVmNames = libvirtComputingResource.getAllVmNames(conn);
+            for (String name : allVmNames) {
+                final Domain domain = libvirtComputingResource.getDomain(conn, name);
+
+                final DomainInfo.DomainState ps = domain.getInfo().state;
+
+                final VirtualMachine.PowerState state = libvirtComputingResource.convertToPowerState(ps);
+
+                s_logger.debug("VM " + domain.getName() + ": powerstate = " + ps + "; vm state=" + state.toString());
+
+                if (state == VirtualMachine.PowerState.PowerOff) {
+                    try {
+                        UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn);
+                        unmanagedInstances.put(instance.getName(), instance);
+                    } catch (Exception e) {
+                        s_logger.error("Error while fetching instance details", e);
+                    }
+                }
+                domain.free();
+            }
+            s_logger.debug("Found Vms: "+ unmanagedInstances.size());
+            return  new GetRemoteVmsAnswer(command, "", unmanagedInstances);
+        } catch (final LibvirtException e) {
+            s_logger.error("Error while listing stopped Vms on remote host: "+ e.getMessage());
+            return new Answer(command, false, result);
+        }
+    }
+
+    private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvirtComputingResource, Domain domain, Connect conn) {
+        try {
+            final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
+            parser.parseDomainXML(domain.getXMLDesc(1));
+
+            final UnmanagedInstanceTO instance = new UnmanagedInstanceTO();
+            instance.setName(domain.getName());
+            if (parser.getCpuModeDef() != null) {
+                instance.setCpuCoresPerSocket(parser.getCpuModeDef().getCoresPerSocket());
+            }
+            Long memory = domain.getMaxMemory();
+            instance.setMemory(memory.intValue()/1024);
+            if (parser.getCpuTuneDef() !=null) {
+                instance.setCpuSpeed(parser.getCpuTuneDef().getShares());
+            }
+            instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName())));
+            instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces()));
+            instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource, domain));
+            instance.setVncPassword(parser.getVncPasswd() + "aaaaaaaaaaaaaa"); // Suffix back extra characters for DB compatibility
+
+            return instance;
+        } catch (Exception e) {
+            s_logger.debug("Unable to retrieve unmanaged instance info. ", e);
+            throw new CloudRuntimeException("Unable to retrieve unmanaged instance info. " + e.getMessage());
+        }
+    }
+
+    private UnmanagedInstanceTO.PowerState getPowerState(VirtualMachine.PowerState vmPowerState) {
+        switch (vmPowerState) {
+            case PowerOn:
+                return UnmanagedInstanceTO.PowerState.PowerOn;
+            case PowerOff:
+                return UnmanagedInstanceTO.PowerState.PowerOff;
+            default:
+                return UnmanagedInstanceTO.PowerState.PowerUnknown;
+
+        }
+    }
+
+    private List<UnmanagedInstanceTO.Nic> getUnmanagedInstanceNics(List<LibvirtVMDef.InterfaceDef> interfaces) {
+        final ArrayList<UnmanagedInstanceTO.Nic> nics = new ArrayList<>(interfaces.size());
+        int counter = 0;
+        for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) {
+            final UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic();
+            nic.setNicId(String.valueOf(counter++));
+            nic.setMacAddress(interfaceDef.getMacAddress());
+            nic.setAdapterType(interfaceDef.getModel().toString());
+            nic.setNetwork(interfaceDef.getDevName());
+            nic.setPciSlot(interfaceDef.getSlot().toString());
+            nic.setVlan(interfaceDef.getVlanTag());
+            nics.add(nic);
+        }
+        return nics;
+    }
+
+    private List<UnmanagedInstanceTO.Disk> getUnmanagedInstanceDisks(List<LibvirtVMDef.DiskDef> disksInfo,
+                                                                     LibvirtComputingResource libvirtComputingResource,
+                                                                     Domain dm){
+        final ArrayList<UnmanagedInstanceTO.Disk> disks = new ArrayList<>(disksInfo.size());
+        int counter = 0;
+        for (LibvirtVMDef.DiskDef diskDef : disksInfo) {
+            if (diskDef.getDeviceType() != LibvirtVMDef.DiskDef.DeviceType.DISK) {
+                continue;
+            }
+
+            final UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk();
+
+            disk.setPosition(counter);
+
+            Long size;
+            try {
+                DomainBlockInfo blockInfo = dm.blockInfo(diskDef.getSourcePath());
+                size = blockInfo.getCapacity();
+            } catch (LibvirtException e) {
+                throw new RuntimeException(e);
+            }
+
+            disk.setCapacity(size);
+            disk.setDiskId(String.valueOf(counter++));
+            disk.setLabel(diskDef.getDiskLabel());
+            disk.setController(diskDef.getBusType().toString());
+
+
+            Pair<String, String> sourceHostPath = getSourceHostPath(libvirtComputingResource, diskDef.getSourcePath());
+            if (sourceHostPath != null) {
+                disk.setDatastoreHost(sourceHostPath.first());
+                disk.setDatastorePath(sourceHostPath.second());
+            } else {
+                disk.setDatastorePath(diskDef.getSourcePath());
+                disk.setDatastoreHost(diskDef.getSourceHost());
+            }
+
+            disk.setDatastoreType(diskDef.getDiskType().toString());
+            disk.setDatastorePort(diskDef.getSourceHostPort());
+            disks.add(disk);
+        }
+        return disks;
+    }
+
+    private Pair<String, String> getSourceHostPath(LibvirtComputingResource libvirtComputingResource, String diskPath) {
+        int pathEnd = diskPath.lastIndexOf("/");
+        if (pathEnd >= 0) {
+            diskPath = diskPath.substring(0, pathEnd);
+            return libvirtComputingResource.getSourceHostPath(diskPath);
+        }
+        return null;
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java
new file mode 100644
index 0000000..65de4f6
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapper.java
@@ -0,0 +1,251 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.GetUnmanagedInstancesAnswer;
+import com.cloud.agent.api.GetUnmanagedInstancesCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.libvirt.Connect;
+import org.libvirt.Domain;
+import org.libvirt.DomainBlockInfo;
+import org.libvirt.LibvirtException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@ResourceWrapper(handles=GetUnmanagedInstancesCommand.class)
+public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWrapper<GetUnmanagedInstancesCommand, GetUnmanagedInstancesAnswer, LibvirtComputingResource> {
+    private static final Logger LOGGER = Logger.getLogger(LibvirtGetUnmanagedInstancesCommandWrapper.class);
+
+    private static final int requiredVncPasswordLength = 22;
+
+    @Override
+    public GetUnmanagedInstancesAnswer execute(GetUnmanagedInstancesCommand command, LibvirtComputingResource libvirtComputingResource) {
+        LOGGER.info("Fetching unmanaged instance on host");
+
+        HashMap<String, UnmanagedInstanceTO> unmanagedInstances = new HashMap<>();
+        try {
+            final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
+            final Connect conn = libvirtUtilitiesHelper.getConnection();
+            final List<Domain> domains = getDomains(command, libvirtComputingResource, conn);
+
+            for (Domain domain : domains) {
+                UnmanagedInstanceTO instance = getUnmanagedInstance(libvirtComputingResource, domain, conn);
+                if (instance != null) {
+                    unmanagedInstances.put(instance.getName(), instance);
+                    domain.free();
+                }
+            }
+        } catch (Exception e) {
+            String err = String.format("Error listing unmanaged instances: %s", e.getMessage());
+            LOGGER.error(err, e);
+            return new GetUnmanagedInstancesAnswer(command, err);
+        }
+
+        return new GetUnmanagedInstancesAnswer(command, "OK", unmanagedInstances);
+    }
+
+    private List<Domain> getDomains(GetUnmanagedInstancesCommand command,
+                                    LibvirtComputingResource libvirtComputingResource,
+                                    Connect conn) throws LibvirtException, CloudRuntimeException {
+        final List<Domain> domains = new ArrayList<>();
+        final String vmNameCmd = command.getInstanceName();
+        if (StringUtils.isNotBlank(vmNameCmd)) {
+            final Domain domain = libvirtComputingResource.getDomain(conn, vmNameCmd);
+            if (domain == null) {
+                String msg = String.format("VM %s not found", vmNameCmd);
+                LOGGER.error(msg);
+                throw new CloudRuntimeException(msg);
+            }
+
+            checkIfVmExists(vmNameCmd,domain);
+            checkIfVmIsManaged(command,vmNameCmd,domain);
+
+            domains.add(domain);
+        } else {
+            final List<String> allVmNames = libvirtComputingResource.getAllVmNames(conn);
+            for (String name : allVmNames) {
+                if (!command.hasManagedInstance(name)) {
+                    final Domain domain = libvirtComputingResource.getDomain(conn, name);
+                    domains.add(domain);
+                }
+            }
+        }
+        return domains;
+    }
+
+    private void checkIfVmExists(String vmNameCmd,final Domain domain) throws LibvirtException {
+        if (StringUtils.isNotEmpty(vmNameCmd) &&
+                !vmNameCmd.equals(domain.getName())) {
+            LOGGER.error("GetUnmanagedInstancesCommand: exact vm name not found " + vmNameCmd);
+            throw new CloudRuntimeException("GetUnmanagedInstancesCommand: exact vm name not found " + vmNameCmd);
+        }
+    }
+
+    private void checkIfVmIsManaged(GetUnmanagedInstancesCommand command,String vmNameCmd,final Domain domain) throws LibvirtException {
+        if (command.hasManagedInstance(domain.getName())) {
+            LOGGER.error("GetUnmanagedInstancesCommand: vm already managed " + vmNameCmd);
+            throw new CloudRuntimeException("GetUnmanagedInstancesCommand:  vm already managed " + vmNameCmd);
+        }
+    }
+    private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvirtComputingResource, Domain domain, Connect conn) {
+        try {
+            final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
+            parser.parseDomainXML(domain.getXMLDesc(1));
+
+            final UnmanagedInstanceTO instance = new UnmanagedInstanceTO();
+            instance.setName(domain.getName());
+
+            instance.setCpuCores((int) LibvirtComputingResource.countDomainRunningVcpus(domain));
+            instance.setCpuSpeed(parser.getCpuTuneDef().getShares()/instance.getCpuCores());
+
+            if (parser.getCpuModeDef() != null) {
+                instance.setCpuCoresPerSocket(parser.getCpuModeDef().getCoresPerSocket());
+            }
+            instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName())));
+            instance.setMemory((int) LibvirtComputingResource.getDomainMemory(domain) / 1024);
+            instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces()));
+            instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource, conn, domain.getName()));
+            instance.setVncPassword(getFormattedVncPassword(parser.getVncPasswd()));
+
+            return instance;
+        } catch (Exception e) {
+            LOGGER.info("Unable to retrieve unmanaged instance info. " + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    protected String getFormattedVncPassword(String vncPasswd) {
+        if (StringUtils.isBlank(vncPasswd)) {
+            return null;
+        }
+        String randomChars = RandomStringUtils.random(requiredVncPasswordLength - vncPasswd.length(), true, false);
+        return String.format("%s%s", vncPasswd, randomChars);
+    }
+
+    private UnmanagedInstanceTO.PowerState getPowerState(VirtualMachine.PowerState vmPowerState) {
+        switch (vmPowerState) {
+            case PowerOn:
+                return UnmanagedInstanceTO.PowerState.PowerOn;
+            case PowerOff:
+                return UnmanagedInstanceTO.PowerState.PowerOff;
+            default:
+                return UnmanagedInstanceTO.PowerState.PowerUnknown;
+
+        }
+    }
+
+    private List<UnmanagedInstanceTO.Nic> getUnmanagedInstanceNics(List<LibvirtVMDef.InterfaceDef> interfaces) {
+        final ArrayList<UnmanagedInstanceTO.Nic> nics = new ArrayList<>(interfaces.size());
+        int counter = 0;
+        for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) {
+            final UnmanagedInstanceTO.Nic nic = new UnmanagedInstanceTO.Nic();
+            nic.setNicId(String.valueOf(counter++));
+            nic.setMacAddress(interfaceDef.getMacAddress());
+            nic.setAdapterType(interfaceDef.getModel().toString());
+            nic.setNetwork(interfaceDef.getDevName());
+            nic.setPciSlot(interfaceDef.getSlot().toString());
+            nic.setVlan(interfaceDef.getVlanTag());
+            nics.add(nic);
+        }
+        return nics;
+    }
+
+    private List<UnmanagedInstanceTO.Disk> getUnmanagedInstanceDisks(List<LibvirtVMDef.DiskDef> disksInfo, LibvirtComputingResource libvirtComputingResource, Connect conn, String domainName) {
+        final ArrayList<UnmanagedInstanceTO.Disk> disks = new ArrayList<>(disksInfo.size());
+        int counter = 0;
+        for (LibvirtVMDef.DiskDef diskDef : disksInfo) {
+            if (diskDef.getDeviceType() != LibvirtVMDef.DiskDef.DeviceType.DISK) {
+                continue;
+            }
+
+            final UnmanagedInstanceTO.Disk disk = new UnmanagedInstanceTO.Disk();
+            Long size = null;
+            try {
+                Domain dm = conn.domainLookupByName(domainName);
+                DomainBlockInfo blockInfo = dm.blockInfo(diskDef.getDiskLabel());
+                size = blockInfo.getCapacity();
+            } catch (LibvirtException e) {
+                throw new RuntimeException(e);
+            }
+
+            disk.setPosition(counter);
+            disk.setCapacity(size);
+            disk.setDiskId(String.valueOf(counter++));
+            disk.setLabel(diskDef.getDiskLabel());
+            disk.setController(diskDef.getBusType().toString());
+
+            Pair<String, String> sourceHostPath = getSourceHostPath(libvirtComputingResource, diskDef.getSourcePath());
+            if (sourceHostPath != null) {
+                disk.setDatastoreHost(sourceHostPath.first());
+                disk.setDatastorePath(sourceHostPath.second());
+            } else {
+                int pathEnd = diskDef.getSourcePath().lastIndexOf("/");
+                if (pathEnd >= 0) {
+                    disk.setDatastorePath(diskDef.getSourcePath().substring(0, pathEnd));
+                } else {
+                    disk.setDatastorePath(diskDef.getSourcePath());
+                }
+                disk.setDatastoreHost(diskDef.getSourceHost());
+            }
+
+            disk.setDatastoreType(diskDef.getDiskType().toString());
+            disk.setDatastorePort(diskDef.getSourceHostPort());
+            disk.setImagePath(diskDef.getSourcePath());
+            disk.setDatastoreName(disk.getDatastorePath());
+            disk.setFileBaseName(getDiskRelativePath(diskDef));
+            disks.add(disk);
+        }
+        return disks;
+    }
+
+    protected String getDiskRelativePath(LibvirtVMDef.DiskDef diskDef) {
+        if (diskDef == null || diskDef.getDiskType() == null || diskDef.getDiskType() == LibvirtVMDef.DiskDef.DiskType.BLOCK) {
+            return null;
+        }
+        String sourcePath = diskDef.getSourcePath();
+        if (StringUtils.isBlank(sourcePath)) {
+            return null;
+        }
+        if (!sourcePath.contains("/")) {
+            return sourcePath;
+        }
+        return sourcePath.substring(sourcePath.lastIndexOf("/") + 1);
+    }
+
+    private Pair<String, String> getSourceHostPath(LibvirtComputingResource libvirtComputingResource, String diskPath) {
+        int pathEnd = diskPath.lastIndexOf("/");
+        if (pathEnd >= 0) {
+            diskPath = diskPath.substring(0, pathEnd);
+            return libvirtComputingResource.getSourceHostPath(diskPath);
+        }
+        return null;
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtListDataStoreObjectsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtListDataStoreObjectsCommandWrapper.java
new file mode 100644
index 0000000..f354413
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtListDataStoreObjectsCommandWrapper.java
@@ -0,0 +1,35 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
+
+@ResourceWrapper(handles = ListDataStoreObjectsCommand.class)
+public final class LibvirtListDataStoreObjectsCommandWrapper extends CommandWrapper<ListDataStoreObjectsCommand, Answer, LibvirtComputingResource> {
+    @Override
+    public Answer execute(final ListDataStoreObjectsCommand command,
+            final LibvirtComputingResource libvirtComputingResource) {
+        return libvirtComputingResource.listFilesAtPath(command);
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java
index 5c893e5..2a09c34 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateVolumeCommandWrapper.java
@@ -279,6 +279,10 @@
 
         Map<String, String> srcDetails = command.getSrcDetails();
         String srcPath = srcDetails != null ? srcDetails.get(DiskTO.IQN) : srcVolumeObjectTO.getPath();
+        // its possible a volume has details but is not using IQN addressing...
+        if (srcPath == null) {
+            srcPath = srcVolumeObjectTO.getPath();
+        }
 
         VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData();
         PrimaryDataStoreTO destPrimaryDataStore = (PrimaryDataStoreTO)destVolumeObjectTO.getDataStore();
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java
index ca78c71..dffa836 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java
@@ -64,6 +64,9 @@
             }
             final VifDriver vifDriver = libvirtComputingResource.getVifDriver(nic.getType(), nic.getName());
             final InterfaceDef interfaceDef = vifDriver.plug(nic, "Other PV", "", null);
+            if (command.getDetails() != null) {
+                libvirtComputingResource.setInterfaceDefQueueSettings(command.getDetails(), null, interfaceDef);
+            }
             vm.attachDevice(interfaceDef.toString());
 
             // apply default network rules on new nic
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
index 3f281e5..6292ca7 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
@@ -80,6 +80,9 @@
 
             for (final NicTO nic : nics) {
                 LibvirtVMDef.InterfaceDef interfaceDef = libvirtComputingResource.getVifDriver(nic.getType(), nic.getName()).plug(nic, null, "", vm.getExtraConfig());
+                if (vm.getDetails() != null) {
+                    libvirtComputingResource.setInterfaceDefQueueSettings(vm.getDetails(), vm.getCpus(), interfaceDef);
+                }
                 if (interfaceDef != null && interfaceDef.getNetType() == GuestNetType.VHOSTUSER) {
                     DpdkTO to = new DpdkTO(interfaceDef.getDpdkOvsPath(), interfaceDef.getDpdkSourcePort(), interfaceDef.getInterfaceMode());
                     dpdkInterfaceMapping.put(nic.getMac(), to);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareUnmanageVMInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareUnmanageVMInstanceCommandWrapper.java
new file mode 100644
index 0000000..6837308
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareUnmanageVMInstanceCommandWrapper.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.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.PrepareUnmanageVMInstanceAnswer;
+import com.cloud.agent.api.PrepareUnmanageVMInstanceCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import org.apache.log4j.Logger;
+import org.libvirt.Connect;
+import org.libvirt.Domain;
+
+@ResourceWrapper(handles=PrepareUnmanageVMInstanceCommand.class)
+public final class LibvirtPrepareUnmanageVMInstanceCommandWrapper extends CommandWrapper<PrepareUnmanageVMInstanceCommand, PrepareUnmanageVMInstanceAnswer, LibvirtComputingResource> {
+    private static final Logger LOGGER = Logger.getLogger(LibvirtPrepareUnmanageVMInstanceCommandWrapper.class);
+    @Override
+    public PrepareUnmanageVMInstanceAnswer execute(PrepareUnmanageVMInstanceCommand command, LibvirtComputingResource libvirtComputingResource) {
+        final String vmName = command.getInstanceName();
+        final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
+        LOGGER.debug(String.format("Verify if KVM instance: [%s] is available before Unmanaging VM.", vmName));
+        try {
+            final Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
+            final Domain domain = libvirtComputingResource.getDomain(conn, vmName);
+            if (domain == null) {
+                LOGGER.error("Prepare Unmanage VMInstanceCommand: vm not found " + vmName);
+                new PrepareUnmanageVMInstanceAnswer(command, false, String.format("Cannot find VM with name [%s] in KVM host.", vmName));
+            }
+        } catch (Exception e){
+            LOGGER.error("PrepareUnmanagedInstancesCommand failed due to " + e.getMessage());
+            return new PrepareUnmanageVMInstanceAnswer(command, false, "Error: " + e.getMessage());
+        }
+
+        return new PrepareUnmanageVMInstanceAnswer(command, true, "OK");
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
index fc57cd4..c0089c0 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
@@ -25,6 +25,8 @@
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.ReadyAnswer;
 import com.cloud.agent.api.ReadyCommand;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 import com.cloud.host.Host;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.resource.CommandWrapper;
@@ -51,11 +53,12 @@
 
     private boolean hostSupportsUefi(boolean isUbuntuHost) {
         String cmd = "rpm -qa | grep -i ovmf";
+        int timeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.AGENT_SCRIPT_TIMEOUT) * 1000; // Get property value & convert to milliseconds
         if (isUbuntuHost) {
             cmd = "dpkg -l ovmf";
         }
-        s_logger.debug("Running command : " + cmd);
-        int result = Script.runSimpleBashScriptForExitValue(cmd);
+        s_logger.debug("Running command : [" + cmd + "] with timeout : " + timeout + " ms");
+        int result = Script.runSimpleBashScriptForExitValue(cmd, timeout, false);
         s_logger.debug("Got result : " + result);
         return result == 0;
     }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapper.java
index ff9ae6d..558c7f0 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapper.java
@@ -66,7 +66,9 @@
 
             final VifDriver newVifDriver = libvirtComputingResource.getVifDriver(nic.getType(), nic.getName());
             final InterfaceDef interfaceDef = newVifDriver.plug(nic, "Other PV", oldPluggedNic.getModel().toString(), null);
-
+            if (command.getDetails() != null) {
+                libvirtComputingResource.setInterfaceDefQueueSettings(command.getDetails(), null, interfaceDef);
+            }
             interfaceDef.setSlot(oldPluggedNic.getSlot());
             interfaceDef.setDevName(oldPluggedNic.getDevName());
             interfaceDef.setLinkStateUp(false);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java
index 36ff69d..4f1ad72 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java
@@ -50,6 +50,7 @@
 import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
 import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
 import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.hypervisor.kvm.storage.MultipathSCSIPool;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.storage.Storage.StoragePoolType;
@@ -84,6 +85,10 @@
             final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
             KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
 
+            if (pool instanceof MultipathSCSIPool) {
+                return handleMultipathSCSIResize(command, pool);
+            }
+
             if (spool.getType().equals(StoragePoolType.PowerFlex)) {
                 pool.connectPhysicalDisk(volumeId, null);
             }
@@ -225,4 +230,9 @@
             throw new CloudRuntimeException("Error when inspecting volume at path " + path, ex);
         }
     }
+
+    private Answer handleMultipathSCSIResize(ResizeVolumeCommand command, KVMStoragePool pool) {
+        ((MultipathSCSIPool)pool).resize(command.getPath(), command.getInstanceName(), command.getNewSize());
+        return new ResizeVolumeAnswer(command, true, "");
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java
index 79d43ba..1536984 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java
@@ -59,14 +59,6 @@
             String message = String.format("Unable to scale %s due to [%s].", scalingDetails, e.getMessage());
             logger.error(message, e);
             return new ScaleVmAnswer(command, false, message);
-        } finally {
-            if (conn != null) {
-                try {
-                    conn.close();
-                } catch (LibvirtException ex) {
-                    logger.warn(String.format("Error trying to close libvirt connection [%s]", ex.getMessage()), ex);
-                }
-            }
         }
     }
 
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/FiberChannelAdapter.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/FiberChannelAdapter.java
new file mode 100644
index 0000000..be7cb72
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/FiberChannelAdapter.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 com.cloud.hypervisor.kvm.storage;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.FiberChannel)
+public class FiberChannelAdapter extends MultipathSCSIAdapterBase {
+    public FiberChannelAdapter() {
+        LOGGER.info("Loaded FiberChannelAdapter for StorageLayer");
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        KVMStoragePool pool = MapStorageUuidToStoragePool.get(uuid);
+        if (pool == null) {
+            // return a dummy pool - this adapter doesn't care about connectivity information
+            pool = new MultipathSCSIPool(uuid, this);
+            MapStorageUuidToStoragePool.put(uuid, pool);
+         }
+        LOGGER.info("FiberChannelAdapter return storage pool [" + uuid + "]");
+        return pool;
+    }
+
+    public String getName() {
+        return "FiberChannelAdapter";
+    }
+
+    public boolean isStoragePoolTypeSupported(Storage.StoragePoolType type) {
+        if (Storage.StoragePoolType.FiberChannel.equals(type)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public AddressInfo parseAndValidatePath(String inPath) {
+            // type=FIBERWWN; address=<address>; connid=<connid>
+            String type = null;
+            String address = null;
+            String connectionId = null;
+            String path = null;
+            String[] parts = inPath.split(";");
+            // handle initial code of wwn only
+            if (parts.length == 1) {
+                type = "FIBERWWN";
+                address = parts[0];
+            } else {
+                for (String part: parts) {
+                    String[] pair = part.split("=");
+                    if (pair.length == 2) {
+                        String key = pair[0].trim();
+                        String value = pair[1].trim();
+                        if (key.equals("type")) {
+                            type = value.toUpperCase();
+                        } else if (key.equals("address")) {
+                            address = value;
+                        } else if (key.equals("connid")) {
+                            connectionId = value;
+                        }
+                    }
+                }
+            }
+
+            if ("FIBERWWN".equals(type)) {
+                path = "/dev/mapper/3" + address;
+            } else {
+                throw new CloudRuntimeException("Invalid address type provided for target disk: " + type);
+            }
+
+            return new AddressInfo(type, address, connectionId, path);
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java
index 09034c6..a89650f 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java
@@ -22,7 +22,10 @@
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
+import org.joda.time.Duration;
 
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.StoragePoolType;
 
@@ -183,4 +186,35 @@
         return new ToStringBuilder(this, ToStringStyle.JSON_STYLE).append("uuid", getUuid()).append("path", getLocalPath()).toString();
     }
 
+    @Override
+    public boolean isPoolSupportHA() {
+        return false;
+    }
+
+    @Override
+    public String getHearthBeatPath() {
+        return null;
+    }
+
+    @Override
+    public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp,
+            boolean hostValidation) {
+        return null;
+    }
+
+    @Override
+    public String getStorageNodeId() {
+        return null;
+    }
+
+    @Override
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
+        return null;
+    }
+
+    @Override
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
+        return null;
+    }
+
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
index 3bff9c9..43a09cc 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
@@ -19,12 +19,25 @@
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.joda.time.Duration;
 
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.StoragePoolType;
 
 public interface KVMStoragePool {
+
+    public static final long HeartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
+    public static final long HeartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
+    public static final long HeartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES);
+    public static final long HeartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP);
+    public static final long HeartBeatCheckerTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_CHECKER_TIMEOUT);
+
+
     public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
 
     public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
@@ -74,4 +87,16 @@
     public boolean supportsConfigDriveIso();
 
     public Map<String, String> getDetails();
+
+    public boolean isPoolSupportHA();
+
+    public String getHearthBeatPath();
+
+    public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation);
+
+    public String getStorageNodeId();
+
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host);
+
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration);
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
index 79c7e2a..b1842f3 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
@@ -362,9 +362,9 @@
         KVMStoragePool pool = adaptor.createStoragePool(name, host, port, path, userInfo, type, details);
 
         // LibvirtStorageAdaptor-specific statement
-        if (type == StoragePoolType.NetworkFilesystem && primaryStorage) {
-            KVMHABase.NfsStoragePool nfspool = new KVMHABase.NfsStoragePool(pool.getUuid(), host, path, pool.getLocalPath(), PoolType.PrimaryStorage);
-            _haMonitor.addStoragePool(nfspool);
+        if (pool.isPoolSupportHA() && primaryStorage) {
+            KVMHABase.HAStoragePool storagePool = new KVMHABase.HAStoragePool(pool, host, path, PoolType.PrimaryStorage);
+            _haMonitor.addStoragePool(storagePool);
         }
         StoragePoolInformation info = new StoragePoolInformation(name, host, port, path, userInfo, type, details, primaryStorage);
         addStoragePool(pool.getUuid(), info);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index 0cfd83a..f0ce56e 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -290,9 +290,12 @@
                 final TemplateObjectTO newTemplate = new TemplateObjectTO();
                 newTemplate.setPath(primaryVol.getName());
                 newTemplate.setSize(primaryVol.getSize());
-                if (primaryPool.getType() == StoragePoolType.RBD ||
-                    primaryPool.getType() == StoragePoolType.PowerFlex ||
-                    primaryPool.getType() == StoragePoolType.Linstor) {
+
+                if(List.of(
+                    StoragePoolType.RBD,
+                    StoragePoolType.PowerFlex,
+                    StoragePoolType.Linstor,
+                    StoragePoolType.FiberChannel).contains(primaryPool.getType())) {
                     newTemplate.setFormat(ImageFormat.RAW);
                 } else {
                     newTemplate.setFormat(ImageFormat.QCOW2);
@@ -584,7 +587,9 @@
     public Answer createTemplateFromVolume(final CopyCommand cmd) {
         Map<String, String> details = cmd.getOptions();
 
-        if (details != null && details.get(DiskTO.IQN) != null) {
+        // handle cases where the managed storage driver had to make a temporary volume from
+        // the snapshot in order to support the copy
+        if (details != null && (details.get(DiskTO.IQN) != null || details.get(DiskTO.PATH) != null)) {
             // use the managed-storage approach
             return createTemplateFromVolumeOrSnapshot(cmd);
         }
@@ -712,7 +717,7 @@
     public Answer createTemplateFromSnapshot(CopyCommand cmd) {
         Map<String, String> details = cmd.getOptions();
 
-        if (details != null && details.get(DiskTO.IQN) != null) {
+        if (details != null && (details.get(DiskTO.IQN) != null || details.get(DiskTO.PATH) != null)) {
             // use the managed-storage approach
             return createTemplateFromVolumeOrSnapshot(cmd);
         }
@@ -750,12 +755,15 @@
         KVMStoragePool secondaryStorage = null;
 
         try {
+            // look for options indicating an overridden path or IQN.  Used when snapshots have to be
+            // temporarily copied on the manaaged storage device before the actual copy to target object
             Map<String, String> details = cmd.getOptions();
-
-            String path = details != null ? details.get(DiskTO.IQN) : null;
-
+            String path = details != null ? details.get(DiskTO.PATH) : null;
             if (path == null) {
-                new CloudRuntimeException("The 'path' field must be specified.");
+                path = details != null ? details.get(DiskTO.IQN) : null;
+                if (path == null) {
+                    new CloudRuntimeException("The 'path' or 'iqn' field must be specified.");
+                }
             }
 
             storagePoolMgr.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path, details);
@@ -2188,7 +2196,16 @@
 
         Map<String, String> details = cmd.getOptions2();
 
-        String path = details != null ? details.get(DiskTO.IQN) : null;
+        String path = cmd.getDestTO().getPath();
+        if (path == null) {
+            path = details != null ? details.get(DiskTO.PATH) : null;
+            if (path == null) {
+                path = details != null ? details.get(DiskTO.IQN) : null;
+                if (path == null) {
+                    new CloudRuntimeException("The 'path' or 'iqn' field must be specified.");
+                }
+            }
+        }
 
         storagePoolMgr.connectPhysicalDisk(pool.getPoolType(), pool.getUuid(), path, details);
 
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
index 183a364..bdaa419 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
@@ -72,6 +72,7 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
+
 public class LibvirtStorageAdaptor implements StorageAdaptor {
     private static final Logger s_logger = Logger.getLogger(LibvirtStorageAdaptor.class);
     private StorageLayer _storageLayer;
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
index 4a449ab..d81b403 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
@@ -21,15 +21,22 @@
 import java.util.Map;
 
 import org.apache.log4j.Logger;
+import org.joda.time.Duration;
 import org.libvirt.StoragePool;
 
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.OutputInterpreter;
+import com.cloud.utils.script.Script;
 
 public class LibvirtStoragePool implements KVMStoragePool {
     private static final Logger s_logger = Logger.getLogger(LibvirtStoragePool.class);
@@ -288,7 +295,95 @@
     }
 
     @Override
+    public boolean isPoolSupportHA() {
+        return type == StoragePoolType.NetworkFilesystem;
+    }
+
+    public String getHearthBeatPath() {
+        if (type == StoragePoolType.NetworkFilesystem) {
+            String kvmScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR);
+            return Script.findScript(kvmScriptsDir, "kvmheartbeat.sh");
+        }
+        return null;
+    }
+
+
+    public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) {
+        Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeout, s_logger);
+        cmd.add("-i", primaryStoragePool.getPoolIp());
+        cmd.add("-p", primaryStoragePool.getPoolMountSourcePath());
+        cmd.add("-m", primaryStoragePool.getMountDestPath());
+
+        if (hostValidation) {
+            cmd.add("-h", hostPrivateIp);
+        }
+
+        if (!hostValidation) {
+            cmd.add("-c");
+        }
+
+        return cmd.execute();
+    }
+
+    @Override
     public String toString() {
         return new ToStringBuilder(this, ToStringStyle.JSON_STYLE).append("uuid", getUuid()).append("path", getLocalPath()).toString();
     }
+
+    @Override
+    public String getStorageNodeId() {
+        return null;
+    }
+
+    @Override
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
+        boolean validResult = false;
+        String hostIp = host.getPrivateNetwork().getIp();
+        Script cmd = new Script(getHearthBeatPath(), HeartBeatCheckerTimeout, s_logger);
+        cmd.add("-i", pool.getPoolIp());
+        cmd.add("-p", pool.getPoolMountSourcePath());
+        cmd.add("-m", pool.getMountDestPath());
+        cmd.add("-h", hostIp);
+        cmd.add("-r");
+        cmd.add("-t", String.valueOf(HeartBeatUpdateFreq / 1000));
+        OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
+        String result = cmd.execute(parser);
+        String parsedLine = parser.getLine();
+
+        s_logger.debug(String.format("Checking heart beat with KVMHAChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine,
+                pool.getPoolIp()));
+
+        if (result == null && parsedLine.contains("DEAD")) {
+            s_logger.warn(String.format("Checking heart beat with KVMHAChecker command [%s] returned [%s]. [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(),
+                    result, parsedLine, hostIp));
+        } else {
+            validResult = true;
+        }
+        return validResult;
+    }
+
+    @Override
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
+        Script cmd = new Script(vmActivityCheckPath, activityScriptTimeout.getStandardSeconds(), s_logger);
+        cmd.add("-i", pool.getPoolIp());
+        cmd.add("-p", pool.getPoolMountSourcePath());
+        cmd.add("-m", pool.getMountDestPath());
+        cmd.add("-h", host.getPrivateNetwork().getIp());
+        cmd.add("-u", volumeUUIDListString);
+        cmd.add("-t", String.valueOf(String.valueOf(System.currentTimeMillis() / 1000)));
+        cmd.add("-d", String.valueOf(duration));
+        OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
+
+        String result = cmd.execute(parser);
+        String parsedLine = parser.getLine();
+
+        s_logger.debug(String.format("Checking heart beat with KVMHAVMActivityChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine, pool.getPoolIp()));
+
+        if (result == null && parsedLine.contains("DEAD")) {
+            s_logger.warn(String.format("Checking heart beat with KVMHAVMActivityChecker command [%s] returned [%s]. It is [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(), result, parsedLine, host.getPrivateNetwork().getIp()));
+            return false;
+        } else {
+            return true;
+        }
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIAdapterBase.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIAdapterBase.java
new file mode 100644
index 0000000..06dea46
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIAdapterBase.java
@@ -0,0 +1,758 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.hypervisor.kvm.storage;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.OutputInterpreter;
+import com.cloud.utils.script.Script;
+import org.apache.commons.lang3.StringUtils;
+import org.libvirt.LibvirtException;
+import org.joda.time.Duration;
+
+public abstract class MultipathSCSIAdapterBase implements StorageAdaptor {
+    static final Logger LOGGER = Logger.getLogger(MultipathSCSIAdapterBase.class);
+    static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    /**
+     * A lock to avoid any possiblity of multiple requests for a scan
+     */
+    static byte[] CLEANUP_LOCK = new byte[0];
+
+    /**
+     * Property keys and defaults
+     */
+    static final Property<Integer> CLEANUP_FREQUENCY_SECS = new Property<Integer>("multimap.cleanup.frequency.secs", 60);
+    static final Property<Integer> CLEANUP_TIMEOUT_SECS = new Property<Integer>("multimap.cleanup.timeout.secs", 4);
+    static final Property<Boolean> CLEANUP_ENABLED = new Property<Boolean>("multimap.cleanup.enabled", true);
+    static final Property<String> CLEANUP_SCRIPT = new Property<String>("multimap.cleanup.script", "cleanStaleMaps.sh");
+    static final Property<String> CONNECT_SCRIPT = new Property<String>("multimap.connect.script", "connectVolume.sh");
+    static final Property<String> COPY_SCRIPT = new Property<String>("multimap.copy.script", "copyVolume.sh");
+    static final Property<String> DISCONNECT_SCRIPT = new Property<String>("multimap.disconnect.script", "disconnectVolume.sh");
+    static final Property<String> RESIZE_SCRIPT = new Property<String>("multimap.resize.script", "resizeVolume.sh");
+    static final Property<Integer> DISK_WAIT_SECS = new Property<Integer>("multimap.disk.wait.secs", 240);
+    static final Property<String> STORAGE_SCRIPTS_DIR = new Property<String>("multimap.storage.scripts.dir", "scripts/storage/multipath");
+
+    static Timer cleanupTimer = new Timer();
+    private static int cleanupTimeoutSecs = CLEANUP_TIMEOUT_SECS.getFinalValue();
+    private static String connectScript = CONNECT_SCRIPT.getFinalValue();
+    private static String disconnectScript = DISCONNECT_SCRIPT.getFinalValue();
+    private static String cleanupScript = CLEANUP_SCRIPT.getFinalValue();
+    private static String resizeScript = RESIZE_SCRIPT.getFinalValue();
+    private static String copyScript = COPY_SCRIPT.getFinalValue();
+    private static int diskWaitTimeSecs = DISK_WAIT_SECS.getFinalValue();
+
+    /**
+     * Initialize static program-wide configurations and background jobs
+     */
+    static {
+        long cleanupFrequency = CLEANUP_FREQUENCY_SECS.getFinalValue() * 1000;
+        boolean cleanupEnabled = CLEANUP_ENABLED.getFinalValue();
+
+
+        connectScript = Script.findScript(STORAGE_SCRIPTS_DIR.getFinalValue(), connectScript);
+        if (connectScript == null) {
+            throw new Error("Unable to find the connectVolume.sh script");
+        }
+
+        disconnectScript = Script.findScript(STORAGE_SCRIPTS_DIR.getFinalValue(), disconnectScript);
+        if (disconnectScript == null) {
+            throw new Error("Unable to find the disconnectVolume.sh script");
+        }
+
+        resizeScript = Script.findScript(STORAGE_SCRIPTS_DIR.getFinalValue(), resizeScript);
+        if (resizeScript == null) {
+            throw new Error("Unable to find the resizeVolume.sh script");
+        }
+
+        copyScript = Script.findScript(STORAGE_SCRIPTS_DIR.getFinalValue(), copyScript);
+        if (copyScript == null) {
+            throw new Error("Unable to find the copyVolume.sh script");
+        }
+
+        if (cleanupEnabled) {
+            cleanupScript = Script.findScript(STORAGE_SCRIPTS_DIR.getFinalValue(), cleanupScript);
+            if (cleanupScript == null) {
+                throw new Error("Unable to find the cleanStaleMaps.sh script and " + CLEANUP_ENABLED.getName() + " is true");
+            }
+
+            TimerTask task = new TimerTask() {
+                @Override
+                public void run() {
+                    try {
+                        MultipathSCSIAdapterBase.cleanupStaleMaps();
+                    } catch (Throwable e) {
+                        LOGGER.warn("Error running stale multipath map cleanup", e);
+                    }
+                }
+            };
+
+            cleanupTimer = new Timer("MultipathMapCleanupJob");
+            cleanupTimer.scheduleAtFixedRate(task, 0, cleanupFrequency);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        return getStoragePool(uuid);
+    }
+
+    public abstract String getName();
+
+    public abstract boolean isStoragePoolTypeSupported(Storage.StoragePoolType type);
+
+    /**
+     * We expect WWN values in the volumePath so need to convert it to an actual physical path
+     */
+    public abstract AddressInfo parseAndValidatePath(String path);
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String volumePath, KVMStoragePool pool) {
+        LOGGER.debug(String.format("getPhysicalDisk(volumePath,pool) called with args (%s,%s)", volumePath, pool));
+
+        if (StringUtils.isEmpty(volumePath) || pool == null) {
+            LOGGER.error("Unable to get physical disk, volume path or pool not specified");
+            return null;
+        }
+
+        AddressInfo address = parseAndValidatePath(volumePath);
+        return getPhysicalDisk(address, pool);
+    }
+
+    private KVMPhysicalDisk getPhysicalDisk(AddressInfo address, KVMStoragePool pool) {
+        LOGGER.debug(String.format("getPhysicalDisk(addressInfo,pool) called with args (%s,%s)", address.getPath(), pool));
+        KVMPhysicalDisk disk = new KVMPhysicalDisk(address.getPath(), address.toString(), pool);
+        disk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+
+        long diskSize = getPhysicalDiskSize(address.getPath());
+        disk.setSize(diskSize);
+        disk.setVirtualSize(diskSize);
+        LOGGER.debug("Physical disk " + disk.getPath() + " with format " + disk.getFormat() + " and size " + disk.getSize() + " provided");
+        return disk;
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, Storage.StoragePoolType type, Map<String, String> details) {
+        LOGGER.info(String.format("createStoragePool(uuid,host,port,path,type) called with args (%s, %s, %s, %s, %s)", uuid, host, ""+port, path, type));
+        MultipathSCSIPool storagePool = new MultipathSCSIPool(uuid, host, port, path, type, details, this);
+        MapStorageUuidToStoragePool.put(uuid, storagePool);
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+   @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details) {
+        LOGGER.info("connectPhysicalDisk called for [" + volumePath + "]");
+
+        if (StringUtils.isEmpty(volumePath)) {
+            LOGGER.error("Unable to connect physical disk due to insufficient data - volume path is undefined");
+            throw new CloudRuntimeException("Unable to connect physical disk due to insufficient data - volume path is underfined");
+        }
+
+        if (pool == null) {
+            LOGGER.error("Unable to connect physical disk due to insufficient data - pool is not set");
+            throw new CloudRuntimeException("Unable to connect physical disk due to insufficient data - pool is not set");
+        }
+
+        AddressInfo address = this.parseAndValidatePath(volumePath);
+        int waitTimeInSec = diskWaitTimeSecs;
+        if (details != null && details.containsKey(StorageManager.STORAGE_POOL_DISK_WAIT.toString())) {
+            String waitTime = details.get(StorageManager.STORAGE_POOL_DISK_WAIT.toString());
+            if (StringUtils.isNotEmpty(waitTime)) {
+                waitTimeInSec = Integer.valueOf(waitTime).intValue();
+            }
+        }
+        return waitForDiskToBecomeAvailable(address, pool, waitTimeInSec);
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool) {
+        LOGGER.debug(String.format("disconnectPhysicalDiskByPath(volumePath,pool) called with args (%s, %s) START", volumePath, pool.getUuid()));
+        AddressInfo address = this.parseAndValidatePath(volumePath);
+        ScriptResult result = runScript(disconnectScript, 60000L, address.getAddress().toLowerCase());
+        if (LOGGER.isDebugEnabled()) LOGGER.debug("multipath flush output: " + result.getResult());
+        LOGGER.debug(String.format("disconnectPhysicalDiskByPath(volumePath,pool) called with args (%s, %s) COMPLETE [rc=%s]", volumePath, pool.getUuid(), result.getResult()));        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
+        LOGGER.debug(String.format("disconnectPhysicalDiskByPath(volumeToDisconnect) called with arg bag [not implemented]:") + " " + volumeToDisconnect);
+        return false;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath) {
+        LOGGER.debug(String.format("disconnectPhysicalDiskByPath(localPath) called with args (%s) STARTED", localPath));
+        ScriptResult result = runScript(disconnectScript, 60000L, localPath.replace("/dev/mapper/3", ""));
+        if (LOGGER.isDebugEnabled()) LOGGER.debug("multipath flush output: " + result.getResult());
+        LOGGER.debug(String.format("disconnectPhysicalDiskByPath(localPath) called with args (%s) COMPLETE [rc=%s]", localPath, result.getExitCode()));       return true;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String uuid, KVMStoragePool pool, Storage.ImageFormat format) {
+        LOGGER.info(String.format("deletePhysicalDisk(uuid,pool,format) called with args (%s, %s, %s) [not implemented]", uuid, pool.getUuid(), format.toString()));
+        return true;
+    }
+
+    @Override
+    public KVMPhysicalDisk createTemplateFromDisk(KVMPhysicalDisk disk, String name, QemuImg.PhysicalDiskFormat format, long size, KVMStoragePool destPool) {
+        LOGGER.info(String.format("createTemplateFromDisk(disk,name,format,size,destPool) called with args (%s, %s, %s, %s, %s) [not implemented]", disk.getPath(), name, format.toString(), ""+size, destPool.getUuid()));
+        return null;
+    }
+
+    @Override
+    public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool) {
+        LOGGER.info(String.format("listPhysicalDisks(uuid,pool) called with args (%s, %s) [not implemented]", storagePoolUuid, pool.getUuid()));
+        return null;
+    }
+
+    @Override
+    public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
+        return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null);
+    }
+
+    @Override
+    public boolean refresh(KVMStoragePool pool) {
+        LOGGER.info(String.format("refresh(pool) called with args (%s)", pool.getUuid()));
+        return true;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        LOGGER.info(String.format("deleteStroagePool(pool) called with args (%s)", pool.getUuid()));
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public boolean createFolder(String uuid, String path) {
+        LOGGER.info(String.format("createFolder(uuid,path) called with args (%s, %s) [not implemented]", uuid, path));
+        return createFolder(uuid, path, null);
+    }
+
+    @Override
+    public boolean createFolder(String uuid, String path, String localPath) {
+        LOGGER.info(String.format("createFolder(uuid,path,localPath) called with args (%s, %s, %s) [not implemented]", uuid, path, localPath));
+        return true;
+    }
+
+    /**
+     * Validate inputs and return the source file for a template copy
+     * @param templateFilePath
+     * @param destTemplatePath
+     * @param destPool
+     * @param format
+     * @return
+     */
+    File createTemplateFromDirectDownloadFileValidate(String templateFilePath, String destTemplatePath, KVMStoragePool destPool, Storage.ImageFormat format) {
+        if (StringUtils.isAnyEmpty(templateFilePath, destTemplatePath) || destPool == null) {
+            LOGGER.error("Unable to create template from direct download template file due to insufficient data");
+            throw new CloudRuntimeException("Unable to create template from direct download template file due to insufficient data");
+        }
+
+        LOGGER.debug("Create template from direct download template - file path: " + templateFilePath + ", dest path: " + destTemplatePath + ", format: " + format.toString());
+
+        File sourceFile = new File(templateFilePath);
+        if (!sourceFile.exists()) {
+            throw new CloudRuntimeException("Direct download template file " + templateFilePath + " does not exist on this host");
+        }
+
+        if (destTemplatePath == null || destTemplatePath.isEmpty()) {
+            LOGGER.error("Failed to create template, target template disk path not provided");
+            throw new CloudRuntimeException("Target template disk path not provided");
+        }
+
+        if (this.isStoragePoolTypeSupported(destPool.getType())) {
+            throw new CloudRuntimeException("Unsupported storage pool type: " + destPool.getType().toString());
+        }
+
+        if (Storage.ImageFormat.RAW.equals(format) && Storage.ImageFormat.QCOW2.equals(format)) {
+            LOGGER.error("Failed to create template, unsupported template format: " + format.toString());
+            throw new CloudRuntimeException("Unsupported template format: " + format.toString());
+        }
+        return sourceFile;
+    }
+
+    String extractSourceTemplateIfNeeded(File sourceFile, String templateFilePath) {
+        String srcTemplateFilePath = templateFilePath;
+        if (isTemplateExtractable(templateFilePath)) {
+            srcTemplateFilePath = sourceFile.getParent() + "/" + UUID.randomUUID().toString();
+            LOGGER.debug("Extract the downloaded template " + templateFilePath + " to " + srcTemplateFilePath);
+            String extractCommand = getExtractCommandForDownloadedFile(templateFilePath, srcTemplateFilePath);
+            Script.runSimpleBashScript(extractCommand);
+            Script.runSimpleBashScript("rm -f " + templateFilePath);
+        }
+        return srcTemplateFilePath;
+    }
+
+    QemuImg.PhysicalDiskFormat deriveImgFileFormat(Storage.ImageFormat format) {
+        if (format == Storage.ImageFormat.RAW) {
+            return QemuImg.PhysicalDiskFormat.RAW;
+        } else if (format == Storage.ImageFormat.QCOW2) {
+            return QemuImg.PhysicalDiskFormat.QCOW2;
+        } else {
+            return QemuImg.PhysicalDiskFormat.RAW;
+        }
+    }
+
+    @Override
+    public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath, KVMStoragePool destPool, Storage.ImageFormat format, int timeout) {
+        File sourceFile = createTemplateFromDirectDownloadFileValidate(templateFilePath, destTemplatePath, destPool, format);
+        LOGGER.debug("Create template from direct download template - file path: " + templateFilePath + ", dest path: " + destTemplatePath + ", format: " + format.toString());
+        KVMPhysicalDisk sourceDisk = destPool.getPhysicalDisk(sourceFile.getAbsolutePath());
+        return copyPhysicalDisk(sourceDisk, destTemplatePath, destPool, timeout, null, null,  Storage.ProvisioningType.THIN);
+    }
+
+    @Override
+    public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout,
+            byte[] srcPassphrase, byte[] dstPassphrase, Storage.ProvisioningType provisioningType) {
+
+        validateForDiskCopy(disk, name, destPool);
+        LOGGER.info("Copying FROM source physical disk " + disk.getPath() + ", size: " + disk.getSize() + ", virtualsize: " + disk.getVirtualSize()+ ", format: " + disk.getFormat());
+
+        KVMPhysicalDisk destDisk = destPool.getPhysicalDisk(name);
+        if (destDisk == null) {
+            LOGGER.error("Failed to find the disk: " + name + " of the storage pool: " + destPool.getUuid());
+            throw new CloudRuntimeException("Failed to find the disk: " + name + " of the storage pool: " + destPool.getUuid());
+        }
+
+        if (srcPassphrase != null || dstPassphrase != null) {
+            throw new CloudRuntimeException("Storage provider does not support user-space encrypted source or destination volumes");
+        }
+
+        destDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+        destDisk.setVirtualSize(disk.getVirtualSize());
+        destDisk.setSize(disk.getSize());
+
+        LOGGER.info("Copying TO destination physical disk " + destDisk.getPath() + ", size: " + destDisk.getSize() + ", virtualsize: " + destDisk.getVirtualSize()+ ", format: " + destDisk.getFormat());
+        QemuImgFile srcFile = new QemuImgFile(disk.getPath(), disk.getFormat());
+        QemuImgFile destFile = new QemuImgFile(destDisk.getPath(), destDisk.getFormat());
+        LOGGER.debug("Starting COPY from source downloaded template " + srcFile.getFileName() + " to Primera volume: " + destDisk.getPath());
+        ScriptResult result = runScript(copyScript, timeout, destDisk.getFormat().toString().toLowerCase(), srcFile.getFileName(), destFile.getFileName());
+        int rc = result.getExitCode();
+        if (rc != 0) {
+            throw new CloudRuntimeException("Failed to convert from " + srcFile.getFileName() + " to " + destFile.getFileName() + " the error was: " + rc + " - " + result.getResult());
+        }
+        LOGGER.debug("Successfully converted source downloaded template " + srcFile.getFileName() + " to Primera volume: " + destDisk.getPath() + " " + result.getResult());
+
+        return destDisk;
+    }
+
+    void validateForDiskCopy(KVMPhysicalDisk disk, String name, KVMStoragePool destPool) {
+        if (StringUtils.isEmpty(name) || disk == null || destPool == null) {
+            LOGGER.error("Unable to copy physical disk due to insufficient data");
+            throw new CloudRuntimeException("Unable to copy physical disk due to insufficient data");
+        }
+    }
+
+    /**
+     * Copy a disk path to another disk path using QemuImg command
+     * @param disk
+     * @param destDisk
+     * @param name
+     * @param timeout
+     */
+    void qemuCopy(KVMPhysicalDisk disk, KVMPhysicalDisk destDisk, String name, int timeout) {
+        QemuImg qemu;
+        try {
+            qemu = new QemuImg(timeout);
+        } catch (LibvirtException | QemuImgException e) {
+            throw new CloudRuntimeException (e);
+        }
+        QemuImgFile srcFile = null;
+        QemuImgFile destFile = null;
+
+        try {
+            srcFile = new QemuImgFile(disk.getPath(), disk.getFormat());
+            destFile = new QemuImgFile(destDisk.getPath(), destDisk.getFormat());
+
+            LOGGER.debug("Starting copy from source disk image " + srcFile.getFileName() + " to volume: " + destDisk.getPath());
+            qemu.convert(srcFile, destFile, true);
+            LOGGER.debug("Successfully converted source disk image " + srcFile.getFileName() + " to volume: " + destDisk.getPath());
+        }  catch (QemuImgException | LibvirtException e) {
+            try {
+                Map<String, String> srcInfo = qemu.info(srcFile);
+                LOGGER.debug("Source disk info: " + Arrays.asList(srcInfo));
+            } catch (Exception ignored) {
+                LOGGER.warn("Unable to get info from source disk: " + disk.getName());
+            }
+
+            String errMsg = String.format("Unable to convert/copy from %s to %s, due to: %s", disk.getName(), name, ((StringUtils.isEmpty(e.getMessage())) ? "an unknown error" : e.getMessage()));
+            LOGGER.error(errMsg);
+            throw new CloudRuntimeException(errMsg, e);
+        }
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template,
+            String name, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size,
+        KVMStoragePool destPool, int timeout, byte[] passphrase) {
+            throw new UnsupportedOperationException("Unimplemented method 'createDiskFromTemplate'");
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template,
+         String name, PhysicalDiskFormat format, long size,
+         KVMStoragePool destPool, int timeout, byte[] passphrase) {
+        throw new UnsupportedOperationException("Unimplemented method 'createDiskFromTemplateBacking'");
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
+            PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
+        throw new UnsupportedOperationException("Unimplemented method 'createPhysicalDisk'");
+    }
+
+    boolean isTemplateExtractable(String templatePath) {
+        ScriptResult result = runScript("file", 5000L, templatePath, "| awk -F' ' '{print $2}'");
+        String type = result.getResult();
+        return type.equalsIgnoreCase("bzip2") || type.equalsIgnoreCase("gzip") || type.equalsIgnoreCase("zip");
+    }
+
+    String getExtractCommandForDownloadedFile(String downloadedTemplateFile, String templateFile) {
+        if (downloadedTemplateFile.endsWith(".zip")) {
+            return "unzip -p " + downloadedTemplateFile + " | cat > " + templateFile;
+        } else if (downloadedTemplateFile.endsWith(".bz2")) {
+            return "bunzip2 -c " + downloadedTemplateFile + " > " + templateFile;
+        } else if (downloadedTemplateFile.endsWith(".gz")) {
+            return "gunzip -c " + downloadedTemplateFile + " > " + templateFile;
+        } else {
+            throw new CloudRuntimeException("Unable to extract template " + downloadedTemplateFile);
+        }
+    }
+
+    private static final ScriptResult runScript(String script, long timeout, String...args) {
+        ScriptResult result = new ScriptResult();
+        Script cmd = new Script(script, Duration.millis(timeout), LOGGER);
+        cmd.add(args);
+        OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
+        String output = cmd.execute(parser);
+        // its possible the process never launches which causes an NPE on getExitValue below
+        if (output != null && output.contains("Unable to execute the command")) {
+            result.setResult(output);
+            result.setExitCode(-1);
+            return result;
+        }
+        result.setResult(output);
+        result.setExitCode(cmd.getExitValue());
+        return result;
+    }
+
+    boolean waitForDiskToBecomeAvailable(AddressInfo address, KVMStoragePool pool, long waitTimeInSec) {
+        LOGGER.debug("Waiting for the volume with id: " + address.getPath() + " of the storage pool: " + pool.getUuid() + " to become available for " + waitTimeInSec + " secs");
+        long scriptTimeoutSecs = 30; // how long to wait for each script execution to run
+        long maxTries = 10; // how many max retries to attempt the script
+        long waitTimeInMillis = waitTimeInSec * 1000; // how long overall to wait
+        int timeBetweenTries = 1000; // how long to sleep between tries
+        // wait at least 60 seconds even if input was lower
+        if (waitTimeInSec < 60) {
+            waitTimeInSec = 60;
+        }
+        KVMPhysicalDisk physicalDisk = null;
+
+        // Rescan before checking for the physical disk
+        int tries = 0;
+        while (waitTimeInMillis > 0 && tries < maxTries) {
+            tries++;
+            long start = System.currentTimeMillis();
+            String lun;
+            if (address.getConnectionId() == null) {
+                lun = "-";
+            } else {
+                lun = address.getConnectionId();
+            }
+
+            Process p = null;
+            try {
+                ProcessBuilder builder = new ProcessBuilder(connectScript, lun, address.getAddress());
+                p = builder.start();
+                if (p.waitFor(scriptTimeoutSecs, TimeUnit.SECONDS)) {
+                    int rc = p.exitValue();
+                    StringBuffer output = new StringBuffer();
+                    if (rc == 0) {
+                        BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
+                        String line = null;
+                        while ((line = input.readLine()) != null) {
+                            output.append(line);
+                            output.append(" ");
+                        }
+
+                        physicalDisk = getPhysicalDisk(address, pool);
+                        if (physicalDisk != null && physicalDisk.getSize() > 0) {
+                            LOGGER.debug("Found the volume using id: " + address.getPath() + " of the storage pool: " + pool.getUuid());
+                            return true;
+                        }
+
+                        break;
+                    } else {
+                        LOGGER.warn("Failure discovering LUN via " + connectScript);
+                        BufferedReader error = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+                        String line = null;
+                        while ((line = error.readLine()) != null) {
+                            LOGGER.warn("error --> " + line);
+                        }
+                    }
+                } else {
+                    LOGGER.debug("Timeout waiting for " + connectScript + " to complete - try " + tries);
+                }
+            } catch (IOException | InterruptedException | IllegalThreadStateException e) {
+                LOGGER.warn("Problem performing scan on SCSI hosts - try " + tries, e);
+            } finally {
+                if (p != null && p.isAlive()) {
+                    p.destroyForcibly();
+                }
+            }
+
+            long elapsed = System.currentTimeMillis() - start;
+            waitTimeInMillis = waitTimeInMillis - elapsed;
+
+            try {
+                Thread.sleep(timeBetweenTries);
+            } catch (Exception ex) {
+                // don't do anything
+            }
+        }
+
+        LOGGER.debug("Unable to find the volume with id: " + address.getPath() + " of the storage pool: " + pool.getUuid());
+        return false;
+    }
+
+    void runConnectScript(String lun, AddressInfo address) {
+        try {
+            ProcessBuilder builder = new ProcessBuilder(connectScript, lun, address.getAddress());
+            Process p = builder.start();
+            int rc = p.waitFor();
+            StringBuffer output = new StringBuffer();
+            if (rc == 0) {
+                BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
+                String line = null;
+                while ((line = input.readLine()) != null) {
+                    output.append(line);
+                    output.append(" ");
+                }
+            } else {
+                LOGGER.warn("Failure discovering LUN via " + connectScript);
+                BufferedReader error = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+                String line = null;
+                while ((line = error.readLine()) != null) {
+                    LOGGER.warn("error --> " + line);
+                }
+            }
+        } catch (IOException | InterruptedException e) {
+            throw new CloudRuntimeException("Problem performing scan on SCSI hosts", e);
+        }
+    }
+
+    void sleep(long sleepTimeMs) {
+        try {
+            Thread.sleep(sleepTimeMs);
+        } catch (Exception ex) {
+            // don't do anything
+        }
+    }
+
+    long getPhysicalDiskSize(String diskPath) {
+        if (StringUtils.isEmpty(diskPath)) {
+            return 0;
+        }
+
+        Script diskCmd = new Script("blockdev", LOGGER);
+        diskCmd.add("--getsize64", diskPath);
+
+        OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
+        String result = diskCmd.execute(parser);
+
+        if (result != null) {
+            LOGGER.debug("Unable to get the disk size at path: " + diskPath);
+            return 0;
+        }
+
+        Long size = Long.parseLong(parser.getLine());
+
+        if (size <= 0) {
+            // its possible the path can't be seen on the host yet, lets rescan
+            // now rerun the command
+            parser = new OutputInterpreter.OneLineParser();
+            result = diskCmd.execute(parser);
+
+            if (result != null) {
+                LOGGER.debug("Unable to get the disk size at path: " + diskPath);
+                return 0;
+            }
+
+            size = Long.parseLong(parser.getLine());
+        }
+
+        return size;
+    }
+
+    public void resize(String path, String vmName, long newSize) {
+        if (LOGGER.isDebugEnabled()) LOGGER.debug("Executing resize of " + path + " to " + newSize + " bytes for VM " + vmName);
+
+        // extract wwid
+        AddressInfo address = parseAndValidatePath(path);
+        if (address == null || address.getAddress() == null) {
+            LOGGER.error("Unable to resize volume, address value is not valid");
+            throw new CloudRuntimeException("Unable to resize volume, address value is not valid");
+        }
+
+        if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Running %s %s %s %s", resizeScript, address.getAddress(), vmName, newSize));
+
+        // call resizeVolume.sh <wwid>
+        ScriptResult result = runScript(resizeScript, 60000L, address.getAddress(), vmName, ""+newSize);
+
+        if (result.getExitCode() != 0) {
+            throw new CloudRuntimeException("Failed to resize volume at address " + address.getAddress() + " to " + newSize + " bytes for VM " + vmName + ": " + result.getResult());
+        }
+
+        LOGGER.info("Resize of volume at address " + address.getAddress() + " completed successfully: " + result.getResult());
+    }
+
+    static void cleanupStaleMaps() {
+        synchronized(CLEANUP_LOCK) {
+            long start = System.currentTimeMillis();
+            ScriptResult result = runScript(cleanupScript, cleanupTimeoutSecs * 1000);
+            LOGGER.debug("Multipath Cleanup Job elapsed time (ms): "+ (System.currentTimeMillis() - start) + "; result: " + result.getExitCode(), null);
+        }
+    }
+
+    public static final class AddressInfo {
+        String type;
+        String address;
+        String connectionId;
+        String path;
+
+        public AddressInfo(String type, String address, String connectionId, String path) {
+            this.type = type;
+            this.address = address;
+            this.connectionId = connectionId;
+            this.path = path;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public String getAddress() {
+            return address;
+        }
+
+        public String getConnectionId() {
+            return connectionId;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String toString() {
+            return String.format("type=%s; address=%s; connid=%s", getType(), getAddress(), getConnectionId());
+        }
+    }
+
+    public static class Property <T> {
+        private String name;
+        private T defaultValue;
+
+        Property(String name, T value) {
+           this.name = name;
+           this.defaultValue = value;
+        }
+
+        public String getName() {
+           return this.name;
+        }
+
+        public T getDefaultValue() {
+           return this.defaultValue;
+        }
+
+        public T getFinalValue() {
+            File agentPropertiesFile = PropertiesUtil.findConfigFile("agent.properties");
+            if (agentPropertiesFile == null) {
+                LOGGER.debug(String.format("File [%s] was not found, we will use default defined values. Property [%s]: [%s].", "agent.properties", name, defaultValue));
+                return defaultValue;
+            } else {
+                try {
+                    String configValue = PropertiesUtil.loadFromFile(agentPropertiesFile).getProperty(name);
+                    if (StringUtils.isBlank(configValue)) {
+                        LOGGER.debug(String.format("Property [%s] has empty or null value. Using default value [%s].", name, defaultValue));
+                        return defaultValue;
+                    } else {
+                        if (defaultValue instanceof Integer) {
+                            return (T)Integer.getInteger(configValue);
+                        } else if (defaultValue instanceof Long) {
+                            return (T)Long.getLong(configValue);
+                        } else if (defaultValue instanceof String) {
+                            return (T)configValue;
+                        } else if (defaultValue instanceof Boolean) {
+                            return (T)Boolean.valueOf(configValue);
+                        } else {
+                            return null;
+                        }
+                    }
+                } catch (IOException var5) {
+                    LOGGER.debug(String.format("Failed to get property [%s]. Using default value [%s].", name, defaultValue), var5);
+                    return defaultValue;
+                }
+            }
+        }
+    }
+
+    public static class ScriptResult {
+        private int exitCode = -1;
+        private String result = null;
+        public int getExitCode() {
+            return exitCode;
+        }
+        public void setExitCode(int exitCode) {
+            this.exitCode = exitCode;
+        }
+        public String getResult() {
+            return result;
+        }
+        public void setResult(String result) {
+            this.result = result;
+        }
+    }
+
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java
new file mode 100644
index 0000000..bc2f072
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java
@@ -0,0 +1,241 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.hypervisor.kvm.storage;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.joda.time.Duration;
+
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
+import com.cloud.storage.Storage;
+import com.cloud.storage.Storage.ProvisioningType;
+
+public class MultipathSCSIPool implements KVMStoragePool {
+    private String uuid;
+    private String sourceHost;
+    private int sourcePort;
+    private String sourceDir;
+    private Storage.StoragePoolType storagePoolType;
+    private StorageAdaptor storageAdaptor;
+    private long capacity;
+    private long used;
+    private long available;
+    private Map<String, String> details;
+
+    public MultipathSCSIPool(String uuid, String host, int port, String path, Storage.StoragePoolType poolType, Map<String, String> poolDetails, StorageAdaptor adaptor) {
+        this.uuid = uuid;
+        sourceHost = host;
+        sourcePort = port;
+        sourceDir = path;
+        storagePoolType = poolType;
+        storageAdaptor = adaptor;
+        capacity = 0;
+        used = 0;
+        available = 0;
+        details = poolDetails;
+    }
+
+    public MultipathSCSIPool(String uuid, StorageAdaptor adapter) {
+        this.uuid = uuid;
+        sourceHost = null;
+        sourcePort = -1;
+        sourceDir = null;
+        storagePoolType = Storage.StoragePoolType.FiberChannel;
+        details = new HashMap<String,String>();
+        this.storageAdaptor = adapter;
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String arg0, ProvisioningType arg1, long arg2, byte[] arg3) {
+        return null;
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String arg0, PhysicalDiskFormat arg1, ProvisioningType arg2, long arg3,
+            byte[] arg4) {
+        return null;
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details) {
+        return storageAdaptor.connectPhysicalDisk(volumeUuid, this, details);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String volumeId) {
+        return storageAdaptor.getPhysicalDisk(volumeId, this);
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumeUuid) {
+        return storageAdaptor.disconnectPhysicalDisk(volumeUuid, this);
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String volumeUuid, Storage.ImageFormat format) {
+        return true;
+    }
+
+    @Override
+    public List<KVMPhysicalDisk> listPhysicalDisks() {
+        return null;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setCapacity(long capacity) {
+        this.capacity = capacity;
+    }
+
+    @Override
+    public long getCapacity() {
+        return this.capacity;
+    }
+
+    public void setUsed(long used) {
+        this.used = used;
+    }
+
+    @Override
+    public long getUsed() {
+        return this.used;
+    }
+
+    public void setAvailable(long available) {
+        this.available = available;
+    }
+
+    @Override
+    public long getAvailable() {
+        return this.available;
+    }
+
+    @Override
+    public boolean refresh() {
+        return false;
+    }
+
+    @Override
+    public boolean isExternalSnapshot() {
+        return true;
+    }
+
+    @Override
+    public String getLocalPath() {
+        return null;
+    }
+
+    @Override
+    public String getSourceHost() {
+        return this.sourceHost;
+    }
+
+    @Override
+    public String getSourceDir() {
+        return this.sourceDir;
+    }
+
+    @Override
+    public int getSourcePort() {
+        return this.sourcePort;
+    }
+
+    @Override
+    public String getAuthUserName() {
+        return null;
+    }
+
+    @Override
+    public String getAuthSecret() {
+        return null;
+    }
+
+    @Override
+    public Storage.StoragePoolType getType() {
+        return storagePoolType;
+    }
+
+    @Override
+    public boolean delete() {
+        return false;
+    }
+
+    @Override
+    public QemuImg.PhysicalDiskFormat getDefaultFormat() {
+        return QemuImg.PhysicalDiskFormat.RAW;
+    }
+
+    @Override
+    public boolean createFolder(String path) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsConfigDriveIso() {
+        return false;
+    }
+
+    @Override
+    public Map<String, String> getDetails() {
+        return this.details;
+    }
+
+    @Override
+    public boolean isPoolSupportHA() {
+        return false;
+    }
+
+    @Override
+    public String getHearthBeatPath() {
+        return null;
+    }
+
+    @Override
+    public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp,
+            boolean hostValidation) {
+        return null;
+    }
+
+    @Override
+    public String getStorageNodeId() {
+        return null;
+    }
+
+    @Override
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
+        return null;
+    }
+
+    @Override
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout,
+            String volumeUUIDListString, String vmActivityCheckPath, long duration) {
+        return null;
+    }
+
+    public void resize(String path, String vmName, long newSize) {
+        ((MultipathSCSIAdapterBase)storageAdaptor).resize(path, vmName, newSize);
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java
index cf977f5..293ff29 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java
@@ -23,7 +23,10 @@
 import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
 import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
 import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.joda.time.Duration;
 
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
 
 public class ScaleIOStoragePool implements KVMStoragePool {
@@ -205,4 +208,35 @@
     public Map<String, String> getDetails() {
         return this.details;
     }
+
+    @Override
+    public boolean isPoolSupportHA() {
+        return false;
+    }
+
+    @Override
+    public String getHearthBeatPath() {
+        return null;
+    }
+
+    @Override
+    public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp,
+            boolean hostValidation) {
+        return null;
+    }
+
+    @Override
+    public String getStorageNodeId() {
+        return null;
+    }
+
+    @Override
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
+        return null;
+    }
+
+    @Override
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
+        return null;
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java
index 5399fd2..1492272 100644
--- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java
+++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java
@@ -86,6 +86,7 @@
 
     @Override
     public boolean fence(Host r) throws HAFenceException {
+
         try {
             if (outOfBandManagementService.isOutOfBandManagementEnabled(r)){
                 final OutOfBandManagementResponse resp = outOfBandManagementService.executePowerOperation(r, PowerOperation.OFF, null);
@@ -96,7 +97,7 @@
             }
         } catch (Exception e){
             LOG.warn("OOBM service is not configured or enabled for this host " + r.getName() + " error is " + e.getMessage());
-            throw new HAFenceException("OOBM service is not configured or enabled for this host " + r.getName() , e);
+            throw new HAFenceException("OBM service is not configured or enabled for this host " + r.getName() , e);
         }
     }
 
@@ -151,7 +152,7 @@
             KVMHAConfig.KvmHAActivityCheckFailureThreshold,
             KVMHAConfig.KvmHADegradedMaxPeriod,
             KVMHAConfig.KvmHARecoverWaitPeriod,
-            KVMHAConfig.KvmHARecoverAttemptThreshold
+            KVMHAConfig.KvmHARecoverAttemptThreshold,
         };
     }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java
index e5752cb..0866d66 100644
--- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java
+++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java
@@ -22,6 +22,7 @@
 import com.cloud.agent.api.CheckOnHostCommand;
 import com.cloud.agent.api.CheckVMActivityOnStoragePoolCommand;
 import com.cloud.exception.StorageUnavailableException;
+import com.cloud.ha.HighAvailabilityManager;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
@@ -90,7 +91,7 @@
         }
         Status hostStatus = Status.Unknown;
         Status neighbourStatus = Status.Unknown;
-        final CheckOnHostCommand cmd = new CheckOnHostCommand(agent);
+        final CheckOnHostCommand cmd = new CheckOnHostCommand(agent, HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value());
         try {
             LOG.debug(String.format("Checking %s status...", agent.toString()));
             Answer answer = agentMgr.easySend(agent.getId(), cmd);
diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java
index 807b254..21da711 100644
--- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java
+++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java
@@ -48,7 +48,8 @@
 
     private static final Logger LOGGER = Logger.getLogger(KVMHostInfo.class);
 
-    private int cpus;
+    private int totalCpus;
+    private int allocatableCpus;
     private int cpusockets;
     private long cpuSpeed;
     private long totalMemory;
@@ -58,16 +59,25 @@
 
     private static String cpuInfoFreqFileName = "/sys/devices/system/cpu/cpu0/cpufreq/base_frequency";
 
-    public KVMHostInfo(long reservedMemory, long overCommitMemory, long manualSpeed) {
+    public KVMHostInfo(long reservedMemory, long overCommitMemory, long manualSpeed, int reservedCpus) {
         this.cpuSpeed = manualSpeed;
         this.reservedMemory = reservedMemory;
         this.overCommitMemory = overCommitMemory;
         this.getHostInfoFromLibvirt();
         this.totalMemory = new MemStat(this.getReservedMemory(), this.getOverCommitMemory()).getTotal();
+        this.allocatableCpus = totalCpus - reservedCpus;
+        if (allocatableCpus < 1) {
+            LOGGER.warn(String.format("Aggressive reserved CPU config leaves no usable CPUs for VMs! Total system CPUs: %d, Reserved: %d, Allocatable: %d", totalCpus, reservedCpus, allocatableCpus));
+            allocatableCpus = 0;
+        }
     }
 
-    public int getCpus() {
-        return this.cpus;
+    public int getTotalCpus() {
+        return this.totalCpus;
+    }
+
+    public int getAllocatableCpus() {
+        return this.allocatableCpus;
     }
 
     public int getCpuSockets() {
@@ -189,7 +199,7 @@
             if (hosts.nodes > 0) {
                 this.cpusockets = hosts.sockets * hosts.nodes;
             }
-            this.cpus = hosts.cpus;
+            this.totalCpus = hosts.cpus;
 
             final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
             parser.parseCapabilitiesXML(capabilities);
@@ -212,7 +222,6 @@
                 We used to check if this was supported, but that is no longer required
             */
             this.capabilities.add("snapshot");
-            conn.close();
         } catch (final LibvirtException e) {
             LOGGER.error("Caught libvirt exception while fetching host information", e);
         }
diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
index 1cd63b9..360c762 100644
--- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
+++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
@@ -812,4 +812,45 @@
         Pattern pattern = Pattern.compile("Supported\\sformats:[a-zA-Z0-9-_\\s]*?\\b" + format + "\\b", CASE_INSENSITIVE);
         return pattern.matcher(text).find();
     }
+
+    /**
+     * check for any leaks for an image and repair.
+     *
+     * @param imageOptions
+     *         Qemu style image options to be used in the checking process.
+     * @param qemuObjects
+     *         Qemu style options (e.g. for passing secrets).
+     * @param repair
+     *         Boolean option whether to repair any leaks
+     */
+    public String checkAndRepair(final QemuImgFile file, final QemuImageOptions imageOptions, final List<QemuObject> qemuObjects, final String repair) throws QemuImgException {
+        final Script script = new Script(_qemuImgPath);
+        script.add("check");
+        if (imageOptions == null) {
+            script.add(file.getFileName());
+        }
+
+        for (QemuObject o : qemuObjects) {
+            script.add(o.toCommandFlag());
+        }
+
+        if (imageOptions != null) {
+            script.add(imageOptions.toCommandFlag());
+        }
+
+        if (StringUtils.isNotEmpty(repair)) {
+            script.add("-r");
+            script.add(repair);
+        }
+
+        script.add("--output=json");
+        script.add("2>/dev/null");
+
+        final String result = Script.runBashScriptIgnoreExitValue(script.toString(), 3);
+        if (result != null) {
+            logger.debug(String.format("Check volume execution result %s", result));
+        }
+
+        return result;
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/resources/META-INF/cloudstack/kvm-compute/module.properties b/plugins/hypervisors/kvm/src/main/resources/META-INF/cloudstack/kvm-compute/module.properties
index 1137972..0b28201 100644
--- a/plugins/hypervisors/kvm/src/main/resources/META-INF/cloudstack/kvm-compute/module.properties
+++ b/plugins/hypervisors/kvm/src/main/resources/META-INF/cloudstack/kvm-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=kvm-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DpdkDriverTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DpdkDriverTest.java
index 8401269..3e18638 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DpdkDriverTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DpdkDriverTest.java
@@ -19,22 +19,22 @@
 package com.cloud.hypervisor.kvm.dpdk;
 
 import com.cloud.utils.script.Script;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Matchers;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+
 
 import java.util.HashMap;
 import java.util.Map;
 
-@PrepareForTest({ Script.class })
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DpdkDriverTest {
 
     private static final int dpdkPortNumber = 7;
@@ -43,14 +43,24 @@
 
     private Map<String, String> extraConfig;
 
+    private MockedStatic<Script> scriptMockedStatic;
+
+    private AutoCloseable closeable;
+
     @Before
     public void initMocks() {
-        MockitoAnnotations.initMocks(this);
-        PowerMockito.mockStatic(Script.class);
+        closeable = MockitoAnnotations.openMocks(this);
+        scriptMockedStatic = Mockito.mockStatic(Script.class);
         Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(null);
         extraConfig = new HashMap<>();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        scriptMockedStatic.close();
+        closeable.close();
+    }
+
     @Test
     public void testGetDpdkLatestPortNumberUsedNoDpdkPorts() {
         Assert.assertEquals(0, driver.getDpdkLatestPortNumberUsed());
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriverTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriverTest.java
index 2814972..48bba2b 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriverTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriverTest.java
@@ -22,7 +22,10 @@
 
 import com.cloud.agent.api.to.NicTO;
 import com.cloud.network.Networks;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class BridgeVifDriverTest {
 
     private BridgeVifDriver driver;
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java
index c5ab780..aac7f73 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java
@@ -21,12 +21,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -47,7 +46,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
-import java.util.Scanner;
 import java.util.UUID;
 import java.util.Vector;
 
@@ -84,7 +82,6 @@
 import org.libvirt.DomainInterfaceStats;
 import org.libvirt.LibvirtException;
 import org.libvirt.MemoryStatistic;
-import org.libvirt.NodeInfo;
 import org.libvirt.SchedUlongParameter;
 import org.libvirt.StorageVol;
 import org.libvirt.VcpuInfo;
@@ -92,13 +89,12 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.BDDMockito;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
 import org.mockito.invocation.InvocationOnMock;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
@@ -178,7 +174,7 @@
 import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
 import com.cloud.exception.InternalErrorException;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.hypervisor.kvm.resource.KVMHABase.NfsStoragePool;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ClockDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ConsoleDef;
@@ -226,9 +222,7 @@
 import com.cloud.vm.VirtualMachine.PowerState;
 import com.cloud.vm.VirtualMachine.Type;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {MemStat.class, SshHelper.class, AgentPropertiesFileHandler.class, AgentProperties.class, Script.class})
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtComputingResourceTest {
 
     @Mock
@@ -271,15 +265,12 @@
     final static String publicIp = "10.10.10.10";
     final static Integer port = 8080;
 
-    final Script scriptMock = Mockito.mock(Script.class);
     final OneLineParser statsParserMock = Mockito.mock(OneLineParser.class);
 
     @Before
     public void setup() throws Exception {
-        libvirtComputingResourceSpy._qemuSocketsPath = new File("/var/run/qemu");
+        libvirtComputingResourceSpy.qemuSocketsPath = new File("/var/run/qemu");
         libvirtComputingResourceSpy.parser = parserMock;
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
         LibvirtComputingResource.s_logger = loggerMock;
     }
 
@@ -475,8 +466,8 @@
     @Test
     public void testConfigureGuestAndSystemVMToUseKVM() {
         VirtualMachineTO to = createDefaultVM(false);
-        libvirtComputingResourceSpy._hypervisorLibvirtVersion = 100;
-        libvirtComputingResourceSpy._hypervisorQemuVersion = 10;
+        libvirtComputingResourceSpy.hypervisorLibvirtVersion = 100;
+        libvirtComputingResourceSpy.hypervisorQemuVersion = 10;
         LibvirtVMDef vm = new LibvirtVMDef();
 
         GuestDef guestFromSpec = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), null);
@@ -487,7 +478,7 @@
     @Test
     public void testConfigureGuestAndUserVMToUseLXC() {
         VirtualMachineTO to = createDefaultVM(false);
-        libvirtComputingResourceSpy._hypervisorType = HypervisorType.LXC;
+        libvirtComputingResourceSpy.hypervisorType = HypervisorType.LXC;
         LibvirtVMDef vm = new LibvirtVMDef();
 
         GuestDef guestFromSpec = libvirtComputingResourceSpy.createGuestFromSpec(to, vm, to.getUuid(), null);
@@ -554,7 +545,7 @@
     @Test
     public void testCreateClockDefKvmclock() {
         VirtualMachineTO to = createDefaultVM(false);
-        libvirtComputingResourceSpy._hypervisorLibvirtVersion = 9020;
+        libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9020;
 
         ClockDef clockDef = libvirtComputingResourceSpy.createClockDef(to);
         Document domainDoc = parse(clockDef.toString());
@@ -661,8 +652,8 @@
     @Test
     public void testCreateVideoDef() {
         VirtualMachineTO to = createDefaultVM(false);
-        libvirtComputingResourceSpy._videoRam = 200;
-        libvirtComputingResourceSpy._videoHw = "vGPU";
+        libvirtComputingResourceSpy.videoRam = 200;
+        libvirtComputingResourceSpy.videoHw = "vGPU";
 
         VideoDef videoDef = libvirtComputingResourceSpy.createVideoDef(to);
         Document domainDoc = parse(videoDef.toString());
@@ -923,12 +914,12 @@
         String uuid = "1";
         final LibvirtComputingResource lcr = new LibvirtComputingResource();
         uuid = lcr.getUuid(uuid);
-        Assert.assertTrue(!uuid.equals("1"));
+        assertNotEquals("1", uuid);
 
         final String oldUuid = UUID.randomUUID().toString();
         uuid = oldUuid;
         uuid = lcr.getUuid(uuid);
-        Assert.assertTrue(uuid.equals(oldUuid));
+        assertEquals(uuid, oldUuid);
     }
 
     private static final String VMNAME = "test";
@@ -944,13 +935,7 @@
         Mockito.when(domain.memoryStats(20)).thenReturn(domainMem);
         Mockito.when(domainMem[0].getTag()).thenReturn(4);
         Mockito.when(connect.domainLookupByName(VMNAME)).thenReturn(domain);
-        final NodeInfo nodeInfo = new NodeInfo();
-        nodeInfo.cpus = 8;
-        nodeInfo.memory = 8 * 1024 * 1024;
-        nodeInfo.sockets = 2;
-        nodeInfo.threads = 2;
-        nodeInfo.model = "Foo processor";
-        Mockito.when(connect.nodeInfo()).thenReturn(nodeInfo);
+
         // this is testing the interface stats, returns an increasing number of sent and received bytes
 
         Mockito.when(domain.interfaceStats(nullable(String.class))).thenAnswer(new org.mockito.stubbing.Answer<DomainInterfaceStats>() {
@@ -1092,9 +1077,7 @@
     @SuppressWarnings("unchecked")
     @Test
     public void testStopCommandCheckException1() {
-        final Connect conn = Mockito.mock(Connect.class);
         final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class);
-        final Domain vm = Mockito.mock(Domain.class);
         final DomainInfo info = Mockito.mock(DomainInfo.class);
         final DomainState state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING;
         info.state = state;
@@ -1105,10 +1088,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         try {
             when(libvirtUtilitiesHelper.getConnectionByVmName(vmName)).thenThrow(LibvirtException.class);
-            when(conn.domainLookupByName(command.getVmName())).thenReturn(vm);
-
-            when(vm.getInfo()).thenReturn(info);
-
         } catch (final LibvirtException e) {
             fail(e.getMessage());
         }
@@ -1608,9 +1587,7 @@
         }
 
         when(vm.getNics()).thenReturn(new NicTO[]{nicTO});
-        when(nicTO.getType()).thenReturn(TrafficType.Guest);
 
-        when(libvirtComputingResourceMock.getVifDriver(nicTO.getType())).thenReturn(vifDriver);
         when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolManager);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
@@ -1711,13 +1688,6 @@
 
         BDDMockito.given(libvirtComputingResourceMock.getVifDriver(nicTO.getType(), nicTO.getName())).willAnswer(invocationOnMock -> {throw new InternalErrorException("Exception Occurred");});
         when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolManager);
-        try {
-            when(libvirtComputingResourceMock.getVolumePath(conn, volume)).thenReturn("/path");
-        } catch (final LibvirtException e) {
-            fail(e.getMessage());
-        } catch (final URISyntaxException e) {
-            fail(e.getMessage());
-        }
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -1775,9 +1745,6 @@
         try {
             when(conn.domainLookupByName(vmName)).thenReturn(dm);
 
-            when(libvirtComputingResourceMock.getPrivateIp()).thenReturn("127.0.0.1");
-            when(dm.getXMLDesc(8)).thenReturn("<domain type='kvm' id='3'>" + "  <devices>" + "    <graphics type='vnc' port='5900' autoport='yes' listen='10.10.10.1'>"
-                    + "      <listen type='address' address='10.10.10.1'/>" + "    </graphics>" + "  </devices>" + "</domain>");
             when(dm.getXMLDesc(1)).thenReturn("<domain type='kvm' id='3'>" + "  <devices>" + "    <graphics type='vnc' port='5900' autoport='yes' listen='10.10.10.1'>"
                     + "      <listen type='address' address='10.10.10.1'/>" + "    </graphics>" + "  </devices>" + "</domain>");
             when(dm.isPersistent()).thenReturn(1);
@@ -1903,8 +1870,6 @@
             fail(e.getMessage());
         }
 
-        when(libvirtComputingResourceMock.getVmState(conn, command.getVmName())).thenReturn(PowerState.PowerOn);
-
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
 
@@ -2338,7 +2303,6 @@
         when(storagePoolMgr.getStoragePoolByURI(mountpoint)).thenReturn(secondaryPool);
         when(secondaryPool.listPhysicalDisks()).thenReturn(disks);
         when(storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPoolUuid())).thenReturn(primaryPool);
-        when(storagePoolMgr.copyPhysicalDisk(tmplVol, UUID.randomUUID().toString(), primaryPool, 0)).thenReturn(primaryVol);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -2374,8 +2338,6 @@
         when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
         when(storagePoolMgr.getStoragePoolByURI(mountpoint)).thenReturn(secondaryPool);
         when(secondaryPool.listPhysicalDisks()).thenReturn(disks);
-        when(storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPoolUuid())).thenReturn(primaryPool);
-        when(storagePoolMgr.copyPhysicalDisk(tmplVol, UUID.randomUUID().toString(), primaryPool, 0)).thenReturn(primaryVol);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -2414,9 +2376,6 @@
         when(secondaryPool.listPhysicalDisks()).thenReturn(spiedDisks);
         when(spiedDisks.isEmpty()).thenReturn(false);
 
-        when(storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPoolUuid())).thenReturn(primaryPool);
-        when(storagePoolMgr.copyPhysicalDisk(tmplVol, UUID.randomUUID().toString(), primaryPool, 0)).thenReturn(primaryVol);
-
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
 
@@ -2450,7 +2409,6 @@
         when(storagePoolMgr.getStoragePoolByURI(mountpoint)).thenReturn(secondaryPool);
         when(secondaryPool.getPhysicalDisk("template.qcow2")).thenReturn(tmplVol);
         when(storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPoolUuid())).thenReturn(primaryPool);
-        when(storagePoolMgr.copyPhysicalDisk(tmplVol, UUID.randomUUID().toString(), primaryPool, 0)).thenReturn(primaryVol);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -3250,8 +3208,6 @@
         final String bridge = command.getNetworkName();
 
         when(libvirtComputingResourceMock.findOrCreateTunnelNetwork(bridge)).thenReturn(false);
-        when(libvirtComputingResourceMock.configureTunnelNetwork(command.getNetworkId(), command.getFrom(),
-                command.getNetworkName())).thenReturn(true);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -3280,7 +3236,6 @@
 
         final String bridge = command.getNetworkName();
 
-        when(libvirtComputingResourceMock.findOrCreateTunnelNetwork(bridge)).thenReturn(true);
         when(libvirtComputingResourceMock.configureTunnelNetwork(command.getNetworkId(), command.getFrom(),
                 command.getNetworkName())).thenThrow(Exception.class);
 
@@ -3391,8 +3346,8 @@
 
         final KVMHAMonitor monitor = Mockito.mock(KVMHAMonitor.class);
 
-        final NfsStoragePool storagePool = Mockito.mock(NfsStoragePool.class);
-        final List<NfsStoragePool> pools = new ArrayList<NfsStoragePool>();
+        final HAStoragePool storagePool = Mockito.mock(HAStoragePool.class);
+        final List<HAStoragePool> pools = new ArrayList<HAStoragePool>();
         pools.add(storagePool);
 
         when(libvirtComputingResourceMock.getMonitor()).thenReturn(monitor);
@@ -3440,15 +3395,6 @@
             fail(e.getMessage());
         }
 
-        when(ingressRuleSet[0].getProto()).thenReturn("tcp");
-        when(ingressRuleSet[0].getStartPort()).thenReturn(22);
-        when(ingressRuleSet[0].getEndPort()).thenReturn(22);
-        when(ingressRuleSet[0].getAllowedCidrs()).thenReturn(cidrs);
-
-        when(egressRuleSet[0].getProto()).thenReturn("tcp");
-        when(egressRuleSet[0].getStartPort()).thenReturn(22);
-        when(egressRuleSet[0].getEndPort()).thenReturn(22);
-        when(egressRuleSet[0].getAllowedCidrs()).thenReturn(cidrs);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -3556,7 +3502,6 @@
         nics.add(interfaceDef);
 
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
-        when(libvirtComputingResourceMock.getInterfaces(conn, command.getVmName())).thenReturn(nics);
         try {
             when(libvirtUtilitiesHelper.getConnectionByVmName(command.getVmName())).thenThrow(LibvirtException.class);
         } catch (final LibvirtException e) {
@@ -3596,8 +3541,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         when(libvirtComputingResourceMock.getInterfaces(conn, command.getVmName())).thenReturn(nics);
 
-        when(intDef.getDevName()).thenReturn("eth0");
-        when(intDef.getBrName()).thenReturn("br0");
         when(intDef.getMacAddress()).thenReturn("00:00:00:00");
 
         when(nic.getMac()).thenReturn("00:00:00:00");
@@ -3645,8 +3588,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         when(libvirtComputingResourceMock.getInterfaces(conn, command.getVmName())).thenReturn(nics);
 
-        when(intDef.getDevName()).thenReturn("eth0");
-        when(intDef.getBrName()).thenReturn("br0");
         when(intDef.getMacAddress()).thenReturn("00:00:00:00");
 
         when(nic.getMac()).thenReturn("00:00:00:01");
@@ -3743,8 +3684,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         when(libvirtComputingResourceMock.getInterfaces(conn, command.getVmName())).thenReturn(nics);
 
-        when(intDef.getDevName()).thenReturn("eth0");
-        when(intDef.getBrName()).thenReturn("br0");
         when(intDef.getMacAddress()).thenReturn("00:00:00:00");
 
         when(nic.getMac()).thenReturn("00:00:00:01");
@@ -3792,7 +3731,6 @@
         final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class);
         final Connect conn = Mockito.mock(Connect.class);
         final Domain vm = Mockito.mock(Domain.class);
-        final InterfaceDef interfaceDef = Mockito.mock(InterfaceDef.class);
 
         final List<InterfaceDef> nics = new ArrayList<InterfaceDef>();
         final InterfaceDef intDef = Mockito.mock(InterfaceDef.class);
@@ -3805,7 +3743,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         when(libvirtComputingResourceMock.getInterfaces(conn, command.getVmName())).thenReturn(nics);
 
-        when(intDef.getDevName()).thenReturn("eth0");
         when(intDef.getBrName()).thenReturn("br0");
         when(intDef.getMacAddress()).thenReturn("00:00:00:00");
 
@@ -3814,16 +3751,7 @@
         try {
             when(libvirtUtilitiesHelper.getConnectionByVmName(command.getVmName())).thenReturn(conn);
             when(libvirtComputingResourceMock.getDomain(conn, instanceName)).thenReturn(vm);
-
-            when(interfaceDef.toString()).thenReturn("Interface");
-
-            final String interfaceDefStr = interfaceDef.toString();
-            doNothing().when(vm).detachDevice(interfaceDefStr);
-
             when(libvirtComputingResourceMock.getAllVifDrivers()).thenReturn(drivers);
-
-            doNothing().when(vifDriver).unplug(intDef, true);
-
         } catch (final LibvirtException e) {
             fail(e.getMessage());
         }
@@ -4362,16 +4290,12 @@
         final String tmplPath = secondaryPool.getLocalPath() + File.separator + templateInstallFolder;
 
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
-        when(libvirtUtilitiesHelper.buildTemplateLocation(storage, tmplPath)).thenReturn(location);
         when(libvirtUtilitiesHelper.generateUUIDName()).thenReturn(tmplName);
 
         try {
             when(libvirtUtilitiesHelper.buildQCOW2Processor(storage)).thenThrow(ConfigurationException.class);
-            when(qcow2Processor.process(tmplPath, null, tmplName)).thenReturn(info);
         } catch (final ConfigurationException e) {
             fail(e.getMessage());
-        } catch (final InternalErrorException e) {
-            fail(e.getMessage());
         }
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
@@ -4435,7 +4359,6 @@
         final String tmplPath = secondaryPool.getLocalPath() + File.separator + templateInstallFolder;
 
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
-        when(libvirtUtilitiesHelper.buildTemplateLocation(storage, tmplPath)).thenReturn(location);
         when(libvirtUtilitiesHelper.generateUUIDName()).thenReturn(tmplName);
 
         try {
@@ -4621,7 +4544,6 @@
         when(secondary.createFolder(volumeDestPath)).thenReturn(true);
         when(storagePoolMgr.deleteStoragePool(secondary.getType(), secondary.getUuid())).thenReturn(true);
         when(storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolURL + volumeDestPath)).thenReturn(secondary);
-        when(storagePoolMgr.copyPhysicalDisk(disk, destVolumeName, secondary, 0)).thenReturn(disk);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -4663,8 +4585,6 @@
         when(secondary.getType()).thenReturn(StoragePoolType.ManagedNFS);
         when(secondary.getUuid()).thenReturn("60d979d8-d132-4181-8eca-8dfde50d7df6");
         when(storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolURL + volumeDestPath)).thenReturn(secondary);
-        when(primary.getPhysicalDisk(command.getVolumePath() + ".qcow2")).thenReturn(disk);
-        when(storagePoolMgr.copyPhysicalDisk(disk, destVolumeName, primary, 0)).thenReturn(disk);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -4780,8 +4700,6 @@
         when(secondary.getType()).thenReturn(StoragePoolType.ManagedNFS);
         when(secondary.getUuid()).thenReturn("60d979d8-d132-4181-8eca-8dfde50d7df6");
         when(storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolURL + volumeDestPath)).thenReturn(secondary);
-        when(primary.getPhysicalDisk(command.getVolumePath() + ".qcow2")).thenReturn(disk);
-        when(storagePoolMgr.copyPhysicalDisk(disk, destVolumeName, primary, 0)).thenReturn(disk);
 
         final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
         assertNotNull(wrapper);
@@ -4892,7 +4810,6 @@
         when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool);
         when(storagePool.getPhysicalDisk(path)).thenReturn(vol);
         when(vol.getPath()).thenReturn(path);
-        when(libvirtComputingResourceMock.getResizeScriptType(storagePool, vol)).thenReturn("FILE");
         when(storagePool.getType()).thenReturn(StoragePoolType.RBD);
         when(vol.getFormat()).thenReturn(PhysicalDiskFormat.FILE);
 
@@ -4953,7 +4870,6 @@
         when(storagePool.getPhysicalDisk(path)).thenReturn(vol);
         when(vol.getPath()).thenReturn(path);
         when(storagePool.getType()).thenReturn(StoragePoolType.Linstor);
-        when(vol.getFormat()).thenReturn(PhysicalDiskFormat.RAW);
 
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         try {
@@ -5051,7 +4967,6 @@
         when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool);
         when(storagePool.getPhysicalDisk(path)).thenReturn(vol);
         when(vol.getPath()).thenReturn(path);
-        when(libvirtComputingResourceMock.getResizeScriptType(storagePool, vol)).thenReturn("FILE");
         when(storagePool.getType()).thenReturn(StoragePoolType.RBD);
         when(vol.getFormat()).thenReturn(PhysicalDiskFormat.FILE);
 
@@ -5213,8 +5128,6 @@
         final String vmName = "Test";
 
         when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
-        when(vmSpec.getNics()).thenReturn(nics);
-        when(vmSpec.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
         when(vmSpec.getName()).thenReturn(vmName);
         when(libvirtComputingResourceMock.createVMFromSpec(vmSpec)).thenReturn(vmDef);
 
@@ -5344,8 +5257,6 @@
 
     @Test
     public void testStartCommand() throws Exception {
-        PowerMockito.mockStatic(SshHelper.class);
-        PowerMockito.doNothing().when(SshHelper.class, "scpTo", Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString());
         final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
         final com.cloud.host.Host host = Mockito.mock(com.cloud.host.Host.class);
         final boolean executeInSequence = false;
@@ -5360,7 +5271,6 @@
 
         final NicTO nic = Mockito.mock(NicTO.class);
         final NicTO[] nics = new NicTO[]{nic};
-        final int[] vms = new int[0];
 
         final String vmName = "Test";
         final String controlIp = "127.0.0.1";
@@ -5374,7 +5284,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         try {
             when(libvirtUtilitiesHelper.getConnectionByType(vmDef.getHvsType())).thenReturn(conn);
-            when(conn.listDomains()).thenReturn(vms);
             doNothing().when(libvirtComputingResourceMock).createVbd(conn, vmSpec, vmName, vmDef);
         } catch (final LibvirtException e) {
             fail(e.getMessage());
@@ -5404,12 +5313,18 @@
             fail(e.getMessage());
         }
 
-        final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
-        assertNotNull(wrapper);
+        try (MockedStatic<SshHelper> sshHelperMockedStatic = Mockito.mockStatic(SshHelper.class)) {
+            sshHelperMockedStatic.when(() -> SshHelper.scpTo(
+                    Mockito.anyString(), Mockito.anyInt(),
+                    Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(),
+                    Mockito.any(String[].class), Mockito.anyString())).thenAnswer(invocation -> null);
 
-        final Answer answer = wrapper.execute(command, libvirtComputingResourceMock);
-        assertTrue(answer.getResult());
+            final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
+            assertNotNull(wrapper);
 
+            final Answer answer = wrapper.execute(command, libvirtComputingResourceMock);
+            assertTrue(answer.getResult());
+        }
         verify(libvirtComputingResourceMock, times(1)).getStoragePoolMgr();
         verify(libvirtComputingResourceMock, times(1)).getLibvirtUtilitiesHelper();
         try {
@@ -5421,8 +5336,6 @@
 
     @Test
     public void testStartCommandIsolationEc2() throws Exception {
-        PowerMockito.mockStatic(SshHelper.class);
-        PowerMockito.doNothing().when(SshHelper.class, "scpTo", Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString());
         final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
         final com.cloud.host.Host host = Mockito.mock(com.cloud.host.Host.class);
         final boolean executeInSequence = false;
@@ -5437,7 +5350,6 @@
 
         final NicTO nic = Mockito.mock(NicTO.class);
         final NicTO[] nics = new NicTO[]{nic};
-        final int[] vms = new int[0];
 
         final String vmName = "Test";
         final String controlIp = "127.0.0.1";
@@ -5451,7 +5363,6 @@
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         try {
             when(libvirtUtilitiesHelper.getConnectionByType(vmDef.getHvsType())).thenReturn(conn);
-            when(conn.listDomains()).thenReturn(vms);
             doNothing().when(libvirtComputingResourceMock).createVbd(conn, vmSpec, vmName, vmDef);
         } catch (final LibvirtException e) {
             fail(e.getMessage());
@@ -5467,9 +5378,6 @@
 
             when(libvirtComputingResourceMock.startVM(conn, vmName, vmDef.toString())).thenReturn("SUCCESS");
 
-            when(nic.isSecurityGroupEnabled()).thenReturn(true);
-            when(nic.getIsolationUri()).thenReturn(new URI("ec2://test"));
-
 
             when(vmSpec.getBootArgs()).thenReturn("ls -lart");
             when(libvirtComputingResourceMock.passCmdLine(vmName, vmSpec.getBootArgs())).thenReturn(true);
@@ -5483,22 +5391,26 @@
             fail(e.getMessage());
         } catch (final LibvirtException e) {
             fail(e.getMessage());
-        } catch (final URISyntaxException e) {
-            fail(e.getMessage());
         }
 
-        final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
-        assertNotNull(wrapper);
+        try (MockedStatic<SshHelper> sshHelperMockedStatic = Mockito.mockStatic(SshHelper.class)) {
+            sshHelperMockedStatic.when(() -> SshHelper.scpTo(
+                    Mockito.anyString(), Mockito.anyInt(),
+                    Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(),
+                    Mockito.any(String[].class), Mockito.anyString())).thenAnswer(invocation -> null);
+            final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
+            assertNotNull(wrapper);
 
-        final Answer answer = wrapper.execute(command, libvirtComputingResourceMock);
-        assertTrue(answer.getResult());
+            final Answer answer = wrapper.execute(command, libvirtComputingResourceMock);
+            assertTrue(answer.getResult());
 
-        verify(libvirtComputingResourceMock, times(1)).getStoragePoolMgr();
-        verify(libvirtComputingResourceMock, times(1)).getLibvirtUtilitiesHelper();
-        try {
-            verify(libvirtUtilitiesHelper, times(1)).getConnectionByType(vmDef.getHvsType());
-        } catch (final LibvirtException e) {
-            fail(e.getMessage());
+            verify(libvirtComputingResourceMock, times(1)).getStoragePoolMgr();
+            verify(libvirtComputingResourceMock, times(1)).getLibvirtUtilitiesHelper();
+            try {
+                verify(libvirtUtilitiesHelper, times(1)).getConnectionByType(vmDef.getHvsType());
+            } catch (final LibvirtException e) {
+                fail(e.getMessage());
+            }
         }
     }
 
@@ -5517,26 +5429,17 @@
 
         final NicTO nic = Mockito.mock(NicTO.class);
         final NicTO[] nics = new NicTO[]{nic};
-        int vmId = 1;
-        final int[] vms = new int[]{vmId};
-        final Domain dm = Mockito.mock(Domain.class);
-
         final String vmName = "Test";
 
         when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
         when(vmSpec.getNics()).thenReturn(nics);
         when(vmSpec.getType()).thenReturn(VirtualMachine.Type.User);
         when(vmSpec.getName()).thenReturn(vmName);
-        when(vmSpec.getMaxRam()).thenReturn(512L);
         when(libvirtComputingResourceMock.createVMFromSpec(vmSpec)).thenReturn(vmDef);
 
         when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
         try {
             when(libvirtUtilitiesHelper.getConnectionByType(vmDef.getHvsType())).thenReturn(conn);
-            when(conn.listDomains()).thenReturn(vms);
-            when(conn.domainLookupByID(vmId)).thenReturn(dm);
-            when(dm.getMaxMemory()).thenReturn(1024L);
-            when(dm.getName()).thenReturn(vmName);
             doNothing().when(libvirtComputingResourceMock).createVbd(conn, vmSpec, vmName, vmDef);
         } catch (final LibvirtException e) {
             fail(e.getMessage());
@@ -5695,9 +5598,7 @@
 
     @Test
     public void testSetQuotaAndPeriodNoCpuLimitUse() {
-        double pct = 0.33d;
         Mockito.when(vmTO.getLimitCpuUse()).thenReturn(false);
-        Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(pct);
         CpuTuneDef cpuTuneDef = new CpuTuneDef();
         final LibvirtComputingResource lcr = new LibvirtComputingResource();
         lcr.setQuotaAndPeriod(vmTO, cpuTuneDef);
@@ -5746,7 +5647,7 @@
     public void testAddExtraConfigComponentEmptyExtraConfig() {
         libvirtComputingResourceMock = new LibvirtComputingResource();
         libvirtComputingResourceMock.addExtraConfigComponent(new HashMap<>(), vmDef);
-        Mockito.verify(vmDef, never()).addComp(any());
+        Mockito.verify(vmDef, never()).addComp(Mockito.any());
     }
 
     @Test
@@ -5757,14 +5658,14 @@
         extraConfig.put("extraconfig-2", "value2");
         extraConfig.put("extraconfig-3", "value3");
         libvirtComputingResourceMock.addExtraConfigComponent(extraConfig, vmDef);
-        Mockito.verify(vmDef, times(1)).addComp(any());
+        Mockito.verify(vmDef, times(1)).addComp(Mockito.any());
     }
 
     public void validateGetCurrentMemAccordingToMemBallooningWithoutMemBalooning(){
         VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
         Mockito.when(vmTo.getType()).thenReturn(Type.User);
         LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
-        libvirtComputingResource._noMemBalloon = true;
+        libvirtComputingResource.noMemBalloon = true;
         long maxMemory = 2048;
 
         long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory);
@@ -5775,7 +5676,7 @@
     @Test
     public void validateGetCurrentMemAccordingToMemBallooningWithtMemBalooning(){
         LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
-        libvirtComputingResource._noMemBalloon = false;
+        libvirtComputingResource.noMemBalloon = false;
 
         long maxMemory = 2048;
         long minMemory = ByteScaleUtils.mebibytesToBytes(64);
@@ -5871,8 +5772,6 @@
     private DiskDef configureAndTestSetDiskIoDriverTest(long hypervisorLibvirtVersion, long hypervisorQemuVersion) {
         DiskDef diskDef = new DiskDef();
         LibvirtComputingResource libvirtComputingResourceSpy = Mockito.spy(new LibvirtComputingResource());
-        Mockito.when(libvirtComputingResourceSpy.getHypervisorLibvirtVersion()).thenReturn(hypervisorLibvirtVersion);
-        Mockito.when(libvirtComputingResourceSpy.getHypervisorQemuVersion()).thenReturn(hypervisorQemuVersion);
         libvirtComputingResourceSpy.setDiskIoDriver(diskDef, IoDriverPolicy.IO_URING);
         return diskDef;
     }
@@ -5937,58 +5836,69 @@
 
     @Test
     public void testConfigureLocalStorageWithMultiplePaths() throws ConfigurationException {
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_PATH))).thenReturn("/var/lib/libvirt/images/,/var/lib/libvirt/images2/");
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_UUID))).thenReturn(UUID.randomUUID().toString() + "," + UUID.randomUUID().toString());
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_PATH)))
+                   .thenReturn("/var/lib/libvirt/images/,/var/lib/libvirt/images2/");
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_UUID)))
+                   .thenReturn(UUID.randomUUID().toString() + "," + UUID.randomUUID().toString());
 
-        libvirtComputingResourceSpy.configureLocalStorage();
+            libvirtComputingResourceSpy.configureLocalStorage();
+        }
     }
 
     @Test(expected = ConfigurationException.class)
     public void testConfigureLocalStorageWithDifferentLength() throws Exception {
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_PATH))).thenReturn("/var/lib/libvirt/images/,/var/lib/libvirt/images2/");
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_UUID))).thenReturn(UUID.randomUUID().toString());
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_PATH)))
+                   .thenReturn("/var/lib/libvirt/images/,/var/lib/libvirt/images2/");
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_UUID)))
+                   .thenReturn(UUID.randomUUID().toString());
 
-        libvirtComputingResourceSpy.configureLocalStorage();
+            libvirtComputingResourceSpy.configureLocalStorage();
+        }
     }
 
     @Test(expected = ConfigurationException.class)
     public void testConfigureLocalStorageWithInvalidUUID() throws ConfigurationException {
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_PATH))).thenReturn("/var/lib/libvirt/images/");
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_UUID))).thenReturn("111111");
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_PATH)))
+                   .thenReturn("/var/lib/libvirt/images/");
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.LOCAL_STORAGE_UUID)))
+                   .thenReturn("111111");
 
-        libvirtComputingResourceSpy.configureLocalStorage();
+            libvirtComputingResourceSpy.configureLocalStorage();
+        }
     }
 
     @Test
-    @PrepareForTest({AgentPropertiesFileHandler.class, NetUtils.class})
     public void defineResourceNetworkInterfacesTestUseProperties() {
-        NetworkInterface networkInterfaceMock1 = PowerMockito.mock(NetworkInterface.class);
-        NetworkInterface networkInterfaceMock2 = PowerMockito.mock(NetworkInterface.class);
+        NetworkInterface networkInterfaceMock1 = Mockito.mock(NetworkInterface.class);
+        NetworkInterface networkInterfaceMock2 = Mockito.mock(NetworkInterface.class);
 
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn("cloudbr15", "cloudbr28");
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class);
+             MockedStatic<NetUtils> netUtilsMockedStatic = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.any())).thenReturn("cloudbr15",
+                    "cloudbr28");
 
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1, networkInterfaceMock2);
+            Mockito.when(NetUtils.getNetworkInterface(Mockito.anyString())).thenReturn(networkInterfaceMock1,
+                    networkInterfaceMock2);
 
-        libvirtComputingResourceSpy.defineResourceNetworkInterfaces(null);
+            libvirtComputingResourceSpy.defineResourceNetworkInterfaces(null);
 
-        ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.times(2));
-        NetUtils.getNetworkInterface(keyCaptor.capture());
+            ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
+            netUtilsMockedStatic.verify(() -> NetUtils.getNetworkInterface(keyCaptor.capture()), Mockito.times(2));
 
-        List<String> keys = keyCaptor.getAllValues();
-        Assert.assertEquals("cloudbr15", keys.get(0));
-        Assert.assertEquals("cloudbr28", keys.get(1));
 
-        Assert.assertEquals("cloudbr15", libvirtComputingResourceSpy._privBridgeName);
-        Assert.assertEquals("cloudbr28", libvirtComputingResourceSpy._publicBridgeName);
+            List<String> keys = keyCaptor.getAllValues();
+            Assert.assertEquals("cloudbr15", keys.get(0));
+            Assert.assertEquals("cloudbr28", keys.get(1));
 
-        Assert.assertEquals(networkInterfaceMock1, libvirtComputingResourceSpy.getPrivateNic());
-        Assert.assertEquals(networkInterfaceMock2, libvirtComputingResourceSpy.getPublicNic());
+            Assert.assertEquals("cloudbr15", libvirtComputingResourceSpy.privBridgeName);
+            Assert.assertEquals("cloudbr28", libvirtComputingResourceSpy.publicBridgeName);
+
+            Assert.assertEquals(networkInterfaceMock1, libvirtComputingResourceSpy.getPrivateNic());
+            Assert.assertEquals(networkInterfaceMock2, libvirtComputingResourceSpy.getPublicNic());
+        }
     }
 
     @Test
@@ -6037,24 +5947,26 @@
     }
 
     @Test
-    @PrepareForTest(value = {LibvirtComputingResource.class})
     public void testGetHaproxyStatsMethod() throws Exception {
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
-        doNothing().when(scriptMock).add(Mockito.anyString());
-        when(scriptMock.execute()).thenReturn(null);
-        when(scriptMock.execute(Mockito.any())).thenReturn(null);
+        try (MockedConstruction<Script> scriptMockedConstruction = Mockito.mockConstruction(Script.class,
+                (mock, context) -> {
+                    doNothing().when(mock).add(Mockito.anyString());
+                    when(mock.execute()).thenReturn(null);
+                    when(mock.execute(Mockito.any())).thenReturn(null);
+                });
+             MockedConstruction<OneLineParser> ignored = Mockito.mockConstruction(OneLineParser.class, (mock, context) -> {
+                 when(mock.getLine()).thenReturn("result");
+             })) {
 
-        PowerMockito.whenNew(OneLineParser.class).withNoArguments().thenReturn(statsParserMock);
-        when(statsParserMock.getLine()).thenReturn("result");
+            String result = libvirtComputingResourceSpy.getHaproxyStats(privateIp, publicIp, port);
 
-        String result = libvirtComputingResourceSpy.getHaproxyStats(privateIp, publicIp, port);
-
-        Assert.assertEquals("result", result);
-        verify(scriptMock, times(4)).add(anyString());
-        verify(scriptMock).add("get_haproxy_stats.sh");
-        verify(scriptMock).add(privateIp);
-        verify(scriptMock).add(publicIp);
-        verify(scriptMock).add(String.valueOf(port));
+            Assert.assertEquals("result", result);
+            verify(scriptMockedConstruction.constructed().get(0), times(4)).add(Mockito.anyString());
+            verify(scriptMockedConstruction.constructed().get(0)).add("get_haproxy_stats.sh");
+            verify(scriptMockedConstruction.constructed().get(0)).add(privateIp);
+            verify(scriptMockedConstruction.constructed().get(0)).add(publicIp);
+            verify(scriptMockedConstruction.constructed().get(0)).add(String.valueOf(port));
+        }
     }
 
    @Test
@@ -6091,45 +6003,45 @@
     }
 
     @Test
-    @PrepareForTest(value = {LibvirtComputingResource.class})
     public void testNetworkUsageMethod1() throws Exception {
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
-        doNothing().when(scriptMock).add(Mockito.anyString());
-        when(scriptMock.execute()).thenReturn(null);
-        when(scriptMock.execute(Mockito.any())).thenReturn(null);
+        try (MockedConstruction<Script> scriptMockedConstruction = Mockito.mockConstruction(Script.class,
+                (mock, context) -> {
+                    doNothing().when(mock).add(Mockito.anyString());
+                    when(mock.execute()).thenReturn(null);
+                    when(mock.execute(Mockito.any())).thenReturn(null);
+                }); MockedConstruction<OneLineParser> ignored2 = Mockito.mockConstruction(OneLineParser.class,
+                (mock, context) -> {when(mock.getLine()).thenReturn("result");})) {
 
-        PowerMockito.whenNew(OneLineParser.class).withNoArguments().thenReturn(statsParserMock);
-        when(statsParserMock.getLine()).thenReturn("result");
+            String result = libvirtComputingResourceSpy.networkUsage(privateIp, "get", "eth0", publicIp);
 
-        String result = libvirtComputingResourceSpy.networkUsage(privateIp, "get", "eth0", publicIp);
+            Assert.assertEquals("result", result);
+            verify(scriptMockedConstruction.constructed().get(0), times(3)).add(Mockito.anyString());
+            verify(scriptMockedConstruction.constructed().get(0)).add("netusage.sh");
+            verify(scriptMockedConstruction.constructed().get(0)).add(privateIp);
+            verify(scriptMockedConstruction.constructed().get(0)).add("-g");
 
-        Assert.assertEquals("result", result);
-        verify(scriptMock, times(3)).add(anyString());
-        verify(scriptMock).add("netusage.sh");
-        verify(scriptMock).add(privateIp);
-        verify(scriptMock).add("-g");
-
-        verify(scriptMock).add("-l", publicIp);
+            verify(scriptMockedConstruction.constructed().get(0)).add("-l", publicIp);
+        }
     }
 
     @Test
-    @PrepareForTest(value = {LibvirtComputingResource.class})
     public void testNetworkUsageMethod2() throws Exception {
-        PowerMockito.whenNew(Script.class).withAnyArguments().thenReturn(scriptMock);
-        doNothing().when(scriptMock).add(Mockito.anyString());
-        when(scriptMock.execute()).thenReturn(null);
-        when(scriptMock.execute(Mockito.any())).thenReturn(null);
+        try (MockedConstruction<Script> scriptMockedConstruction = Mockito.mockConstruction(Script.class,
+                (mock, context) -> {
+                    doNothing().when(mock).add(Mockito.anyString());
+                    when(mock.execute()).thenReturn(null);
+                    when(mock.execute(Mockito.any())).thenReturn(null);
+                }); MockedConstruction<OneLineParser> ignored2 = Mockito.mockConstruction(OneLineParser.class,
+                (mock, context) -> {when(mock.getLine()).thenReturn("result");})) {
 
-        PowerMockito.whenNew(OneLineParser.class).withNoArguments().thenReturn(statsParserMock);
-        when(statsParserMock.getLine()).thenReturn("result");
+            String result = libvirtComputingResourceSpy.networkUsage(privateIp, "get", "eth0", null);
 
-        String result = libvirtComputingResourceSpy.networkUsage(privateIp, "get", "eth0", null);
-
-        Assert.assertEquals("result", result);
-        verify(scriptMock, times(3)).add(anyString());
-        verify(scriptMock).add("netusage.sh");
-        verify(scriptMock).add(privateIp);
-        verify(scriptMock).add("-g");
+            Assert.assertEquals("result", result);
+            verify(scriptMockedConstruction.constructed().get(0), times(3)).add(Mockito.anyString());
+            verify(scriptMockedConstruction.constructed().get(0)).add("netusage.sh");
+            verify(scriptMockedConstruction.constructed().get(0)).add(privateIp);
+            verify(scriptMockedConstruction.constructed().get(0)).add("-g");
+        }
     }
 
     @Test
@@ -6166,38 +6078,49 @@
     @Test
     public void getCurrentVmBalloonStatsPeriodTestWhenMemBalloonIsDisabled() {
         Integer expected = 0;
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(true);
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE)))
+                   .thenReturn(true);
 
-        Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
+            Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
 
-        Assert.assertEquals(expected, result);
+            Assert.assertEquals(expected, result);
+        }
     }
 
     @Test
     public void getCurrentVmBalloonStatsPeriodTestWhenStatsPeriodIsZero() {
         Integer expected = 0;
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(false);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD))).thenReturn(0);
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE)))
+                   .thenReturn(false);
+            Mockito.when(
+                           AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD)))
+                   .thenReturn(0);
 
-        Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
+            Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
 
-        Mockito.verify(loggerMock).info(String.format("The [%s] property is set to '0', this prevents memory statistics from being displayed correctly. "
-                + "Adjust (increase) the value of this parameter to correct this.", AgentProperties.VM_MEMBALLOON_STATS_PERIOD.getName()));
-        Assert.assertEquals(expected, result);
+            Mockito.verify(loggerMock).info(String.format(
+                    "The [%s] property is set to '0', this prevents memory statistics from being displayed correctly. "
+                            + "Adjust (increase) the value of this parameter to correct this.",
+                    AgentProperties.VM_MEMBALLOON_STATS_PERIOD.getName()));
+            Assert.assertEquals(expected, result);
+        }
     }
 
     @Test
     public void getCurrentVmBalloonStatsPeriodTestSuccess() {
         Integer expected = 60;
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(false);
-        PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD))).thenReturn(60);
+        try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE)))
+                        .thenReturn(false);
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD)))
+                        .thenReturn(60);
 
-        Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
+            Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod();
 
-        Assert.assertEquals(expected, result);
+            Assert.assertEquals(expected, result);
+        }
     }
 
     private void prepareMocksToSetupMemoryBalloonStatsPeriod(Integer currentVmBalloonStatsPeriod) throws LibvirtException {
@@ -6216,12 +6139,14 @@
         memBalloonDef.defVirtioMemBalloon("60");
         Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
         Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
-        PowerMockito.mockStatic(Script.class);
-        PowerMockito.when(Script.runSimpleBashScript(Mockito.any())).thenReturn(null);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.any())).thenReturn(null);
 
-        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+            libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
 
-        Mockito.verify(loggerMock).debug("The memory balloon stats period [0] has been set successfully for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name].");
+            Mockito.verify(loggerMock).debug(
+                    "The memory balloon stats period [0] has been set successfully for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name].");
+        }
     }
 
     @Test
@@ -6231,13 +6156,16 @@
         memBalloonDef.defVirtioMemBalloon("0");
         Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
         Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
-        PowerMockito.mockStatic(Script.class);
-        PowerMockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live"))).thenReturn("some-fake-error");
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live")))
+                        .thenReturn("some-fake-error");
 
-        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+            libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
 
-        Mockito.verify(loggerMock).error("Unable to set up memory balloon stats period for VM (Libvirt Domain) with ID [1] due to an error when running the [virsh "
-                + "dommemstat 1 --period 60 --live] command. Output: [some-fake-error].");
+            Mockito.verify(loggerMock).error(
+                    "Unable to set up memory balloon stats period for VM (Libvirt Domain) with ID [1] due to an error when running the [virsh "
+                            + "dommemstat 1 --period 60 --live] command. Output: [some-fake-error].");
+        }
     }
 
     @Test
@@ -6247,14 +6175,16 @@
         memBalloonDef.defVirtioMemBalloon("0");
         Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
         Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef);
-        PowerMockito.mockStatic(Script.class);
-        PowerMockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live"))).thenReturn(null);
+        try (MockedStatic<Script> scriptMockedStatic = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live")))
+                        .thenReturn(null);
 
-        libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
+            libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock);
 
-        PowerMockito.verifyStatic(Script.class);
-        Script.runSimpleBashScript("virsh dommemstat 1 --period 60 --live");
-        Mockito.verify(loggerMock, Mockito.never()).error(Mockito.anyString());
+            scriptMockedStatic.verify(() -> Script.runSimpleBashScript("virsh dommemstat 1 --period 60 --live"),
+                    Mockito.times(1));
+            Mockito.verify(loggerMock, Mockito.never()).error(Mockito.anyString());
+        }
     }
 
     @Test
@@ -6341,12 +6271,13 @@
         int expectedShares = 5000;
 
         String hostCgroupVersion = LibvirtComputingResource.CGROUP_V2;
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(hostCgroupVersion);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(hostCgroupVersion);
 
-        libvirtComputingResourceSpy.calculateHostCpuMaxCapacity(cpuCores, cpuSpeed);
+            libvirtComputingResourceSpy.calculateHostCpuMaxCapacity(cpuCores, cpuSpeed);
 
-        Assert.assertEquals(expectedShares, libvirtComputingResourceSpy.getHostCpuMaxCapacity());
+            Assert.assertEquals(expectedShares, libvirtComputingResourceSpy.getHostCpuMaxCapacity());
+        }
     }
 
     @Test
@@ -6356,11 +6287,12 @@
         int expectedShares = 0;
 
         String hostCgroupVersion = "tmpfs";
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(hostCgroupVersion);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(hostCgroupVersion);
 
-        libvirtComputingResourceSpy.calculateHostCpuMaxCapacity(cpuCores, cpuSpeed);
+            libvirtComputingResourceSpy.calculateHostCpuMaxCapacity(cpuCores, cpuSpeed);
 
-        Assert.assertEquals(expectedShares, libvirtComputingResourceSpy.getHostCpuMaxCapacity());
+            Assert.assertEquals(expectedShares, libvirtComputingResourceSpy.getHostCpuMaxCapacity());
+        }
     }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java
index 5dc8e1f..3813cb3 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java
@@ -31,9 +31,14 @@
 
 import junit.framework.TestCase;
 import org.apache.cloudstack.utils.qemu.QemuObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtDomainXMLParserTest extends TestCase {
 
+    @Test
     public void testDomainXMLParser() {
         int vncPort = 5900;
 
@@ -254,4 +259,126 @@
         assertEquals(WatchDogDef.WatchDogModel.I6300ESB, watchDogs.get(0).getModel());
         assertEquals(WatchDogDef.WatchDogAction.RESET, watchDogs.get(0).getAction());
     }
+
+    @Test
+    public void testDomainXMLParserWithoutModelName() {
+        String xml = "<domain type='kvm'>\n" +
+                "  <name>testkiran</name>\n" +
+                "  <uuid>aafaaabc-8657-4efc-9c52-3422d4e04088</uuid>\n" +
+                "  <memory unit='KiB'>2097152</memory>\n" +
+                "  <currentMemory unit='KiB'>2097152</currentMemory>\n" +
+                "  <vcpu placement='static'>2</vcpu>\n" +
+                "  <os>\n" +
+                "    <type arch='x86_64' machine='pc-i440fx-rhel7.0.0'>hvm</type>\n" +
+                "    <boot dev='hd'/>\n" +
+                "  </os>\n" +
+                "  <features>\n" +
+                "    <acpi/>\n" +
+                "    <apic/>\n" +
+                "  </features>\n" +
+                "  <cpu mode='host-model' check='partial'>\n" +
+                "    <model fallback='allow'/>\n" +
+                "  </cpu>\n" +
+                "  <clock offset='utc'>\n" +
+                "    <timer name='rtc' tickpolicy='catchup'/>\n" +
+                "    <timer name='pit' tickpolicy='delay'/>\n" +
+                "    <timer name='hpet' present='no'/>\n" +
+                "  </clock>\n" +
+                "  <on_poweroff>destroy</on_poweroff>\n" +
+                "  <on_reboot>restart</on_reboot>\n" +
+                "  <on_crash>destroy</on_crash>\n" +
+                "  <pm>\n" +
+                "    <suspend-to-mem enabled='no'/>\n" +
+                "    <suspend-to-disk enabled='no'/>\n" +
+                "  </pm>\n" +
+                "  <devices>\n" +
+                "    <emulator>/usr/libexec/qemu-kvm</emulator>\n" +
+                "    <disk type='file' device='disk'>\n" +
+                "      <driver name='qemu' type='qcow2'/>\n" +
+                "      <source file='/var/lib/libvirt/images/ubuntu-22.04.qcow2'/>\n" +
+                "      <target dev='vda' bus='virtio'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>\n" +
+                "    </disk>\n" +
+                "    <disk type='file' device='disk'>\n" +
+                "      <driver name='qemu' type='qcow2'/>\n" +
+                "      <source file='/var/lib/libvirt/images/testkiran.qcow2'/>\n" +
+                "      <target dev='vdb' bus='virtio'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>\n" +
+                "    </disk>\n" +
+                "    <controller type='usb' index='0' model='ich9-ehci1'>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x7'/>\n" +
+                "    </controller>\n" +
+                "    <controller type='usb' index='0' model='ich9-uhci1'>\n" +
+                "      <master startport='0'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0' multifunction='on'/>\n" +
+                "    </controller>\n" +
+                "    <controller type='usb' index='0' model='ich9-uhci2'>\n" +
+                "      <master startport='2'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x1'/>\n" +
+                "    </controller>\n" +
+                "    <controller type='usb' index='0' model='ich9-uhci3'>\n" +
+                "      <master startport='4'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x2'/>\n" +
+                "    </controller>\n" +
+                "    <controller type='pci' index='0' model='pci-root'/>\n" +
+                "    <controller type='virtio-serial' index='0'>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>\n" +
+                "    </controller>\n" +
+                "    <interface type='network'>\n" +
+                "      <mac address='52:54:00:09:73:b8'/>\n" +
+                "      <source network='default'/>\n" +
+                "      <model type='virtio'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>\n" +
+                "    </interface>\n" +
+                "    <serial type='pty'>\n" +
+                "      <target type='isa-serial' port='0'>\n" +
+                "        <model name='isa-serial'/>\n" +
+                "      </target>\n" +
+                "    </serial>\n" +
+                "    <console type='pty'>\n" +
+                "      <target type='serial' port='0'/>\n" +
+                "    </console>\n" +
+                "    <channel type='spicevmc'>\n" +
+                "      <target type='virtio' name='com.redhat.spice.0'/>\n" +
+                "      <address type='virtio-serial' controller='0' bus='0' port='1'/>\n" +
+                "    </channel>\n" +
+                "    <input type='tablet' bus='usb'>\n" +
+                "      <address type='usb' bus='0' port='1'/>\n" +
+                "    </input>\n" +
+                "    <input type='mouse' bus='ps2'/>\n" +
+                "    <input type='keyboard' bus='ps2'/>\n" +
+                "    <graphics type='vnc' port='-1' autoport='yes'>\n" +
+                "      <listen type='address'/>\n" +
+                "    </graphics>\n" +
+                "    <graphics type='spice' autoport='yes'>\n" +
+                "      <listen type='address'/>\n" +
+                "      <image compression='off'/>\n" +
+                "    </graphics>\n" +
+                "    <sound model='ich6'>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>\n" +
+                "    </sound>\n" +
+                "    <video>\n" +
+                "      <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>\n" +
+                "    </video>\n" +
+                "    <redirdev bus='usb' type='spicevmc'>\n" +
+                "      <address type='usb' bus='0' port='2'/>\n" +
+                "    </redirdev>\n" +
+                "    <redirdev bus='usb' type='spicevmc'>\n" +
+                "      <address type='usb' bus='0' port='3'/>\n" +
+                "    </redirdev>\n" +
+                "    <memballoon model='virtio'>\n" +
+                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>\n" +
+                "    </memballoon>\n" +
+                "  </devices>\n" +
+                "</domain>";
+
+        LibvirtDomainXMLParser libvirtDomainXMLParser = new LibvirtDomainXMLParser();
+        try {
+            libvirtDomainXMLParser.parseDomainXML(xml);
+        } catch (Exception e) {
+            System.out.println("Got exception " + e.getMessage());
+            throw e;
+        }
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtKvmAgentHookTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtKvmAgentHookTest.java
index 1f63914..9cf9ca3 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtKvmAgentHookTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtKvmAgentHookTest.java
@@ -20,12 +20,18 @@
 import groovy.util.ResourceException;
 import groovy.util.ScriptException;
 import junit.framework.TestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.UUID;
 
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtKvmAgentHookTest extends TestCase {
 
     private final String source = "<xml />";
@@ -47,20 +53,21 @@
             "new BaseTransform()\n" +
             "\n";
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
         PrintWriter pw = new PrintWriter(new File(dir, script));
         pw.println(testImpl);
         pw.close();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         new File(dir, script).delete();
         super.tearDown();
     }
 
+    @Test
     public void testTransform() throws IOException, ResourceException, ScriptException {
         LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, script, method);
         assertEquals(t.isInitialized(), true);
@@ -68,24 +75,28 @@
         assertEquals(result, source + source);
     }
 
+    @Test
     public void testWrongMethod() throws IOException, ResourceException, ScriptException {
         LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, script, "methodX");
         assertEquals(t.isInitialized(), true);
         assertEquals(t.handle(source), source);
     }
 
+    @Test
     public void testNullMethod() throws IOException, ResourceException, ScriptException {
         LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, script, methodNull);
         assertEquals(t.isInitialized(), true);
         assertEquals(t.handle(source), null);
     }
 
+    @Test
     public void testWrongScript() throws IOException, ResourceException, ScriptException {
         LibvirtKvmAgentHook t = new LibvirtKvmAgentHook(dir, "wrong-script.groovy", method);
         assertEquals(t.isInitialized(), false);
         assertEquals(t.handle(source), source);
     }
 
+    @Test
     public void testWrongDir() throws IOException, ResourceException, ScriptException {
         LibvirtKvmAgentHook t = new LibvirtKvmAgentHook("/" + UUID.randomUUID().toString() + "-dir", script, method);
         assertEquals(t.isInitialized(), false);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtSecretDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtSecretDefTest.java
index 7baf69d..45e6927 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtSecretDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtSecretDefTest.java
@@ -21,9 +21,14 @@
 
 import junit.framework.TestCase;
 import com.cloud.hypervisor.kvm.resource.LibvirtSecretDef.Usage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtSecretDefTest extends TestCase {
 
+    @Test
     public void testVolumeSecretDef() {
         String uuid = "db66f42b-a79e-4666-9910-9dfc8a024427";
         String name = "myEncryptedQCOW2";
@@ -38,6 +43,7 @@
         assertEquals(expectedXml, def.toString());
     }
 
+    @Test
     public void testCephSecretDef() {
         String uuid = "a9febe83-ac5c-467a-bf19-eb75325ec23c";
         String name = "admin";
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
index f5f3cc9..51a47e9 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
@@ -23,9 +23,13 @@
 import com.cloud.hypervisor.kvm.resource.LibvirtStoragePoolDef.PoolType;
 import com.cloud.hypervisor.kvm.resource.LibvirtStoragePoolDef.AuthenticationType;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtStoragePoolDefTest extends TestCase {
 
+    @Test
     public void testSetGetStoragePool() {
         PoolType type = PoolType.NETFS;
         String name = "myNFSPool";
@@ -45,6 +49,7 @@
         assertEquals(targetPath, pool.getTargetPath());
     }
 
+    @Test
     public void testNfsStoragePool() {
         PoolType type = PoolType.NETFS;
         String name = "myNFSPool";
@@ -62,6 +67,7 @@
         assertEquals(expectedXml, pool.toString());
     }
 
+    @Test
     public void testRbdStoragePool() {
         PoolType type = PoolType.RBD;
         String name = "myRBDPool";
@@ -83,6 +89,7 @@
         assertEquals(expectedXml, pool.toString());
     }
 
+    @Test
     public void testRbdStoragePoolWithoutPort() {
         PoolType type = PoolType.RBD;
         String name = "myRBDPool";
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java
index 90dfae0..3637b7b 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java
@@ -21,9 +21,14 @@
 
 import junit.framework.TestCase;
 import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtStoragePoolXMLParserTest extends TestCase {
 
+    @Test
     public void testParseNfsStoragePoolXML() {
         String poolXML = "<pool type='netfs'>\n" +
                 "  <name>feff06b5-84b2-3258-b5f9-1953217295de</name>\n" +
@@ -52,6 +57,7 @@
         Assert.assertEquals("10.11.12.13", pool.getSourceHost());
     }
 
+    @Test
     public void testParseRbdStoragePoolXMLWithMultipleHosts() {
         String poolXML = "<pool type='rbd'>\n" +
                 "  <name>feff06b5-84b2-3258-b5f9-1953217295de</name>\n" +
@@ -77,6 +83,7 @@
         Assert.assertEquals(6789, pool.getSourcePort());
     }
 
+    @Test
     public void testParseRbdStoragePoolXMLWithMultipleHostsIpv6() {
         String poolXML = "<pool type='rbd'>\n" +
                 "  <name>feff06b5-84b2-3258-b5f9-1953217295de</name>\n" +
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
index 741f0a8..5bc2516 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
@@ -22,7 +22,6 @@
 import java.io.File;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Scanner;
 import java.util.UUID;
 
 import junit.framework.TestCase;
@@ -31,17 +30,13 @@
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
-import org.apache.cloudstack.utils.linux.MemStat;
 import org.apache.cloudstack.utils.qemu.QemuObject;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {MemStat.class})
+
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtVMDefTest extends TestCase {
 
     final String memInfo = "MemTotal:        5830236 kB\n" +
@@ -52,12 +47,6 @@
             "Active:          4260808 kB\n" +
             "Inactive:         949392 kB\n";
 
-    @Before
-    public void setup() throws Exception {
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
-    }
-
     @Test
     public void testInterfaceTypeUserWithNetwork() {
         LibvirtVMDef.InterfaceDef interfaceDef = new LibvirtVMDef.InterfaceDef();
@@ -151,6 +140,58 @@
     }
 
     @Test
+    public void testInterfaceWithMultiQueueAndPacked() {
+        LibvirtVMDef.InterfaceDef ifDef = new LibvirtVMDef.InterfaceDef();
+        ifDef.defBridgeNet("targetDeviceName", null, "00:11:22:aa:bb:dd", LibvirtVMDef.InterfaceDef.NicModel.VIRTIO);
+        ifDef.setMultiQueueNumber(6);
+
+        LibvirtVMDef.setGlobalQemuVersion(5000000L);
+        LibvirtVMDef.setGlobalLibvirtVersion(6400000L);
+
+        String expected =
+                "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE + "'>\n"
+                        + "<source bridge='targetDeviceName'/>\n"
+                        + "<mac address='00:11:22:aa:bb:dd'/>\n"
+                        + "<model type='virtio'/>\n"
+                        + "<driver queues='6'/>\n"
+                        + "<link state='up'/>\n"
+                        + "</interface>\n";
+        assertEquals(expected, ifDef.toString());
+
+        ifDef.setPackedVirtQueues(true);
+        expected =
+                "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE + "'>\n"
+                        + "<source bridge='targetDeviceName'/>\n"
+                        + "<mac address='00:11:22:aa:bb:dd'/>\n"
+                        + "<model type='virtio'/>\n"
+                        + "<driver queues='6' packed='on'/>\n"
+                        + "<link state='up'/>\n"
+                        + "</interface>\n";
+        assertEquals(expected, ifDef.toString());
+
+        ifDef.setMultiQueueNumber(null);
+        expected =
+                "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE + "'>\n"
+                        + "<source bridge='targetDeviceName'/>\n"
+                        + "<mac address='00:11:22:aa:bb:dd'/>\n"
+                        + "<model type='virtio'/>\n"
+                        + "<driver packed='on'/>\n"
+                        + "<link state='up'/>\n"
+                        + "</interface>\n";
+        assertEquals(expected, ifDef.toString());
+
+        LibvirtVMDef.setGlobalLibvirtVersion(300000L);
+        expected =
+                "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE + "'>\n"
+                        + "<source bridge='targetDeviceName'/>\n"
+                        + "<mac address='00:11:22:aa:bb:dd'/>\n"
+                        + "<model type='virtio'/>\n"
+                        + "<link state='up'/>\n"
+                        + "</interface>\n";
+        assertEquals(expected, ifDef.toString());
+        }
+
+    @Test
     public void testCpuModeDef() {
         LibvirtVMDef.CpuModeDef cpuModeDef = new LibvirtVMDef.CpuModeDef();
         cpuModeDef.setMode("custom");
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVifDriverTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVifDriverTest.java
index b49f7fe..c76fbbf 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVifDriverTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVifDriverTest.java
@@ -21,18 +21,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyMap;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Scanner;
 
 import javax.naming.ConfigurationException;
 
-import org.apache.cloudstack.utils.linux.MemStat;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -41,13 +39,11 @@
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource.BridgeType;
 import com.cloud.network.Networks.TrafficType;
 import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {MemStat.class, AgentPropertiesFileHandler.class})
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtVifDriverTest {
     private LibvirtComputingResource res;
 
@@ -68,8 +64,6 @@
             "Inactive:         949392 kB\n";
     @Before
     public void setUp() throws Exception {
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
         // Use a spy because we only want to override getVifDriverClass
         LibvirtComputingResource resReal = new LibvirtComputingResource();
         res = spy(resReal);
@@ -127,54 +121,63 @@
         // map to the default vif driver for the bridge type
         Map<String, Object> params = new HashMap<String, Object>();
 
-        res._bridgeType = BridgeType.NATIVE;
+        res.bridgeType = BridgeType.NATIVE;
         configure(params);
         checkAllSame(bridgeVifDriver);
 
-        res._bridgeType = BridgeType.OPENVSWITCH;
+        res.bridgeType = BridgeType.OPENVSWITCH;
         configure(params);
         checkAllSame(ovsVifDriver);
     }
 
     @Test
     public void configureVifDriversTestWhenSetEqualToDefault() throws Exception {
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.doReturn(LibvirtComputingResource.DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME, LibvirtComputingResource.DEFAULT_OVS_VIF_DRIVER_CLASS_NAME).when(AgentPropertiesFileHandler.class, "getPropertyValue", Mockito.eq(AgentProperties.LIBVIRT_VIF_DRIVER));
+        try (MockedStatic<AgentPropertiesFileHandler> agentPropertiesFileHandlerMockedStatic = Mockito.mockStatic(
+                AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_VIF_DRIVER)).thenReturn(
+                    LibvirtComputingResource.DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME,
+                    LibvirtComputingResource.DEFAULT_OVS_VIF_DRIVER_CLASS_NAME);
 
-        Map<String, Object> params = new HashMap<String, Object>();
+            Map<String, Object> params = new HashMap<String, Object>();
 
-        // Switch res' bridge type for test purposes
-        res._bridgeType = BridgeType.NATIVE;
-        configure(params);
-        checkAllSame(bridgeVifDriver);
+            // Switch res' bridge type for test purposes
+            res.bridgeType = BridgeType.NATIVE;
+            configure(params);
+            checkAllSame(bridgeVifDriver);
 
-        res._bridgeType = BridgeType.OPENVSWITCH;
-        configure(params);
-        checkAllSame(ovsVifDriver);
+            res.bridgeType = BridgeType.OPENVSWITCH;
+            configure(params);
+            checkAllSame(ovsVifDriver);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.times(2));
-        AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_VIF_DRIVER);
+            agentPropertiesFileHandlerMockedStatic.verify(
+                    () -> AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_VIF_DRIVER),
+                    Mockito.times(2));
+        }
     }
 
     @Test
     public void configureVifDriversTestWhenSetDifferentFromDefault() throws Exception {
-        PowerMockito.mockStatic(AgentPropertiesFileHandler.class);
-        PowerMockito.doReturn(LibvirtComputingResource.DEFAULT_OVS_VIF_DRIVER_CLASS_NAME, LibvirtComputingResource.DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME).when(AgentPropertiesFileHandler.class, "getPropertyValue", Mockito.eq(AgentProperties.LIBVIRT_VIF_DRIVER));
+        try (MockedStatic<AgentPropertiesFileHandler> agentPropertiesFileHandlerMockedStatic = Mockito.mockStatic(
+                AgentPropertiesFileHandler.class)) {
+            Mockito.when(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_VIF_DRIVER))
+                   .thenReturn(LibvirtComputingResource.DEFAULT_OVS_VIF_DRIVER_CLASS_NAME,
+                           LibvirtComputingResource.DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME);
 
-        // Tests when explicitly set vif driver to OVS when using regular bridges and vice versa
-        Map<String, Object> params = new HashMap<String, Object>();
+            // Tests when explicitly set vif driver to OVS when using regular bridges and vice versa
+            Map<String, Object> params = new HashMap<String, Object>();
 
-        // Switch res' bridge type for test purposes
-        res._bridgeType = BridgeType.NATIVE;
-        configure(params);
-        checkAllSame(ovsVifDriver);
+            // Switch res' bridge type for test purposes
+            res.bridgeType = BridgeType.NATIVE;
+            configure(params);
+            checkAllSame(ovsVifDriver);
 
-        res._bridgeType = BridgeType.OPENVSWITCH;
-        configure(params);
-        checkAllSame(bridgeVifDriver);
+            res.bridgeType = BridgeType.OPENVSWITCH;
+            configure(params);
+            checkAllSame(bridgeVifDriver);
 
-        PowerMockito.verifyStatic(AgentPropertiesFileHandler.class, Mockito.times(2));
-        AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_VIF_DRIVER);
+            agentPropertiesFileHandlerMockedStatic.verify(() -> AgentPropertiesFileHandler.getPropertyValue(
+                    AgentProperties.LIBVIRT_VIF_DRIVER), Mockito.times(2));
+        }
     }
 
     @Test
@@ -183,7 +186,7 @@
         Map<String, Object> params = new HashMap<String, Object>();
         params.put(LibVirtVifDriver + "." + "Public", FakeVifDriverClassName);
         params.put(LibVirtVifDriver + "." + "Guest", LibvirtComputingResource.DEFAULT_OVS_VIF_DRIVER_CLASS_NAME);
-        res._bridgeType = BridgeType.NATIVE;
+        res.bridgeType = BridgeType.NATIVE;
         configure(params);
 
         // Initially, set all traffic types to use default
@@ -201,7 +204,7 @@
     public void testBadTrafficType() throws ConfigurationException {
         Map<String, Object> params = new HashMap<String, Object>();
         params.put(LibVirtVifDriver + "." + "NonExistentTrafficType", FakeVifDriverClassName);
-        res._bridgeType = BridgeType.NATIVE;
+        res.bridgeType = BridgeType.NATIVE;
         configure(params);
 
         // Set all traffic types to use default, because bad traffic type should be ignored
@@ -216,7 +219,7 @@
     public void testEmptyTrafficType() throws ConfigurationException {
         Map<String, Object> params = new HashMap<String, Object>();
         params.put(LibVirtVifDriver + ".", FakeVifDriverClassName);
-        res._bridgeType = BridgeType.NATIVE;
+        res.bridgeType = BridgeType.NATIVE;
         configure(params);
 
         // Set all traffic types to use default, because bad traffic type should be ignored
@@ -231,7 +234,7 @@
     public void testBadVifDriverClassName() throws ConfigurationException {
         Map<String, Object> params = new HashMap<String, Object>();
         params.put(LibVirtVifDriver + "." + "Public", NonExistentVifDriverClassName);
-        res._bridgeType = BridgeType.NATIVE;
+        res.bridgeType = BridgeType.NATIVE;
         configure(params);
     }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckAndRepairVolumeCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckAndRepairVolumeCommandWrapperTest.java
new file mode 100644
index 0000000..e2120e4
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckAndRepairVolumeCommandWrapperTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
+import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.storage.Storage;
+
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LibvirtCheckAndRepairVolumeCommandWrapperTest {
+
+    @Spy
+    LibvirtCheckAndRepairVolumeCommandWrapper libvirtCheckAndRepairVolumeCommandWrapperSpy = Mockito.spy(LibvirtCheckAndRepairVolumeCommandWrapper.class);
+
+    @Mock
+    LibvirtComputingResource libvirtComputingResourceMock;
+
+    @Mock
+    CheckAndRepairVolumeCommand checkAndRepairVolumeCommand;
+
+    @Mock
+    QemuImg qemuImgMock;
+
+    @Before
+    public void init() {
+        when(libvirtComputingResourceMock.getCmdsTimeout()).thenReturn(60);
+    }
+
+    @Test
+    public void testCheckAndRepairVolume() throws Exception {
+
+        CheckAndRepairVolumeCommand cmd = Mockito.mock(CheckAndRepairVolumeCommand.class);
+        when(cmd.getPath()).thenReturn("cbac516a-0f1f-4559-921c-1a7c6c408ccf");
+        when(cmd.getRepair()).thenReturn(null);
+        StorageFilerTO spool = Mockito.mock(StorageFilerTO.class);
+        when(cmd.getPool()).thenReturn(spool);
+
+        KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
+        when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
+        KVMStoragePool pool = Mockito.mock(KVMStoragePool.class);
+        when(spool.getType()).thenReturn(Storage.StoragePoolType.PowerFlex);
+        when(spool.getUuid()).thenReturn("b6be258b-42b8-49a4-ad51-3634ef8ff76a");
+        when(storagePoolMgr.getStoragePool(Storage.StoragePoolType.PowerFlex, "b6be258b-42b8-49a4-ad51-3634ef8ff76a")).thenReturn(pool);
+
+        KVMPhysicalDisk vol = Mockito.mock(KVMPhysicalDisk.class);
+        when(pool.getPhysicalDisk("cbac516a-0f1f-4559-921c-1a7c6c408ccf")).thenReturn(vol);
+        Mockito.when(vol.getFormat()).thenReturn(QemuImg.PhysicalDiskFormat.QCOW2);
+
+        String checkResult = "{\n" +
+                "    \"image-end-offset\": 6442582016,\n" +
+                "    \"total-clusters\": 163840,\n" +
+                "    \"check-errors\": 0,\n" +
+                "    \"leaks\": 124,\n" +
+                "    \"allocated-clusters\": 98154,\n" +
+                "    \"filename\": \"/var/lib/libvirt/images/26be20c7-b9d0-43f6-a76e-16c70737a0e0\",\n" +
+                "    \"format\": \"qcow2\",\n" +
+                "    \"fragmented-clusters\": 96135\n" +
+                "}";
+
+        try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock, context) -> {
+            when(mock.checkAndRepair(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(checkResult);
+        })) {
+            CheckAndRepairVolumeAnswer result = (CheckAndRepairVolumeAnswer) libvirtCheckAndRepairVolumeCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock);
+            Assert.assertEquals(checkResult, result.getVolumeCheckExecutionResult());
+        }
+    }
+
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java
new file mode 100644
index 0000000..14c63b5
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.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.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.ConvertInstanceCommand;
+import com.cloud.agent.api.to.NfsTO;
+import com.cloud.agent.api.to.RemoteInstanceTO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser;
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
+import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.storage.Storage;
+import com.cloud.utils.Pair;
+import com.cloud.utils.script.Script;
+import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LibvirtConvertInstanceCommandWrapperTest {
+
+    @Spy
+    private LibvirtConvertInstanceCommandWrapper convertInstanceCommandWrapper = Mockito.spy(LibvirtConvertInstanceCommandWrapper.class);
+
+    @Mock
+    private LibvirtComputingResource libvirtComputingResourceMock;
+
+    @Mock
+    private KVMStoragePool temporaryPool;
+    @Mock
+    private KVMStoragePool destinationPool;
+    @Mock
+    private PrimaryDataStoreTO primaryDataStore;
+    @Mock
+    private NfsTO secondaryDataStore;
+    @Mock
+    private KVMStoragePoolManager storagePoolManager;
+
+    private static final String secondaryPoolUrl = "nfs://192.168.1.1/secondary";
+    private static final String vmName = "VmToImport";
+    private static final String hostName = "VmwareHost1";
+    private static final String vmwareVcenter = "192.168.1.2";
+    private static final String vmwareDatacenter = "Datacenter";
+    private static final String vmwareCluster = "Cluster";
+    private static final String vmwareUsername = "administrator@vsphere.local";
+    private static final String vmwarePassword = "password";
+
+    @Before
+    public void setUp() {
+        Mockito.when(secondaryDataStore.getUrl()).thenReturn(secondaryPoolUrl);
+        Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolManager);
+        Mockito.when(storagePoolManager.getStoragePoolByURI(secondaryPoolUrl)).thenReturn(temporaryPool);
+        KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class);
+        KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2));
+    }
+
+    @Test
+    public void testIsInstanceConversionSupportedOnHost() {
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(0);
+            boolean supported = convertInstanceCommandWrapper.isInstanceConversionSupportedOnHost();
+            Assert.assertTrue(supported);
+        }
+    }
+
+    @Test
+    public void testAreSourceAndDestinationHypervisorsSupported() {
+        boolean supported = convertInstanceCommandWrapper.areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM);
+        Assert.assertTrue(supported);
+    }
+
+    @Test
+    public void testAreSourceAndDestinationHypervisorsSupportedUnsupportedSource() {
+        boolean supported = convertInstanceCommandWrapper.areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType.XenServer, Hypervisor.HypervisorType.KVM);
+        Assert.assertFalse(supported);
+    }
+
+    @Test
+    public void testAreSourceAndDestinationHypervisorsSupportedUnsupportedDestination() {
+        boolean supported = convertInstanceCommandWrapper.areSourceAndDestinationHypervisorsSupported(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.VMware);
+        Assert.assertFalse(supported);
+    }
+
+    @Test
+    public void testGetTemporaryStoragePool() {
+        KVMStoragePool temporaryStoragePool = convertInstanceCommandWrapper.getTemporaryStoragePool(secondaryDataStore, libvirtComputingResourceMock.getStoragePoolMgr());
+        Assert.assertNotNull(temporaryStoragePool);
+    }
+
+    @Test
+    public void testGetTemporaryDisksWithPrefixFromTemporaryPool() {
+        String convertPath = "/xyz";
+        String convertPrefix = UUID.randomUUID().toString();
+        KVMPhysicalDisk physicalDisk1 = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(physicalDisk1.getName()).thenReturn("disk1");
+        KVMPhysicalDisk physicalDisk2 = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(physicalDisk2.getName()).thenReturn("disk2");
+
+        KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(convertedDisk1.getName()).thenReturn(String.format("%s-sda", convertPrefix));
+        KVMPhysicalDisk convertedDisk2 = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(convertedDisk2.getName()).thenReturn(String.format("%s-sdb", convertPrefix));
+        KVMPhysicalDisk convertedXml = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(convertedXml.getName()).thenReturn(String.format("%s.xml", convertPrefix));
+        Mockito.when(temporaryPool.listPhysicalDisks()).thenReturn(Arrays.asList(physicalDisk1, physicalDisk2,
+                convertedDisk1, convertedDisk2, convertedXml));
+
+        List<KVMPhysicalDisk> convertedDisks = convertInstanceCommandWrapper.getTemporaryDisksWithPrefixFromTemporaryPool(temporaryPool, convertPath, convertPrefix);
+        Assert.assertEquals(2, convertedDisks.size());
+    }
+
+    @Test
+    public void testGetTemporaryDisksFromParsedXml() {
+        String relativePath = UUID.randomUUID().toString();
+        String fullPath = String.format("/mnt/xyz/%s", relativePath);
+
+        LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef();
+        LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO;
+        LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2;
+        diskDef.defFileBasedDisk(fullPath, "test", bus, type);
+
+        LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class);
+        Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef));
+
+        KVMPhysicalDisk convertedDisk1 = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(convertedDisk1.getName()).thenReturn("disk1");
+        Mockito.when(temporaryPool.getPhysicalDisk(relativePath)).thenReturn(convertedDisk1);
+
+        List<KVMPhysicalDisk> disks = convertInstanceCommandWrapper.getTemporaryDisksFromParsedXml(temporaryPool, parser, "");
+        Mockito.verify(convertInstanceCommandWrapper).sanitizeDisksPath(List.of(diskDef));
+        Assert.assertEquals(1, disks.size());
+        Assert.assertEquals("disk1", disks.get(0).getName());
+    }
+
+    @Test
+    public void testSanitizeDisksPath() {
+        String relativePath = UUID.randomUUID().toString();
+        String fullPath = String.format("/mnt/xyz/%s", relativePath);
+        LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef();
+        LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.VIRTIO;
+        LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2;
+        diskDef.defFileBasedDisk(fullPath, "test", bus, type);
+
+        convertInstanceCommandWrapper.sanitizeDisksPath(List.of(diskDef));
+        Assert.assertEquals(relativePath, diskDef.getDiskPath());
+    }
+
+    @Test
+    public void testMoveTemporaryDisksToDestination() {
+        KVMPhysicalDisk sourceDisk = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(sourceDisk.getPool()).thenReturn(temporaryPool);
+        List<KVMPhysicalDisk> disks = List.of(sourceDisk);
+        String destinationPoolUuid = UUID.randomUUID().toString();
+        List<String> destinationPools = List.of(destinationPoolUuid);
+
+        KVMPhysicalDisk destDisk = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(destDisk.getPath()).thenReturn("xyz");
+        Mockito.when(storagePoolManager.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, destinationPoolUuid))
+                .thenReturn(destinationPool);
+        Mockito.when(storagePoolManager.copyPhysicalDisk(Mockito.eq(sourceDisk), Mockito.anyString(), Mockito.eq(destinationPool), Mockito.anyInt()))
+                .thenReturn(destDisk);
+
+        List<KVMPhysicalDisk> movedDisks = convertInstanceCommandWrapper.moveTemporaryDisksToDestination(disks, destinationPools, storagePoolManager);
+        Assert.assertEquals(1, movedDisks.size());
+        Assert.assertEquals("xyz", movedDisks.get(0).getPath());
+    }
+
+    @Test
+    public void testGetUnmanagedInstanceDisks() {
+        String relativePath = UUID.randomUUID().toString();
+        LibvirtVMDef.DiskDef diskDef = new LibvirtVMDef.DiskDef();
+        LibvirtVMDef.DiskDef.DiskBus bus = LibvirtVMDef.DiskDef.DiskBus.IDE;
+        LibvirtVMDef.DiskDef.DiskFmtType type = LibvirtVMDef.DiskDef.DiskFmtType.QCOW2;
+        diskDef.defFileBasedDisk(relativePath, relativePath, bus, type);
+
+        KVMPhysicalDisk sourceDisk = Mockito.mock(KVMPhysicalDisk.class);
+        Mockito.when(sourceDisk.getName()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(sourceDisk.getPool()).thenReturn(destinationPool);
+        Mockito.when(destinationPool.getType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        List<KVMPhysicalDisk> disks = List.of(sourceDisk);
+
+        LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class);
+        Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef));
+
+        List<UnmanagedInstanceTO.Disk> unmanagedInstanceDisks = convertInstanceCommandWrapper.getUnmanagedInstanceDisks(disks, parser);
+        Assert.assertEquals(1, unmanagedInstanceDisks.size());
+        UnmanagedInstanceTO.Disk disk = unmanagedInstanceDisks.get(0);
+        Assert.assertEquals(LibvirtVMDef.DiskDef.DiskBus.IDE.toString(), disk.getController());
+    }
+
+    @Test
+    public void testGetNfsStoragePoolHostAndPath() {
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            String localMountPoint = "/mnt/xyz";
+            String host = "192.168.1.2";
+            String path = "/secondary";
+            String mountOutput = String.format("%s:%s on %s type nfs (...)", host, path, localMountPoint);
+            Mockito.when(temporaryPool.getLocalPath()).thenReturn(localMountPoint);
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString()))
+                    .thenReturn(mountOutput);
+
+            Pair<String, String> pair = convertInstanceCommandWrapper.getNfsStoragePoolHostAndPath(temporaryPool);
+            Assert.assertEquals(host, pair.first());
+            Assert.assertEquals(path, pair.second());
+        }
+    }
+
+    private RemoteInstanceTO getRemoteInstanceTO(Hypervisor.HypervisorType hypervisorType) {
+        RemoteInstanceTO remoteInstanceTO = Mockito.mock(RemoteInstanceTO.class);
+        Mockito.when(remoteInstanceTO.getHypervisorType()).thenReturn(hypervisorType);
+        Mockito.when(remoteInstanceTO.getInstanceName()).thenReturn(vmName);
+        Mockito.when(remoteInstanceTO.getHostName()).thenReturn(hostName);
+        Mockito.when(remoteInstanceTO.getVcenterHost()).thenReturn(vmwareVcenter);
+        Mockito.when(remoteInstanceTO.getDatacenterName()).thenReturn(vmwareDatacenter);
+        Mockito.when(remoteInstanceTO.getClusterName()).thenReturn(vmwareCluster);
+        Mockito.when(remoteInstanceTO.getVcenterUsername()).thenReturn(vmwareUsername);
+        Mockito.when(remoteInstanceTO.getVcenterPassword()).thenReturn(vmwarePassword);
+        return remoteInstanceTO;
+    }
+
+    private ConvertInstanceCommand getConvertInstanceCommand(RemoteInstanceTO remoteInstanceTO, Hypervisor.HypervisorType hypervisorType) {
+        ConvertInstanceCommand cmd = Mockito.mock(ConvertInstanceCommand.class);
+        Mockito.when(cmd.getSourceInstance()).thenReturn(remoteInstanceTO);
+        Mockito.when(cmd.getDestinationHypervisorType()).thenReturn(hypervisorType);
+        Mockito.when(cmd.getWait()).thenReturn(14400);
+        Mockito.when(cmd.getConversionTemporaryLocation()).thenReturn(secondaryDataStore);
+        return cmd;
+    }
+
+    @Test
+    public void testExecuteConvertUnsupportedOnTheHost() {
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            RemoteInstanceTO remoteInstanceTO = getRemoteInstanceTO(Hypervisor.HypervisorType.VMware);
+            ConvertInstanceCommand cmd = getConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM);
+            Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(1);
+            Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
+            Assert.assertFalse(answer.getResult());
+        }
+    }
+
+    @Test
+    public void testExecuteConvertUnsupportedHypervisors() {
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            RemoteInstanceTO remoteInstanceTO = getRemoteInstanceTO(Hypervisor.HypervisorType.XenServer);
+            ConvertInstanceCommand cmd = getConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM);
+            Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(0);
+            Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
+            Assert.assertFalse(answer.getResult());
+        }
+    }
+
+    @Test
+    public void testExecuteConvertFailure() {
+        RemoteInstanceTO remoteInstanceTO = getRemoteInstanceTO(Hypervisor.HypervisorType.VMware);
+        ConvertInstanceCommand cmd = getConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM);
+        String localMountPoint = "/mnt/xyz";
+        Mockito.when(temporaryPool.getLocalPath()).thenReturn(localMountPoint);
+
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class);
+             MockedConstruction<Script> ignored2 = Mockito.mockConstruction(Script.class, (mock, context) -> {
+                 Mockito.when(mock.execute()).thenReturn("");
+                 Mockito.when(mock.getExitValue()).thenReturn(1);
+             })
+        ) {
+            Mockito.when(Script.runSimpleBashScriptForExitValue(LibvirtConvertInstanceCommandWrapper.checkIfConversionIsSupportedCommand)).thenReturn(0);
+            Mockito.when(Script.runSimpleBashScriptForExitValueAvoidLogging(Mockito.anyString())).thenReturn(0);
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn("");
+
+            Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
+            Assert.assertFalse(answer.getResult());
+            Mockito.verify(convertInstanceCommandWrapper).performInstanceConversion(Mockito.anyString(),
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean());
+        }
+    }
+
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetAutoScaleMetricsCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetAutoScaleMetricsCommandWrapperTest.java
index 1c8094c..086e44b 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetAutoScaleMetricsCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetAutoScaleMetricsCommandWrapperTest.java
@@ -32,13 +32,13 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtGetAutoScaleMetricsCommandWrapperTest {
 
     @Spy
@@ -68,8 +68,8 @@
     public void validateVpcStats() {
 
         Mockito.when(getAutoScaleMetricsCommandMock.isForVpc()).thenReturn(true);
-        PowerMockito.when(libvirtComputingResourceMock.getVPCNetworkStats(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(vpcStats);
-        PowerMockito.when(libvirtComputingResourceMock.getNetworkLbStats(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(lbStats);
+        Mockito.when(libvirtComputingResourceMock.getVPCNetworkStats(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(vpcStats);
+        Mockito.when(libvirtComputingResourceMock.getNetworkLbStats(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(lbStats);
 
         Answer answer = libvirtGetAutoScaleMetricsCommandWrapperSpy.execute(getAutoScaleMetricsCommandMock, libvirtComputingResourceMock);
         assertTrue(answer instanceof GetAutoScaleMetricsAnswer);
@@ -95,8 +95,8 @@
     public void validateNetworkStats() {
 
         Mockito.when(getAutoScaleMetricsCommandMock.isForVpc()).thenReturn(false);
-        PowerMockito.when(libvirtComputingResourceMock.getNetworkStats(Mockito.any(), Mockito.any())).thenReturn(networkStats);
-        PowerMockito.when(libvirtComputingResourceMock.getNetworkLbStats(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(lbStats);
+        Mockito.when(libvirtComputingResourceMock.getNetworkStats(Mockito.any(), Mockito.any())).thenReturn(networkStats);
+        Mockito.when(libvirtComputingResourceMock.getNetworkLbStats(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(lbStats);
 
         Answer answer = libvirtGetAutoScaleMetricsCommandWrapperSpy.execute(getAutoScaleMetricsCommandMock, libvirtComputingResourceMock);
         assertTrue(answer instanceof GetAutoScaleMetricsAnswer);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapperTest.java
new file mode 100644
index 0000000..df40a3f
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetUnmanagedInstancesCommandWrapperTest.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.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.UUID;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LibvirtGetUnmanagedInstancesCommandWrapperTest {
+
+    @Spy
+    private LibvirtGetUnmanagedInstancesCommandWrapper wrapper = new LibvirtGetUnmanagedInstancesCommandWrapper();
+
+    @Test
+    public void testGetDiskRelativePathNullDisk() {
+        Assert.assertNull(wrapper.getDiskRelativePath(null));
+    }
+
+    @Test
+    public void testGetDiskRelativePathBlockType() {
+        LibvirtVMDef.DiskDef diskDef = Mockito.mock(LibvirtVMDef.DiskDef.class);
+        Mockito.when(diskDef.getDiskType()).thenReturn(LibvirtVMDef.DiskDef.DiskType.BLOCK);
+        Assert.assertNull(wrapper.getDiskRelativePath(diskDef));
+    }
+
+    @Test
+    public void testGetDiskRelativePathNullPath() {
+        LibvirtVMDef.DiskDef diskDef = Mockito.mock(LibvirtVMDef.DiskDef.class);
+        Mockito.when(diskDef.getDiskType()).thenReturn(LibvirtVMDef.DiskDef.DiskType.FILE);
+        Mockito.when(diskDef.getSourcePath()).thenReturn(null);
+        Assert.assertNull(wrapper.getDiskRelativePath(diskDef));
+    }
+
+    @Test
+    public void testGetDiskRelativePathWithoutSlashes() {
+        LibvirtVMDef.DiskDef diskDef = Mockito.mock(LibvirtVMDef.DiskDef.class);
+        Mockito.when(diskDef.getDiskType()).thenReturn(LibvirtVMDef.DiskDef.DiskType.FILE);
+        String imagePath = UUID.randomUUID().toString();
+        Mockito.when(diskDef.getSourcePath()).thenReturn(imagePath);
+        Assert.assertEquals(imagePath, wrapper.getDiskRelativePath(diskDef));
+    }
+
+    @Test
+    public void testGetDiskRelativePathFullPath() {
+        LibvirtVMDef.DiskDef diskDef = Mockito.mock(LibvirtVMDef.DiskDef.class);
+        Mockito.when(diskDef.getDiskType()).thenReturn(LibvirtVMDef.DiskDef.DiskType.FILE);
+        String relativePath = "ea4b2296-d349-4968-ab72-c8eb523b556e";
+        String imagePath = String.format("/mnt/97e4c9ed-e3bc-3e26-b103-7967fc9feae1/%s", relativePath);
+        Mockito.when(diskDef.getSourcePath()).thenReturn(imagePath);
+        Assert.assertEquals(relativePath, wrapper.getDiskRelativePath(diskDef));
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
index c206f89..67e00aa 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java
@@ -29,7 +29,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Scanner;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -38,24 +37,20 @@
 import javax.xml.xpath.XPathExpressionException;
 import javax.xml.xpath.XPathFactory;
 
-import org.apache.cloudstack.utils.linux.MemStat;
-import com.cloud.agent.api.to.VirtualMachineTO;
+
 import org.apache.cloudstack.utils.security.ParserUtils;
 import org.apache.commons.io.IOUtils;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.libvirt.Connect;
 import org.libvirt.StorageVol;
 import org.mockito.InOrder;
-import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -67,14 +62,13 @@
 import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DriverType;
 import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.Source;
 import com.cloud.agent.api.to.DpdkTO;
+import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {LibvirtConnection.class, LibvirtMigrateCommandWrapper.class, MemStat.class})
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtMigrateCommandWrapperTest {
     String fullfile =
 "<domain type='kvm' id='4'>\n" +
@@ -471,11 +465,6 @@
             "Active:          4260808 kB\n" +
             "Inactive:         949392 kB\n";
 
-    @Before
-    public void setup() throws Exception {
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
-    }
 
     private static final String sourcePoolUuid = "07eb495b-5590-3877-9fb7-23c6e9a40d40";
     private static final String destPoolUuid = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
@@ -589,7 +578,7 @@
     public void testReplaceIpForVNCInDescFile() {
         final String targetIp = "192.168.22.21";
         final String result = libvirtMigrateCmdWrapper.replaceIpForVNCInDescFileAndNormalizePassword(fullfile, targetIp, null, "");
-        assertTrue("transformation does not live up to expectation:\n" + result, targetfile.equals(result));
+        assertEquals("transformation does not live up to expectation:\n" + result, targetfile, result);
     }
 
     @Test
@@ -613,7 +602,7 @@
         final String targetIp = "10.10.10.10";
         final String password = "12345678";
         final String result = libvirtMigrateCmdWrapper.replaceIpForVNCInDescFileAndNormalizePassword(xmlDesc, targetIp, password, "");
-        assertTrue("transformation does not live up to expectation:\n" + result, expectedXmlDesc.equals(result));
+        assertEquals("transformation does not live up to expectation:\n" + result, expectedXmlDesc, result);
     }
 
     @Test
@@ -637,7 +626,7 @@
         final String targetIp = "localhost.localdomain";
         final String password = "12345678";
         final String result = libvirtMigrateCmdWrapper.replaceIpForVNCInDescFileAndNormalizePassword(xmlDesc, targetIp, password, "");
-        assertTrue("transformation does not live up to expectation:\n" + result, expectedXmlDesc.equals(result));
+        assertEquals("transformation does not live up to expectation:\n" + result, expectedXmlDesc, result);
     }
 
     @Test
@@ -658,21 +647,21 @@
 
     @Test
     public void deleteLocalVolumeTest() throws Exception {
-        PowerMockito.mockStatic(LibvirtConnection.class);
-        Connect conn = Mockito.mock(Connect.class);
-
-        PowerMockito.doReturn(conn).when(LibvirtConnection.class, "getConnection");
-
         StorageVol storageVolLookupByPath = Mockito.mock(StorageVol.class);
-        Mockito.when(conn.storageVolLookupByPath("localPath")).thenReturn(storageVolLookupByPath);
+        try (MockedStatic<LibvirtConnection> libvirtConnectionMockedStatic = Mockito.mockStatic(LibvirtConnection.class)) {
+            Connect conn = Mockito.mock(Connect.class);
 
-        libvirtMigrateCmdWrapper.deleteLocalVolume("localPath");
+            Mockito.when(LibvirtConnection.getConnection()).thenReturn(conn);
 
-        PowerMockito.verifyStatic(LibvirtConnection.class, Mockito.times(1));
-        LibvirtConnection.getConnection();
-        InOrder inOrder = Mockito.inOrder(conn, storageVolLookupByPath);
-        inOrder.verify(conn, Mockito.times(1)).storageVolLookupByPath("localPath");
-        inOrder.verify(storageVolLookupByPath, Mockito.times(1)).delete(0);
+            Mockito.when(conn.storageVolLookupByPath("localPath")).thenReturn(storageVolLookupByPath);
+
+            libvirtMigrateCmdWrapper.deleteLocalVolume("localPath");
+
+            libvirtConnectionMockedStatic.verify(LibvirtConnection::getConnection, Mockito.times(1));
+            InOrder inOrder = Mockito.inOrder(conn, storageVolLookupByPath);
+            inOrder.verify(conn, Mockito.times(1)).storageVolLookupByPath("localPath");
+            inOrder.verify(storageVolLookupByPath, Mockito.times(1)).delete(0);
+        }
     }
 
     @Test
@@ -696,14 +685,14 @@
         MigrateDiskInfo returnedMigrateDiskInfo = libvirtMigrateCmdWrapper.searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, disk);
 
         if (isExpectedDiskInfoNull)
-            Assert.assertEquals(null, returnedMigrateDiskInfo);
+            Assert.assertNull(returnedMigrateDiskInfo);
         else
             Assert.assertEquals(migrateDiskInfo, returnedMigrateDiskInfo);
     }
 
     @Test
     public void deleteOrDisconnectDisksOnSourcePoolTest() {
-        LibvirtMigrateCommandWrapper spyLibvirtMigrateCmdWrapper = PowerMockito.spy(libvirtMigrateCmdWrapper);
+        LibvirtMigrateCommandWrapper spyLibvirtMigrateCmdWrapper = Mockito.spy(libvirtMigrateCmdWrapper);
         Mockito.doNothing().when(spyLibvirtMigrateCmdWrapper).deleteLocalVolume("volPath");
 
         List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
@@ -767,7 +756,7 @@
         mapMigrateStorage.put("/mnt/812ea6a3-7ad0-30f4-9cab-01e3f2985b98/4650a2f7-fce5-48e2-beaa-bcdf063194e6", diskInfo);
         final String result = libvirtMigrateCmdWrapper.replaceStorage(fullfile, mapMigrateStorage, true);
 
-        InputStream in = IOUtils.toInputStream(result);
+        InputStream in = IOUtils.toInputStream(result, "UTF-8");
         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
         DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
         Document doc = docBuilder.parse(in);
@@ -804,7 +793,7 @@
         final String result = libvirtMigrateCmdWrapper.replaceStorage(xmlDesc, mapMigrateStorage, false);
         final String expectedSecretUuid = LibvirtComputingResource.generateSecretUUIDFromString(volumeFile);
 
-        InputStream in = IOUtils.toInputStream(result);
+        InputStream in = IOUtils.toInputStream(result, "UTF-8");
         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
         DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
         Document doc = docBuilder.parse(in);
@@ -844,7 +833,7 @@
         mapMigrateStorage.put("/mnt/07eb495b-5590-3877-9fb7-23c6e9a40d40/bf8621b3-027c-497d-963b-06319650f048", diskInfo);
 
         final String result = libvirtMigrateCmdWrapper.replaceStorage(xmlDesc, mapMigrateStorage, false);
-        InputStream in = IOUtils.toInputStream(result);
+        InputStream in = IOUtils.toInputStream(result, "UTF-8");
         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
         DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
         Document doc = docBuilder.parse(in);
@@ -856,6 +845,7 @@
         assertXpath(doc, "/domain/devices/disk/encryption/secret/@uuid", expectedSecretUuid);
     }
 
+    @Test
     public void testReplaceStorageXmlDiskNotManagedStorage() throws ParserConfigurationException, TransformerException, SAXException, IOException {
         final LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
         String destDisk1FileName = "XXXXXXXXXXXXXX";
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkElementCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkElementCommandWrapperTest.java
index 5a73490..c5f38f9 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkElementCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkElementCommandWrapperTest.java
@@ -25,19 +25,13 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import java.util.Scanner;
 
-import org.apache.cloudstack.utils.linux.MemStat;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.libvirt.Connect;
 import org.libvirt.Domain;
 import org.libvirt.LibvirtException;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.agent.api.routing.IpAssocVpcCommand;
 import com.cloud.agent.api.routing.NetworkElementCommand;
@@ -45,10 +39,9 @@
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.network.Networks;
 import com.cloud.utils.ExecutionResult;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {MemStat.class})
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtNetworkElementCommandWrapperTest {
     private static final String fullfile = "<domain type='kvm' id='143'>\n"
             + "  <name>r-3-VM</name>\n"
@@ -218,7 +211,7 @@
             + "</domain>\n";
 
     private LibvirtComputingResource res;
-    private final Domain _domain = mock(Domain.class);
+    private final Domain domain = mock(Domain.class);
 
     final String memInfo = "MemTotal:        5830236 kB\n" +
             "MemFree:          156752 kB\n" +
@@ -229,15 +222,13 @@
             "Inactive:         949392 kB\n";
     @Before
     public void setUp() throws Exception {
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
         // Use a spy because we only want to override getVifDriverClass
         LibvirtComputingResource resReal = new LibvirtComputingResource() {
             {
-                _linkLocalBridgeName = "cloud0";
-                _guestBridgeName = "guestbr";
-                _publicBridgeName = "publicbr";
-                _privBridgeName = "mgmtbr";
+                linkLocalBridgeName = "cloud0";
+                guestBridgeName = "guestbr";
+                publicBridgeName = "publicbr";
+                privBridgeName = "mgmtbr";
             }
         };
 
@@ -246,8 +237,8 @@
         Connect conn = mock(Connect.class);
         LibvirtUtilitiesHelper helper = mock(LibvirtUtilitiesHelper.class);
 
-        when(_domain.getXMLDesc(0)).thenReturn(fullfile);
-        when(conn.domainLookupByName(nullable(String.class))).thenReturn(_domain);
+        when(domain.getXMLDesc(0)).thenReturn(fullfile);
+        when(conn.domainLookupByName(nullable(String.class))).thenReturn(domain);
         when(helper.getConnectionByVmName(nullable(String.class))).thenReturn(conn);
 
         doReturn(helper).when(res).getLibvirtUtilitiesHelper();
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
index 5530819..3ee09b0 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
@@ -30,14 +30,12 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.HashMap;
 import java.util.Map;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {LibvirtPrepareForMigrationCommandWrapper.class})
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtPrepareForMigrationCommandWrapperTest {
 
     @Mock
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapperTest.java
index 2189e2a..8a6827f 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReplugNicCommandWrapperTest.java
@@ -16,9 +16,6 @@
 // under the License.
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
-import static org.mockito.AdditionalMatchers.not;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -31,9 +28,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Scanner;
 
-import org.apache.cloudstack.utils.linux.MemStat;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,11 +37,9 @@
 import org.libvirt.LibvirtException;
 import org.mockito.BDDMockito;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.ReplugNicCommand;
@@ -59,9 +52,7 @@
 import com.cloud.utils.script.Script;
 import com.cloud.vm.VirtualMachine;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {Script.class, MemStat.class})
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtReplugNicCommandWrapperTest {
 
     @Mock
@@ -206,8 +197,6 @@
 
     @Before
     public void setUp() throws Exception {
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
 
         // Use a spy because we only want to override getVifDriverClass
         LibvirtComputingResource resReal = new LibvirtComputingResource();
@@ -219,10 +208,9 @@
         when(_domain.getXMLDesc(0))
                 .thenReturn(fullfile)
                 .thenReturn(part_1 + part_3);
-        when(conn.domainLookupByName(anyString())).thenReturn(_domain);
-        when(helper.getConnectionByVmName(anyString())).thenReturn(conn);
-        PowerMockito.mockStatic(Script.class);
-        BDDMockito.given(Script.findScript(anyString(), anyString())).willReturn("dummypath/tofile.sh");
+        when(conn.domainLookupByName(Mockito.anyString())).thenReturn(_domain);
+        when(helper.getConnectionByVmName(Mockito.anyString())).thenReturn(conn);
+
 
         Map<String, String> pifs = new HashMap<>();
         pifs.put(GUEST_BR, "eth0");
@@ -238,13 +226,18 @@
         doNothing().when(ovsVifDriver).getPifs();
 
         doReturn(helper).when(res).getLibvirtUtilitiesHelper();
-        doReturn(bridgeVifDriver).when(res).getVifDriver(eq(Networks.TrafficType.Guest), anyString());
         doReturn(ovsVifDriver).when(res).getVifDriver(Networks.TrafficType.Guest, GUEST_BR);
-        doReturn(bridgeVifDriver).when(res).getVifDriver(not(eq(Networks.TrafficType.Guest)));
         doReturn(Arrays.asList(bridgeVifDriver, ovsVifDriver)).when(res).getAllVifDrivers();
 
-        bridgeVifDriver.configure(params);
-        ovsVifDriver.configure(params);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            BDDMockito.given(Script.findScript(Mockito.anyString(), Mockito.anyString())).willReturn(
+                    "dummypath/tofile.sh");
+
+            bridgeVifDriver.configure(params);
+            ovsVifDriver.configure(params);
+        }
+
+        LibvirtVMDef.setGlobalLibvirtVersion(6400000L);
     }
 
     @Test
@@ -255,6 +248,10 @@
                         + "<target dev='vnet10'/>\n"
                         + "<mac address='02:00:7c:98:00:02'/>\n"
                         + "<model type='virtio'/>\n"
+                        + "<bandwidth>\n"
+                        + "<inbound average='25600' peak='25600'/>\n"
+                        + "<outbound average='25600' peak='25600'/>\n"
+                        + "</bandwidth>\n"
                         + "<link state='up'/>\n"
                         + "</interface>\n";
         final String expectedAttachXml =
@@ -292,7 +289,6 @@
 
         final Connect conn = Mockito.mock(Connect.class);
 
-        when(libvirtComputingResource.getInterfaces(conn, "")).thenReturn(ifaces);
         final LibvirtReplugNicCommandWrapper wrapper = new LibvirtReplugNicCommandWrapper();
         final NicTO nic = new NicTO();
         nic.setType(Networks.TrafficType.Guest);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapperTest.java
index 8333c6e..3fa3f7d 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapperTest.java
@@ -30,17 +30,16 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
 import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtRevertSnapshotCommandWrapperTest {
 
     LibvirtRevertSnapshotCommandWrapper libvirtRevertSnapshotCommandWrapperSpy = Mockito.spy(LibvirtRevertSnapshotCommandWrapper.class);
@@ -82,76 +81,84 @@
     }
 
     @Test
-    @PrepareForTest(LibvirtRevertSnapshotCommandWrapper.class)
     public void validateReplaceVolumeWithSnapshotReplaceFiles() throws IOException {
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.copy(Mockito.any(Path.class), Mockito.any(Path.class), Mockito.any(CopyOption.class))).thenReturn(pathMock);
-        libvirtRevertSnapshotCommandWrapperSpy.replaceVolumeWithSnapshot("test", "test");
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.copy(Mockito.any(Path.class), Mockito.any(Path.class), Mockito.any(CopyOption.class)))
+                   .thenReturn(pathMock);
+            libvirtRevertSnapshotCommandWrapperSpy.replaceVolumeWithSnapshot("test", "test");
+        }
     }
 
     @Test (expected = IOException.class)
-    @PrepareForTest(LibvirtRevertSnapshotCommandWrapper.class)
     public void validateReplaceVolumeWithSnapshotThrowsIOException() throws IOException {
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.copy(Mockito.any(Path.class), Mockito.any(Path.class), Mockito.any(CopyOption.class))).thenThrow(IOException.class);
-        libvirtRevertSnapshotCommandWrapperSpy.replaceVolumeWithSnapshot("test", "test");
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(
+                                Files.copy(Mockito.any(Path.class), Mockito.any(Path.class), Mockito.any(CopyOption.class)))
+                        .thenThrow(IOException.class);
+            libvirtRevertSnapshotCommandWrapperSpy.replaceVolumeWithSnapshot("test", "test");
+        }
     }
 
     @Test
-    @PrepareForTest(LibvirtRevertSnapshotCommandWrapper.class)
     public void validateGetSnapshotPathExistsOnPrimaryStorage() {
         String snapshotPath = "test";
         Pair<String, SnapshotObjectTO> expectedResult = new Pair<>(snapshotPath, snapshotObjectToPrimaryMock);
 
         Mockito.doReturn(snapshotPath).when(snapshotObjectToPrimaryMock).getPath();
 
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.exists(Mockito.any(Path.class), Mockito.any())).thenReturn(true);
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.exists(Mockito.any(Path.class), Mockito.any())).thenReturn(true);
 
-        Pair<String, SnapshotObjectTO> result = libvirtRevertSnapshotCommandWrapperSpy.getSnapshot(snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock,
-                kvmStoragePoolPrimaryMock, kvmStoragePoolSecondaryMock);
+            Pair<String, SnapshotObjectTO> result = libvirtRevertSnapshotCommandWrapperSpy.getSnapshot(
+                    snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock,
+                    kvmStoragePoolPrimaryMock, kvmStoragePoolSecondaryMock);
 
-        Assert.assertEquals(expectedResult.first(), result.first());
-        Assert.assertEquals(expectedResult.second(), result.second());
+            Assert.assertEquals(expectedResult.first(), result.first());
+            Assert.assertEquals(expectedResult.second(), result.second());
+        }
     }
 
     @Test
-    @PrepareForTest(LibvirtRevertSnapshotCommandWrapper.class)
     public void validateGetSnapshotPathExistsOnSecondaryStorage() {
         String snapshotPath = "test";
         Pair<String, SnapshotObjectTO> expectedResult = new Pair<>(snapshotPath, snapshotObjectToSecondaryMock);
 
-        PowerMockito.mockStatic(Files.class, Paths.class);
-        PowerMockito.when(Paths.get(Mockito.any(), Mockito.any())).thenReturn(pathMock);
-        PowerMockito.when(Files.exists(Mockito.any(Path.class), Mockito.any())).thenReturn(false);
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class); MockedStatic<Paths> ignored2 = Mockito.mockStatic(Paths.class)) {
 
-        Mockito.doReturn(snapshotPath).when(snapshotObjectToSecondaryMock).getPath();
-        Mockito.doReturn(snapshotPath).when(libvirtRevertSnapshotCommandWrapperSpy).getFullPathAccordingToStorage(Mockito.any(), Mockito.any());
+            Mockito.when(Paths.get(Mockito.any(), Mockito.any())).thenReturn(pathMock);
+            Mockito.when(Files.exists(Mockito.any(Path.class), Mockito.any())).thenReturn(false);
 
-        Pair<String, SnapshotObjectTO> result = libvirtRevertSnapshotCommandWrapperSpy.getSnapshot(snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock,
-                kvmStoragePoolPrimaryMock, kvmStoragePoolSecondaryMock);
+            Mockito.doReturn(snapshotPath).when(snapshotObjectToSecondaryMock).getPath();
+            Mockito.doReturn(snapshotPath).when(libvirtRevertSnapshotCommandWrapperSpy).getFullPathAccordingToStorage(
+                    Mockito.any(), Mockito.any());
 
-        Assert.assertEquals(expectedResult.first(), result.first());
-        Assert.assertEquals(expectedResult.second(), result.second());
+            Pair<String, SnapshotObjectTO> result = libvirtRevertSnapshotCommandWrapperSpy.getSnapshot(
+                    snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock,
+                    kvmStoragePoolPrimaryMock, kvmStoragePoolSecondaryMock);
+
+            Assert.assertEquals(expectedResult.first(), result.first());
+            Assert.assertEquals(expectedResult.second(), result.second());
+        }
     }
 
     @Test (expected = CloudRuntimeException.class)
-    @PrepareForTest(LibvirtRevertSnapshotCommandWrapper.class)
     public void validateGetSnapshotPathDoesNotExistsOnSecondaryStorageThrows() {
-        PowerMockito.mockStatic(Files.class, Paths.class);
-        PowerMockito.when(Paths.get(Mockito.any(), Mockito.any())).thenReturn(pathMock);
-        PowerMockito.when(Files.exists(Mockito.any(Path.class), Mockito.any())).thenReturn(false);
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class); MockedStatic<Paths> ignored2 = Mockito.mockStatic(Paths.class)) {
 
-        Mockito.doReturn(null).when(snapshotObjectToSecondaryMock).getPath();
+            Mockito.when(Paths.get(Mockito.any(), Mockito.any())).thenReturn(pathMock);
+            Mockito.when(Files.exists(Mockito.any(Path.class), Mockito.any())).thenReturn(false);
 
-        libvirtRevertSnapshotCommandWrapperSpy.getSnapshot(snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock,
-                kvmStoragePoolPrimaryMock, kvmStoragePoolSecondaryMock);
+            Mockito.doReturn(null).when(snapshotObjectToSecondaryMock).getPath();
+
+            libvirtRevertSnapshotCommandWrapperSpy.getSnapshot(snapshotObjectToPrimaryMock,
+                    snapshotObjectToSecondaryMock,
+                    kvmStoragePoolPrimaryMock, kvmStoragePoolSecondaryMock);
+        }
     }
 
     @Test
     public void validateRevertVolumeToSnapshotReplaceSuccessfully() throws IOException {
         Mockito.doReturn(volumeObjectToMock).when(snapshotObjectToSecondaryMock).getVolume();
-        Mockito.doReturn("").when(libvirtRevertSnapshotCommandWrapperSpy).getFullPathAccordingToStorage(Mockito.any(), Mockito.anyString());
         Mockito.doReturn(pairStringSnapshotObjectToMock).when(libvirtRevertSnapshotCommandWrapperSpy).getSnapshot(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
         Mockito.doNothing().when(libvirtRevertSnapshotCommandWrapperSpy).replaceVolumeWithSnapshot(Mockito.any(), Mockito.any());
         libvirtRevertSnapshotCommandWrapperSpy.revertVolumeToSnapshot(snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock, dataStoreToMock, kvmStoragePoolPrimaryMock,
@@ -161,7 +168,6 @@
     @Test (expected = CloudRuntimeException.class)
     public void validateRevertVolumeToSnapshotReplaceVolumeThrowsIOException() throws IOException {
         Mockito.doReturn(volumeObjectToMock).when(snapshotObjectToSecondaryMock).getVolume();
-        Mockito.doReturn("").when(libvirtRevertSnapshotCommandWrapperSpy).getFullPathAccordingToStorage(Mockito.any(), Mockito.anyString());
         Mockito.doReturn(pairStringSnapshotObjectToMock).when(libvirtRevertSnapshotCommandWrapperSpy).getSnapshot(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
         Mockito.doThrow(IOException.class).when(libvirtRevertSnapshotCommandWrapperSpy).replaceVolumeWithSnapshot(Mockito.any(), Mockito.any());
         libvirtRevertSnapshotCommandWrapperSpy.revertVolumeToSnapshot(snapshotObjectToPrimaryMock, snapshotObjectToSecondaryMock, dataStoreToMock, kvmStoragePoolPrimaryMock,
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java
index 56f99d4..5c5bc43 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java
@@ -23,6 +23,7 @@
 import com.cloud.vm.VirtualMachine;
 import junit.framework.TestCase;
 import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,14 +31,12 @@
 import org.libvirt.Domain;
 import org.libvirt.LibvirtException;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(LibvirtComputingResource.class)
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtScaleVmCommandWrapperTest extends TestCase {
 
     @Spy
@@ -69,6 +68,8 @@
 
     String scalingDetails;
 
+    MockedStatic<LibvirtComputingResource> libvirtComputingResourceMocked;
+
     @Before
     public void init() {
         wrapper = LibvirtRequestWrapper.getInstance();
@@ -81,7 +82,13 @@
         int cpuShares = vcpus * vmTo.getSpeed();
         scalingDetails = String.format("%s memory to [%s KiB], CPU cores to [%s] and cpu_shares to [%s]", vmTo.toString(), memory, vcpus, cpuShares);
 
-        PowerMockito.mockStatic(LibvirtComputingResource.class);
+        libvirtComputingResourceMocked = Mockito.mockStatic(LibvirtComputingResource.class);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        libvirtComputingResourceMocked.close();
     }
 
     @Test
@@ -89,7 +96,7 @@
         long runningVcpus = 1;
         int newVcpus = 2;
 
-        PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
+        Mockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
         Mockito.doNothing().when(domainMock).setVcpus(Mockito.anyInt());
 
         libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails);
@@ -102,7 +109,7 @@
         long runningVcpus = 2;
         int newVcpus = 2;
 
-        PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
+        Mockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
 
         libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails);
 
@@ -114,7 +121,7 @@
         long runningVcpus = 2;
         int newVcpus = 1;
 
-        PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
+        Mockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
 
         libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails);
 
@@ -126,7 +133,7 @@
         long runningVcpus = 1;
         int newVcpus = 2;
 
-        PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
+        Mockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus);
         Mockito.doThrow(LibvirtException.class).when(domainMock).setVcpus(Mockito.anyInt());
 
         libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails);
@@ -139,7 +146,7 @@
         long currentMemory = 1l;
         long newMemory = 0l;
 
-        PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
+        Mockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
 
         libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails);
 
@@ -152,7 +159,7 @@
         long currentMemory = 1l;
         long newMemory = 1l;
 
-        PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
+        Mockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
 
         libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails);
 
@@ -165,7 +172,7 @@
         long currentMemory = 1l;
         long newMemory = 2l;
 
-        PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
+        Mockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
         Mockito.doReturn("").when(domainMock).getXMLDesc(Mockito.anyInt());
 
         libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails);
@@ -179,7 +186,7 @@
         long currentMemory = 1l;
         long newMemory = 2l;
 
-        PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
+        Mockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
         Mockito.doReturn("<maxMemory slots='16' unit='KiB'>").when(domainMock).getXMLDesc(Mockito.anyInt());
         Mockito.doThrow(LibvirtException.class).when(domainMock).attachDevice(Mockito.anyString());
 
@@ -194,7 +201,7 @@
         long currentMemory = 1l;
         long newMemory = 2l;
 
-        PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
+        Mockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory);
         Mockito.doReturn("<maxMemory slots='16' unit='KiB'>").when(domainMock).getXMLDesc(Mockito.anyInt());
         Mockito.doNothing().when(domainMock).attachDevice(Mockito.anyString());
 
@@ -253,11 +260,12 @@
         int oldShares = 2000;
         int newShares = 3000;
 
-        PowerMockito.when(LibvirtComputingResource.getCpuShares(Mockito.any())).thenReturn(oldShares);
+        Mockito.when(LibvirtComputingResource.getCpuShares(Mockito.any())).thenReturn(oldShares);
         libvirtScaleVmCommandWrapperSpy.updateCpuShares(domainMock, newShares);
 
-        PowerMockito.verifyStatic(LibvirtComputingResource.class, Mockito.times(1));
-        libvirtComputingResourceMock.setCpuShares(domainMock, newShares);
+        libvirtComputingResourceMocked.verify(() -> LibvirtComputingResource.setCpuShares(domainMock, newShares),
+                Mockito.times(1));
+        ;
     }
 
     @Test
@@ -265,11 +273,12 @@
         int oldShares = 3000;
         int newShares = 2000;
 
-        PowerMockito.when(LibvirtComputingResource.getCpuShares(Mockito.any())).thenReturn(oldShares);
+        Mockito.when(LibvirtComputingResource.getCpuShares(Mockito.any())).thenReturn(oldShares);
         libvirtScaleVmCommandWrapperSpy.updateCpuShares(domainMock, newShares);
 
-        PowerMockito.verifyStatic(LibvirtComputingResource.class, Mockito.times(0));
-        libvirtComputingResourceMock.setCpuShares(domainMock, newShares);
+
+        libvirtComputingResourceMocked.verify(() -> LibvirtComputingResource.setCpuShares(domainMock, newShares),
+                Mockito.times(0));
     }
 
     @Test
@@ -277,10 +286,10 @@
         int oldShares = 2000;
         int newShares = 2000;
 
-        PowerMockito.when(LibvirtComputingResource.getCpuShares(Mockito.any())).thenReturn(oldShares);
+        Mockito.when(LibvirtComputingResource.getCpuShares(Mockito.any())).thenReturn(oldShares);
         libvirtScaleVmCommandWrapperSpy.updateCpuShares(domainMock, newShares);
 
-        PowerMockito.verifyStatic(LibvirtComputingResource.class, Mockito.times(0));
-        libvirtComputingResourceMock.setCpuShares(domainMock, newShares);
+        libvirtComputingResourceMocked.verify(() -> LibvirtComputingResource.setCpuShares(domainMock, newShares),
+                Mockito.times(0));
     }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelperTest.java
index 6340987..f18f3f8 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelperTest.java
@@ -23,13 +23,13 @@
 import org.libvirt.LibvirtException;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.utils.Pair;
 
 import junit.framework.TestCase;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtUtilitiesHelperTest extends TestCase {
 
     LibvirtUtilitiesHelper libvirtUtilitiesHelperSpy = Mockito.spy(LibvirtUtilitiesHelper.class);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java
index 4d5b086..187565c 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java
@@ -17,17 +17,24 @@
 package com.cloud.hypervisor.kvm.storage;
 
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
 import junit.framework.TestCase;
+import org.mockito.junit.MockitoJUnitRunner;
 
+
+@RunWith(MockitoJUnitRunner.class)
 public class KVMPhysicalDiskTest extends TestCase {
 
+    @Test
     public void testRBDStringBuilder() {
         assertEquals(KVMPhysicalDisk.RBDStringBuilder("ceph-monitor", 8000, "admin", "supersecret", "volume1"),
                      "rbd:volume1:mon_host=ceph-monitor\\:8000:auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30");
     }
 
+    @Test
     public void testRBDStringBuilder2() {
         String monHosts = "ceph-monitor1,ceph-monitor2,ceph-monitor3";
         int monPort = 3300;
@@ -38,6 +45,7 @@
         assertEquals(expected, actualResult);
     }
 
+    @Test
     public void testRBDStringBuilder3() {
         String monHosts = "[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]";
         int monPort = 3300;
@@ -48,6 +56,7 @@
         assertEquals(expected, actualResult);
     }
 
+    @Test
     public void testAttributes() {
         String name = "3bc186e0-6c29-45bf-b2b0-ddef6f91f5ef";
         String path = "/" + name;
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
index aab0ce1..4f3ed6b 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessorTest.java
@@ -42,6 +42,7 @@
 import org.apache.cloudstack.utils.qemu.QemuImg;
 import org.apache.cloudstack.utils.qemu.QemuImgException;
 import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -50,19 +51,15 @@
 import org.libvirt.Domain;
 import org.libvirt.LibvirtException;
 import org.mockito.InjectMocks;
-import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@PrepareForTest({ Script.class })
-@PowerMockIgnore({"javax.xml.*", "org.xml.*", "org.w3c.dom.*"})
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class KVMStorageProcessorTest {
 
     @Mock
@@ -106,48 +103,56 @@
     private static final String directDownloadTemporaryPath = "/var/lib/libvirt/images/dd";
     private static final long templateSize = 80000L;
 
+    private AutoCloseable closeable;
+
     @Before
     public void setUp() throws ConfigurationException {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         storageProcessor = new KVMStorageProcessor(storagePoolManager, resource);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationAssumeEnoughSpaceWhenNotProvided() {
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
         boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(null);
         Assert.assertTrue(result);
     }
 
     @Test
     public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationNotEnoughSpace() {
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
-        String output = String.valueOf(templateSize - 30000L);
-        Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(output);
-        boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
-        Assert.assertFalse(result);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
+            String output = String.valueOf(templateSize - 30000L);
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(output);
+            boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
+            Assert.assertFalse(result);
+        }
     }
 
     @Test
     public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationEnoughSpace() {
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
-        String output = String.valueOf(templateSize + 30000L);
-        Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(output);
-        boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
-        Assert.assertTrue(result);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
+            String output = String.valueOf(templateSize + 30000L);
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(output);
+            boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
+            Assert.assertTrue(result);
+        }
     }
 
     @Test
     public void testIsEnoughSpaceForDownloadTemplateOnTemporaryLocationNotExistingLocation() {
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
-        String output = String.format("df: ‘%s’: No such file or directory", directDownloadTemporaryPath);
-        Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(output);
-        boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
-        Assert.assertFalse(result);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(resource.getDirectDownloadTemporaryDownloadPath()).thenReturn(directDownloadTemporaryPath);
+            String output = String.format("df: ‘%s’: No such file or directory", directDownloadTemporaryPath);
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(output);
+            boolean result = storageProcessor.isEnoughSpaceForDownloadTemplateOnTemporaryLocation(templateSize);
+            Assert.assertFalse(result);
+        }
     }
 
     @Test
@@ -233,7 +238,6 @@
     @Test (expected = LibvirtException.class)
     public void validateTakeVolumeSnapshotFailToCreateSnapshotThrowLibvirtException() throws LibvirtException{
         Mockito.doReturn(diskToSnapshotAndDisksToAvoidMock).when(storageProcessorSpy).getDiskToSnapshotAndDisksToAvoid(Mockito.any(), Mockito.anyString(), Mockito.any());
-        Mockito.doReturn("").when(domainMock).getName();
         Mockito.doReturn(new HashSet<>()).when(diskToSnapshotAndDisksToAvoidMock).second();
         Mockito.doThrow(LibvirtException.class).when(domainMock).snapshotCreateXML(Mockito.anyString(), Mockito.anyInt());
 
@@ -245,7 +249,6 @@
         String expectedResult = "label";
 
         Mockito.doReturn(diskToSnapshotAndDisksToAvoidMock).when(storageProcessorSpy).getDiskToSnapshotAndDisksToAvoid(Mockito.any(), Mockito.anyString(), Mockito.any());
-        Mockito.doReturn("").when(domainMock).getName();
         Mockito.doReturn(expectedResult).when(diskToSnapshotAndDisksToAvoidMock).first();
         Mockito.doReturn(new HashSet<>()).when(diskToSnapshotAndDisksToAvoidMock).second();
         Mockito.doReturn(null).when(domainMock).snapshotCreateXML(Mockito.anyString(), Mockito.anyInt());
@@ -256,7 +259,6 @@
     }
 
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithQemuImgExceptionReturnErrorMessage() throws Exception {
         String baseFile = "baseFile";
         String snapshotPath = "snapshotPath";
@@ -264,17 +266,15 @@
         String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
 
         Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
-
-        PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
-        Mockito.doThrow(new QemuImgException(errorMessage)).when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
-
-        String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
-
-        Assert.assertEquals(expectedResult, result);
+        try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock,context) -> {
+            Mockito.doThrow(new QemuImgException(errorMessage)).when(mock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
+        })) {
+            String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestFailToConvertWithLibvirtExceptionReturnErrorMessage() throws Exception {
         String baseFile = "baseFile";
         String snapshotPath = "snapshotPath";
@@ -282,61 +282,66 @@
         String expectedResult = String.format("Failed to convert %s snapshot of volume [%s] to [%s] due to [%s].", volumeObjectToMock, baseFile, snapshotPath, errorMessage);
 
         Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
-
-        PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
-        Mockito.doThrow(LibvirtException.class).when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
-
-        String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
-
-        Assert.assertEquals(expectedResult, result);
+        try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock,context) -> {
+            Mockito.doThrow(LibvirtException.class).when(mock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
+        })) {
+            String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
-
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void convertBaseFileToSnapshotFileInPrimaryStorageDirTestConvertSuccessReturnNull() throws Exception {
         String baseFile = "baseFile";
         String snapshotPath = "snapshotPath";
 
         Mockito.doReturn(true).when(kvmStoragePoolMock).createFolder(Mockito.anyString());
-
-        PowerMockito.whenNew(QemuImg.class).withArguments(Mockito.anyInt()).thenReturn(qemuImgMock);
-        Mockito.doNothing().when(qemuImgMock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
-
-        String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
-
-        Assert.assertNull(result);
+        try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock, context) -> {
+            Mockito.doNothing().when(mock).convert(Mockito.any(QemuImgFile.class), Mockito.any(QemuImgFile.class));
+        })) {
+            String result = storageProcessorSpy.convertBaseFileToSnapshotFileInPrimaryStorageDir(kvmStoragePoolMock, baseFile, snapshotPath, volumeObjectToMock, 1);
+            Assert.assertNull(result);
+        }
     }
 
     @Test (expected = CloudRuntimeException.class)
-    @PrepareForTest({Script.class, LibvirtUtilitiesHelper.class})
     public void validateMergeSnapshotIntoBaseFileErrorOnMergeThrowCloudRuntimeException() throws Exception {
-        PowerMockito.mockStatic(Script.class, LibvirtUtilitiesHelper.class);
-        PowerMockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn("");
-        PowerMockito.when(LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any())).thenReturn(true);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(
+                Script.class); MockedStatic<LibvirtUtilitiesHelper> ignored2 = Mockito.mockStatic(
+                LibvirtUtilitiesHelper.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn("");
+            Mockito.when(LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any()))
+                   .thenReturn(true);
 
-        storageProcessorSpy.mergeSnapshotIntoBaseFile(domainMock, "", "", "", volumeObjectToMock, connectMock);
+            storageProcessorSpy.mergeSnapshotIntoBaseFile(domainMock, "", "", "", volumeObjectToMock, connectMock);
+        }
     }
 
     @Test
-    @PrepareForTest({Script.class, LibvirtUtilitiesHelper.class})
     public void validateMergeSnapshotIntoBaseFileMergeSuccessDoNothing() throws Exception {
-        PowerMockito.mockStatic(Script.class, LibvirtUtilitiesHelper.class);
-        PowerMockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(null);
-        PowerMockito.when(LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any())).thenReturn(true);
-        Mockito.doNothing().when(storageProcessorSpy).manuallyDeleteUnusedSnapshotFile(Mockito.anyBoolean(), Mockito.any());
+        try (MockedStatic<Script> scriptMockedStatic = Mockito.mockStatic(
+                Script.class); MockedStatic<LibvirtUtilitiesHelper> libvirtUtilitiesHelperMockedStatic = Mockito.mockStatic(
+                LibvirtUtilitiesHelper.class)) {
+            Mockito.when(Script.runSimpleBashScript(Mockito.anyString())).thenReturn(null);
+            Mockito.when(LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any()))
+                        .thenReturn(true);
+            Mockito.doNothing().when(storageProcessorSpy).manuallyDeleteUnusedSnapshotFile(Mockito.anyBoolean(),
+                    Mockito.any());
 
-        storageProcessorSpy.mergeSnapshotIntoBaseFile(domainMock, "", "", "", volumeObjectToMock, connectMock);
+            storageProcessorSpy.mergeSnapshotIntoBaseFile(domainMock, "", "", "", volumeObjectToMock, connectMock);
+            libvirtUtilitiesHelperMockedStatic.verify(() -> LibvirtUtilitiesHelper.isLibvirtSupportingFlagDeleteOnCommandVirshBlockcommit(Mockito.any()), Mockito.times(1));
+            scriptMockedStatic.verify(() -> Script.runSimpleBashScript(Mockito.anyString()), Mockito.times(1));
+
+        }
     }
 
     @Test (expected = CloudRuntimeException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void validateManuallyDeleteUnusedSnapshotFileLibvirtDoesNotSupportsFlagDeleteExceptionOnFileDeletionThrowsException() throws IOException {
-        Mockito.doReturn("").when(snapshotObjectToMock).getPath();
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenThrow(IOException.class);
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenThrow(IOException.class);
 
-        storageProcessorSpy.manuallyDeleteUnusedSnapshotFile(false, "");
+            storageProcessorSpy.manuallyDeleteUnusedSnapshotFile(false, "");
+        }
     }
 
     @Test
@@ -353,77 +358,84 @@
 
     @Test (expected = IOException.class)
     public void validateValidateCopyResultFailToDeleteThrowIOException() throws CloudRuntimeException, IOException{
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenThrow(new IOException(""));
-        storageProcessorSpy.validateConvertResult("", "");
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.deleteIfExists(Mockito.any())).thenThrow(new IOException(""));
+            storageProcessorSpy.validateConvertResult("", "");
+        }
     }
 
     @Test (expected = CloudRuntimeException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void validateValidateCopyResulResultNotNullThrowCloudRuntimeException() throws CloudRuntimeException, IOException{
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.deleteIfExists(Mockito.any())).thenReturn(true);
-        storageProcessorSpy.validateConvertResult("", "");
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.deleteIfExists(Mockito.any())).thenReturn(true);
+            storageProcessorSpy.validateConvertResult("", "");
+        }
     }
 
     @Test (expected = CloudRuntimeException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void validateDeleteSnapshotFileErrorOnDeleteThrowsCloudRuntimeException() throws Exception {
         Mockito.doReturn("").when(snapshotObjectToMock).getPath();
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenThrow(IOException.class);
+        try (MockedStatic<Files> ignored = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenThrow(IOException.class);
 
-        storageProcessorSpy.deleteSnapshotFile(snapshotObjectToMock);
+            storageProcessorSpy.deleteSnapshotFile(snapshotObjectToMock);
+        }
     }
 
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void validateDeleteSnapshotFileSuccess () throws IOException {
         Mockito.doReturn("").when(snapshotObjectToMock).getPath();
-        PowerMockito.mockStatic(Files.class);
-        PowerMockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenReturn(true);
+        try (MockedStatic<Files> filesMockedStatic = Mockito.mockStatic(Files.class)) {
+            Mockito.when(Files.deleteIfExists(Mockito.any(Path.class))).thenReturn(true);
 
-        storageProcessorSpy.deleteSnapshotFile(snapshotObjectToMock);
-    }
+            storageProcessorSpy.deleteSnapshotFile(snapshotObjectToMock);
 
-    private void checkDetachSucessTest(boolean duplicate) throws Exception {
-        List<LibvirtVMDef.DiskDef> disks = createDiskDefs(2, duplicate);
-        PowerMockito.when(domainMock.getXMLDesc(Mockito.anyInt())).thenReturn("test");
-        PowerMockito.whenNew(LibvirtDomainXMLParser.class).withAnyArguments().thenReturn(libvirtDomainXMLParserMock);
-        PowerMockito.when(libvirtDomainXMLParserMock.parseDomainXML(Mockito.anyString())).thenReturn(true);
-        PowerMockito.when(libvirtDomainXMLParserMock.getDisks()).thenReturn(disks);
+            filesMockedStatic.verify(() -> Files.deleteIfExists(Mockito.any(Path.class)), Mockito.times(1));
+        }
     }
 
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void checkDetachSucessTestDetachReturnTrue() throws Exception {
-        checkDetachSucessTest(false);
-        Assert.assertTrue(storageProcessorSpy.checkDetachSuccess("path", domainMock));
+
+        List<LibvirtVMDef.DiskDef> disks = createDiskDefs(2, false);
+        Mockito.when(domainMock.getXMLDesc(Mockito.anyInt())).thenReturn("test");
+        try (MockedConstruction<LibvirtDomainXMLParser> ignored = Mockito.mockConstruction(
+                LibvirtDomainXMLParser.class, (mock, context) -> {
+                    Mockito.when(mock.parseDomainXML(Mockito.anyString())).thenReturn(true);
+                    Mockito.when(mock.getDisks()).thenReturn(disks);
+                })) {
+            Assert.assertTrue(storageProcessorSpy.checkDetachSuccess("path", domainMock));
+        }
     }
 
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void checkDetachSucessTestDetachReturnFalse() throws Exception {
-        checkDetachSucessTest(true);
-        Assert.assertFalse(storageProcessorSpy.checkDetachSuccess("path", domainMock));
+        List<LibvirtVMDef.DiskDef> disks = createDiskDefs(2, true);
+        Mockito.when(domainMock.getXMLDesc(Mockito.anyInt())).thenReturn("test");
+        try (MockedConstruction<LibvirtDomainXMLParser> ignored = Mockito.mockConstruction(
+                LibvirtDomainXMLParser.class, (mock, context) -> {
+                    Mockito.when(mock.parseDomainXML(Mockito.anyString())).thenReturn(true);
+                    Mockito.when(mock.getDisks()).thenReturn(disks);
+                })) {
+
+            Assert.assertFalse(storageProcessorSpy.checkDetachSuccess("path", domainMock));
+        }
     }
 
     private void attachOrDetachDeviceTest (boolean attach, String vmName, LibvirtVMDef.DiskDef xml) throws LibvirtException, InternalErrorException {
         storageProcessorSpy.attachOrDetachDevice(connectMock, attach, vmName, xml);
     }
-    @PrepareForTest(KVMStorageProcessor.class)
     private void attachOrDetachDeviceTest (boolean attach, String vmName, LibvirtVMDef.DiskDef xml, long waitDetachDevice) throws LibvirtException, InternalErrorException {
         storageProcessorSpy.attachOrDetachDevice(connectMock, attach, vmName, xml, waitDetachDevice);
     }
 
     @Test (expected = LibvirtException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void attachOrDetachDeviceTestThrowLibvirtException() throws LibvirtException, InternalErrorException {
         Mockito.when(connectMock.domainLookupByName(Mockito.anyString())).thenThrow(LibvirtException.class);
         attachOrDetachDeviceTest(true, "vmName", diskDefMock);
     }
 
-    @PrepareForTest(KVMStorageProcessor.class)
+    @Test
     public void attachOrDetachDeviceTestAttachSuccess() throws LibvirtException, InternalErrorException {
         Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
         attachOrDetachDeviceTest(true, "vmName", diskDefMock);
@@ -431,7 +443,6 @@
     }
 
     @Test (expected = LibvirtException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void attachOrDetachDeviceTestAttachThrowLibvirtException() throws LibvirtException, InternalErrorException {
         Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
         Mockito.when(diskDefMock.toString()).thenReturn("diskDef");
@@ -441,7 +452,6 @@
     }
 
     @Test (expected = LibvirtException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void attachOrDetachDeviceTestDetachThrowLibvirtException() throws LibvirtException, InternalErrorException {
         Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
         Mockito.doThrow(LibvirtException.class).when(domainMock).detachDevice(Mockito.anyString());
@@ -449,10 +459,9 @@
     }
 
     @Test
-    @PrepareForTest(KVMStorageProcessor.class)
     public void attachOrDetachDeviceTestDetachSuccess() throws LibvirtException, InternalErrorException {
         Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
-        PowerMockito.doReturn(true).when(storageProcessorSpy).checkDetachSuccess(Mockito.anyString(), Mockito.any(Domain.class));
+        Mockito.doReturn(true).when(storageProcessorSpy).checkDetachSuccess(Mockito.anyString(), Mockito.any(Domain.class));
         Mockito.when(diskDefMock.toString()).thenReturn("diskDef");
         Mockito.when(diskDefMock.getDiskPath()).thenReturn("diskDef");
         attachOrDetachDeviceTest( false, "vmName", diskDefMock, 10000);
@@ -460,10 +469,9 @@
     }
 
     @Test (expected = InternalErrorException.class)
-    @PrepareForTest(KVMStorageProcessor.class)
     public void attachOrDetachDeviceTestDetachThrowInternalErrorException() throws LibvirtException, InternalErrorException {
         Mockito.when(connectMock.domainLookupByName("vmName")).thenReturn(domainMock);
-        PowerMockito.doReturn(false).when(storageProcessorSpy).checkDetachSuccess(Mockito.anyString(), Mockito.any(Domain.class));
+        Mockito.doReturn(false).when(storageProcessorSpy).checkDetachSuccess(Mockito.anyString(), Mockito.any(Domain.class));
         Mockito.when(diskDefMock.toString()).thenReturn("diskDef");
         Mockito.when(diskDefMock.getDiskPath()).thenReturn("diskDef");
         attachOrDetachDeviceTest( false, "vmName", diskDefMock);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePoolTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePoolTest.java
index ca0c6ac..88d4daa 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePoolTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePoolTest.java
@@ -17,15 +17,20 @@
 package com.cloud.hypervisor.kvm.storage;
 
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.libvirt.StoragePool;
 import org.mockito.Mockito;
 
 import com.cloud.storage.Storage.StoragePoolType;
 
 import junit.framework.TestCase;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class LibvirtStoragePoolTest extends TestCase {
 
+    @Test
     public void testAttributes() {
         String uuid = "4c4fb08b-373e-4f30-a120-3aa3a43f31da";
         String name = "myfirstpool";
@@ -52,6 +57,7 @@
         assertEquals(pool.getAvailable(), 1023);
     }
 
+    @Test
     public void testDefaultFormats() {
         String uuid = "f40cbf53-1f37-4c62-8912-801edf398f47";
         String name = "myfirstpool";
@@ -72,6 +78,7 @@
         assertEquals(clvmPool.getStoragePoolType(), StoragePoolType.CLVM);
     }
 
+    @Test
     public void testExternalSnapshot() {
         String uuid = "60b46738-c5d0-40a9-a79e-9a4fe6295db7";
         String name = "myfirstpool";
@@ -80,6 +87,9 @@
         StoragePool storage = Mockito.mock(StoragePool.class);
 
         LibvirtStoragePool nfsPool = new LibvirtStoragePool(uuid, name, StoragePoolType.NetworkFilesystem, adapter, storage);
+        if (nfsPool.getType() != StoragePoolType.NetworkFilesystem) {
+            System.out.println("tested");
+        }
         assertFalse(nfsPool.isExternalSnapshot());
 
         LibvirtStoragePool rbdPool = new LibvirtStoragePool(uuid, name, StoragePoolType.RBD, adapter, storage);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
index 6115dad..25fab1a 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
@@ -19,7 +19,10 @@
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class ScaleIOStorageAdaptorTest {
     @Test
     public void getUsableBytesFromRawBytesTest() {
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java
index 152be69..492bc27 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java
@@ -33,22 +33,21 @@
 import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
 import org.apache.cloudstack.utils.qemu.QemuImg;
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StorageLayer;
 import com.cloud.utils.script.Script;
 
-@PrepareForTest({ScaleIOUtil.class, Script.class})
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ScaleIOStoragePoolTest {
 
     ScaleIOStoragePool pool;
@@ -70,10 +69,6 @@
         pool = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type, details, adapter);
     }
 
-    @After
-    public void tearDown() throws Exception {
-    }
-
     @Test
     public void testAttributes() {
         assertEquals(0, pool.getCapacity());
@@ -104,12 +99,16 @@
         Map<String,String> details = new HashMap<String, String>();
         details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
 
-        PowerMockito.mockStatic(Script.class);
-        when(Script.runSimpleBashScript("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(sdcId);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            when(Script.runSimpleBashScript(
+                    "/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(
+                    sdcId);
 
-        ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type, details, adapter);
-        assertEquals(systemId, pool1.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
-        assertEquals(sdcId, pool1.getDetails().get(ScaleIOGatewayClient.SDC_ID));
+            ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type,
+                    details, adapter);
+            assertEquals(systemId, pool1.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
+            assertEquals(sdcId, pool1.getDetails().get(ScaleIOGatewayClient.SDC_ID));
+        }
     }
 
     @Test
@@ -121,13 +120,17 @@
         Map<String,String> details = new HashMap<String, String>();
         details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
 
-        PowerMockito.mockStatic(Script.class);
-        when(Script.runSimpleBashScript("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(null);
-        when(Script.runSimpleBashScript("/opt/emc/scaleio/sdc/bin/drv_cfg --query_guid")).thenReturn(sdcGuid);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            when(Script.runSimpleBashScript(
+                    "/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(
+                    null);
+            when(Script.runSimpleBashScript("/opt/emc/scaleio/sdc/bin/drv_cfg --query_guid")).thenReturn(sdcGuid);
 
-        ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type, details, adapter);
-        assertEquals(systemId, pool1.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
-        assertEquals(sdcGuid, pool1.getDetails().get(ScaleIOGatewayClient.SDC_GUID));
+            ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type,
+                    details, adapter);
+            assertEquals(systemId, pool1.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
+            assertEquals(sdcGuid, pool1.getDetails().get(ScaleIOGatewayClient.SDC_GUID));
+        }
     }
 
     @Test
@@ -146,35 +149,35 @@
         final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
         final String systemId = "218ce1797566a00f";
 
-        File dir = PowerMockito.mock(File.class);
-        PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(dir);
-
         // TODO: Mock file in dir
-        File[] files = new File[1];
-        String volumeId = ScaleIOUtil.getVolumePath(volumePath);
-        String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumeId;
-        files[0] = new File(diskFilePath);
-        PowerMockito.when(dir.listFiles(any(FileFilter.class))).thenReturn(files);
 
-        KVMPhysicalDisk disk = adapter.getPhysicalDisk(volumePath, pool);
-        assertNull(disk);
+        try (MockedConstruction<File> ignored = Mockito.mockConstruction(File.class, (mock, context) -> {
+            File[] files = new File[1];
+            String volumeId = ScaleIOUtil.getVolumePath(volumePath);
+            String diskFilePath =
+                    ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumeId;
+            files[0] = new File(diskFilePath);
+            Mockito.when(mock.listFiles(any(FileFilter.class))).thenReturn(files);
+        })) {
+            KVMPhysicalDisk disk = adapter.getPhysicalDisk(volumePath, pool);
+            assertNull(disk);
+        }
     }
 
+
     @Test
     public void testGetPhysicalDiskWithSystemId() throws Exception {
         final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
         final String volumeId = ScaleIOUtil.getVolumePath(volumePath);
         final String systemId = "218ce1797566a00f";
-        PowerMockito.mockStatic(ScaleIOUtil.class);
-        when(ScaleIOUtil.getSystemIdForVolume(volumeId)).thenReturn(systemId);
+        try (MockedStatic<ScaleIOUtil> ignored = Mockito.mockStatic(ScaleIOUtil.class, Mockito.CALLS_REAL_METHODS)) {
+            when(ScaleIOUtil.getSystemIdForVolume(volumeId)).thenReturn(systemId);
 
-        // TODO: Mock file exists
-        File file = PowerMockito.mock(File.class);
-        PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(file);
-        PowerMockito.when(file.exists()).thenReturn(true);
+            // TODO: Mock file exists
 
-        KVMPhysicalDisk disk = adapter.getPhysicalDisk(volumePath, pool);
-        assertNull(disk);
+            KVMPhysicalDisk disk = adapter.getPhysicalDisk(volumePath, pool);
+            assertNull(disk);
+        }
     }
 
     @Test
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java.bkp b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java.bkp
new file mode 100644
index 0000000..92e0210
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePoolTest.java.bkp
@@ -0,0 +1,202 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.hypervisor.kvm.storage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
+import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.StorageLayer;
+import com.cloud.utils.script.Script;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ScaleIOStoragePoolTest {
+
+    ScaleIOStoragePool pool;
+
+    StorageAdaptor adapter;
+
+    @Mock
+    StorageLayer storageLayer;
+
+    @Before
+    public void setUp() throws Exception {
+        final String uuid = "345fc603-2d7e-47d2-b719-a0110b3732e6";
+        final String systemId = "218ce1797566a00f";
+        final StoragePoolType type = StoragePoolType.PowerFlex;
+        Map<String,String> details = new HashMap<String, String>();
+        details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
+
+        adapter = spy(new ScaleIOStorageAdaptor(storageLayer));
+        pool = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type, details, adapter);
+    }
+
+    @Test
+    public void testAttributes() {
+        assertEquals(0, pool.getCapacity());
+        assertEquals(0, pool.getUsed());
+        assertEquals(0, pool.getAvailable());
+        assertEquals("345fc603-2d7e-47d2-b719-a0110b3732e6", pool.getUuid());
+        assertEquals("192.168.1.19", pool.getSourceHost());
+        assertEquals(443, pool.getSourcePort());
+        assertEquals("a519be2f00000000", pool.getSourceDir());
+        assertEquals(StoragePoolType.PowerFlex, pool.getType());
+        assertEquals("218ce1797566a00f", pool.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
+
+        pool.setCapacity(131072);
+        pool.setUsed(24576);
+        pool.setAvailable(106496);
+
+        assertEquals(131072, pool.getCapacity());
+        assertEquals(24576, pool.getUsed());
+        assertEquals(106496, pool.getAvailable());
+    }
+
+    @Test
+    public void testSdcIdAttribute() {
+        final String uuid = "345fc603-2d7e-47d2-b719-a0110b3732e6";
+        final String systemId = "218ce1797566a00f";
+        final String sdcId = "301b852c00000003";
+        final StoragePoolType type = StoragePoolType.PowerFlex;
+        Map<String,String> details = new HashMap<String, String>();
+        details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
+
+        try(MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            when(Script.runSimpleBashScript(
+                    "/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(
+                    sdcId);
+
+            ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type,
+                    details, adapter);
+            assertEquals(systemId, pool1.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
+            assertEquals(sdcId, pool1.getDetails().get(ScaleIOGatewayClient.SDC_ID));
+        }
+    }
+
+    @Test
+    public void testSdcGuidAttribute() {
+        final String uuid = "345fc603-2d7e-47d2-b719-a0110b3732e6";
+        final String systemId = "218ce1797566a00f";
+        final String sdcGuid = "B0E3BFB8-C20B-43BF-93C8-13339E85AA50";
+        final StoragePoolType type = StoragePoolType.PowerFlex;
+        Map<String,String> details = new HashMap<String, String>();
+        details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
+
+        try(MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            when(Script.runSimpleBashScript(
+                    "/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(
+                    null);
+            when(Script.runSimpleBashScript("/opt/emc/scaleio/sdc/bin/drv_cfg --query_guid")).thenReturn(sdcGuid);
+
+            ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type,
+                    details, adapter);
+            assertEquals(systemId, pool1.getDetails().get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID));
+            assertEquals(sdcGuid, pool1.getDetails().get(ScaleIOGatewayClient.SDC_GUID));
+        }
+    }
+
+    @Test
+    public void testDefaults() {
+        assertEquals(PhysicalDiskFormat.RAW, pool.getDefaultFormat());
+        assertEquals(StoragePoolType.PowerFlex, pool.getType());
+
+        assertNull(pool.getAuthUserName());
+        assertNull(pool.getAuthSecret());
+
+        Assert.assertFalse(pool.supportsConfigDriveIso());
+        assertTrue(pool.isExternalSnapshot());
+    }
+
+    public void testGetPhysicalDiskWithWildcardFileFilter() throws Exception {
+        final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
+        final String systemId = "218ce1797566a00f";
+
+        // TODO: Mock file in dir
+
+        try(MockedConstruction<File> ignored = Mockito.mockConstruction(File.class, (mock, context) -> {
+            File[] files = new File[1];
+            String volumeId = ScaleIOUtil.getVolumePath(volumePath);
+            String diskFilePath =
+                    ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumeId;
+            files[0] = new File(diskFilePath);
+            Mockito.when(mock.listFiles(any(FileFilter.class))).thenReturn(files);
+        })) {
+            KVMPhysicalDisk disk = adapter.getPhysicalDisk(volumePath, pool);
+            assertNull(disk);
+        }
+    }
+
+    @Test
+    public void testGetPhysicalDiskWithSystemId() throws Exception {
+        final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
+        final String volumeId = ScaleIOUtil.getVolumePath(volumePath);
+        final String systemId = "218ce1797566a00f";
+
+        try (MockedConstruction<File> ignored = Mockito.mockConstruction(File.class, (mock, context) -> {
+            Mockito.when(mock.exists()).thenReturn(true);
+        }); MockedStatic<ScaleIOUtil> scaleIOUtilMockedStatic = Mockito.mockStatic(ScaleIOUtil.class)) {
+            scaleIOUtilMockedStatic.when(() -> ScaleIOUtil.getSystemIdForVolume(volumeId)).thenReturn(systemId);
+
+            KVMPhysicalDisk disk = adapter.getPhysicalDisk(volumePath, pool);
+            assertNull(disk);
+        }
+    }
+
+    @Test
+    public void testConnectPhysicalDisk() {
+        final String volumePath = "6c3362b500000001:vol-139-3d2c-12f0";
+        final String volumeId = ScaleIOUtil.getVolumePath(volumePath);
+        final String systemId = "218ce1797566a00f";
+        final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + ScaleIOUtil.DISK_NAME_PREFIX + systemId + "-" + volumeId;
+        KVMPhysicalDisk disk = new KVMPhysicalDisk(diskFilePath, volumePath, pool);
+        disk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+        disk.setSize(8192);
+        disk.setVirtualSize(8192);
+
+        assertEquals("/dev/disk/by-id/emc-vol-218ce1797566a00f-6c3362b500000001", disk.getPath());
+
+        when(adapter.getPhysicalDisk(volumeId, pool)).thenReturn(disk);
+
+        final boolean result = adapter.connectPhysicalDisk(volumePath, pool, null);
+        assertTrue(result);
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/kvm/ha/KVMHostHATest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/kvm/ha/KVMHostHATest.java
index b6b3fb7..5a7c156 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/kvm/ha/KVMHostHATest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/kvm/ha/KVMHostHATest.java
@@ -29,7 +29,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.runners.MockitoJUnitRunner;
 
 import com.cloud.exception.StorageUnavailableException;
@@ -47,7 +46,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         kvmHAProvider = new KVMHAProvider();
         kvmHAProvider.hostActivityChecker = kvmHostActivityChecker;
     }
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/CryptSetupTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/CryptSetupTest.java
index c54bbe7..74f8103 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/CryptSetupTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/CryptSetupTest.java
@@ -23,6 +23,8 @@
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -32,6 +34,8 @@
 import java.nio.file.attribute.PosixFilePermissions;
 import java.util.Set;
 
+
+@RunWith(MockitoJUnitRunner.class)
 public class CryptSetupTest {
     CryptSetup cryptSetup = new CryptSetup();
 
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/KeyFileTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/KeyFileTest.java
index 2cb9512..93ac58b 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/KeyFileTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/cryptsetup/KeyFileTest.java
@@ -20,11 +20,14 @@
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
+@RunWith(MockitoJUnitRunner.class)
 public class KeyFileTest {
 
     @Test
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java
index c8d5721..b41e2bf 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java
@@ -17,24 +17,19 @@
 package org.apache.cloudstack.utils.linux;
 
 import org.apache.commons.lang.SystemUtils;
-import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.libvirt.Connect;
 import org.libvirt.NodeInfo;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {LibvirtConnection.class})
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class KVMHostInfoTest {
     @Test
     public void getCpuSpeed() {
@@ -44,7 +39,7 @@
         Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
         NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
         nodeInfo.mhz = 1000;
-        Assert.assertThat(KVMHostInfo.getCpuSpeed(null, nodeInfo), Matchers.greaterThan(0l));
+        Assert.assertTrue(KVMHostInfo.getCpuSpeed(null, nodeInfo) > 0L);
     }
 
     @Test
@@ -57,7 +52,7 @@
                 "    <vendor>AMD</vendor>\n" +
                 "    <counter name='tsc' frequency='2350000000' scaling='no'/>\n" +
                 "  </cpu>\n" +
-                "</host>\n";;
+                "</host>\n";
         Assert.assertEquals(2350L, KVMHostInfo.getCpuSpeedFromHostCapabilities(capabilities));
     }
 
@@ -66,19 +61,45 @@
         if (!System.getProperty("os.name").equals("Linux")) {
             return;
         }
-        PowerMockito.mockStatic(LibvirtConnection.class);
-        Connect conn = Mockito.mock(Connect.class);
-        NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
-        nodeInfo.mhz = 1000;
-        String capabilitiesXml = "<capabilities></capabilities>";
+        try (MockedStatic<LibvirtConnection> ignored = Mockito.mockStatic(LibvirtConnection.class)) {
+            Connect conn = Mockito.mock(Connect.class);
+            NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
+            nodeInfo.mhz = 1000;
+            String capabilitiesXml = "<capabilities></capabilities>";
 
-        PowerMockito.doReturn(conn).when(LibvirtConnection.class, "getConnection");
-        PowerMockito.when(conn.nodeInfo()).thenReturn(nodeInfo);
-        PowerMockito.when(conn.getCapabilities()).thenReturn(capabilitiesXml);
-        PowerMockito.when(conn.close()).thenReturn(0);
-        int manualSpeed = 500;
+            Mockito.when(LibvirtConnection.getConnection()).thenReturn(conn);
+            Mockito.when(conn.nodeInfo()).thenReturn(nodeInfo);
+            Mockito.when(conn.getCapabilities()).thenReturn(capabilitiesXml);
+            int manualSpeed = 500;
 
-        KVMHostInfo kvmHostInfo = new KVMHostInfo(10, 10, manualSpeed);
-        Assert.assertEquals(kvmHostInfo.getCpuSpeed(), manualSpeed);
+            KVMHostInfo kvmHostInfo = new KVMHostInfo(10, 10, manualSpeed, 0);
+            Assert.assertEquals(kvmHostInfo.getCpuSpeed(), manualSpeed);
+        }
+    }
+
+    @Test
+    public void reservedCpuCoresTest() throws Exception {
+        if (!System.getProperty("os.name").equals("Linux")) {
+            return;
+        }
+        try (MockedStatic<LibvirtConnection> ignored = Mockito.mockStatic(LibvirtConnection.class)) {
+            Connect conn = Mockito.mock(Connect.class);
+            NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
+            nodeInfo.cpus = 10;
+            String capabilitiesXml = "<capabilities></capabilities>";
+
+            Mockito.when(LibvirtConnection.getConnection()).thenReturn(conn);
+            Mockito.when(conn.nodeInfo()).thenReturn(nodeInfo);
+            Mockito.when(conn.getCapabilities()).thenReturn(capabilitiesXml);
+
+            KVMHostInfo kvmHostInfo = new KVMHostInfo(10, 10, 100, 2);
+            Assert.assertEquals("reserve two CPU cores", 8, kvmHostInfo.getAllocatableCpus());
+
+            kvmHostInfo = new KVMHostInfo(10, 10, 100, 0);
+            Assert.assertEquals("no reserve CPU core setting", 10, kvmHostInfo.getAllocatableCpus());
+
+            kvmHostInfo = new KVMHostInfo(10, 10, 100, 12);
+            Assert.assertEquals("Misconfigured/too large CPU reserve", 0, kvmHostInfo.getAllocatableCpus());
+        }
     }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/MemStatTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/MemStatTest.java
index 57cb381..86cc91b 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/MemStatTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/MemStatTest.java
@@ -16,18 +16,21 @@
 // under the License.
 package org.apache.cloudstack.utils.linux;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Scanner;
+import java.util.stream.Collectors;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(MemStat.class)
+@RunWith(MockitoJUnitRunner.class)
 public class MemStatTest {
     final String memInfo = "MemTotal:        5830236 kB\n" +
                            "MemFree:          156752 kB\n" +
@@ -37,10 +40,26 @@
                            "Active:          4260808 kB\n" +
                            "Inactive:         949392 kB\n";
 
+    MockedConstruction<Scanner> scanner;
+
     @Before
     public void setup() throws Exception {
-        Scanner scanner = new Scanner(memInfo);
-        PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);
+        scanner = Mockito.mockConstruction(Scanner.class, (mock, context) -> {
+            String[] memInfoLines = memInfo.split("\\n");
+            List<Boolean> hasNextReturnList = Arrays.stream(memInfoLines).map(line -> true).collect(
+                    Collectors.toList());
+            hasNextReturnList.add(false);
+            Mockito.when(mock.next()).thenReturn(memInfoLines[0], Arrays.copyOfRange(memInfoLines, 1,
+                    memInfoLines.length));
+            Mockito.when(mock.hasNext()).thenReturn(true,
+                    Arrays.copyOfRange(hasNextReturnList.toArray(new Boolean[0]), 1, hasNextReturnList.size()));
+        });
+
+    }
+
+    @After
+    public void tearDown() {
+        scanner.close();
     }
 
     @Test
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgFileTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgFileTest.java
index aed76ea..edc6b84 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgFileTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgFileTest.java
@@ -18,12 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@Ignore
+
+@RunWith(MockitoJUnitRunner.class)
 public class QemuImgFileTest {
     @Test
     public void testFileNameAtContructor() {
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java
index 8bb762c..b0981dd 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java
@@ -368,4 +368,21 @@
         Assert.assertTrue("should support qcow2", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.QCOW2));
         Assert.assertFalse("should not support http", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.SHEEPDOG));
     }
+
+    @Test
+    public void testCheckAndRepair() throws LibvirtException {
+        String filename = "/tmp/" + UUID.randomUUID() + ".qcow2";
+
+        QemuImgFile file = new QemuImgFile(filename);
+
+        try {
+            QemuImg qemu = new QemuImg(0);
+            qemu.checkAndRepair(file, null, null, null);
+        } catch (QemuImgException e) {
+            fail(e.getMessage());
+        }
+
+        File f = new File(filename);
+        f.delete();
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/hypervisors/kvm/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/hypervisors/ovm/pom.xml b/plugins/hypervisors/ovm/pom.xml
index ec94cbd..d198864 100644
--- a/plugins/hypervisors/ovm/pom.xml
+++ b/plugins/hypervisors/ovm/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-compute/module.properties b/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-compute/module.properties
index 1d93fa1..165a1c7 100644
--- a/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-compute/module.properties
+++ b/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=ovm-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-discoverer/module.properties b/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-discoverer/module.properties
index 3a4b1f8..9f432a5 100644
--- a/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-discoverer/module.properties
+++ b/plugins/hypervisors/ovm/src/main/resources/META-INF/cloudstack/ovm-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=ovm-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmVolumeModule.py b/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmVolumeModule.py
index 0723276..d2152b1 100755
--- a/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmVolumeModule.py
+++ b/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmVolumeModule.py
@@ -100,7 +100,7 @@
             priStorageMountPoint = sr.mountpoint
             volDir = join(priStorageMountPoint, 'running_pool', volDirUuid)
             if exists(volDir):
-                raise Exception("Volume dir %s alreay existed, can not override"%volDir)
+                raise Exception("Volume dir %s already existed, can not override"%volDir)
             os.makedirs(volDir)
             OvmStoragePool()._checkDirSizeForImage(volDir, templateUrl)
             volName = volUuid + '.raw'
diff --git a/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/configureOvm.sh b/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/configureOvm.sh
index 4ed8aa9..2cc3a0c 100755
--- a/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/configureOvm.sh
+++ b/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/configureOvm.sh
@@ -128,4 +128,3 @@
     *)
         errExit "Valid commands: preSetup postSetup"
 esac
-
diff --git a/plugins/hypervisors/ovm3/pom.xml b/plugins/hypervisors/ovm3/pom.xml
index cc8b4b1..086a796 100644
--- a/plugins/hypervisors/ovm3/pom.xml
+++ b/plugins/hypervisors/ovm3/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/hypervisors/simulator/pom.xml b/plugins/hypervisors/simulator/pom.xml
index e945c29..09cf702 100644
--- a/plugins/hypervisors/simulator/pom.xml
+++ b/plugins/hypervisors/simulator/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>cloud-plugin-hypervisor-simulator</artifactId>
diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/api/commands/ConfigureSimulatorHAProviderState.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/api/commands/ConfigureSimulatorHAProviderState.java
index 1d68a18..ef6f2db 100644
--- a/plugins/hypervisors/simulator/src/main/java/com/cloud/api/commands/ConfigureSimulatorHAProviderState.java
+++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/api/commands/ConfigureSimulatorHAProviderState.java
@@ -69,12 +69,12 @@
     private Boolean activity;
 
     @Parameter(name = ApiConstants.RECOVER, type = CommandType.BOOLEAN,
-            description = "Set true is haprovider for simulator host should be be recoverable",
+            description = "Set true is haprovider for simulator host should be recoverable",
             required = true)
     private Boolean recovery;
 
     @Parameter(name = ApiConstants.FENCE, type = CommandType.BOOLEAN,
-            description = "Set true is haprovider for simulator host should be be fence-able",
+            description = "Set true is haprovider for simulator host should be fence-able",
             required = true)
     private Boolean fenceable;
 
diff --git a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-compute/module.properties b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-compute/module.properties
index 7b12a08..6bb4e80 100644
--- a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-compute/module.properties
+++ b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=simulator-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-discoverer/module.properties b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-discoverer/module.properties
index 536cf15..59ecba6 100644
--- a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-discoverer/module.properties
+++ b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=simulator-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-storage/module.properties b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-storage/module.properties
index 97a1784..7a9bcd4 100644
--- a/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-storage/module.properties
+++ b/plugins/hypervisors/simulator/src/main/resources/META-INF/cloudstack/simulator-storage/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=simulator-storage
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/hypervisors/ucs/pom.xml b/plugins/hypervisors/ucs/pom.xml
index 0e393a9..879de24 100644
--- a/plugins/hypervisors/ucs/pom.xml
+++ b/plugins/hypervisors/ucs/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>cloud-plugin-hypervisor-ucs</artifactId>
diff --git a/plugins/hypervisors/vmware/pom.xml b/plugins/hypervisors/vmware/pom.xml
index f827964..4b99a93 100644
--- a/plugins/hypervisors/vmware/pom.xml
+++ b/plugins/hypervisors/vmware/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
index 410fc7c..fd4d915 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java
@@ -27,6 +27,12 @@
 
 import javax.inject.Inject;
 
+import com.cloud.hypervisor.vmware.mo.DatastoreMO;
+import com.cloud.hypervisor.vmware.mo.HostMO;
+import com.cloud.hypervisor.vmware.util.VmwareClient;
+import com.cloud.hypervisor.vmware.util.VmwareHelper;
+import com.cloud.vm.VmDetailConstants;
+import com.vmware.vim25.VirtualMachinePowerState;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.backup.Backup;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
@@ -44,6 +50,7 @@
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -80,9 +87,9 @@
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.HypervisorGuru;
 import com.cloud.hypervisor.HypervisorGuruBase;
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 import com.cloud.hypervisor.vmware.manager.VmwareManager;
 import com.cloud.hypervisor.vmware.mo.DatacenterMO;
@@ -1220,4 +1227,118 @@
     protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) {
         return super.toVirtualMachineTO(vmProfile);
     }
+
+    private VmwareContext connectToVcenter(String vcenter, String username, String password) throws Exception {
+        VmwareClient vimClient = new VmwareClient(vcenter);
+        String serviceUrl = "https://" + vcenter + "/sdk/vimService";
+        vimClient.connect(serviceUrl, username, password);
+        return new VmwareContext(vimClient, vcenter);
+    }
+
+    private void relocateClonedVMToSourceHost(VirtualMachineMO clonedVM, HostMO sourceHost) throws Exception {
+        if (!clonedVM.getRunningHost().getMor().equals(sourceHost.getMor())) {
+            s_logger.debug(String.format("Relocating VM to the same host as the source VM: %s", sourceHost.getHostName()));
+            if (!clonedVM.relocate(sourceHost.getMor())) {
+                String err = String.format("Cannot relocate cloned VM %s to the source host %s", clonedVM.getVmName(), sourceHost.getHostName());
+                s_logger.error(err);
+                throw new CloudRuntimeException(err);
+            }
+        }
+    }
+
+    private VirtualMachineMO createCloneFromSourceVM(String vmName, VirtualMachineMO vmMo,
+                                                     DatacenterMO dataCenterMO) throws Exception {
+        HostMO sourceHost = vmMo.getRunningHost();
+        String cloneName = UUID.randomUUID().toString();
+        DatastoreMO datastoreMO = vmMo.getAllDatastores().get(0); //pick the first datastore
+        ManagedObjectReference morPool = vmMo.getRunningHost().getHyperHostOwnerResourcePool();
+        boolean result = vmMo.createFullClone(cloneName, dataCenterMO.getVmFolder(), morPool, datastoreMO.getMor(), Storage.ProvisioningType.THIN);
+        VirtualMachineMO clonedVM = dataCenterMO.findVm(cloneName);
+        if (!result || clonedVM == null) {
+            String err = String.format("Could not clone VM %s before migration from VMware", vmName);
+            s_logger.error(err);
+            throw new CloudRuntimeException(err);
+        }
+        relocateClonedVMToSourceHost(clonedVM, sourceHost);
+        return clonedVM;
+    }
+
+    @Override
+    public UnmanagedInstanceTO cloneHypervisorVMOutOfBand(String hostIp, String vmName,
+                                                                 Map<String, String> params) {
+        s_logger.debug(String.format("Cloning VM %s on external vCenter %s", vmName, hostIp));
+        String vcenter = params.get(VmDetailConstants.VMWARE_VCENTER_HOST);
+        String datacenter = params.get(VmDetailConstants.VMWARE_DATACENTER_NAME);
+        String username = params.get(VmDetailConstants.VMWARE_VCENTER_USERNAME);
+        String password = params.get(VmDetailConstants.VMWARE_VCENTER_PASSWORD);
+
+        try {
+            VmwareContext context = connectToVcenter(vcenter, username, password);
+            DatacenterMO dataCenterMO = new DatacenterMO(context, datacenter);
+            VirtualMachineMO vmMo = dataCenterMO.findVm(vmName);
+            if (vmMo == null) {
+                String err = String.format("Cannot find VM with name %s on %s/%s", vmName, vcenter, datacenter);
+                s_logger.error(err);
+                throw new CloudRuntimeException(err);
+            }
+            VirtualMachinePowerState sourceVmPowerState = vmMo.getPowerState();
+            if (sourceVmPowerState == VirtualMachinePowerState.POWERED_ON && isWindowsVm(vmMo)) {
+                s_logger.debug(String.format("VM %s is a Windows VM and its Running, cannot be imported." +
+                                "Please gracefully shut it down before attempting the import",
+                        vmName));
+            }
+
+            VirtualMachineMO clonedVM = createCloneFromSourceVM(vmName, vmMo, dataCenterMO);
+            s_logger.debug(String.format("VM %s cloned successfully", vmName));
+            UnmanagedInstanceTO clonedInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), clonedVM);
+            setNicsFromSourceVM(clonedInstance, vmMo);
+            clonedInstance.setCloneSourcePowerState(sourceVmPowerState == VirtualMachinePowerState.POWERED_ON ? UnmanagedInstanceTO.PowerState.PowerOn : UnmanagedInstanceTO.PowerState.PowerOff);
+            return clonedInstance;
+        } catch (Exception e) {
+            String err = String.format("Error cloning VM: %s from external vCenter %s: %s", vmName, vcenter, e.getMessage());
+            s_logger.error(err, e);
+            throw new CloudRuntimeException(err, e);
+        }
+    }
+
+    private boolean isWindowsVm(VirtualMachineMO vmMo) throws Exception {
+        UnmanagedInstanceTO sourceInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), vmMo);
+        return sourceInstance.getOperatingSystem().toLowerCase().contains("windows");
+    }
+
+    private void setNicsFromSourceVM(UnmanagedInstanceTO clonedInstance, VirtualMachineMO vmMo) throws Exception {
+        UnmanagedInstanceTO sourceInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), vmMo);
+        List<UnmanagedInstanceTO.Disk> sourceDisks = sourceInstance.getDisks();
+        List<UnmanagedInstanceTO.Disk> clonedDisks = clonedInstance.getDisks();
+        for (int i = 0; i < sourceDisks.size(); i++) {
+            UnmanagedInstanceTO.Disk sourceDisk = sourceDisks.get(i);
+            UnmanagedInstanceTO.Disk clonedDisk = clonedDisks.get(i);
+            clonedDisk.setDiskId(sourceDisk.getDiskId());
+        }
+    }
+
+    @Override
+    public boolean removeClonedHypervisorVMOutOfBand(String hostIp, String vmName, Map<String, String> params) {
+        s_logger.debug(String.format("Removing VM %s on external vCenter %s", vmName, hostIp));
+        String vcenter = params.get(VmDetailConstants.VMWARE_VCENTER_HOST);
+        String datacenter = params.get(VmDetailConstants.VMWARE_DATACENTER_NAME);
+        String username = params.get(VmDetailConstants.VMWARE_VCENTER_USERNAME);
+        String password = params.get(VmDetailConstants.VMWARE_VCENTER_PASSWORD);
+        try {
+            VmwareContext context = connectToVcenter(vcenter, username, password);
+            DatacenterMO dataCenterMO = new DatacenterMO(context, datacenter);
+            VirtualMachineMO vmMo = dataCenterMO.findVm(vmName);
+            if (vmMo == null) {
+                String err = String.format("Cannot find VM %s on datacenter %s, not possible to remove VM out of band",
+                        vmName, datacenter);
+                s_logger.error(err);
+                return false;
+            }
+            return vmMo.destroy();
+        } catch (Exception e) {
+            String err = String.format("Error destroying external VM %s: %s", vmName, e.getMessage());
+            s_logger.error(err, e);
+            return false;
+        }
+    }
 }
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java
index 990a187..d60dc99 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java
@@ -129,15 +129,15 @@
                 }
             }
         } else {
-            // for user-VM, use E1000 as default
             if (nicDeviceType == null) {
-                details.put(VmDetailConstants.NIC_ADAPTER, VirtualEthernetCardType.E1000.toString());
+                details.put(VmDetailConstants.NIC_ADAPTER, vmwareMgr.VmwareUserVmNicDeviceType.value());
             } else {
                 try {
                     VirtualEthernetCardType.valueOf(nicDeviceType);
                 } catch (Exception e) {
-                    LOGGER.warn("Invalid NIC device type " + nicDeviceType + " is specified in VM details, switch to default E1000");
-                    details.put(VmDetailConstants.NIC_ADAPTER, VirtualEthernetCardType.E1000.toString());
+                    LOGGER.warn(String.format("Invalid NIC device type [%s] specified in VM details, switching to value [%s] of configuration [%s].",
+                            nicDeviceType, vmwareMgr.VmwareUserVmNicDeviceType.value(), vmwareMgr.VmwareUserVmNicDeviceType.toString()));
+                    details.put(VmDetailConstants.NIC_ADAPTER, vmwareMgr.VmwareUserVmNicDeviceType.value());
                 }
             }
         }
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenter.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenter.java
deleted file mode 100644
index b1f233c..0000000
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenter.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package com.cloud.hypervisor.vmware;
-
-import org.apache.cloudstack.api.Identity;
-import org.apache.cloudstack.api.InternalIdentity;
-
-public interface VmwareDatacenter extends Identity, InternalIdentity {
-
-    String getVmwareDatacenterName();
-
-    String getGuid();
-
-    String getVcenterHost();
-
-    @Override
-    long getId();
-
-    String getPassword();
-
-    String getUser();
-}
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterService.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterService.java
index 431526a..bbac78b 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterService.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterService.java
@@ -17,6 +17,8 @@
 
 package com.cloud.hypervisor.vmware;
 
+import com.cloud.dc.VmwareDatacenter;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.dc.VsphereStoragePolicy;
 import com.cloud.exception.DiscoveryException;
 import com.cloud.exception.ResourceInUseException;
@@ -25,11 +27,13 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.api.command.admin.zone.AddVmwareDcCmd;
 import org.apache.cloudstack.api.command.admin.zone.ImportVsphereStoragePoliciesCmd;
+import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcVmsCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcsCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePoliciesCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePolicyCompatiblePoolsCmd;
 import org.apache.cloudstack.api.command.admin.zone.RemoveVmwareDcCmd;
 import org.apache.cloudstack.api.command.admin.zone.UpdateVmwareDcCmd;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 
 import java.util.List;
 
@@ -48,4 +52,6 @@
     List<? extends VsphereStoragePolicy> listVsphereStoragePolicies(ListVsphereStoragePoliciesCmd cmd);
 
     List<StoragePool> listVsphereStoragePolicyCompatibleStoragePools(ListVsphereStoragePolicyCompatiblePoolsCmd cmd);
+
+    List<UnmanagedInstanceTO> listVMsInDatacenter(ListVmwareDcVmsCmd cmd);
 }
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterVO.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterVO.java
deleted file mode 100644
index 86597b2..0000000
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareDatacenterVO.java
+++ /dev/null
@@ -1,163 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package com.cloud.hypervisor.vmware;
-
-import java.util.UUID;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.Table;
-
-import com.cloud.utils.NumbersUtil;
-import com.cloud.utils.db.Encrypt;
-
-/**
- * VmwareDatacenterVO contains information of Vmware Datacenter associated with a CloudStack zone.
- */
-
-@Entity
-@Table(name = "vmware_data_center")
-public class VmwareDatacenterVO implements VmwareDatacenter {
-
-    private static final long serialVersionUID = -9114941929893819232L;
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private long id;
-
-    @Column(name = "guid")
-    private String guid;
-
-    @Column(name = "name")
-    private String vmwareDatacenterName;
-
-    @Column(name = "vcenter_host")
-    private String vcenterHost;
-
-    @Column(name = "uuid")
-    private String uuid;
-
-    @Column(name = "username")
-    private String user;
-
-    @Encrypt
-    @Column(name = "password")
-    private String password;
-
-    @Override
-    public String getUuid() {
-        return uuid;
-    }
-
-    @Override
-    public long getId() {
-        return id;
-    }
-
-    @Override
-    public String getVmwareDatacenterName() {
-        return vmwareDatacenterName;
-    }
-
-    @Override
-    public String getGuid() {
-        return guid;
-    }
-
-    @Override
-    public String getUser() {
-        return user;
-    }
-
-    @Override
-    public String getPassword() {
-        return password;
-    }
-
-    @Override
-    public String getVcenterHost() {
-        return vcenterHost;
-    }
-
-    public void setUuid(String uuid) {
-        this.uuid = uuid;
-    }
-
-    public void setGuid(String guid) {
-        this.guid = guid;
-    }
-
-    public void setVmwareDatacenterName(String name) {
-        vmwareDatacenterName = name;
-    }
-
-    public void setVcenterHost(String vCenterHost) {
-        vcenterHost = vCenterHost;
-    }
-
-    public void setUser(String user) {
-        this.user = user;
-        ;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    @Override
-    public String toString() {
-        return new StringBuilder("VmwareDatacenter[").append(guid).append("]").toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return NumbersUtil.hash(id);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof VmwareDatacenterVO) {
-            return ((VmwareDatacenterVO)obj).getId() == getId();
-        } else {
-            return false;
-        }
-    }
-
-    public VmwareDatacenterVO(String guid, String name, String vCenterHost, String user, String password) {
-        uuid = UUID.randomUUID().toString();
-        vmwareDatacenterName = name;
-        this.guid = guid;
-        vcenterHost = vCenterHost;
-        this.user = user;
-        this.password = password;
-    }
-
-    public VmwareDatacenterVO(long id, String guid, String name, String vCenterHost, String user, String password) {
-        this(guid, name, vCenterHost, user, password);
-        this.id = id;
-    }
-
-    public VmwareDatacenterVO() {
-        uuid = UUID.randomUUID().toString();
-    }
-
-}
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareServerDiscoverer.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareServerDiscoverer.java
index 6a8bcda..1989d3d 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareServerDiscoverer.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/VmwareServerDiscoverer.java
@@ -27,6 +27,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.dc.VmwareDatacenterVO;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.log4j.Logger;
 
@@ -46,7 +47,7 @@
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 import com.cloud.hypervisor.vmware.manager.VmwareManager;
 import com.cloud.hypervisor.vmware.mo.ClusterMO;
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDao.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDao.java
deleted file mode 100644
index 2754e91..0000000
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDao.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package com.cloud.hypervisor.vmware.dao;
-
-import java.util.List;
-
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
-import com.cloud.utils.db.GenericDao;
-
-public interface VmwareDatacenterDao extends GenericDao<VmwareDatacenterVO, Long> {
-
-    /**
-     * Return a VMware Datacenter given guid
-     * @param guid of VMware datacenter
-     * @return VmwareDatacenterVO for the VMware datacenter having the specified guid.
-     */
-    VmwareDatacenterVO getVmwareDatacenterByGuid(String guid);
-
-    /**
-     * Return a VMware Datacenter given name and vCenter host.
-     * For legacy zones multiple records will be present in the table.
-     * @param name of VMware datacenter
-     * @param vCenter host
-     * @return VmwareDatacenterVO for the VMware datacenter with given name and
-     * belonging to specified vCenter host.
-     */
-    List<VmwareDatacenterVO> getVmwareDatacenterByNameAndVcenter(String name, String vCenterHost);
-
-    /**
-     * Return a list of VMware Datacenter given name.
-     * @param name of Vmware datacenter
-     * @return list of VmwareDatacenterVO for VMware datacenters having the specified name.
-     */
-    List<VmwareDatacenterVO> listVmwareDatacenterByName(String name);
-
-    /**
-     * Return a list of VMware Datacenters belonging to specified vCenter
-     * @param vCenter Host
-     * @return list of VmwareDatacenterVO for all VMware datacenters belonging to
-     * specified vCenter
-     */
-    List<VmwareDatacenterVO> listVmwareDatacenterByVcenter(String vCenterHost);
-
-    /**
-     * Lists all associated VMware datacenter on the management server.
-     * @return list of VmwareDatacenterVO for all associated VMware datacenters
-     */
-    List<VmwareDatacenterVO> listAllVmwareDatacenters();
-
-}
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDaoImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDaoImpl.java
deleted file mode 100644
index e44087e..0000000
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/dao/VmwareDatacenterDaoImpl.java
+++ /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.
-
-package com.cloud.hypervisor.vmware.dao;
-
-import java.util.List;
-
-
-import org.apache.log4j.Logger;
-import org.springframework.stereotype.Component;
-
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
-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 com.cloud.utils.db.SearchCriteria.Op;
-
-@Component
-@DB
-public class VmwareDatacenterDaoImpl extends GenericDaoBase<VmwareDatacenterVO, Long> implements VmwareDatacenterDao {
-    protected static final Logger s_logger = Logger.getLogger(VmwareDatacenterDaoImpl.class);
-
-    final SearchBuilder<VmwareDatacenterVO> nameSearch;
-    final SearchBuilder<VmwareDatacenterVO> guidSearch;
-    final SearchBuilder<VmwareDatacenterVO> vcSearch;
-    final SearchBuilder<VmwareDatacenterVO> nameVcSearch;
-    final SearchBuilder<VmwareDatacenterVO> fullTableSearch;
-
-    public VmwareDatacenterDaoImpl() {
-        super();
-
-        nameSearch = createSearchBuilder();
-        nameSearch.and("name", nameSearch.entity().getVmwareDatacenterName(), Op.EQ);
-        nameSearch.done();
-
-        nameVcSearch = createSearchBuilder();
-        nameVcSearch.and("name", nameVcSearch.entity().getVmwareDatacenterName(), Op.EQ);
-        nameVcSearch.and("vCenterHost", nameVcSearch.entity().getVcenterHost(), Op.EQ);
-        nameVcSearch.done();
-
-        vcSearch = createSearchBuilder();
-        vcSearch.and("vCenterHost", vcSearch.entity().getVcenterHost(), Op.EQ);
-        vcSearch.done();
-
-        guidSearch = createSearchBuilder();
-        guidSearch.and("guid", guidSearch.entity().getGuid(), Op.EQ);
-        guidSearch.done();
-
-        fullTableSearch = createSearchBuilder();
-        fullTableSearch.done();
-    }
-
-    @Override
-    public VmwareDatacenterVO getVmwareDatacenterByGuid(String guid) {
-        SearchCriteria<VmwareDatacenterVO> sc = guidSearch.create();
-        sc.setParameters("guid", guid);
-        return findOneBy(sc);
-    }
-
-    @Override
-    public List<VmwareDatacenterVO> getVmwareDatacenterByNameAndVcenter(String name, String vCenterHost) {
-        SearchCriteria<VmwareDatacenterVO> sc = nameVcSearch.create();
-        sc.setParameters("name", name);
-        sc.setParameters("vCenterHost", vCenterHost);
-        return search(sc, null);
-    }
-
-    @Override
-    public List<VmwareDatacenterVO> listVmwareDatacenterByName(String name) {
-        SearchCriteria<VmwareDatacenterVO> sc = nameSearch.create();
-        sc.setParameters("name", name);
-        return search(sc, null);
-    }
-
-    @Override
-    public List<VmwareDatacenterVO> listVmwareDatacenterByVcenter(String vCenterHost) {
-        SearchCriteria<VmwareDatacenterVO> sc = vcSearch.create();
-        sc.setParameters("vCenterHost", vCenterHost);
-        return search(sc, null);
-    }
-
-    @Override
-    public List<VmwareDatacenterVO> listAllVmwareDatacenters() {
-        SearchCriteria<VmwareDatacenterVO> sc = fullTableSearch.create();
-        return search(sc, null);
-    }
-
-}
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java
index c2cdbcc..d64a2d3 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java
@@ -53,6 +53,14 @@
             "VMware interval window (in seconds) to collect metrics. If this is set to less than 20, then default (300 seconds) will be used. The interval used must be enabled in vCenter for this change to work, "
             + "otherwise the collection of metrics will result in an error. Check VMWare docs to know how to enable metrics interval.", true);
 
+    static final ConfigKey<String> VmwareUserVmNicDeviceType = new ConfigKey<String>(
+            String.class,
+            "vmware.uservm.nic.device.type",
+            "Advanced",
+            "E1000",
+            "Specify the default network device type for user VMs, valid values are E1000, PCNet32, Vmxnet2, Vmxnet3",
+            true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "E1000,PCNet32,Vmxnet2,Vmxnet3");
+
     String composeWorkerName();
 
     String getSystemVMIsoFileNameOnDatastore();
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java
index 199f96a..b5f4cf3 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java
@@ -43,8 +43,10 @@
 import javax.naming.ConfigurationException;
 import javax.persistence.EntityExistsException;
 
+import com.cloud.hypervisor.vmware.util.VmwareClient;
 import org.apache.cloudstack.api.command.admin.zone.AddVmwareDcCmd;
 import org.apache.cloudstack.api.command.admin.zone.ImportVsphereStoragePoliciesCmd;
+import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcVmsCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListVmwareDcsCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePoliciesCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListVsphereStoragePolicyCompatiblePoolsCmd;
@@ -61,6 +63,7 @@
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -109,13 +112,13 @@
 import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
 import com.cloud.hypervisor.vmware.LegacyZoneVO;
 import com.cloud.hypervisor.vmware.VmwareCleanupMaid;
-import com.cloud.hypervisor.vmware.VmwareDatacenter;
+import com.cloud.dc.VmwareDatacenter;
 import com.cloud.hypervisor.vmware.VmwareDatacenterService;
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
 import com.cloud.hypervisor.vmware.dao.LegacyZoneDao;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 import com.cloud.hypervisor.vmware.mo.CustomFieldConstants;
 import com.cloud.hypervisor.vmware.mo.DatacenterMO;
@@ -296,7 +299,7 @@
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {s_vmwareNicHotplugWaitTimeout, s_vmwareCleanOldWorderVMs, templateCleanupInterval, s_vmwareSearchExcludeFolder, s_vmwareOVAPackageTimeout, s_vmwareCleanupPortGroups, VMWARE_STATS_TIME_WINDOW};
+        return new ConfigKey<?>[] {s_vmwareNicHotplugWaitTimeout, s_vmwareCleanOldWorderVMs, templateCleanupInterval, s_vmwareSearchExcludeFolder, s_vmwareOVAPackageTimeout, s_vmwareCleanupPortGroups, VMWARE_STATS_TIME_WINDOW, VmwareUserVmNicDeviceType};
     }
     @Override
     public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
@@ -1113,6 +1116,7 @@
         cmdList.add(ImportVsphereStoragePoliciesCmd.class);
         cmdList.add(ListVsphereStoragePoliciesCmd.class);
         cmdList.add(ListVsphereStoragePolicyCompatiblePoolsCmd.class);
+        cmdList.add(ListVmwareDcVmsCmd.class);
         return cmdList;
     }
 
@@ -1587,6 +1591,62 @@
     }
 
     @Override
+    public List<UnmanagedInstanceTO> listVMsInDatacenter(ListVmwareDcVmsCmd cmd) {
+        String vcenter = cmd.getVcenter();
+        String datacenterName = cmd.getDatacenterName();
+        String username = cmd.getUsername();
+        String password = cmd.getPassword();
+        Long existingVcenterId = cmd.getExistingVcenterId();
+        String keyword = cmd.getKeyword();
+
+        if ((existingVcenterId == null && StringUtils.isBlank(vcenter)) ||
+                (existingVcenterId != null && StringUtils.isNotBlank(vcenter))) {
+            throw new InvalidParameterValueException("Please provide an existing vCenter ID or a vCenter IP/Name, parameters are mutually exclusive");
+        }
+
+        if (existingVcenterId == null && StringUtils.isAnyBlank(vcenter, datacenterName, username, password)) {
+            throw new InvalidParameterValueException("Please set all the information for a vCenter IP/Name, datacenter, username and password");
+        }
+
+        if (existingVcenterId != null) {
+            VmwareDatacenterVO vmwareDc = vmwareDcDao.findById(existingVcenterId);
+            if (vmwareDc == null) {
+                throw new InvalidParameterValueException(String.format("Cannot find a VMware datacenter with ID %s", existingVcenterId));
+            }
+            vcenter = vmwareDc.getVcenterHost();
+            datacenterName = vmwareDc.getVmwareDatacenterName();
+            username = vmwareDc.getUser();
+            password = vmwareDc.getPassword();
+        }
+
+        try {
+            s_logger.debug(String.format("Connecting to the VMware datacenter %s at vCenter %s to retrieve VMs",
+                    datacenterName, vcenter));
+            String serviceUrl = String.format("https://%s/sdk/vimService", vcenter);
+            VmwareClient vimClient = new VmwareClient(vcenter);
+            vimClient.connect(serviceUrl, username, password);
+            VmwareContext context = new VmwareContext(vimClient, vcenter);
+
+            DatacenterMO dcMo = new DatacenterMO(context, datacenterName);
+            ManagedObjectReference dcMor = dcMo.getMor();
+            if (dcMor == null) {
+                String msg = String.format("Unable to find VMware datacenter %s in vCenter %s",
+                        datacenterName, vcenter);
+                s_logger.error(msg);
+                throw new InvalidParameterValueException(msg);
+            }
+            List<UnmanagedInstanceTO> instances = dcMo.getAllVmsOnDatacenter();
+            return StringUtils.isBlank(keyword) ? instances :
+                    instances.stream().filter(x -> x.getName().toLowerCase().contains(keyword.toLowerCase())).collect(Collectors.toList());
+        } catch (Exception e) {
+            String errorMsg = String.format("Error retrieving stopped VMs from the VMware VC %s datacenter %s: %s",
+                    vcenter, datacenterName, e.getMessage());
+            s_logger.error(errorMsg, e);
+            throw new CloudRuntimeException(errorMsg);
+        }
+    }
+
+    @Override
     public boolean hasNexusVSM(Long clusterId) {
         ClusterVSMMapVO vsmMapVo = null;
 
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java
index d7a736e..7e6b8c1 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java
@@ -198,7 +198,7 @@
     }
 
     //Fang: new command added;
-    // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature)
+    // Important! we need to sync file system before we can safely use tar to work around a linux kernel bug(or feature)
     public String createOvaForVolume(VolumeObjectTO volume, int archiveTimeout) {
         DataStoreTO storeTO = volume.getDataStore();
         if (!(storeTO instanceof NfsTO)) {
@@ -1054,7 +1054,7 @@
             }
             String exportDir = ova_metafile.getParent();
             s_logger.info("exportDir: " + exportDir);
-            // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature)
+            // Important! we need to sync file system before we can safely use tar to work around a linux kernel bug(or feature)
             s_logger.info("Sync file system before we package OVA..., before tar ");
             s_logger.info("ova: " + ovaFileName + ", ovf:" + ovfFileName + ", vmdk:" + disks[0] + ".");
             Script commandSync = new Script(true, "sync", 0, s_logger);
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
index b65a784..1c24464 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java
@@ -48,10 +48,19 @@
 import javax.naming.ConfigurationException;
 import javax.xml.datatype.XMLGregorianCalendar;
 
+import com.cloud.hypervisor.vmware.mo.HostDatastoreBrowserMO;
+import com.vmware.vim25.FileInfo;
+import com.vmware.vim25.FileQueryFlags;
+import com.vmware.vim25.FolderFileInfo;
+import com.vmware.vim25.HostDatastoreBrowserSearchResults;
+import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
+import com.vmware.vim25.VirtualMachineConfigSummary;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.backup.PrepareForBackupRestorationCommand;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
 import org.apache.cloudstack.storage.configdrive.ConfigDrive;
 import org.apache.cloudstack.storage.resource.NfsSecondaryStorageResource;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
@@ -74,6 +83,8 @@
 import com.cloud.agent.api.AttachIsoCommand;
 import com.cloud.agent.api.BackupSnapshotAnswer;
 import com.cloud.agent.api.BackupSnapshotCommand;
+import com.cloud.agent.api.CheckGuestOsMappingAnswer;
+import com.cloud.agent.api.CheckGuestOsMappingCommand;
 import com.cloud.agent.api.CheckHealthAnswer;
 import com.cloud.agent.api.CheckHealthCommand;
 import com.cloud.agent.api.CheckNetworkAnswer;
@@ -95,6 +106,8 @@
 import com.cloud.agent.api.DeleteVMSnapshotCommand;
 import com.cloud.agent.api.GetHostStatsAnswer;
 import com.cloud.agent.api.GetHostStatsCommand;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesAnswer;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesCommand;
 import com.cloud.agent.api.GetStoragePoolCapabilitiesAnswer;
 import com.cloud.agent.api.GetStoragePoolCapabilitiesCommand;
 import com.cloud.agent.api.GetStorageStatsAnswer;
@@ -231,7 +244,6 @@
 import com.cloud.hypervisor.vmware.mo.DatastoreFile;
 import com.cloud.hypervisor.vmware.mo.DatastoreMO;
 import com.cloud.hypervisor.vmware.mo.DiskControllerType;
-import com.cloud.hypervisor.vmware.mo.DistributedVirtualSwitchMO;
 import com.cloud.hypervisor.vmware.mo.FeatureKeyConstants;
 import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO;
 import com.cloud.hypervisor.vmware.mo.HostMO;
@@ -301,22 +313,19 @@
 import com.vmware.vim25.DVPortConfigInfo;
 import com.vmware.vim25.DVPortConfigSpec;
 import com.vmware.vim25.DasVmPriority;
-import com.vmware.vim25.DatastoreInfo;
 import com.vmware.vim25.DatastoreSummary;
 import com.vmware.vim25.DistributedVirtualPort;
 import com.vmware.vim25.DistributedVirtualSwitchPortConnection;
 import com.vmware.vim25.DistributedVirtualSwitchPortCriteria;
 import com.vmware.vim25.DynamicProperty;
 import com.vmware.vim25.GuestInfo;
-import com.vmware.vim25.GuestNicInfo;
+import com.vmware.vim25.GuestOsDescriptor;
 import com.vmware.vim25.HostCapability;
 import com.vmware.vim25.HostConfigInfo;
 import com.vmware.vim25.HostFileSystemMountInfo;
 import com.vmware.vim25.HostHostBusAdapter;
 import com.vmware.vim25.HostInternetScsiHba;
-import com.vmware.vim25.HostPortGroupSpec;
 import com.vmware.vim25.ManagedObjectReference;
-import com.vmware.vim25.NasDatastoreInfo;
 import com.vmware.vim25.ObjectContent;
 import com.vmware.vim25.OptionValue;
 import com.vmware.vim25.PerfCounterInfo;
@@ -341,14 +350,12 @@
 import com.vmware.vim25.VirtualDeviceBackingInfo;
 import com.vmware.vim25.VirtualDeviceConfigSpec;
 import com.vmware.vim25.VirtualDeviceConfigSpecOperation;
-import com.vmware.vim25.VirtualDeviceFileBackingInfo;
 import com.vmware.vim25.VirtualDisk;
 import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
 import com.vmware.vim25.VirtualEthernetCard;
 import com.vmware.vim25.VirtualEthernetCardDistributedVirtualPortBackingInfo;
 import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo;
 import com.vmware.vim25.VirtualEthernetCardOpaqueNetworkBackingInfo;
-import com.vmware.vim25.VirtualIDEController;
 import com.vmware.vim25.VirtualMachineBootOptions;
 import com.vmware.vim25.VirtualMachineConfigSpec;
 import com.vmware.vim25.VirtualMachineDefinedProfileSpec;
@@ -362,14 +369,10 @@
 import com.vmware.vim25.VirtualMachineRuntimeInfo;
 import com.vmware.vim25.VirtualMachineToolsStatus;
 import com.vmware.vim25.VirtualMachineVideoCard;
-import com.vmware.vim25.VirtualPCNet32;
 import com.vmware.vim25.VirtualSCSIController;
 import com.vmware.vim25.VirtualUSBController;
-import com.vmware.vim25.VirtualVmxnet2;
-import com.vmware.vim25.VirtualVmxnet3;
 import com.vmware.vim25.VmConfigInfo;
 import com.vmware.vim25.VmConfigSpec;
-import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec;
 import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec;
 
 public class VmwareResource extends ServerResourceBase implements StoragePoolResource, ServerResource, VmwareHostService, VirtualRouterDeployer {
@@ -441,10 +444,6 @@
     protected static final String s_relativePathSystemVmKeyFileInstallDir = "scripts/vm/systemvm/id_rsa.cloud";
     protected static final String s_defaultPathSystemVmKeyFile = "/usr/share/cloudstack-common/scripts/vm/systemvm/id_rsa.cloud";
 
-    public Gson getGson() {
-        return _gson;
-    }
-
     public VmwareResource() {
         _gson = GsonHelper.getGsonLogger();
     }
@@ -607,6 +606,12 @@
                 answer = execute((GetVmVncTicketCommand) cmd);
             } else if (clz == GetAutoScaleMetricsCommand.class) {
                 answer = execute((GetAutoScaleMetricsCommand) cmd);
+            } else if (clz == CheckGuestOsMappingCommand.class) {
+                answer = execute((CheckGuestOsMappingCommand) cmd);
+            } else if (clz == GetHypervisorGuestOsNamesCommand.class) {
+                answer = execute((GetHypervisorGuestOsNamesCommand) cmd);
+            } else if (clz == ListDataStoreObjectsCommand.class) {
+                answer = execute((ListDataStoreObjectsCommand) cmd);
             } else if (clz == PrepareForBackupRestorationCommand.class) {
                 answer = execute((PrepareForBackupRestorationCommand) cmd);
             } else {
@@ -971,7 +976,7 @@
 
             // OfflineVmwareMigration: 5. ignore/replace the rest of the try-block; It is the functional bit
             VirtualDisk disk = getDiskAfterResizeDiskValidations(vmMo, path);
-            String vmdkAbsFile = getAbsoluteVmdkFile(disk);
+            String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(disk);
 
             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
                 vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
@@ -3540,7 +3545,7 @@
                     if (diskInfo == null) {
                         diskInfo = diskInfoBuilder.getDiskInfoByDeviceBusName(infoInChain.getDiskDeviceBusName());
                         if (diskInfo != null) {
-                            s_logger.info("Found existing disk from from chain device bus information: " + infoInChain.getDiskDeviceBusName());
+                            s_logger.info("Found existing disk from chain device bus information: " + infoInChain.getDiskDeviceBusName());
                             return diskInfo;
                         }
                     }
@@ -4713,7 +4718,7 @@
             s_logger.debug(String.format("locating disk for volume (%d) using path %s", volumeId, volumePath));
         }
         Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, volumePath + VMDK_EXTENSION);
-        String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first());
+        String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(diskInfo.first());
         if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
             vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
         }
@@ -4922,7 +4927,7 @@
             }
 
             VirtualDisk disk = vdisk.first();
-            String vmdkAbsFile = getAbsoluteVmdkFile(disk);
+            String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(disk);
             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
                 vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
             }
@@ -5042,7 +5047,7 @@
 
             String fullVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(targetDsMo, vmName, volumePath + VMDK_EXTENSION);
             Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, appendFileType(volumePath, VMDK_EXTENSION));
-            String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first());
+            String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(diskInfo.first());
             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
                 vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
             }
@@ -7099,16 +7104,6 @@
         return dcMo.findVm(vol.getPath());
     }
 
-    public String getAbsoluteVmdkFile(VirtualDisk disk) {
-        String vmdkAbsFile = null;
-        VirtualDeviceBackingInfo backingInfo = disk.getBacking();
-        if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
-            VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;
-            vmdkAbsFile = diskBackingInfo.getFileName();
-        }
-        return vmdkAbsFile;
-    }
-
     protected File getSystemVmKeyFile() {
         if (s_systemVmKeyFile == null) {
             syncFetchSystemVmKeyFile();
@@ -7143,255 +7138,6 @@
         return keyFile;
     }
 
-    private List<UnmanagedInstanceTO.Disk> getUnmanageInstanceDisks(VirtualMachineMO vmMo) {
-        List<UnmanagedInstanceTO.Disk> instanceDisks = new ArrayList<>();
-        VirtualDisk[] disks = null;
-        try {
-            disks = vmMo.getAllDiskDevice();
-        } catch (Exception e) {
-            s_logger.info("Unable to retrieve unmanaged instance disks. " + e.getMessage());
-        }
-        if (disks != null) {
-            for (VirtualDevice diskDevice : disks) {
-                try {
-                    if (diskDevice instanceof VirtualDisk) {
-                        UnmanagedInstanceTO.Disk instanceDisk = new UnmanagedInstanceTO.Disk();
-                        VirtualDisk disk = (VirtualDisk) diskDevice;
-                        instanceDisk.setDiskId(disk.getDiskObjectId());
-                        instanceDisk.setLabel(disk.getDeviceInfo() != null ? disk.getDeviceInfo().getLabel() : "");
-                        instanceDisk.setFileBaseName(vmMo.getVmdkFileBaseName(disk));
-                        instanceDisk.setImagePath(getAbsoluteVmdkFile(disk));
-                        instanceDisk.setCapacity(disk.getCapacityInBytes());
-                        instanceDisk.setPosition(diskDevice.getUnitNumber());
-                        DatastoreFile file = new DatastoreFile(getAbsoluteVmdkFile(disk));
-                        if (StringUtils.isNoneEmpty(file.getFileBaseName(), file.getDatastoreName())) {
-                            VirtualMachineDiskInfo diskInfo = vmMo.getDiskInfoBuilder().getDiskInfoByBackingFileBaseName(file.getFileBaseName(), file.getDatastoreName());
-                            instanceDisk.setChainInfo(getGson().toJson(diskInfo));
-                        }
-                        for (VirtualDevice device : vmMo.getAllDeviceList()) {
-                            if (diskDevice.getControllerKey() == device.getKey()) {
-                                if (device instanceof VirtualIDEController) {
-                                    instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
-                                    instanceDisk.setControllerUnit(((VirtualIDEController) device).getBusNumber());
-                                } else if (device instanceof VirtualSCSIController) {
-                                    instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
-                                    instanceDisk.setControllerUnit(((VirtualSCSIController) device).getBusNumber());
-                                } else {
-                                    instanceDisk.setController(DiskControllerType.none.toString());
-                                }
-                                break;
-                            }
-                        }
-                        if (disk.getBacking() instanceof VirtualDeviceFileBackingInfo) {
-                            VirtualDeviceFileBackingInfo diskBacking = (VirtualDeviceFileBackingInfo) disk.getBacking();
-                            ManagedObjectReference morDs = diskBacking.getDatastore();
-                            DatastoreInfo info = (DatastoreInfo)vmMo.getContext().getVimClient().getDynamicProperty(diskBacking.getDatastore(), "info");
-                            if (info instanceof NasDatastoreInfo) {
-                                NasDatastoreInfo dsInfo = (NasDatastoreInfo) info;
-                                instanceDisk.setDatastoreName(dsInfo.getName());
-                                if (dsInfo.getNas() != null) {
-                                    instanceDisk.setDatastoreHost(dsInfo.getNas().getRemoteHost());
-                                    instanceDisk.setDatastorePath(dsInfo.getNas().getRemotePath());
-                                    instanceDisk.setDatastoreType(dsInfo.getNas().getType());
-                                }
-                            } else {
-                                instanceDisk.setDatastoreName(info.getName());
-                            }
-                        }
-                        s_logger.info(vmMo.getName() + " " + disk.getDeviceInfo().getLabel() + " " + disk.getDeviceInfo().getSummary() + " " + disk.getDiskObjectId() + " " + disk.getCapacityInKB() + " " + instanceDisk.getController());
-                        instanceDisks.add(instanceDisk);
-                    }
-                } catch (Exception e) {
-                    s_logger.info("Unable to retrieve unmanaged instance disk info. " + e.getMessage());
-                }
-            }
-            Collections.sort(instanceDisks, new Comparator<UnmanagedInstanceTO.Disk>() {
-                @Override
-                public int compare(final UnmanagedInstanceTO.Disk disk1, final UnmanagedInstanceTO.Disk disk2) {
-                    return extractInt(disk1) - extractInt(disk2);
-                }
-
-                int extractInt(UnmanagedInstanceTO.Disk disk) {
-                    String num = disk.getLabel().replaceAll("\\D", "");
-                    // return 0 if no digits found
-                    return num.isEmpty() ? 0 : Integer.parseInt(num);
-                }
-            });
-        }
-        return instanceDisks;
-    }
-
-    private List<UnmanagedInstanceTO.Nic> getUnmanageInstanceNics(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
-        List<UnmanagedInstanceTO.Nic> instanceNics = new ArrayList<>();
-
-        HashMap<String, List<String>> guestNicMacIPAddressMap = new HashMap<>();
-        try {
-            GuestInfo guestInfo = vmMo.getGuestInfo();
-            if (guestInfo.getToolsStatus() == VirtualMachineToolsStatus.TOOLS_OK) {
-                for (GuestNicInfo nicInfo: guestInfo.getNet()) {
-                    if (CollectionUtils.isNotEmpty(nicInfo.getIpAddress())) {
-                        List<String> ipAddresses = new ArrayList<>();
-                        for (String ipAddress : nicInfo.getIpAddress()) {
-                            if (NetUtils.isValidIp4(ipAddress)) {
-                                ipAddresses.add(ipAddress);
-                            }
-                        }
-                        guestNicMacIPAddressMap.put(nicInfo.getMacAddress(), ipAddresses);
-                    }
-                }
-            } else {
-                s_logger.info(String.format("Unable to retrieve guest nics for instance: %s from VMware tools as tools status: %s", vmMo.getName(), guestInfo.getToolsStatus().toString()));
-            }
-        } catch (Exception e) {
-            s_logger.info("Unable to retrieve guest nics for instance from VMware tools. " + e.getMessage());
-        }
-        VirtualDevice[] nics = null;
-        try {
-            nics = vmMo.getNicDevices();
-        } catch (Exception e) {
-            s_logger.info("Unable to retrieve unmanaged instance nics. " + e.getMessage());
-        }
-        if (nics != null) {
-            for (VirtualDevice nic : nics) {
-                try {
-                    VirtualEthernetCard ethCardDevice = (VirtualEthernetCard) nic;
-                    s_logger.error(nic.getClass().getCanonicalName() + " " + nic.getBacking().getClass().getCanonicalName() + " " + ethCardDevice.getMacAddress());
-                    UnmanagedInstanceTO.Nic instanceNic = new UnmanagedInstanceTO.Nic();
-                    instanceNic.setNicId(ethCardDevice.getDeviceInfo().getLabel());
-                    if (ethCardDevice instanceof VirtualPCNet32) {
-                        instanceNic.setAdapterType(VirtualEthernetCardType.PCNet32.toString());
-                    } else if (ethCardDevice instanceof VirtualVmxnet2) {
-                        instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet2.toString());
-                    } else if (ethCardDevice instanceof VirtualVmxnet3) {
-                        instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet3.toString());
-                    } else {
-                        instanceNic.setAdapterType(VirtualEthernetCardType.E1000.toString());
-                    }
-                    instanceNic.setMacAddress(ethCardDevice.getMacAddress());
-                    if (guestNicMacIPAddressMap.containsKey(instanceNic.getMacAddress())) {
-                        instanceNic.setIpAddress(guestNicMacIPAddressMap.get(instanceNic.getMacAddress()));
-                    }
-                    if (ethCardDevice.getSlotInfo() != null) {
-                        instanceNic.setPciSlot(ethCardDevice.getSlotInfo().toString());
-                    }
-                    VirtualDeviceBackingInfo backing = ethCardDevice.getBacking();
-                    if (backing instanceof VirtualEthernetCardDistributedVirtualPortBackingInfo) {
-                        VirtualEthernetCardDistributedVirtualPortBackingInfo backingInfo = (VirtualEthernetCardDistributedVirtualPortBackingInfo) backing;
-                        DistributedVirtualSwitchPortConnection port = backingInfo.getPort();
-                        String portKey = port.getPortKey();
-                        String portGroupKey = port.getPortgroupKey();
-                        String dvSwitchUuid = port.getSwitchUuid();
-
-                        s_logger.debug("NIC " + nic.toString() + " is connected to dvSwitch " + dvSwitchUuid + " pg " + portGroupKey + " port " + portKey);
-
-                        ManagedObjectReference dvSwitchManager = vmMo.getContext().getVimClient().getServiceContent().getDvSwitchManager();
-                        ManagedObjectReference dvSwitch = vmMo.getContext().getVimClient().getService().queryDvsByUuid(dvSwitchManager, dvSwitchUuid);
-
-                        // Get all ports
-                        DistributedVirtualSwitchPortCriteria criteria = new DistributedVirtualSwitchPortCriteria();
-                        criteria.setInside(true);
-                        criteria.getPortgroupKey().add(portGroupKey);
-                        List<DistributedVirtualPort> dvPorts = vmMo.getContext().getVimClient().getService().fetchDVPorts(dvSwitch, criteria);
-
-                        for (DistributedVirtualPort dvPort : dvPorts) {
-                            // Find the port for this NIC by portkey
-                            if (portKey.equals(dvPort.getKey())) {
-                                VMwareDVSPortSetting settings = (VMwareDVSPortSetting) dvPort.getConfig().getSetting();
-                                if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchVlanIdSpec) {
-                                    VmwareDistributedVirtualSwitchVlanIdSpec vlanId = (VmwareDistributedVirtualSwitchVlanIdSpec) settings.getVlan();
-                                    s_logger.trace("Found port " + dvPort.getKey() + " with vlan " + vlanId.getVlanId());
-                                    if (vlanId.getVlanId() > 0 && vlanId.getVlanId() < 4095) {
-                                        instanceNic.setVlan(vlanId.getVlanId());
-                                    }
-                                } else if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchPvlanSpec) {
-                                    VmwareDistributedVirtualSwitchPvlanSpec pvlanSpec = (VmwareDistributedVirtualSwitchPvlanSpec) settings.getVlan();
-                                    s_logger.trace("Found port " + dvPort.getKey() + " with pvlan " + pvlanSpec.getPvlanId());
-                                    if (pvlanSpec.getPvlanId() > 0 && pvlanSpec.getPvlanId() < 4095) {
-                                        DistributedVirtualSwitchMO dvSwitchMo = new DistributedVirtualSwitchMO(vmMo.getContext(), dvSwitch);
-                                        Pair<Integer, HypervisorHostHelper.PvlanType> vlanDetails = dvSwitchMo.retrieveVlanFromPvlan(pvlanSpec.getPvlanId(), dvSwitch);
-                                        if (vlanDetails != null && vlanDetails.first() != null && vlanDetails.second() != null) {
-                                            instanceNic.setVlan(vlanDetails.first());
-                                            instanceNic.setPvlan(pvlanSpec.getPvlanId());
-                                            instanceNic.setPvlanType(vlanDetails.second().toString());
-                                        }
-                                    }
-                                }
-                                break;
-                            }
-                        }
-                    } else if (backing instanceof VirtualEthernetCardNetworkBackingInfo) {
-                        VirtualEthernetCardNetworkBackingInfo backingInfo = (VirtualEthernetCardNetworkBackingInfo) backing;
-                        instanceNic.setNetwork(backingInfo.getDeviceName());
-                        if (hyperHost instanceof HostMO) {
-                            HostMO hostMo = (HostMO) hyperHost;
-                            HostPortGroupSpec portGroupSpec = hostMo.getHostPortGroupSpec(backingInfo.getDeviceName());
-                            instanceNic.setVlan(portGroupSpec.getVlanId());
-                        }
-                    }
-                    instanceNics.add(instanceNic);
-                } catch (Exception e) {
-                    s_logger.info("Unable to retrieve unmanaged instance nic info. " + e.getMessage());
-                }
-            }
-            Collections.sort(instanceNics, new Comparator<UnmanagedInstanceTO.Nic>() {
-                @Override
-                public int compare(final UnmanagedInstanceTO.Nic nic1, final UnmanagedInstanceTO.Nic nic2) {
-                    return extractInt(nic1) - extractInt(nic2);
-                }
-
-                int extractInt(UnmanagedInstanceTO.Nic nic) {
-                    String num = nic.getNicId().replaceAll("\\D", "");
-                    // return 0 if no digits found
-                    return num.isEmpty() ? 0 : Integer.parseInt(num);
-                }
-            });
-        }
-        return  instanceNics;
-    }
-
-    private UnmanagedInstanceTO getUnmanagedInstance(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
-        UnmanagedInstanceTO instance = null;
-        try {
-            instance = new UnmanagedInstanceTO();
-            instance.setName(vmMo.getVmName());
-            instance.setInternalCSName(vmMo.getInternalCSName());
-            instance.setCpuCores(vmMo.getConfigSummary().getNumCpu());
-            instance.setCpuCoresPerSocket(vmMo.getCoresPerSocket());
-            instance.setCpuSpeed(vmMo.getConfigSummary().getCpuReservation());
-            instance.setMemory(vmMo.getConfigSummary().getMemorySizeMB());
-            instance.setOperatingSystemId(vmMo.getVmGuestInfo().getGuestId());
-            if (StringUtils.isEmpty(instance.getOperatingSystemId())) {
-                instance.setOperatingSystemId(vmMo.getConfigSummary().getGuestId());
-            }
-            VirtualMachineGuestOsIdentifier osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST;
-            try {
-                osIdentifier = VirtualMachineGuestOsIdentifier.fromValue(instance.getOperatingSystemId());
-            } catch (IllegalArgumentException iae) {
-                if (StringUtils.isNotEmpty(instance.getOperatingSystemId()) && instance.getOperatingSystemId().contains("64")) {
-                    osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST_64;
-                }
-            }
-            instance.setOperatingSystem(vmMo.getGuestInfo().getGuestFullName());
-            if (StringUtils.isEmpty(instance.getOperatingSystem())) {
-                instance.setOperatingSystem(vmMo.getConfigSummary().getGuestFullName());
-            }
-            UnmanagedInstanceTO.PowerState powerState = UnmanagedInstanceTO.PowerState.PowerUnknown;
-            if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_ON")) {
-                powerState = UnmanagedInstanceTO.PowerState.PowerOn;
-            }
-            if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_OFF")) {
-                powerState = UnmanagedInstanceTO.PowerState.PowerOff;
-            }
-            instance.setPowerState(powerState);
-            instance.setDisks(getUnmanageInstanceDisks(vmMo));
-            instance.setNics(getUnmanageInstanceNics(hyperHost, vmMo));
-        } catch (Exception e) {
-            s_logger.info("Unable to retrieve unmanaged instance info. " + e.getMessage());
-        }
-
-        return  instance;
-    }
-
     private Answer execute(GetUnmanagedInstancesCommand cmd) {
         VmwareContext context = getServiceContext();
         HashMap<String, UnmanagedInstanceTO> unmanagedInstances = new HashMap<>();
@@ -7420,8 +7166,10 @@
                         !cmd.getInstanceName().equals(vmMo.getVmName())) {
                     continue;
                 }
-                UnmanagedInstanceTO instance = getUnmanagedInstance(hyperHost, vmMo);
+                UnmanagedInstanceTO instance = VmwareHelper.getUnmanagedInstance(hyperHost, vmMo);
                 if (instance != null) {
+                    VirtualMachineConfigSummary configSummary = vmMo.getConfigSummary();
+                    instance.setCpuSpeed(configSummary != null ? configSummary.getCpuReservation() : 0);
                     unmanagedInstances.put(instance.getName(), instance);
                 }
             }
@@ -7549,7 +7297,7 @@
                     VirtualMachineRelocateSpecDiskLocator diskLocator = new VirtualMachineRelocateSpecDiskLocator();
                     diskLocator.setDatastore(morVolumeDatastore);
                     Pair<VirtualDisk, String> diskInfo = getVirtualDiskInfo(vmMo, volume.getPath() + VMDK_EXTENSION);
-                    String vmdkAbsFile = getAbsoluteVmdkFile(diskInfo.first());
+                    String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(diskInfo.first());
                     if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
                         vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
                     }
@@ -7764,6 +7512,129 @@
         }
     }
 
+    protected CheckGuestOsMappingAnswer execute(CheckGuestOsMappingCommand cmd) {
+        String guestOsName = cmd.getGuestOsName();
+        String guestOsMappingName = cmd.getGuestOsHypervisorMappingName();
+        s_logger.info("Checking guest os mapping name: " + guestOsMappingName + " for the guest os: " + guestOsName + " in the hypervisor");
+        try {
+            VmwareContext context = getServiceContext();
+            VmwareHypervisorHost hyperHost = getHyperHost(context);
+            GuestOsDescriptor guestOsDescriptor = hyperHost.getGuestOsDescriptor(guestOsMappingName);
+            if (guestOsDescriptor == null) {
+                return new CheckGuestOsMappingAnswer(cmd, "Guest os mapping name: " + guestOsMappingName + " not found in the hypervisor");
+            }
+            s_logger.debug("Matching hypervisor guest os - id: " + guestOsDescriptor.getId() + ", full name: " + guestOsDescriptor.getFullName() + ", family: " + guestOsDescriptor.getFamily());
+            if (guestOsDescriptor.getFullName().equalsIgnoreCase(guestOsName)) {
+                s_logger.debug("Hypervisor guest os name in the descriptor matches with os name: " + guestOsName);
+            }
+            s_logger.info("Hypervisor guest os name in the descriptor matches with os mapping: " + guestOsMappingName + " from user");
+            return new CheckGuestOsMappingAnswer(cmd);
+        } catch (Exception e) {
+            s_logger.error("Failed to check the hypervisor guest os mapping name: " + guestOsMappingName, e);
+            return new CheckGuestOsMappingAnswer(cmd, e.getLocalizedMessage());
+        }
+    }
+
+    protected ListDataStoreObjectsAnswer execute(ListDataStoreObjectsCommand cmd) {
+        String path = cmd.getPath();
+        int startIndex = cmd.getStartIndex();
+        int pageSize = cmd.getPageSize();
+        PrimaryDataStoreTO dataStore = (PrimaryDataStoreTO) cmd.getStore();
+
+        if (path.startsWith("/")) {
+            path = path.substring(1);
+        }
+
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
+
+        VmwareContext context = getServiceContext();
+        VmwareHypervisorHost hyperHost = getHyperHost(context);
+        ManagedObjectReference morDatastore = null;
+
+        int count = 0;
+        List<String> names = new ArrayList<>();
+        List<String> paths = new ArrayList<>();
+        List<String> absPaths = new ArrayList<>();
+        List<Boolean> isDirs = new ArrayList<>();
+        List<Long> sizes = new ArrayList<>();
+        List<Long> modifiedList = new ArrayList<>();
+
+        try {
+            morDatastore = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, dataStore.getUuid());
+
+            DatastoreMO dsMo = new DatastoreMO(context, morDatastore);
+            HostDatastoreBrowserMO browserMo = dsMo.getHostDatastoreBrowserMO();
+            FileQueryFlags fqf = new FileQueryFlags();
+            fqf.setFileSize(true);
+            fqf.setFileType(true);
+            fqf.setModification(true);
+            fqf.setFileOwner(false);
+
+            HostDatastoreBrowserSearchSpec spec = new HostDatastoreBrowserSearchSpec();
+            spec.setSearchCaseInsensitive(true);
+            spec.setDetails(fqf);
+
+            String dsPath = String.format("[%s] %s", dsMo.getName(), path);
+
+            HostDatastoreBrowserSearchResults results = browserMo.searchDatastore(dsPath, spec);
+            List<FileInfo> fileInfoList = results.getFile();
+            count = fileInfoList.size();
+            for (int i = startIndex; i < startIndex + pageSize && i < count; i++) {
+                FileInfo file = fileInfoList.get(i);
+
+                names.add(file.getPath());
+                paths.add(path + "/" + file.getPath());
+                absPaths.add(dsPath + "/" + file.getPath());
+                isDirs.add(file instanceof FolderFileInfo);
+                sizes.add(file.getFileSize());
+                modifiedList.add(file.getModification().toGregorianCalendar().getTimeInMillis());
+            }
+
+            return new ListDataStoreObjectsAnswer(true, count, names, paths, absPaths, isDirs, sizes, modifiedList);
+        } catch (Exception e) {
+            if (e.getMessage().contains("was not found")) {
+                return new ListDataStoreObjectsAnswer(false, count, names, paths, absPaths, isDirs, sizes, modifiedList);
+            }
+            String errorMsg = String.format("Failed to list files at path [%s] due to: [%s].", path, e.getMessage());
+            s_logger.error(errorMsg, e);
+        }
+
+        return null;
+    }
+
+    protected GetHypervisorGuestOsNamesAnswer execute(GetHypervisorGuestOsNamesCommand cmd) {
+        String keyword = cmd.getKeyword();
+        s_logger.info("Getting guest os names in the hypervisor");
+        try {
+            VmwareContext context = getServiceContext();
+            VmwareHypervisorHost hyperHost = getHyperHost(context);
+            List<GuestOsDescriptor> guestOsDescriptors = hyperHost.getGuestOsDescriptors();
+            if (guestOsDescriptors == null) {
+                return new GetHypervisorGuestOsNamesAnswer(cmd, "Guest os names not found in the hypervisor");
+            }
+            List<Pair<String, String>> hypervisorGuestOsNames = new ArrayList<>();
+            for (GuestOsDescriptor guestOsDescriptor : guestOsDescriptors) {
+                String osDescriptorFullName = guestOsDescriptor.getFullName();
+                String osDescriptorId = guestOsDescriptor.getId();
+                if (StringUtils.isNotBlank(keyword)) {
+                    if (osDescriptorFullName.toLowerCase().contains(keyword.toLowerCase()) || osDescriptorId.toLowerCase().contains(keyword.toLowerCase())) {
+                        Pair<String, String> hypervisorGuestOs = new Pair<>(osDescriptorFullName, osDescriptorId);
+                        hypervisorGuestOsNames.add(hypervisorGuestOs);
+                    }
+                } else {
+                    Pair<String, String> hypervisorGuestOs = new Pair<>(osDescriptorFullName, osDescriptorId);
+                    hypervisorGuestOsNames.add(hypervisorGuestOs);
+                }
+            }
+            return new GetHypervisorGuestOsNamesAnswer(cmd, hypervisorGuestOsNames);
+        } catch (Exception e) {
+            s_logger.error("Failed to get the hypervisor guest names due to: " + e.getLocalizedMessage(), e);
+            return new GetHypervisorGuestOsNamesAnswer(cmd, e.getLocalizedMessage());
+        }
+    }
+
     private Answer execute(PrepareForBackupRestorationCommand command) {
         try {
             VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext());
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
index 7f4f517..57522a6 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java
@@ -2190,7 +2190,7 @@
                         if (diskInfo == null) {
                             diskInfo = diskInfoBuilder.getDiskInfoByDeviceBusName(infoInChain.getDiskDeviceBusName());
                             if (diskInfo != null) {
-                                s_logger.info("Found existing disk from from chain device bus information: " + infoInChain.getDiskDeviceBusName());
+                                s_logger.info("Found existing disk from chain device bus information: " + infoInChain.getDiskDeviceBusName());
                                 return diskInfo;
                             }
                         }
@@ -2252,7 +2252,7 @@
                         "Please re-try when virtual disk is attached to a VM using a SCSI controller.");
             }
 
-            String vmdkAbsFile = resource.getAbsoluteVmdkFile(vDisk);
+            String vmdkAbsFile = VmwareHelper.getAbsoluteVmdkFile(vDisk);
 
             if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
                 vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageSubsystemCommandHandler.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageSubsystemCommandHandler.java
index 15caa1d..e56f41e 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageSubsystemCommandHandler.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageSubsystemCommandHandler.java
@@ -19,17 +19,25 @@
 package com.cloud.storage.resource;
 
 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.EnumMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-import com.cloud.hypervisor.vmware.manager.VmwareManager;
-import com.cloud.utils.NumbersUtil;
-import org.apache.log4j.Logger;
 import org.apache.cloudstack.storage.command.CopyCmdAnswer;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.DeleteCommand;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
 import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.to.DataObjectType;
@@ -38,9 +46,11 @@
 import com.cloud.agent.api.to.NfsTO;
 import com.cloud.agent.api.to.S3TO;
 import com.cloud.agent.api.to.SwiftTO;
+import com.cloud.hypervisor.vmware.manager.VmwareManager;
 import com.cloud.hypervisor.vmware.manager.VmwareStorageManager;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.resource.VmwareStorageProcessor.VmwareStorageProcessorConfigurableFields;
+import com.cloud.utils.NumbersUtil;
 
 public class VmwareStorageSubsystemCommandHandler extends StorageSubsystemCommandHandlerBase {
 
@@ -202,4 +212,32 @@
         }
     }
 
+    @Override
+    protected Answer execute(QuerySnapshotZoneCopyCommand cmd) {
+        SnapshotObjectTO snapshot = cmd.getSnapshot();
+        String parentPath = storageResource.getRootDir(snapshot.getDataStore().getUrl(), _nfsVersion);
+        String path = snapshot.getPath();
+        File snapFile = new File(parentPath + File.separator + path);
+        if (snapFile.exists() && !snapFile.isDirectory()) {
+            return new QuerySnapshotZoneCopyAnswer(cmd, List.of(path));
+        }
+        int index = path.lastIndexOf(File.separator);
+        String snapDir = path.substring(0, index);
+        List<String> files = new ArrayList<>();
+        try (Stream<Path> stream = Files.list(Paths.get(parentPath + File.separator + snapDir))) {
+            List<String> fileNames = stream
+                    .filter(file -> !Files.isDirectory(file))
+                    .map(Path::getFileName)
+                    .map(Path::toString)
+                    .collect(Collectors.toList());
+            for (String file : fileNames) {
+                file = snapDir + "/" + file;
+                s_logger.debug(String.format("Found snapshot file %s", file));
+                files.add(file);
+            }
+        } catch (IOException ioe) {
+            s_logger.error("Error preparing file list for snapshot copy", ioe);
+        }
+        return new QuerySnapshotZoneCopyAnswer(cmd, files);
+    }
 }
diff --git a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/AddVmwareDcCmd.java b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/AddVmwareDcCmd.java
index 6100713..9f4985a 100644
--- a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/AddVmwareDcCmd.java
+++ b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/AddVmwareDcCmd.java
@@ -33,7 +33,7 @@
 import com.cloud.exception.DiscoveryException;
 import com.cloud.exception.ResourceInUseException;
 import com.cloud.hypervisor.vmware.VmwareDatacenterService;
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.user.Account;
 import com.cloud.utils.exception.CloudRuntimeException;
 
diff --git a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.java b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.java
new file mode 100644
index 0000000..4dd1b4b
--- /dev/null
+++ b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.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.command.admin.zone;
+
+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.hypervisor.vmware.VmwareDatacenterService;
+import com.cloud.user.Account;
+import com.cloud.utils.exception.CloudRuntimeException;
+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.BaseResponse;
+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.UnmanagedInstanceResponse;
+import org.apache.cloudstack.api.response.VmwareDatacenterResponse;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+@APICommand(name = "listVmwareDcVms", responseObject = UnmanagedInstanceResponse.class,
+        description = "Lists the VMs in a VMware Datacenter",
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class ListVmwareDcVmsCmd extends BaseListCmd {
+
+    @Inject
+    public VmwareDatacenterService _vmwareDatacenterService;
+
+    @Parameter(name = ApiConstants.EXISTING_VCENTER_ID,
+            type = CommandType.UUID,
+            entityType = VmwareDatacenterResponse.class,
+            description = "UUID of a linked existing vCenter")
+    private Long existingVcenterId;
+
+    @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.DATACENTER_NAME, type = CommandType.STRING, description = "Name of VMware datacenter.")
+    private String datacenterName;
+
+    @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "The Username required to connect to resource.")
+    private String username;
+
+    @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, description = "The password for specified username.")
+    private String password;
+
+    public String getVcenter() {
+        return vcenter;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getDatacenterName() {
+        return datacenterName;
+    }
+
+    public Long getExistingVcenterId() {
+        return existingVcenterId;
+    }
+
+    @Override
+    public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+        checkParameters();
+        try {
+            List<UnmanagedInstanceTO> vms = _vmwareDatacenterService.listVMsInDatacenter(this);
+            List<BaseResponse> baseResponseList = new ArrayList<>();
+            if (CollectionUtils.isNotEmpty(vms)) {
+                for (UnmanagedInstanceTO vmwareVm : vms) {
+                    UnmanagedInstanceResponse resp = _responseGenerator.createUnmanagedInstanceResponse(vmwareVm, null, null);
+                    baseResponseList.add(resp);
+                }
+            }
+            List<BaseResponse> pagingList = com.cloud.utils.StringUtils.applyPagination(baseResponseList, this.getStartIndex(), this.getPageSizeVal());
+            if (CollectionUtils.isEmpty(pagingList)) {
+                pagingList = baseResponseList;
+            }
+            ListResponse<BaseResponse> response = new ListResponse<>();
+            response.setResponses(pagingList, baseResponseList.size());
+            response.setResponseName(getCommandName());
+            setResponseObject(response);
+        } catch (CloudRuntimeException e) {
+            String errorMsg = String.format("Error retrieving VMs from VMware VC: %s", e.getMessage());
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg);
+        }
+    }
+
+    private void checkParameters() {
+        if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    "Please provide an existing vCenter ID or a vCenter IP/Name, parameters are mutually exclusive");
+        }
+        if (existingVcenterId == null && StringUtils.isAnyBlank(vcenter, datacenterName, username, password)) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    "Please set all the information for a vCenter IP/Name, datacenter, username and password");
+        }
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    @Override
+    public String getCommandName() {
+        return "listvmwaredcvmsresponse";
+    }
+}
diff --git a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcsCmd.java b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcsCmd.java
index a91605c..61b5210 100644
--- a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcsCmd.java
+++ b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcsCmd.java
@@ -39,7 +39,7 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.hypervisor.vmware.VmwareDatacenter;
+import com.cloud.dc.VmwareDatacenter;
 import com.cloud.hypervisor.vmware.VmwareDatacenterService;
 import com.cloud.user.Account;
 
diff --git a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateVmwareDcCmd.java b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateVmwareDcCmd.java
index bb62a48..2b6cf59 100644
--- a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateVmwareDcCmd.java
+++ b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateVmwareDcCmd.java
@@ -30,7 +30,7 @@
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.log4j.Logger;
 
-import com.cloud.hypervisor.vmware.VmwareDatacenter;
+import com.cloud.dc.VmwareDatacenter;
 import com.cloud.hypervisor.vmware.VmwareDatacenterService;
 import com.cloud.user.Account;
 
diff --git a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/response/VmwareDatacenterResponse.java b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/response/VmwareDatacenterResponse.java
deleted file mode 100644
index ddd4a36..0000000
--- a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/response/VmwareDatacenterResponse.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.apache.cloudstack.api.response;
-
-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 com.cloud.hypervisor.vmware.VmwareDatacenter;
-import com.cloud.serializer.Param;
-
-@EntityReference(value = VmwareDatacenter.class)
-public class VmwareDatacenterResponse extends BaseResponse {
-    @SerializedName(ApiConstants.ID)
-    @Param(description = "The VMware Datacenter ID")
-    private String id;
-
-    @SerializedName(ApiConstants.ZONE_ID)
-    @Param(description = "the Zone ID associated with this VMware Datacenter")
-    private Long zoneId;
-
-    @SerializedName(ApiConstants.NAME)
-    @Param(description = "The VMware Datacenter name")
-    private String name;
-
-    @SerializedName(ApiConstants.VCENTER)
-    @Param(description = "The VMware vCenter name/ip")
-    private String vCenter;
-
-    public String getName() {
-        return name;
-    }
-
-    public String getVcenter() {
-        return vCenter;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public Long getZoneId() {
-        return zoneId;
-    }
-
-    public void setZoneId(Long zoneId) {
-        this.zoneId = zoneId;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public void setVcenter(String vCenter) {
-        this.vCenter = vCenter;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-}
diff --git a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/core/spring-vmware-core-context.xml b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/core/spring-vmware-core-context.xml
index a2d8314..d955ede 100644
--- a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/core/spring-vmware-core-context.xml
+++ b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/core/spring-vmware-core-context.xml
@@ -31,12 +31,10 @@
         class="com.cloud.hypervisor.vmware.manager.VmwareManagerImpl" />
     <bean id="vmwareContextFactory"
         class="com.cloud.hypervisor.vmware.resource.VmwareContextFactory" />
-    <bean id="VmwareDatacenterDaoImpl"
-        class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterDaoImpl" />
     <bean id="VmwareDatacenterZoneMapDaoImpl"
         class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDaoImpl" />
     <bean id="LegacyZoneDaoImpl" class="com.cloud.hypervisor.vmware.dao.LegacyZoneDaoImpl" />
 
     <bean id="ciscoNexusVSMDeviceDaoImpl" class="com.cloud.network.dao.CiscoNexusVSMDeviceDaoImpl" />
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-compute/module.properties b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-compute/module.properties
index b605835..bb8972b 100644
--- a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-compute/module.properties
+++ b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vmware-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-discoverer/module.properties b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-discoverer/module.properties
index 0d726f8..cc9e0a5 100644
--- a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-discoverer/module.properties
+++ b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vmware-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/module.properties b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/module.properties
index 91ea24c..0a4f387 100644
--- a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/module.properties
+++ b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vmware-network
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/spring-vmware-network-context.xml b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/spring-vmware-network-context.xml
index cdfe009..e8eec03 100644
--- a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/spring-vmware-network-context.xml
+++ b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-network/spring-vmware-network-context.xml
@@ -31,4 +31,4 @@
         <property name="name" value="CiscoNexus1000vVSM" />
     </bean>
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-storage/module.properties b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-storage/module.properties
index 9c3bab6..9ac4183 100644
--- a/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-storage/module.properties
+++ b/plugins/hypervisors/vmware/src/main/resources/META-INF/cloudstack/vmware-storage/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vmware-storage
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java
index 7398ef9..06bf539 100644
--- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java
+++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java
@@ -25,6 +25,7 @@
 
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,8 +35,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
@@ -54,8 +54,7 @@
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineManager;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({VMwareGuru.class})
+@RunWith(MockitoJUnitRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
 public class VMwareGuruTest {
 
@@ -78,9 +77,16 @@
     @Mock
     ClusterDetailsDao _clusterDetailsDao;
 
+    AutoCloseable closeable;
+
     @Before
     public void testSetUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -99,7 +105,6 @@
         HostVO hostVO = Mockito.mock(HostVO.class);
 
         Mockito.when(localStorage.getId()).thenReturn(1L);
-        Mockito.when(vm.getId()).thenReturn(1L);
         Mockito.when(_storagePoolDao.findById(1L)).thenReturn(storagePoolVO);
         Mockito.when(rootVolume.getVolumeType()).thenReturn(Volume.Type.ROOT);
         Mockito.when(dataVolume.getVolumeType()).thenReturn(Volume.Type.DATADISK);
diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VmwareVmImplementerTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VmwareVmImplementerTest.java
index f164780..c958fa7 100755
--- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VmwareVmImplementerTest.java
+++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VmwareVmImplementerTest.java
@@ -23,6 +23,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -32,7 +33,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.storage.GuestOSHypervisorVO;
@@ -40,7 +43,8 @@
 import com.cloud.storage.dao.GuestOSHypervisorDao;
 import com.cloud.vm.VmDetailConstants;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
+@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
 public class VmwareVmImplementerTest {
 
     @Spy
@@ -55,9 +59,16 @@
 
     private Map<String,String> vmDetails = new HashMap<String, String>();
 
+    AutoCloseable closeable;
+
     @Before
     public void testSetUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     private void setConfigValues(Boolean globalNV, Boolean globalNVPVM, String localNV){
diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/VmwareDatacenterApiUnitTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/VmwareDatacenterApiUnitTest.java
index f0a06d0..940bfca 100644
--- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/VmwareDatacenterApiUnitTest.java
+++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/VmwareDatacenterApiUnitTest.java
@@ -27,6 +27,7 @@
 import com.cloud.dc.DataCenter.NetworkType;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.HostPodVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.ClusterVSMMapDao;
 import com.cloud.dc.dao.DataCenterDao;
@@ -42,7 +43,7 @@
 import com.cloud.hypervisor.HypervisorGuruManager;
 import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
 import com.cloud.hypervisor.vmware.dao.LegacyZoneDao;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 import com.cloud.hypervisor.vmware.manager.VmwareManagerImpl;
 import com.cloud.network.NetworkModel;
@@ -80,12 +81,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.ComponentScan.Filter;
@@ -104,11 +102,11 @@
 import java.util.List;
 import java.util.UUID;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
-@PrepareForTest({ComponentContext.class, ApplicationContext.class})
 public class VmwareDatacenterApiUnitTest {
 
     @Inject
@@ -166,11 +164,13 @@
     @Mock
     private static RemoveVmwareDcCmd removeCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void testSetUp() {
         Mockito.when(_configDao.isPremium()).thenReturn(true);
         ComponentContext.initComponentsLifeCycle();
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
 
         DataCenterVO zone =
             new DataCenterVO(UUID.randomUUID().toString(), "test", "8.8.8.8", null, "10.0.0.1", null, "10.0.0.1/24", null, null, NetworkType.Basic, null, null, true,
@@ -208,21 +208,21 @@
 
         dcZoneMap = new VmwareDatacenterZoneMapVO(zoneId, vmwareDcId);
 
-        Mockito.when(_dcDao.persist(Matchers.any(DataCenterVO.class))).thenReturn(zone);
+        Mockito.when(_dcDao.persist(any(DataCenterVO.class))).thenReturn(zone);
         Mockito.when(_dcDao.findById(1L)).thenReturn(zone);
-        Mockito.when(_podDao.persist(Matchers.any(HostPodVO.class))).thenReturn(pod);
+        Mockito.when(_podDao.persist(any(HostPodVO.class))).thenReturn(pod);
         Mockito.when(_podDao.findById(1L)).thenReturn(pod);
-        Mockito.when(_clusterDao.persist(Matchers.any(ClusterVO.class))).thenReturn(cluster);
+        Mockito.when(_clusterDao.persist(any(ClusterVO.class))).thenReturn(cluster);
         Mockito.when(_clusterDao.findById(1L)).thenReturn(cluster);
         Mockito.when(_clusterDao.listByZoneId(1L)).thenReturn(null);
         Mockito.when(_clusterDao.expunge(1L)).thenReturn(true);
-        Mockito.when(_clusterDetailsDao.persist(Matchers.any(ClusterDetailsVO.class))).thenReturn(clusterDetails);
+        Mockito.when(_clusterDetailsDao.persist(any(ClusterDetailsVO.class))).thenReturn(clusterDetails);
         Mockito.when(_clusterDetailsDao.expunge(1L)).thenReturn(true);
-        Mockito.when(_vmwareDcDao.persist(Matchers.any(VmwareDatacenterVO.class))).thenReturn(dc);
+        Mockito.when(_vmwareDcDao.persist(any(VmwareDatacenterVO.class))).thenReturn(dc);
         Mockito.when(_vmwareDcDao.findById(1L)).thenReturn(null);
         Mockito.when(_vmwareDcDao.expunge(1L)).thenReturn(true);
         Mockito.when(_vmwareDcDao.getVmwareDatacenterByNameAndVcenter(vmwareDcName, vCenterHost)).thenReturn(null);
-        Mockito.when(_vmwareDcZoneMapDao.persist(Matchers.any(VmwareDatacenterZoneMapVO.class))).thenReturn(dcZoneMap);
+        Mockito.when(_vmwareDcZoneMapDao.persist(any(VmwareDatacenterZoneMapVO.class))).thenReturn(dcZoneMap);
         Mockito.when(_vmwareDcZoneMapDao.findByZoneId(1L)).thenReturn(null);
         Mockito.when(_vmwareDcZoneMapDao.expunge(1L)).thenReturn(true);
         Mockito.when(addCmd.getZoneId()).thenReturn(1L);
@@ -234,8 +234,9 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         CallContext.unregister();
+        closeable.close();
     }
 
     //@Test(expected = InvalidParameterValueException.class)
diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImplTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImplTest.java
index 714036d..68e2ae4 100644
--- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImplTest.java
+++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImplTest.java
@@ -29,7 +29,7 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.dc.ClusterDetailsDao;
 import com.cloud.dc.ClusterVO;
@@ -38,10 +38,10 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.host.dao.HostDetailsDao;
 import com.cloud.hypervisor.Hypervisor;
-import com.cloud.hypervisor.vmware.VmwareDatacenter;
-import com.cloud.hypervisor.vmware.VmwareDatacenterVO;
+import com.cloud.dc.VmwareDatacenter;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMapVO;
-import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -112,8 +112,8 @@
         Assert.assertEquals(vmwareDatacenter.getUser(), updateVmwareDcCmd.getUsername());
         Assert.assertEquals(vmwareDatacenter.getPassword(), updateVmwareDcCmd.getPassword());
         Mockito.verify(clusterDetails, Mockito.times(2)).put(Mockito.anyString(), Mockito.anyString());
-        Mockito.verify(clusterDetailsDao, Mockito.times(1)).persist(Mockito.anyLong(), Mockito.anyMapOf(String.class, String.class));
+        Mockito.verify(clusterDetailsDao, Mockito.times(1)).persist(Mockito.anyLong(), Mockito.anyMap());
         Mockito.verify(hostDetails, Mockito.times(3)).put(Mockito.anyString(), Mockito.anyString());
-        Mockito.verify(hostDetailsDao, Mockito.times(1)).persist(Mockito.anyLong(), Mockito.anyMapOf(String.class, String.class));
+        Mockito.verify(hostDetailsDao, Mockito.times(1)).persist(Mockito.anyLong(), Mockito.anyMap());
     }
 }
diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java
index e974c23..58d8e5e 100644
--- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java
+++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java
@@ -18,41 +18,60 @@
 
 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.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
 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 static org.powermock.api.mockito.PowerMockito.whenNew;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
 import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.hypervisor.vmware.mo.DatastoreMO;
+import com.cloud.hypervisor.vmware.mo.HostDatastoreBrowserMO;
+import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper;
+import com.cloud.hypervisor.vmware.util.VmwareHelper;
+import com.vmware.vim25.FileInfo;
+import com.vmware.vim25.HostDatastoreBrowserSearchResults;
+import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
 import org.apache.cloudstack.storage.command.CopyCommand;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 import org.mockito.InjectMocks;
-import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.Command;
+import com.cloud.agent.api.CheckGuestOsMappingAnswer;
+import com.cloud.agent.api.CheckGuestOsMappingCommand;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesAnswer;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesCommand;
 import com.cloud.agent.api.ScaleVmAnswer;
 import com.cloud.agent.api.ScaleVmCommand;
 import com.cloud.agent.api.routing.GetAutoScaleMetricsAnswer;
@@ -79,6 +98,7 @@
 import com.cloud.utils.ExecutionResult;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VmDetailConstants;
+import com.vmware.vim25.GuestOsDescriptor;
 import com.vmware.vim25.HostCapability;
 import com.vmware.vim25.ManagedObjectReference;
 import com.vmware.vim25.VimPortType;
@@ -87,8 +107,7 @@
 import com.vmware.vim25.VirtualMachineConfigSpec;
 import com.vmware.vim25.VirtualMachineVideoCard;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({CopyCommand.class})
+@RunWith(MockitoJUnitRunner.class)
 public class VmwareResourceTest {
 
     private static final String VOLUME_PATH = "XXXXXXXXXXXX";
@@ -182,10 +201,12 @@
 
     private Map<String,String> specsArray = new HashMap<String,String>();
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        storageCmd = PowerMockito.mock(CopyCommand.class);
+        closeable = MockitoAnnotations.openMocks(this);
+        storageCmd = mock(CopyCommand.class);
         doReturn(context).when(_resource).getServiceContext(null);
         when(cmd.getVirtualMachine()).thenReturn(vmSpec);
 
@@ -213,19 +234,17 @@
         when(hostCapability.isNestedHVSupported()).thenReturn(true);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     //Test successful scaling up the vm
     @Test
     public void testScaleVMF1() throws Exception {
         when(_resource.getHyperHost(context, null)).thenReturn(hyperHost);
         doReturn("i-2-3-VM").when(cmd).getVmName();
         when(hyperHost.findVmOnHyperHost("i-2-3-VM")).thenReturn(vmMo);
-        doReturn(536870912L).when(vmSpec).getMinRam();
-        doReturn(1).when(vmSpec).getCpus();
-        doReturn(1000).when(vmSpec).getMinSpeed();
-        doReturn(1000).when(vmSpec).getMaxSpeed();
-        doReturn(536870912L).when(vmSpec).getMaxRam();
-        doReturn(false).when(vmSpec).getLimitCpuUse();
-        when(vmMo.configureVm(vmConfigSpec)).thenReturn(true);
 
         _resource.execute(cmd);
         verify(_resource).execute(cmd);
@@ -246,7 +265,7 @@
         final NicTO[] nics = new NicTO[] {nicTo1, nicTo2};
 
         String macSequence = _resource.generateMacSequence(nics);
-        assertEquals(macSequence, "02:00:65:b5:00:03|01:23:45:67:89:AB");
+        assertEquals("02:00:65:b5:00:03|01:23:45:67:89:AB", macSequence);
     }
 
     @Test
@@ -399,8 +418,8 @@
     @Test
     public void checkStorageProcessorAndHandlerNfsVersionAttributeVersionNotSet(){
         _resource.checkStorageProcessorAndHandlerNfsVersionAttribute(storageCmd);
-        verify(_resource).examineStorageSubSystemCommandNfsVersion(Matchers.eq(storageCmd), any(EnumMap.class));
-        verify(_resource).examineStorageSubSystemCommandFullCloneFlagForVmware(Matchers.eq(storageCmd), any(EnumMap.class));
+        verify(_resource).examineStorageSubSystemCommandNfsVersion(eq(storageCmd), any(EnumMap.class));
+        verify(_resource).examineStorageSubSystemCommandFullCloneFlagForVmware(eq(storageCmd), any(EnumMap.class));
         verify(_resource).reconfigureProcessorByHandler(any(EnumMap.class));
         assertEquals(NFS_VERSION, _resource.storageNfsVersion);
     }
@@ -410,24 +429,26 @@
     public void checkStorageProcessorAndHandlerNfsVersionAttributeVersionSet(){
         _resource.storageNfsVersion = NFS_VERSION;
         _resource.checkStorageProcessorAndHandlerNfsVersionAttribute(storageCmd);
-        verify(_resource, never()).examineStorageSubSystemCommandNfsVersion(Matchers.eq(storageCmd), any(EnumMap.class));
+        verify(_resource, never()).examineStorageSubSystemCommandNfsVersion(eq(storageCmd), any(EnumMap.class));
     }
 
-    @Test(expected= CloudRuntimeException.class)
+    @Test(expected=CloudRuntimeException.class)
     public void testFindVmOnDatacenterNullHyperHostReference() throws Exception {
-        when(hyperHost.getMor()).thenReturn(null);
-        _resource.findVmOnDatacenter(context, hyperHost, volume);
+        try (MockedConstruction<DatacenterMO> ignored = mockConstruction(DatacenterMO.class)) {
+            _resource.findVmOnDatacenter(context, hyperHost, volume);
+        }
     }
 
     @Test
-    @PrepareForTest({DatacenterMO.class, VmwareResource.class})
     public void testFindVmOnDatacenter() throws Exception {
         when(hyperHost.getHyperHostDatacenter()).thenReturn(mor);
-        when(datacenter.getMor()).thenReturn(mor);
-        when(datacenter.findVm(VOLUME_PATH)).thenReturn(vmMo);
-        whenNew(DatacenterMO.class).withArguments(context, mor).thenReturn(datacenter);
-        VirtualMachineMO result = _resource.findVmOnDatacenter(context, hyperHost, volume);
-        assertEquals(vmMo, result);
+        try (MockedConstruction<DatacenterMO> ignored = mockConstruction(DatacenterMO.class, (mock, context) -> {
+            when(mock.findVm(VOLUME_PATH)).thenReturn(vmMo);
+            when(mock.getMor()).thenReturn(mor);
+        })) {
+            VirtualMachineMO result = _resource.findVmOnDatacenter(context, hyperHost, volume);
+            assertEquals(vmMo, result);
+        }
     }
 
     @Test
@@ -462,7 +483,7 @@
 
         GetAutoScaleMetricsCommand getAutoScaleMetricsCommand = new GetAutoScaleMetricsCommand("192.168.10.1", true, "10.10.10.10", 8080, metrics);
 
-        doReturn(vpcStats).when(vmwareResource).getVPCNetworkStats(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
+        doReturn(vpcStats).when(vmwareResource).getVPCNetworkStats(anyString(), anyString(), anyString(), anyString());
         doReturn(lbStats).when(vmwareResource).getNetworkLbStats(Mockito.nullable(String.class), Mockito.nullable(String.class), Mockito.nullable(Integer.class));
 
         Answer answer = vmwareResource.executeRequest(getAutoScaleMetricsCommand);
@@ -492,7 +513,7 @@
 
         GetAutoScaleMetricsCommand getAutoScaleMetricsCommand = new GetAutoScaleMetricsCommand("192.168.10.1", false, "10.10.10.10", 8080, metrics);
 
-        doReturn(networkStats).when(vmwareResource).getNetworkStats(Mockito.anyString(), Mockito.anyString());
+        doReturn(networkStats).when(vmwareResource).getNetworkStats(anyString(), anyString());
         doReturn(lbStats).when(vmwareResource).getNetworkLbStats(Mockito.nullable(String.class), Mockito.nullable(String.class), Mockito.nullable(Integer.class));
 
         Answer answer = vmwareResource.executeRequest(getAutoScaleMetricsCommand);
@@ -554,4 +575,271 @@
         assertEquals(1, stats.length);
         assertEquals(lbStats[0], stats[0]);
     }
+
+    @Test
+    public void testCheckGuestOsMappingCommandFailure() throws Exception {
+        CheckGuestOsMappingCommand cmd = mock(CheckGuestOsMappingCommand.class);
+        when(cmd.getGuestOsName()).thenReturn("CentOS 7.2");
+        when(cmd.getGuestOsHypervisorMappingName()).thenReturn("centosWrongName");
+        when(_resource.getHyperHost(context, null)).thenReturn(hyperHost);
+        when(hyperHost.getGuestOsDescriptor("centosWrongName")).thenReturn(null);
+
+        CheckGuestOsMappingAnswer answer = _resource.execute(cmd);
+
+        assertFalse(answer.getResult());
+    }
+
+    @Test
+    public void testCheckGuestOsMappingCommandSuccess() throws Exception {
+        CheckGuestOsMappingCommand cmd = mock(CheckGuestOsMappingCommand.class);
+        when(cmd.getGuestOsName()).thenReturn("CentOS 7.2");
+        when(cmd.getGuestOsHypervisorMappingName()).thenReturn("centos64Guest");
+        when(_resource.getHyperHost(context, null)).thenReturn(hyperHost);
+        GuestOsDescriptor guestOsDescriptor = mock(GuestOsDescriptor.class);
+        when(hyperHost.getGuestOsDescriptor("centos64Guest")).thenReturn(guestOsDescriptor);
+        when(guestOsDescriptor.getFullName()).thenReturn("centos64Guest");
+
+        CheckGuestOsMappingAnswer answer = _resource.execute(cmd);
+
+        assertTrue(answer.getResult());
+    }
+
+    @Test
+    public void testCheckGuestOsMappingCommandException() {
+        CheckGuestOsMappingCommand cmd = mock(CheckGuestOsMappingCommand.class);
+        when(cmd.getGuestOsName()).thenReturn("CentOS 7.2");
+        when(cmd.getGuestOsHypervisorMappingName()).thenReturn("centos64Guest");
+        when(_resource.getHyperHost(context, null)).thenReturn(null);
+
+        CheckGuestOsMappingAnswer answer = _resource.execute(cmd);
+
+        assertFalse(answer.getResult());
+    }
+
+    @Test
+    public void testGetHypervisorGuestOsNamesCommandFailure() throws Exception {
+        GetHypervisorGuestOsNamesCommand cmd = mock(GetHypervisorGuestOsNamesCommand.class);
+        when(cmd.getKeyword()).thenReturn("CentOS");
+        when(_resource.getHyperHost(context, null)).thenReturn(hyperHost);
+        when(hyperHost.getGuestOsDescriptors()).thenReturn(null);
+
+        GetHypervisorGuestOsNamesAnswer answer = _resource.execute(cmd);
+
+        assertFalse(answer.getResult());
+    }
+
+    @Test
+    public void testGetHypervisorGuestOsNamesCommandSuccessWithKeyword() throws Exception {
+        GetHypervisorGuestOsNamesCommand cmd = mock(GetHypervisorGuestOsNamesCommand.class);
+        when(cmd.getKeyword()).thenReturn("CentOS");
+        when(_resource.getHyperHost(context, null)).thenReturn(hyperHost);
+        GuestOsDescriptor guestOsDescriptor = mock(GuestOsDescriptor.class);
+        when(guestOsDescriptor.getFullName()).thenReturn("centos64Guest");
+        when(guestOsDescriptor.getId()).thenReturn("centos64Guest");
+        List<GuestOsDescriptor> guestOsDescriptors = new ArrayList<>();
+        guestOsDescriptors.add(guestOsDescriptor);
+        when(hyperHost.getGuestOsDescriptors()).thenReturn(guestOsDescriptors);
+
+        GetHypervisorGuestOsNamesAnswer answer = _resource.execute(cmd);
+
+        assertTrue(answer.getResult());
+        assertEquals("centos64Guest", answer.getHypervisorGuestOsNames().get(0).first());
+    }
+
+    @Test
+    public void testGetHypervisorGuestOsNamesCommandSuccessWithoutKeyword() throws Exception {
+        GetHypervisorGuestOsNamesCommand cmd = mock(GetHypervisorGuestOsNamesCommand.class);
+        when(_resource.getHyperHost(context, null)).thenReturn(hyperHost);
+        GuestOsDescriptor guestOsDescriptor = mock(GuestOsDescriptor.class);
+        when(guestOsDescriptor.getFullName()).thenReturn("centos64Guest");
+        when(guestOsDescriptor.getId()).thenReturn("centos64Guest");
+        List<GuestOsDescriptor> guestOsDescriptors = new ArrayList<>();
+        guestOsDescriptors.add(guestOsDescriptor);
+        when(hyperHost.getGuestOsDescriptors()).thenReturn(guestOsDescriptors);
+
+        GetHypervisorGuestOsNamesAnswer answer = _resource.execute(cmd);
+
+        assertTrue(answer.getResult());
+        assertEquals("centos64Guest", answer.getHypervisorGuestOsNames().get(0).first());
+    }
+
+    @Test
+    public void testGetHypervisorGuestOsNamesCommandException() {
+        GetHypervisorGuestOsNamesCommand cmd = mock(GetHypervisorGuestOsNamesCommand.class);
+        when(cmd.getKeyword()).thenReturn("CentOS");
+        when(_resource.getHyperHost(context, null)).thenReturn(null);
+
+        GetHypervisorGuestOsNamesAnswer answer = _resource.execute(cmd);
+
+        assertFalse(answer.getResult());
+    }
+
+    @Test
+    public void testExecuteWithValidPath() throws Exception {
+        // Setup
+        ListDataStoreObjectsCommand cmd = new ListDataStoreObjectsCommand(destDataStoreTO, "valid/path", 0, 10);
+        HostDatastoreBrowserMO browserMo = mock(HostDatastoreBrowserMO.class);
+        HostDatastoreBrowserSearchResults results = mock(HostDatastoreBrowserSearchResults.class);
+        FileInfo fileInfo = mock(FileInfo.class);
+        List<FileInfo> fileInfoList = new ArrayList<>();
+        fileInfoList.add(fileInfo);
+        Date date = new Date();
+
+        doReturn(context).when(vmwareResource).getServiceContext(any());
+        doReturn(hyperHost).when(vmwareResource).getHyperHost(context);
+        when(browserMo.searchDatastore(anyString(), any(HostDatastoreBrowserSearchSpec.class))).thenReturn(results);
+        when(results.getFile()).thenReturn(fileInfoList);
+        when(fileInfo.getPath()).thenReturn("file.txt");
+        when(fileInfo.getFileSize()).thenReturn(1L);
+        when(fileInfo.getModification()).thenReturn(VmwareHelper.getXMLGregorianCalendar(date, 0));
+
+        // Execute
+        ListDataStoreObjectsAnswer answer;
+        try (MockedStatic<HypervisorHostHelper> ignored = mockStatic(HypervisorHostHelper.class);
+             MockedConstruction<DatastoreMO> ignored2 = mockConstruction(DatastoreMO.class, (mock, context1) -> {
+                 when(mock.getName()).thenReturn("datastore");
+                 when(mock.getHostDatastoreBrowserMO()).thenReturn(browserMo);
+             })
+        ) {
+            when(HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(any(), any())).thenReturn(mor);
+
+            answer = vmwareResource.execute(cmd);
+
+        }
+        // Verify
+        assertNotNull(answer);
+        assertTrue(answer.getResult());
+        assertEquals(1, answer.getCount());
+        assertEquals(Collections.singletonList("file.txt"), answer.getNames());
+        assertEquals(Collections.singletonList("valid/path/file.txt"), answer.getPaths());
+        assertEquals(Collections.singletonList("[datastore] valid/path/file.txt"), answer.getAbsPaths());
+        assertEquals(Collections.singletonList(false), answer.getIsDirs());
+        assertEquals(Collections.singletonList(1L), answer.getSizes());
+        assertEquals(Collections.singletonList(date.getTime()), answer.getLastModified());
+    }
+
+    @Test
+    public void testExecuteWithInvalidPath() throws Exception {
+        // Setup
+        ListDataStoreObjectsCommand cmd = new ListDataStoreObjectsCommand(destDataStoreTO, "invalid/path", 0, 10);
+        HostDatastoreBrowserMO browserMo = mock(HostDatastoreBrowserMO.class);
+        doReturn(context).when(vmwareResource).getServiceContext();
+        doReturn(hyperHost).when(vmwareResource).getHyperHost(context);
+        when(browserMo.searchDatastore(anyString(), any(HostDatastoreBrowserSearchSpec.class))).thenThrow(new Exception("was not found"));
+
+        ListDataStoreObjectsAnswer answer;
+        try (MockedStatic<HypervisorHostHelper> ignored = mockStatic(HypervisorHostHelper.class);
+             MockedConstruction<DatastoreMO> ignored2 = mockConstruction(DatastoreMO.class, (mock, context1) -> {
+                 when(mock.getName()).thenReturn("datastore");
+                 when(mock.fileExists(anyString())).thenReturn(false);
+                 when(mock.getHostDatastoreBrowserMO()).thenReturn(browserMo);
+             })
+        ) {
+            when(HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(any(), any())).thenReturn(mor);
+
+            // Execute
+            answer = vmwareResource.execute(cmd);
+        }
+
+        // Verify
+        assertNotNull(answer);
+        assertTrue(answer.getResult());
+        assertEquals(0, answer.getCount());
+        assertEquals(Collections.emptyList(), answer.getNames());
+        assertEquals(Collections.emptyList(), answer.getPaths());
+        assertEquals(Collections.emptyList(), answer.getAbsPaths());
+        assertEquals(Collections.emptyList(), answer.getIsDirs());
+        assertEquals(Collections.emptyList(), answer.getSizes());
+        assertEquals(Collections.emptyList(), answer.getLastModified());
+    }
+
+    @Test
+    public void testExecuteWithRootPath() throws Exception {
+        // Setup
+        ListDataStoreObjectsCommand cmd = new ListDataStoreObjectsCommand(destDataStoreTO, "/", 0, 10);
+        HostDatastoreBrowserMO browserMo = mock(HostDatastoreBrowserMO.class);
+        HostDatastoreBrowserSearchResults results = mock(HostDatastoreBrowserSearchResults.class);
+        FileInfo fileInfo = mock(FileInfo.class);
+        List<FileInfo> fileInfoList = new ArrayList<>();
+        fileInfoList.add(fileInfo);
+        Date date = new Date();
+
+        doReturn(context).when(vmwareResource).getServiceContext();
+        doReturn(hyperHost).when(vmwareResource).getHyperHost(context);
+        when(browserMo.searchDatastore(anyString(), any(HostDatastoreBrowserSearchSpec.class))).thenReturn(results);
+        when(results.getFile()).thenReturn(fileInfoList);
+        when(fileInfo.getPath()).thenReturn("file.txt");
+        when(fileInfo.getFileSize()).thenReturn(1L);
+        when(fileInfo.getModification()).thenReturn(VmwareHelper.getXMLGregorianCalendar(date, 0));
+
+        // Execute
+        ListDataStoreObjectsAnswer answer;
+        try (MockedStatic<HypervisorHostHelper> ignored = mockStatic(HypervisorHostHelper.class);
+             MockedConstruction<DatastoreMO> ignored2 = mockConstruction(DatastoreMO.class, (mock, context1) -> {
+                 when(mock.getName()).thenReturn("datastore");
+                 when(mock.getHostDatastoreBrowserMO()).thenReturn(browserMo);
+             })
+        ) {
+            when(HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(any(), any())).thenReturn(mor);
+
+            // Execute
+            answer = vmwareResource.execute(cmd);
+        }
+
+        // Verify
+        assertNotNull(answer);
+        assertTrue(answer.getResult());
+        assertEquals(1, answer.getCount());
+        assertEquals(Collections.singletonList("file.txt"), answer.getNames());
+        assertEquals(Collections.singletonList("/file.txt"), answer.getPaths());
+        assertEquals(Collections.singletonList("[datastore] /file.txt"), answer.getAbsPaths());
+        assertEquals(Collections.singletonList(false), answer.getIsDirs());
+        assertEquals(Collections.singletonList(1L), answer.getSizes());
+        assertEquals(Collections.singletonList(date.getTime()), answer.getLastModified());
+    }
+
+    @Test
+    public void testExecuteWithEmptyPath() throws Exception {
+        // Setup
+        ListDataStoreObjectsCommand cmd = new ListDataStoreObjectsCommand(destDataStoreTO, "", 0, 10);
+        HostDatastoreBrowserMO browserMo = mock(HostDatastoreBrowserMO.class);
+        HostDatastoreBrowserSearchResults results = mock(HostDatastoreBrowserSearchResults.class);
+        FileInfo fileInfo = mock(FileInfo.class);
+        List<FileInfo> fileInfoList = new ArrayList<>();
+        fileInfoList.add(fileInfo);
+        Date date = new Date();
+
+        doReturn(context).when(vmwareResource).getServiceContext();
+        doReturn(hyperHost).when(vmwareResource).getHyperHost(context);
+        when(browserMo.searchDatastore(anyString(), any(HostDatastoreBrowserSearchSpec.class))).thenReturn(results);
+        when(results.getFile()).thenReturn(fileInfoList);
+        when(fileInfo.getPath()).thenReturn("file.txt");
+        when(fileInfo.getFileSize()).thenReturn(1L);
+        when(fileInfo.getModification()).thenReturn(VmwareHelper.getXMLGregorianCalendar(date, 0));
+
+        ListDataStoreObjectsAnswer answer;
+        try (MockedStatic<HypervisorHostHelper> ignored = mockStatic(HypervisorHostHelper.class);
+             MockedConstruction<DatastoreMO> ignored2 = mockConstruction(DatastoreMO.class, (mock, context1) -> {
+                 when(mock.getName()).thenReturn("datastore");
+                 when(mock.getHostDatastoreBrowserMO()).thenReturn(browserMo);
+             })
+        ) {
+            when(HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(any(), any())).thenReturn(mor);
+
+            // Execute
+            answer = vmwareResource.execute(cmd);
+
+        }
+
+        // Verify
+        assertNotNull(answer);
+        assertTrue(answer.getResult());
+        assertEquals(1, answer.getCount());
+        assertEquals(Collections.singletonList("file.txt"), answer.getNames());
+        assertEquals(Collections.singletonList("/file.txt"), answer.getPaths());
+        assertEquals(Collections.singletonList("[datastore] /file.txt"), answer.getAbsPaths());
+        assertEquals(Collections.singletonList(false), answer.getIsDirs());
+        assertEquals(Collections.singletonList(1L), answer.getSizes());
+        assertEquals(Collections.singletonList(date.getTime()), answer.getLastModified());
+    }
 }
diff --git a/plugins/hypervisors/vmware/src/test/java/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java b/plugins/hypervisors/vmware/src/test/java/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java
index fa1137f..35a8ffa 100644
--- a/plugins/hypervisors/vmware/src/test/java/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java
+++ b/plugins/hypervisors/vmware/src/test/java/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java
@@ -18,8 +18,8 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.isA;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
diff --git a/plugins/hypervisors/vmware/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/hypervisors/vmware/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/hypervisors/vmware/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/hypervisors/xenserver/pom.xml b/plugins/hypervisors/xenserver/pom.xml
index 6a5f103..0cbeb7d 100644
--- a/plugins/hypervisors/xenserver/pom.xml
+++ b/plugins/hypervisors/xenserver/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java
index 79a1ef0..9047370 100644
--- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java
+++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java
@@ -45,16 +45,23 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
+import java.util.Vector;
 import java.util.concurrent.TimeoutException;
 
 import javax.naming.ConfigurationException;
 import javax.xml.parsers.ParserConfigurationException;
 
+import com.trilead.ssh2.SFTPException;
+import com.trilead.ssh2.SFTPv3Client;
+import com.trilead.ssh2.SFTPv3DirectoryEntry;
+import com.trilead.ssh2.SFTPv3FileAttributes;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer;
 import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand;
 import org.apache.cloudstack.diagnostics.DiagnosticsService;
 import org.apache.cloudstack.hypervisor.xenserver.ExtraConfigurationUtility;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.cloudstack.utils.security.ParserUtils;
@@ -801,7 +808,7 @@
                 }
             }
         } catch (final Throwable e) {
-            final String msg = "Unable to get vms through host " + _host.getUuid() + " due to to " + e.toString();
+            final String msg = "Unable to get vms through host " + _host.getUuid() + " due to " + e.toString();
             s_logger.warn(msg, e);
             throw new CloudRuntimeException(msg);
         }
@@ -1574,7 +1581,7 @@
         try {
             final Set<VDI> vdis = VDI.getByNameLabel(conn, nameLabel);
             if (vdis.size() != 1) {
-                s_logger.warn("destoryVDIbyNameLabel failed due to there are " + vdis.size() + " VDIs with name " + nameLabel);
+                s_logger.warn("destroyVDIbyNameLabel failed due to there are " + vdis.size() + " VDIs with name " + nameLabel);
                 return;
             }
             for (final VDI vdi : vdis) {
@@ -3192,7 +3199,7 @@
         // constraint
         // for
         // stability
-        if (dynamicMaxRam > staticMax) { // XS contraint that dynamic max <=
+        if (dynamicMaxRam > staticMax) { // XS constraint that dynamic max <=
             // static max
             s_logger.warn("dynamic max " + toHumanReadableSize(dynamicMaxRam) + " can't be greater than static max " + toHumanReadableSize(staticMax) + ", this can lead to stability issues. Setting static max as much as dynamic max ");
             return dynamicMaxRam;
@@ -3206,7 +3213,7 @@
             return dynamicMinRam;
         }
 
-        if (dynamicMinRam < recommendedValue) { // XS contraint that dynamic min
+        if (dynamicMinRam < recommendedValue) { // XS constraint that dynamic min
             // > static min
             s_logger.warn("Vm ram is set to dynamic min " + toHumanReadableSize(dynamicMinRam) + " and is less than the recommended static min " + toHumanReadableSize(recommendedValue) + ", this could lead to stability issues");
         }
@@ -4582,7 +4589,7 @@
             removeSR(conn, sr);
             return;
         } catch (XenAPIException | XmlRpcException e) {
-            s_logger.warn(logX(sr, "Unable to get current opertions " + e.toString()), e);
+            s_logger.warn(logX(sr, "Unable to get current operations " + e.toString()), e);
         }
         String msg = "Remove SR failed";
         s_logger.warn(msg);
@@ -4677,9 +4684,9 @@
             removeSR(conn, sr);
             return null;
         } catch (final XenAPIException e) {
-            s_logger.warn(logX(sr, "Unable to get current opertions " + e.toString()), e);
+            s_logger.warn(logX(sr, "Unable to get current operations " + e.toString()), e);
         } catch (final XmlRpcException e) {
-            s_logger.warn(logX(sr, "Unable to get current opertions " + e.getMessage()), e);
+            s_logger.warn(logX(sr, "Unable to get current operations " + e.getMessage()), e);
         }
         final String msg = "Remove SR failed";
         s_logger.warn(msg);
@@ -5722,6 +5729,72 @@
 
     }
 
+    public Answer listFilesAtPath(ListDataStoreObjectsCommand command) throws IOException, XmlRpcException {
+        DataStoreTO store = command.getStore();
+        int startIndex = command.getStartIndex();
+        int pageSize = command.getPageSize();
+        String relativePath = command.getPath();
+        if (relativePath.endsWith("/")) {
+            relativePath = relativePath.substring(0, relativePath.length() - 1);
+        }
+
+        final Connection conn = getConnection();
+
+        final SR sr = getStorageRepository(conn, store.getUuid());
+        final com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_host.getIp(), 22);
+        try {
+            sshConnection.connect(null, 60000, 60000);
+            if (!sshConnection.authenticateWithPassword(_username, _password.peek())) {
+                throw new CloudRuntimeException("Unable to authenticate");
+            }
+            String mountPoint = "/var/run/sr-mount/" + sr.getUuid(conn);
+            boolean pathExists = true;
+            SFTPv3FileAttributes fileAttr = null;
+            int count = 0;
+            List<String> names = new ArrayList<>();
+            List<String> paths = new ArrayList<>();
+            List<String> absPaths = new ArrayList<>();
+            List<Boolean> isDirs = new ArrayList<>();
+            List<Long> sizes = new ArrayList<>();
+            List<Long> modifiedList = new ArrayList<>();
+            SFTPv3Client client = new SFTPv3Client(sshConnection);
+            try {
+                fileAttr = client._stat(mountPoint + "/" + relativePath);
+
+                // Path doesn't exist
+                if (fileAttr == null) {
+                    return new ListDataStoreObjectsAnswer(false, count, names, paths, absPaths, isDirs, sizes, modifiedList);
+                }
+
+                try {
+                    Vector fileList = client.ls(mountPoint + "/" + relativePath);
+                    count = fileList.size() - 2; // -2 for . and ..
+                    for (int i = startIndex + 2; i < startIndex + pageSize + 2 && i < fileList.size(); i++) {
+                        SFTPv3DirectoryEntry entry = (SFTPv3DirectoryEntry) fileList.get(i);
+                        names.add(entry.filename);
+                        paths.add(relativePath + "/" + entry.filename);
+                        isDirs.add(entry.attributes.isDirectory());
+                        sizes.add(entry.attributes.size);
+                        modifiedList.add(entry.attributes.mtime * 1000L);
+                    }
+                } catch (SFTPException e) {
+                    // Path is a file
+                    count = 1;
+                    names.add(relativePath.substring(relativePath.lastIndexOf("/") + 1));
+                    paths.add(relativePath);
+                    isDirs.add(false);
+                    sizes.add(fileAttr.size);
+                    modifiedList.add(fileAttr.mtime * 1000L);
+                }
+            } finally {
+                client.close();
+            }
+            return new ListDataStoreObjectsAnswer(pathExists, count, names, paths, absPaths, isDirs, sizes, modifiedList);
+        } finally {
+            sshConnection.close();
+        }
+    }
+
     /**
      * Get Diagnostics Data API
      * Copy zip file from system vm and copy file directly to secondary storage
diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java
index 7c289de..cb226ed 100644
--- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java
+++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java
@@ -633,7 +633,7 @@
         try {
             final Set<VDI> vdis = VDI.getByNameLabel(conn, nameLabel);
             if (vdis.size() != 1) {
-                s_logger.warn("destoryVDIbyNameLabel failed due to there are " + vdis.size() + " VDIs with name " + nameLabel);
+                s_logger.warn("destroyVDIbyNameLabel failed due to there are " + vdis.size() + " VDIs with name " + nameLabel);
                 return;
             }
             for (final VDI vdi : vdis) {
diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckGuestOsMappingCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckGuestOsMappingCommandWrapper.java
new file mode 100644
index 0000000..68403d7
--- /dev/null
+++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckGuestOsMappingCommandWrapper.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 com.cloud.hypervisor.xenserver.resource.wrapper.xenbase;
+
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckGuestOsMappingAnswer;
+import com.cloud.agent.api.CheckGuestOsMappingCommand;
+import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.xensource.xenapi.Connection;
+import com.xensource.xenapi.VM;
+
+@ResourceWrapper(handles =  CheckGuestOsMappingCommand.class)
+public final class CitrixCheckGuestOsMappingCommandWrapper extends CommandWrapper<CheckGuestOsMappingCommand, Answer, CitrixResourceBase> {
+
+    private static final Logger s_logger = Logger.getLogger(CitrixCheckGuestOsMappingCommandWrapper.class);
+
+    @Override
+    public Answer execute(final CheckGuestOsMappingCommand command, final CitrixResourceBase citrixResourceBase) {
+        final Connection conn = citrixResourceBase.getConnection();
+        String guestOsName = command.getGuestOsName();
+        String guestOsMappingName = command.getGuestOsHypervisorMappingName();
+        try {
+            s_logger.info("Checking guest os mapping name: " + guestOsMappingName + " for the guest os: " + guestOsName + " in the hypervisor");
+            final Set<VM> vms = VM.getAll(conn);
+            if (CollectionUtils.isEmpty(vms)) {
+                return new CheckGuestOsMappingAnswer(command, "Unable to match guest os mapping name: " + guestOsMappingName + " in the hypervisor");
+            }
+            for (VM vm : vms) {
+                if (vm != null && vm.getIsATemplate(conn) && guestOsMappingName.equalsIgnoreCase(vm.getNameLabel(conn))) {
+                    if (guestOsName.equalsIgnoreCase(vm.getNameLabel(conn))) {
+                        s_logger.debug("Hypervisor guest os name label matches with os name: " + guestOsName);
+                    }
+                    s_logger.info("Hypervisor guest os name label matches with os mapping: " + guestOsMappingName + " from user");
+                    return new CheckGuestOsMappingAnswer(command);
+                }
+            }
+            return new CheckGuestOsMappingAnswer(command, "Guest os mapping name: " + guestOsMappingName + " not found in the hypervisor");
+        } catch (final Exception e) {
+            s_logger.error("Failed to find the hypervisor guest os mapping name: " + guestOsMappingName, e);
+            return new CheckGuestOsMappingAnswer(command, e.getLocalizedMessage());
+        }
+    }
+}
diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetHypervisorGuestOsNamesCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetHypervisorGuestOsNamesCommandWrapper.java
new file mode 100644
index 0000000..0be3e5a
--- /dev/null
+++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetHypervisorGuestOsNamesCommandWrapper.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.hypervisor.xenserver.resource.wrapper.xenbase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesAnswer;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesCommand;
+import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.Pair;
+import com.xensource.xenapi.Connection;
+import com.xensource.xenapi.VM;
+
+@ResourceWrapper(handles =  GetHypervisorGuestOsNamesCommand.class)
+public final class CitrixGetHypervisorGuestOsNamesCommandWrapper extends CommandWrapper<GetHypervisorGuestOsNamesCommand, Answer, CitrixResourceBase> {
+
+    private static final Logger s_logger = Logger.getLogger(CitrixGetHypervisorGuestOsNamesCommandWrapper.class);
+
+    @Override
+    public Answer execute(final GetHypervisorGuestOsNamesCommand command, final CitrixResourceBase citrixResourceBase) {
+        final Connection conn = citrixResourceBase.getConnection();
+        String keyword = command.getKeyword();
+        try {
+            s_logger.info("Getting guest os names in the hypervisor");
+            final Set<VM> vms = VM.getAll(conn);
+            if (CollectionUtils.isEmpty(vms)) {
+                return new GetHypervisorGuestOsNamesAnswer(command, "Guest os names not found in the hypervisor");
+            }
+            List<Pair<String, String>> hypervisorGuestOsNames = new ArrayList<>();
+            for (VM vm : vms) {
+                if (vm != null && vm.getIsATemplate(conn)) {
+                    String guestOSNameLabel = vm.getNameLabel(conn);
+                    if (StringUtils.isNotBlank(keyword)) {
+                        if (guestOSNameLabel.toLowerCase().contains(keyword.toLowerCase())) {
+                            Pair<String, String> hypervisorGuestOs = new Pair<>(guestOSNameLabel, guestOSNameLabel);
+                            hypervisorGuestOsNames.add(hypervisorGuestOs);
+                        }
+                    } else {
+                        Pair<String, String> hypervisorGuestOs = new Pair<>(guestOSNameLabel, guestOSNameLabel);
+                        hypervisorGuestOsNames.add(hypervisorGuestOs);
+                    }
+                }
+            }
+            return new GetHypervisorGuestOsNamesAnswer(command, hypervisorGuestOsNames);
+        } catch (final Exception e) {
+            s_logger.error("Failed to fetch hypervisor guest os names due to: " + e.getLocalizedMessage(), e);
+            return new GetHypervisorGuestOsNamesAnswer(command, e.getLocalizedMessage());
+        }
+    }
+}
diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixListDataStoreObjectsCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixListDataStoreObjectsCommandWrapper.java
new file mode 100644
index 0000000..1be7879
--- /dev/null
+++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixListDataStoreObjectsCommandWrapper.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.hypervisor.xenserver.resource.wrapper.xenbase;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.xensource.xenapi.Types.XenAPIException;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
+import org.apache.log4j.Logger;
+import org.apache.xmlrpc.XmlRpcException;
+
+@ResourceWrapper(handles = ListDataStoreObjectsCommand.class)
+public final class CitrixListDataStoreObjectsCommandWrapper extends CommandWrapper<ListDataStoreObjectsCommand, Answer, CitrixResourceBase> {
+
+    private static final Logger LOGGER = Logger.getLogger(CitrixListDataStoreObjectsCommandWrapper.class);
+
+    @Override
+    public Answer execute(final ListDataStoreObjectsCommand command, final CitrixResourceBase citrixResourceBase) {
+        try {
+            return citrixResourceBase.listFilesAtPath(command);
+        } catch (XenAPIException e) {
+            LOGGER.warn("XenAPI exception", e);
+
+        } catch (XmlRpcException e) {
+            LOGGER.warn("Xml Rpc Exception", e);
+        } catch (Exception e) {
+            LOGGER.warn("Caught exception", e);
+        }
+        return null;
+    }
+}
diff --git a/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-compute/module.properties b/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-compute/module.properties
index c6c91f6..875ae36 100644
--- a/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-compute/module.properties
+++ b/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=xenserver-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-discoverer/module.properties b/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-discoverer/module.properties
index 10d0ecd..f24912b 100644
--- a/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-discoverer/module.properties
+++ b/plugins/hypervisors/xenserver/src/main/resources/META-INF/cloudstack/xenserver-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=xenserver-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBaseTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBaseTest.java
index df9e9a4..a765ddc 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBaseTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBaseTest.java
@@ -16,14 +16,25 @@
 package com.cloud.hypervisor.xenserver.resource;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Vector;
 
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.trilead.ssh2.SFTPException;
+import com.trilead.ssh2.SFTPv3Client;
+import com.trilead.ssh2.SFTPv3DirectoryEntry;
+import com.trilead.ssh2.SFTPv3FileAttributes;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
 import org.apache.xmlrpc.XmlRpcException;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -31,11 +42,10 @@
 import org.mockito.BDDMockito;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.agent.api.StartupStorageCommand;
 import com.cloud.agent.api.StoragePoolInfo;
@@ -52,13 +62,14 @@
 import com.xensource.xenapi.PBD;
 import com.xensource.xenapi.SR;
 import com.xensource.xenapi.Types.XenAPIException;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import static com.cloud.hypervisor.xenserver.resource.CitrixResourceBase.PLATFORM_CORES_PER_SOCKET_KEY;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doReturn;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({Host.class, Script.class, SR.class})
+@RunWith(MockitoJUnitRunner.class)
 public class CitrixResourceBaseTest {
 
     @Spy
@@ -80,47 +91,55 @@
 
     private static final String platformString = "device-model:qemu-upstream-compat;vga:std;videoram:8;apic:true;viridian:false;timeoffset:0;pae:true;acpi:1;hpet:true;secureboot:false;nx:true";
 
-    final static long[] vpcStats = { 1L, 2L };
-    final static long[] networkStats = { 3L, 4L };
-    final static long[] lbStats = { 5L };
+    final static long[] vpcStats = {1L, 2L};
+    final static long[] networkStats = {3L, 4L};
+    final static long[] lbStats = {5L};
     final static String privateIp = "192.168.1.1";
     final static String publicIp = "10.10.10.10";
     final static Integer port = 8080;
 
+    MockedStatic<Host> hostMocked;
+
     @Before
     public void beforeTest() throws XenAPIException, XmlRpcException {
         citrixResourceBase._host.setUuid(hostUuidMock);
 
-        PowerMockito.mockStatic(Host.class);
-        PowerMockito.when(Host.getByUuid(connectionMock, hostUuidMock)).thenReturn(hostMock);
+        hostMocked = Mockito.mockStatic(Host.class);
+        hostMocked.when(() -> Host.getByUuid(connectionMock, hostUuidMock)).thenReturn(hostMock);
 
         hostRecordMock.softwareVersion = new HashMap<>();
         Mockito.when(hostMock.getRecord(connectionMock)).thenReturn(hostRecordMock);
 
     }
 
-    public void testGetPathFilesExeption() {
+    public void testGetPathFilesException() {
         String patch = citrixResourceBase.getPatchFilePath();
 
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(Script.findScript("", patch)).thenReturn(null);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.findScript("", patch)).thenReturn(null);
 
-        citrixResourceBase.getPatchFiles();
-
+            citrixResourceBase.getPatchFiles();
+        }
     }
 
     public void testGetPathFilesListReturned() {
         String patch = citrixResourceBase.getPatchFilePath();
 
-        PowerMockito.mockStatic(Script.class);
-        Mockito.when(Script.findScript("", patch)).thenReturn(patch);
+        try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
+            Mockito.when(Script.findScript("", patch)).thenReturn(patch);
 
-        File expected = new File(patch);
-        String pathExpected = expected.getAbsolutePath();
+            File expected = new File(patch);
+            String pathExpected = expected.getAbsolutePath();
 
-        List<File> files = citrixResourceBase.getPatchFiles();
-        String receivedPath = files.get(0).getAbsolutePath();
-        Assert.assertEquals(receivedPath, pathExpected);
+            List<File> files = citrixResourceBase.getPatchFiles();
+            String receivedPath = files.get(0).getAbsolutePath();
+            Assert.assertEquals(receivedPath, pathExpected);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        hostMocked.close();
     }
 
     @Test
@@ -233,14 +252,14 @@
         Map<SR, SR.Record> mapOfSrsRecords = new HashMap<>();
         mapOfSrsRecords.put(srExtShared, srExtSharedRecord);
         mapOfSrsRecords.put(srExtNonShared, srExtNonSharedRecord);
+        try (MockedStatic<SR> ignored = Mockito.mockStatic(SR.class)) {
+            BDDMockito.given(SR.getAllRecords(connectionMock)).willReturn(mapOfSrsRecords);
 
-        PowerMockito.mockStatic(SR.class);
-        BDDMockito.given(SR.getAllRecords(connectionMock)).willReturn(mapOfSrsRecords);
+            List<SR> allLocalSrForType = citrixResourceBase.getAllLocalSrForType(connectionMock, SRType.EXT);
 
-        List<SR> allLocalSrForType = citrixResourceBase.getAllLocalSrForType(connectionMock, SRType.EXT);
-
-        Assert.assertEquals(expectedListOfSrs.size(), allLocalSrForType.size());
-        Assert.assertEquals(expectedListOfSrs.get(0), allLocalSrForType.get(0));
+            Assert.assertEquals(expectedListOfSrs.size(), allLocalSrForType.size());
+            Assert.assertEquals(expectedListOfSrs.get(0), allLocalSrForType.get(0));
+        }
     }
 
     @Test
@@ -287,9 +306,7 @@
         String hostUuid = "hostUuid";
         citrixResourceBase._host.setUuid(hostUuid);
 
-        PowerMockito.mockStatic(Host.class);
-        PowerMockito.when(Host.getByUuid(connectionMock, hostUuid)).thenReturn(hostMock);
-
+        Mockito.when(Host.getByUuid(connectionMock, hostUuid)).thenReturn(hostMock);
         String srType = "ext";
         String srUuid = "srUuid";
         long srPhysicalSize = 100l;
@@ -432,7 +449,6 @@
         CitrixResourceBase citrixResourceBaseSpy = Mockito.spy(CitrixResourceBase.class);
         Connection connection = Mockito.mock(Connection.class);
 
-        String args = "-g -l " + publicIp;
         doReturn(networkStats[0] + ":" + networkStats[1]).when(citrixResourceBaseSpy).networkUsage(Mockito.any(Connection.class),
                 Mockito.eq(privateIp), Mockito.eq("get"), Mockito.any(), Mockito.eq(publicIp));
 
@@ -466,4 +482,135 @@
         result = citrixResourceBaseSpy.networkUsage(connectionMock, null, "put", null);
         Assert.assertNull(result);
     }
+
+    @Test
+    public void testListFilesAtPath() throws IOException, XmlRpcException {
+        SR srMock = Mockito.mock(SR.class);
+        SFTPv3FileAttributes fileAttributesMock = Mockito.mock(SFTPv3FileAttributes.class);
+        SFTPv3DirectoryEntry directoryEntryMock = Mockito.mock(SFTPv3DirectoryEntry.class);
+        Vector fileListMock = new Vector();
+        for (int i=0; i < 16; ++i) {
+            fileListMock.add(directoryEntryMock);
+        }
+
+        ListDataStoreObjectsCommand command = Mockito.mock(ListDataStoreObjectsCommand.class);
+        DataStoreTO store = Mockito.mock(DataStoreTO.class);
+        Mockito.when(command.getStore()).thenReturn(store);
+        Mockito.when(store.getUuid()).thenReturn("storeUuid");
+        Mockito.when(command.getStartIndex()).thenReturn(0);
+        Mockito.when(command.getPageSize()).thenReturn(10);
+        Mockito.when(command.getPath()).thenReturn("/path/to/files");
+
+        Mockito.doReturn(connectionMock).when(citrixResourceBase).getConnection();
+        Mockito.doReturn(srMock).when(citrixResourceBase).getStorageRepository(connectionMock, "storeUuid");
+        ReflectionTestUtils.setField(directoryEntryMock, "filename", "file1");
+        ReflectionTestUtils.setField(directoryEntryMock, "attributes", fileAttributesMock);
+        Mockito.when(fileAttributesMock.isDirectory()).thenReturn(false);
+        ReflectionTestUtils.setField(fileAttributesMock, "size", 1024L);
+        ReflectionTestUtils.setField(fileAttributesMock, "mtime", 123456789L);
+
+        Answer answer;
+        try (MockedConstruction<com.trilead.ssh2.Connection> ignored = Mockito.mockConstruction(com.trilead.ssh2.Connection.class, (mock, context) -> {
+            Mockito.when(mock.authenticateWithPassword(Mockito.any(), Mockito.any())).thenReturn(true);
+            Mockito.when(mock.connect(null, 60000, 60000)).thenReturn(null);
+        }); MockedConstruction<SFTPv3Client> ignored2 = Mockito.mockConstruction(SFTPv3Client.class, (mock, context) -> {
+            Mockito.when(mock._stat(Mockito.anyString())).thenReturn(fileAttributesMock);
+            Mockito.when(mock.ls(Mockito.anyString())).thenReturn(fileListMock);
+        })) {
+
+            answer = citrixResourceBase.listFilesAtPath(command);
+        }
+
+        Assert.assertTrue(answer instanceof ListDataStoreObjectsAnswer);
+        ListDataStoreObjectsAnswer listAnswer = (ListDataStoreObjectsAnswer) answer;
+        Assert.assertTrue(listAnswer.isPathExists());
+        Assert.assertEquals(14, listAnswer.getCount());
+        Assert.assertEquals("file1", listAnswer.getNames().get(0));
+        Assert.assertEquals("/path/to/files/file1", listAnswer.getPaths().get(0));
+        Assert.assertFalse(listAnswer.getIsDirs().get(0));
+        Assert.assertEquals(1024L, listAnswer.getSizes().get(0).longValue());
+        Assert.assertEquals(123456789000L, listAnswer.getLastModified().get(0).longValue());
+    }
+
+    @Test
+    public void testListFilesAtPathWithNonExistentPath() throws IOException, XmlRpcException {
+        Connection connectionMock = Mockito.mock(Connection.class);
+        SR srMock = Mockito.mock(SR.class);
+
+        ListDataStoreObjectsCommand command = Mockito.mock(ListDataStoreObjectsCommand.class);
+        DataStoreTO store = Mockito.mock(DataStoreTO.class);
+        Mockito.when(command.getStore()).thenReturn(store);
+        Mockito.when(store.getUuid()).thenReturn("storeUuid");
+        Mockito.when(command.getStartIndex()).thenReturn(0);
+        Mockito.when(command.getPageSize()).thenReturn(10);
+        Mockito.when(command.getPath()).thenReturn("/path/to/non/existent/files");
+
+        Mockito.doReturn(connectionMock).when(citrixResourceBase).getConnection();
+        Mockito.doReturn(srMock).when(citrixResourceBase).getStorageRepository(connectionMock, "storeUuid");
+
+        Answer answer;
+        try (MockedConstruction<com.trilead.ssh2.Connection> ignored = Mockito.mockConstruction(com.trilead.ssh2.Connection.class, (mock, context) -> {
+            Mockito.when(mock.authenticateWithPassword(Mockito.any(), Mockito.any())).thenReturn(true);
+            Mockito.when(mock.connect(null, 60000, 60000)).thenReturn(null);
+        }); MockedConstruction<SFTPv3Client> ignored2 = Mockito.mockConstruction(SFTPv3Client.class, (mock, context) -> {
+            Mockito.when(mock._stat(Mockito.anyString())).thenReturn(null);
+        })) {
+
+            answer = citrixResourceBase.listFilesAtPath(command);
+        }
+
+        Assert.assertTrue(answer instanceof ListDataStoreObjectsAnswer);
+        ListDataStoreObjectsAnswer listAnswer = (ListDataStoreObjectsAnswer) answer;
+        Assert.assertFalse(listAnswer.isPathExists());
+        Assert.assertEquals(0, listAnswer.getCount());
+        Assert.assertEquals(0, listAnswer.getNames().size());
+        Assert.assertEquals(0, listAnswer.getPaths().size());
+        Assert.assertEquals(0, listAnswer.getIsDirs().size());
+        Assert.assertEquals(0, listAnswer.getSizes().size());
+        Assert.assertEquals(0, listAnswer.getLastModified().size());
+    }
+
+    @Test
+    public void testListFilesAtPathWithFile() throws IOException, XmlRpcException {
+        Connection connectionMock = Mockito.mock(Connection.class);
+        SR srMock = Mockito.mock(SR.class);
+        SFTPv3FileAttributes fileAttributesMock = Mockito.mock(SFTPv3FileAttributes.class);
+        Vector fileListMock = new Vector();
+        fileListMock.add(fileAttributesMock);
+
+        ListDataStoreObjectsCommand command = Mockito.mock(ListDataStoreObjectsCommand.class);
+        DataStoreTO store = Mockito.mock(DataStoreTO.class);
+        Mockito.when(command.getStore()).thenReturn(store);
+        Mockito.when(store.getUuid()).thenReturn("storeUuid");
+        Mockito.when(command.getStartIndex()).thenReturn(0);
+        Mockito.when(command.getPageSize()).thenReturn(10);
+        Mockito.when(command.getPath()).thenReturn("/path/to/file");
+
+        Mockito.doReturn(connectionMock).when(citrixResourceBase).getConnection();
+        Mockito.doReturn(srMock).when(citrixResourceBase).getStorageRepository(connectionMock, "storeUuid");
+        ReflectionTestUtils.setField(fileAttributesMock, "size", 1024L);
+        ReflectionTestUtils.setField(fileAttributesMock, "mtime", 123456789L);
+
+        Answer answer;
+        try (MockedConstruction<com.trilead.ssh2.Connection> ignored = Mockito.mockConstruction(com.trilead.ssh2.Connection.class, (mock, context) -> {
+            Mockito.when(mock.authenticateWithPassword(Mockito.any(), Mockito.any())).thenReturn(true);
+            Mockito.when(mock.connect(null, 60000, 60000)).thenReturn(null);
+        }); MockedConstruction<SFTPv3Client> ignored2 = Mockito.mockConstruction(SFTPv3Client.class, (mock, context) -> {
+            Mockito.when(mock._stat(Mockito.anyString())).thenReturn(fileAttributesMock);
+            Mockito.when(mock.ls(Mockito.anyString())).thenThrow(Mockito.mock(SFTPException.class));
+        })) {
+
+            answer = citrixResourceBase.listFilesAtPath(command);
+        }
+
+        Assert.assertTrue(answer instanceof ListDataStoreObjectsAnswer);
+        ListDataStoreObjectsAnswer listAnswer = (ListDataStoreObjectsAnswer) answer;
+        Assert.assertTrue(listAnswer.isPathExists());
+        Assert.assertEquals(1, listAnswer.getCount());
+        Assert.assertEquals("file", listAnswer.getNames().get(0));
+        Assert.assertEquals("/path/to/file", listAnswer.getPaths().get(0));
+        Assert.assertFalse(listAnswer.getIsDirs().get(0));
+        Assert.assertEquals(1024L, listAnswer.getSizes().get(0).longValue());
+        Assert.assertEquals(123456789000L, listAnswer.getLastModified().get(0).longValue());
+    }
 }
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpOssResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpOssResourceTest.java
index 8f703ed..e02279b 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpOssResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpOssResourceTest.java
@@ -43,7 +43,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpServerResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpServerResourceTest.java
index 6a926a7..ed074be 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpServerResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpServerResourceTest.java
@@ -46,8 +46,8 @@
     }
 
     @Test(expected = CloudRuntimeException.class)
-    public void testGetFilesExeption() {
-        testGetPathFilesExeption();
+    public void testGetFilesException() {
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56FP1ResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56FP1ResourceTest.java
index 902e8fd..d7f1774 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56FP1ResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56FP1ResourceTest.java
@@ -43,7 +43,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56ResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56ResourceTest.java
index 1762772..a33549a 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56ResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56ResourceTest.java
@@ -47,7 +47,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56SP2ResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56SP2ResourceTest.java
index 65a9b47..c3c8d46 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56SP2ResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56SP2ResourceTest.java
@@ -43,7 +43,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer600ResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer600ResourceTest.java
index 2175884..773dd57 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer600ResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer600ResourceTest.java
@@ -43,7 +43,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer625ResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer625ResourceTest.java
index fc14d9f..7fb3f7e 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer625ResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer625ResourceTest.java
@@ -43,7 +43,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer650ResourceTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer650ResourceTest.java
index be41840..dbb4198 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer650ResourceTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer650ResourceTest.java
@@ -43,7 +43,7 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testGetFiles() {
-        testGetPathFilesExeption();
+        testGetPathFilesException();
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessorTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessorTest.java
index 0705194..5f2ae88 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessorTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessorTest.java
@@ -37,10 +37,9 @@
 import org.mockito.InOrder;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.xensource.xenapi.Connection;
@@ -51,7 +50,7 @@
 import com.xensource.xenapi.Types.InternalError;
 import com.xensource.xenapi.Types.XenAPIException;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class Xenserver625StorageProcessorTest {
 
     @InjectMocks
@@ -109,10 +108,8 @@
     @Test
     public void createFileSRTestSrAlreadyConfigured() {
         SR srMockRetrievedMethod = Mockito.mock(SR.class);
-        SR srMockCreateMethod = Mockito.mock(SR.class);
 
         Mockito.doReturn(srMockRetrievedMethod).when(xenserver625StorageProcessor).retrieveAlreadyConfiguredSrWithoutException(connectionMock, pathMock);
-        Mockito.doReturn(srMockCreateMethod).when(xenserver625StorageProcessor).createNewFileSr(connectionMock, pathMock);
 
         SR methodCreateFileSrResult = xenserver625StorageProcessor.createFileSR(connectionMock, pathMock);
 
@@ -172,47 +169,39 @@
     }
 
     @Test
-    @PrepareForTest(SR.class)
     public void retrieveAlreadyConfiguredSrTestNoSrFound() throws XenAPIException, XmlRpcException {
-        prepareToReturnSrs(null);
+        try (MockedStatic<SR> srMocked = Mockito.mockStatic(SR.class)) {
+            Mockito.when(SR.getByNameLabel(connectionMock, pathMock)).thenReturn(null);
+            SR sr = xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
 
-        SR sr = xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
-
-        PowerMockito.verifyStatic(SR.class);
-        SR.getByNameLabel(connectionMock, pathMock);
-        Assert.assertNull(sr);
+            srMocked.verify(() -> SR.getByNameLabel(connectionMock, pathMock), times(1));
+            Assert.assertNull(sr);
+        }
     }
 
-    private void prepareToReturnSrs(Set<SR> srs) throws XenAPIException, XmlRpcException {
-        PowerMockito.mockStatic(SR.class);
-        PowerMockito.when(SR.getByNameLabel(connectionMock, pathMock)).thenReturn(srs);
-    }
-
-    @PrepareForTest(SR.class)
     @Test(expected = CloudRuntimeException.class)
     public void retrieveAlreadyConfiguredSrTestMultipleSrsFound() throws XenAPIException, XmlRpcException {
         HashSet<SR> srs = new HashSet<>();
         srs.add(Mockito.mock(SR.class));
         srs.add(Mockito.mock(SR.class));
 
-        prepareToReturnSrs(srs);
+        try (MockedStatic<SR> ignored = Mockito.mockStatic(SR.class)) {
+            Mockito.when(SR.getByNameLabel(connectionMock, pathMock)).thenReturn(srs);
 
-        xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
+            xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
+        }
     }
 
     @Test
-    @PrepareForTest(SR.class)
     public void retrieveAlreadyConfiguredSrTestSrFailsSanityCheckWithXenAPIException() throws XenAPIException, XmlRpcException {
         configureAndExecuteMethodRetrieveAlreadyConfiguredSrTestSrFailsSanityCheckForException(XenAPIException.class);
     }
 
     @Test
-    @PrepareForTest(SR.class)
     public void retrieveAlreadyConfiguredSrTestSrFailsSanityCheckWithXmlRpcException() throws XenAPIException, XmlRpcException {
         configureAndExecuteMethodRetrieveAlreadyConfiguredSrTestSrFailsSanityCheckForException(XmlRpcException.class);
     }
 
-    @PrepareForTest(SR.class)
     @Test(expected = RuntimeException.class)
     public void retrieveAlreadyConfiguredSrTestSrFailsSanityCheckWithRuntimeException() throws XenAPIException, XmlRpcException {
         configureAndExecuteMethodRetrieveAlreadyConfiguredSrTestSrFailsSanityCheckForException(RuntimeException.class);
@@ -225,17 +214,18 @@
         HashSet<SR> srs = new HashSet<>();
         srs.add(srMock);
 
-        prepareToReturnSrs(srs);
-        Mockito.doNothing().when(xenserver625StorageProcessor).forgetSr(connectionMock, srMock);
+        try (MockedStatic<SR> ignored = Mockito.mockStatic(SR.class)) {
+            Mockito.when(SR.getByNameLabel(connectionMock, pathMock)).thenReturn(srs);
+            Mockito.doNothing().when(xenserver625StorageProcessor).forgetSr(connectionMock, srMock);
 
-        SR sr = xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
+            SR sr = xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
 
-        Assert.assertNull(sr);
-        Mockito.verify(xenserver625StorageProcessor).forgetSr(connectionMock, srMock);
+            Assert.assertNull(sr);
+            Mockito.verify(xenserver625StorageProcessor).forgetSr(connectionMock, srMock);
+        }
     }
 
     @Test
-    @PrepareForTest(SR.class)
     public void methodRetrieveAlreadyConfiguredSrTestSrScanSucceeds() throws XenAPIException, XmlRpcException {
         SR srMock = Mockito.mock(SR.class);
         Mockito.doNothing().when(srMock).scan(connectionMock);
@@ -243,13 +233,14 @@
         HashSet<SR> srs = new HashSet<>();
         srs.add(srMock);
 
-        prepareToReturnSrs(srs);
-        Mockito.doNothing().when(xenserver625StorageProcessor).forgetSr(connectionMock, srMock);
+        try (MockedStatic<SR> ignored = Mockito.mockStatic(SR.class)) {
+            Mockito.when(SR.getByNameLabel(connectionMock, pathMock)).thenReturn(srs);
 
-        SR sr = xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
+            SR sr = xenserver625StorageProcessor.retrieveAlreadyConfiguredSr(connectionMock, pathMock);
 
-        Assert.assertEquals(srMock, sr);
-        Mockito.verify(xenserver625StorageProcessor, times(0)).forgetSr(connectionMock, srMock);
+            Assert.assertEquals(srMock, sr);
+            Mockito.verify(xenserver625StorageProcessor, times(0)).forgetSr(connectionMock, srMock);
+        }
     }
 
     @Test
@@ -301,19 +292,16 @@
     }
 
     @Test
-    @PrepareForTest({Host.class, SR.class})
     public void createNewFileSrTestThrowingXenAPIException() throws XenAPIException, XmlRpcException {
         prepareAndExecuteTestcreateNewFileSrTestThrowingException(XenAPIException.class);
     }
 
     @Test
-    @PrepareForTest({Host.class, SR.class})
     public void createNewFileSrTestThrowingXmlRpcException() throws XenAPIException, XmlRpcException {
         prepareAndExecuteTestcreateNewFileSrTestThrowingException(XmlRpcException.class);
     }
 
     @Test(expected = RuntimeException.class)
-    @PrepareForTest({Host.class, SR.class})
     public void createNewFileSrTestThrowingRuntimeException() throws XenAPIException, XmlRpcException {
         prepareAndExecuteTestcreateNewFileSrTestThrowingException(RuntimeException.class);
     }
@@ -326,23 +314,23 @@
 
         Host hostMock = Mockito.mock(Host.class);
 
-        PowerMockito.mockStatic(Host.class);
-        PowerMockito.when(Host.getByUuid(connectionMock, uuid)).thenReturn(hostMock);
+        try (MockedStatic<Host> ignored = Mockito.mockStatic(
+                Host.class); MockedStatic<SR> ignored1 = Mockito.mockStatic(SR.class)) {
+            Mockito.when(Host.getByUuid(connectionMock, uuid)).thenReturn(hostMock);
+            Mockito.when(SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock),
+                    Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
+                    Mockito.anyMap())).thenThrow(Mockito.mock(exceptionClass));
 
-        PowerMockito.mockStatic(SR.class);
-        PowerMockito.when(SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock), Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
-                Mockito.anyMapOf(String.class, String.class))).thenThrow(Mockito.mock(exceptionClass));
 
-        Mockito.doNothing().when(xenserver625StorageProcessor).removeSrAndPbdIfPossible(Mockito.eq(connectionMock), Mockito.any(SR.class), Mockito.any(PBD.class));
+            SR sr = xenserver625StorageProcessor.createNewFileSr(connectionMock, pathMock);
 
-        SR sr = xenserver625StorageProcessor.createNewFileSr(connectionMock, pathMock);
-
-        assertNull(sr);
-        Mockito.verify(xenserver625StorageProcessor).removeSrAndPbdIfPossible(Mockito.eq(connectionMock), nullable(SR.class), nullable(PBD.class));
+            assertNull(sr);
+            Mockito.verify(xenserver625StorageProcessor).removeSrAndPbdIfPossible(Mockito.eq(connectionMock),
+                    nullable(SR.class), nullable(PBD.class));
+        }
     }
 
     @Test
-    @PrepareForTest({Host.class, SR.class})
     public void createNewFileSrTestThrowingDbUniqueException() throws XenAPIException, XmlRpcException {
         String uuid = "hostUuid";
         Mockito.when(citrixResourceBase._host.getUuid()).thenReturn(uuid);
@@ -353,61 +341,66 @@
 
         Host hostMock = Mockito.mock(Host.class);
 
-        PowerMockito.mockStatic(Host.class);
-        PowerMockito.when(Host.getByUuid(connectionMock, uuid)).thenReturn(hostMock);
+        try (MockedStatic<Host> ignored = Mockito.mockStatic(Host.class);MockedStatic<SR> ignored1 =
+                Mockito.mockStatic(SR.class) ) {
+            Mockito.when(Host.getByUuid(connectionMock, uuid)).thenReturn(hostMock);
 
-        PowerMockito.mockStatic(SR.class);
-        InternalError dbUniquenessException = new InternalError("message: Db_exn.Uniqueness_constraint_violation(\"SR\", \"uuid\", \"fd3edbcf-f142-83d1-3fcb-029ca2446b68\")");
+            InternalError dbUniquenessException = new InternalError(
+                    "message: Db_exn.Uniqueness_constraint_violation(\"SR\", \"uuid\", \"fd3edbcf-f142-83d1-3fcb-029ca2446b68\")");
 
-        PowerMockito.when(SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock), Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
-                Mockito.anyMapOf(String.class, String.class))).thenThrow(dbUniquenessException);
+            Mockito.when(SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock),
+                    Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
+                    Mockito.anyMap())).thenThrow(dbUniquenessException);
 
-        Mockito.doNothing().when(xenserver625StorageProcessor).removeSrAndPbdIfPossible(Mockito.eq(connectionMock), Mockito.any(SR.class), Mockito.any(PBD.class));
+            SR sr = xenserver625StorageProcessor.createNewFileSr(connectionMock, pathMock);
 
-        SR sr = xenserver625StorageProcessor.createNewFileSr(connectionMock, pathMock);
-
-        Assert.assertEquals(srMock, sr);
-        Mockito.verify(xenserver625StorageProcessor, times(0)).removeSrAndPbdIfPossible(Mockito.eq(connectionMock), Mockito.any(SR.class), Mockito.any(PBD.class));
-        Mockito.verify(xenserver625StorageProcessor).retrieveAlreadyConfiguredSrWithoutException(connectionMock, pathMock);
+            Assert.assertEquals(srMock, sr);
+            Mockito.verify(xenserver625StorageProcessor, times(0)).removeSrAndPbdIfPossible(Mockito.eq(connectionMock),
+                    Mockito.any(SR.class), Mockito.any(PBD.class));
+            Mockito.verify(xenserver625StorageProcessor).retrieveAlreadyConfiguredSrWithoutException(connectionMock,
+                    pathMock);
+        }
     }
 
     @Test
-    @PrepareForTest({Host.class, SR.class, PBD.class})
     public void createNewFileSrTest() throws XenAPIException, XmlRpcException {
         String uuid = "hostUuid";
         Mockito.when(citrixResourceBase._host.getUuid()).thenReturn(uuid);
 
         SR srMock = Mockito.mock(SR.class);
-        Mockito.doReturn(srMock).when(xenserver625StorageProcessor).retrieveAlreadyConfiguredSrWithoutException(connectionMock, pathMock);
         String srUuid = UUID.nameUUIDFromBytes(pathMock.getBytes()).toString();
 
         Host hostMock = Mockito.mock(Host.class);
 
-        PowerMockito.mockStatic(Host.class);
-        PowerMockito.when(Host.getByUuid(connectionMock, uuid)).thenReturn(hostMock);
+        try (MockedStatic<Host> ignored = Mockito.mockStatic(Host.class);MockedStatic<SR> ignored1 =
+                Mockito.mockStatic(SR.class);MockedStatic<PBD> pbdMockedStatic = Mockito.mockStatic(PBD.class)) {
+            Mockito.when(Host.getByUuid(connectionMock, uuid)).thenReturn(hostMock);
 
-        PowerMockito.mockStatic(SR.class);
-        PowerMockito.when(SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock), Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
-                Mockito.anyMapOf(String.class, String.class))).thenReturn(srMock);
 
-        PowerMockito.mockStatic(PBD.class);
-        PBD pbdMock = Mockito.mock(PBD.class);
-        PowerMockito.when(PBD.create(Mockito.eq(connectionMock), Mockito.any(Record.class))).thenReturn(pbdMock);
+            Mockito.when(SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock),
+                    Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
+                    Mockito.anyMap())).thenReturn(srMock);
 
-        Mockito.doNothing().when(xenserver625StorageProcessor).removeSrAndPbdIfPossible(Mockito.eq(connectionMock), Mockito.any(SR.class), Mockito.any(PBD.class));
-        SR sr = xenserver625StorageProcessor.createNewFileSr(connectionMock, pathMock);
+            PBD pbdMock = Mockito.mock(PBD.class);
+            Mockito.when(PBD.create(Mockito.eq(connectionMock), Mockito.any(Record.class))).thenReturn(pbdMock);
 
-        Assert.assertEquals(srMock, sr);
-        Mockito.verify(xenserver625StorageProcessor, times(0)).removeSrAndPbdIfPossible(Mockito.eq(connectionMock), Mockito.any(SR.class), Mockito.any(PBD.class));
-        Mockito.verify(xenserver625StorageProcessor, times(0)).retrieveAlreadyConfiguredSrWithoutException(connectionMock, pathMock);
+            SR sr = xenserver625StorageProcessor.createNewFileSr(connectionMock, pathMock);
 
-        Mockito.verify(srMock).scan(connectionMock);
-        Mockito.verify(pbdMock).plug(connectionMock);
+            Assert.assertEquals(srMock, sr);
+            Mockito.verify(xenserver625StorageProcessor, times(0)).removeSrAndPbdIfPossible(Mockito.eq(connectionMock),
+                    Mockito.any(SR.class), Mockito.any(PBD.class));
+            Mockito.verify(xenserver625StorageProcessor, times(0)).retrieveAlreadyConfiguredSrWithoutException(
+                    connectionMock, pathMock);
 
-        PowerMockito.verifyStatic(PBD.class);
-        SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock), Mockito.eq(pathMock), Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
-                Mockito.anyMapOf(String.class, String.class));
-        PBD.create(Mockito.eq(connectionMock), Mockito.any(Record.class));
+            Mockito.verify(srMock).scan(connectionMock);
+            Mockito.verify(pbdMock).plug(connectionMock);
+
+            SR.introduce(Mockito.eq(connectionMock), Mockito.eq(srUuid), Mockito.eq(pathMock), Mockito.eq(pathMock),
+                    Mockito.eq("file"), Mockito.eq("file"), Mockito.eq(false),
+                    Mockito.anyMap());
+
+            pbdMockedStatic.verify(() -> PBD.create(Mockito.eq(connectionMock), Mockito.any(Record.class)));
+        }
     }
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java
index 9d18e73..9ecb14d 100755
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java
@@ -40,14 +40,12 @@
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.xmlrpc.XmlRpcException;
-import org.apache.xmlrpc.client.XmlRpcClient;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.AttachIsoCommand;
@@ -144,9 +142,9 @@
 import com.xensource.xenapi.Types.XenAPIException;
 import com.xensource.xenapi.VM;
 import com.xensource.xenapi.VMGuestMetrics;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(value = {Pool.Record.class})
+@RunWith(MockitoJUnitRunner.class)
 public class CitrixRequestWrapperTest {
 
     @Mock
@@ -411,15 +409,12 @@
     @Test
     public void testDeleteStoragePoolCommand() {
         final StoragePoolVO poolVO = Mockito.mock(StoragePoolVO.class);
-        final XsHost xsHost = Mockito.mock(XsHost.class);
 
         final DeleteStoragePoolCommand deleteStorageCommand = new DeleteStoragePoolCommand(poolVO);
 
         final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance();
         assertNotNull(wrapper);
 
-        when(citrixResourceBase.getHost()).thenReturn(xsHost);
-
         final Answer answer = wrapper.execute(deleteStorageCommand, citrixResourceBase);
         verify(citrixResourceBase, times(1)).getConnection();
 
@@ -536,7 +531,6 @@
 
     @Test
     public void testSetupCommand() {
-        final XsHost xsHost = Mockito.mock(XsHost.class);
         final HostEnvironment env = Mockito.mock(HostEnvironment.class);
 
         final SetupCommand setupCommand = new SetupCommand(env);
@@ -544,8 +538,6 @@
         final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance();
         assertNotNull(wrapper);
 
-        when(citrixResourceBase.getHost()).thenReturn(xsHost);
-
         final Answer answer = wrapper.execute(setupCommand, citrixResourceBase);
         verify(citrixResourceBase, times(1)).getConnection();
 
@@ -560,10 +552,6 @@
 
         final Connection conn = Mockito.mock(Connection.class);
         final XsHost xsHost = Mockito.mock(XsHost.class);
-        final XmlRpcClient client = Mockito.mock(XmlRpcClient.class);
-
-        // final Host.Record hr = PowerMockito.mock(Host.Record.class);
-        // final Host host = PowerMockito.mock(Host.class);
 
         final MaintainCommand maintainCommand = new MaintainCommand();
 
@@ -582,8 +570,8 @@
 
         try {
             final Object[] params = { Marshalling.toXMLRPC("befc4dcd"), Marshalling.toXMLRPC(uuid) };
-            when(client.execute("host.get_by_uuid", new Object[] { "befc4dcd", uuid })).thenReturn(spiedMap);
-            PowerMockito.when(conn, "dispatch", "host.get_by_uuid", params).thenReturn(spiedMap);
+
+            when(ReflectionTestUtils.invokeMethod(conn, "dispatch", "host.get_by_uuid", params)).thenReturn(spiedMap);
         } catch (final Exception e) {
             fail(e.getMessage());
         }
@@ -936,11 +924,6 @@
             when(citrixResourceBase.findOrCreateTunnelNetwork(conn, physicalTopology.getBridgeName())).thenReturn(network);
             when(network.getBridge(conn)).thenReturn(bridge);
 
-            when(
-                            citrixResourceBase.callHostPlugin(conn, "ovstunnel", "configure_ovs_bridge_for_network_topology", "bridge", bridge, "config",
-                                            physicalTopology.getVpcConfigInJson(), "host-id", ((Long) physicalTopology.getHostId()).toString(), "seq-no", Long.toString(1))).thenReturn(
-                            "SUCCESS");
-
         } catch (final BadServerResponse e) {
             fail(e.getMessage());
         } catch (final XenAPIException e) {
@@ -975,11 +958,6 @@
             when(citrixResourceBase.findOrCreateTunnelNetwork(conn, routingPolicy.getBridgeName())).thenReturn(network);
             when(network.getBridge(conn)).thenReturn(bridge);
 
-            when(
-                            citrixResourceBase.callHostPlugin(conn, "ovstunnel", "configure_ovs_bridge_for_routing_policies", "bridge", bridge, "host-id",
-                                            ((Long) routingPolicy.getHostId()).toString(), "config", routingPolicy.getVpcConfigInJson(), "seq-no", Long.toString(1))).thenReturn(
-                            "SUCCESS");
-
         } catch (final BadServerResponse e) {
             fail(e.getMessage());
         } catch (final XenAPIException e) {
@@ -1468,7 +1446,7 @@
         final Connection conn = Mockito.mock(Connection.class);
         final XsHost xsHost = Mockito.mock(XsHost.class);
 
-        final Pool pool = PowerMockito.mock(Pool.class);
+        final Pool pool = Mockito.mock(Pool.class);
         final Pool.Record poolr = Mockito.mock(Pool.Record.class);
         final Host.Record hostr = Mockito.mock(Host.Record.class);
         final Host master = Mockito.mock(Host.class);
@@ -1479,30 +1457,16 @@
         assertNotNull(wrapper);
 
         when(citrixResourceBase.getConnection()).thenReturn(conn);
-        try {
+        try (MockedStatic<Pool.Record> ignored = Mockito.mockStatic(Pool.Record.class)){
             when(citrixResourceBase.getHost()).thenReturn(xsHost);
-            when(citrixResourceBase.getHost().getUuid()).thenReturn(uuid);
-
-            PowerMockito.mockStatic(Pool.Record.class);
-
-            when(pool.getRecord(conn)).thenReturn(poolr);
             poolr.master = master;
-            when(poolr.master.getRecord(conn)).thenReturn(hostr);
-            hostr.uuid = uuid;
 
-        } catch (final BadServerResponse e) {
-            fail(e.getMessage());
-        } catch (final XenAPIException e) {
-            fail(e.getMessage());
-        } catch (final XmlRpcException e) {
-            fail(e.getMessage());
+            final Answer answer = wrapper.execute(vmDataSync, citrixResourceBase);
+
+            verify(citrixResourceBase, times(1)).getConnection();
+
+            assertTrue(answer.getResult());
         }
-
-        final Answer answer = wrapper.execute(vmDataSync, citrixResourceBase);
-
-        verify(citrixResourceBase, times(1)).getConnection();
-
-        assertTrue(answer.getResult());
     }
 
     @Test
@@ -1695,14 +1659,6 @@
         when(citrixResourceBase.getHost()).thenReturn(xsHost);
         when(citrixResourceBase.getHost().getUuid()).thenReturn(uuid);
 
-        try {
-            when(citrixResourceBase.isDmcEnabled(conn, host)).thenReturn(true);
-        } catch (final XenAPIException e) {
-            fail(e.getMessage());
-        } catch (final XmlRpcException e) {
-            fail(e.getMessage());
-        }
-
         final Answer answer = wrapper.execute(scaleVm, citrixResourceBase);
 
         verify(citrixResourceBase, times(1)).getConnection();
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XcpServerWrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XcpServerWrapperTest.java
index 6b99905..98a6f2b 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XcpServerWrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XcpServerWrapperTest.java
@@ -31,18 +31,17 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.NetworkUsageCommand;
 import com.cloud.hypervisor.xenserver.resource.XcpServerResource;
-import com.cloud.utils.exception.CloudRuntimeException;
 import com.xensource.xenapi.Connection;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class XcpServerWrapperTest {
 
     @Mock
@@ -116,7 +115,6 @@
         assertNotNull(wrapper);
 
         when(XcpServerResource.getConnection()).thenReturn(conn);
-        when(XcpServerResource.networkUsage(conn, usageCommand.getPrivateIP(), "create", null)).thenThrow(new CloudRuntimeException("FAILED"));
 
         final Answer answer = wrapper.execute(usageCommand, XcpServerResource);
 
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56FP1WrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56FP1WrapperTest.java
index 77be68b..3b8525d 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56FP1WrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56FP1WrapperTest.java
@@ -26,7 +26,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.FenceCommand;
@@ -35,7 +35,7 @@
 import com.cloud.vm.VMInstanceVO;
 import com.xensource.xenapi.Connection;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class XenServer56FP1WrapperTest {
 
     @Mock
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56WrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56WrapperTest.java
index 21d6c53..a0c3e45 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56WrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56WrapperTest.java
@@ -28,7 +28,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckOnHostCommand;
@@ -40,7 +40,6 @@
 import com.cloud.host.Host;
 import com.cloud.host.HostEnvironment;
 import com.cloud.hypervisor.xenserver.resource.XenServer56Resource;
-import com.cloud.hypervisor.xenserver.resource.XsHost;
 import com.cloud.network.router.VirtualRouterAutoScale;
 import com.cloud.network.router.VirtualRouterAutoScale.AutoScaleMetrics;
 import com.cloud.network.router.VirtualRouterAutoScale.AutoScaleMetricsValue;
@@ -52,7 +51,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class XenServer56WrapperTest {
 
     @Mock
@@ -260,7 +259,6 @@
 
     @Test
     public void testSetupCommand() {
-        final XsHost xsHost = Mockito.mock(XsHost.class);
         final HostEnvironment env = Mockito.mock(HostEnvironment.class);
 
         final SetupCommand setupCommand = new SetupCommand(env);
@@ -268,8 +266,6 @@
         final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance();
         assertNotNull(wrapper);
 
-        when(xenServer56Resource.getHost()).thenReturn(xsHost);
-
         final Answer answer = wrapper.execute(setupCommand, xenServer56Resource);
         verify(xenServer56Resource, times(1)).getConnection();
 
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
index fdb09f1..4b2dd1a 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java
@@ -29,13 +29,12 @@
 import java.util.List;
 import java.util.Map;
 
-import com.google.gson.Gson;
 import org.apache.xmlrpc.XmlRpcException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckNetworkCommand;
@@ -60,13 +59,12 @@
 import com.xensource.xenapi.Connection;
 import com.xensource.xenapi.Network;
 import com.xensource.xenapi.SR;
-import com.xensource.xenapi.Task;
-import com.xensource.xenapi.Types.BadServerResponse;
 import com.xensource.xenapi.Types.XenAPIException;
 import com.xensource.xenapi.VDI;
 import com.xensource.xenapi.VIF;
 
-@RunWith(PowerMockRunner.class)
+
+@RunWith(MockitoJUnitRunner.class)
 public class XenServer610WrapperTest {
 
     @Mock
@@ -105,7 +103,6 @@
     public void testMigrateWithStorageCommand() {
         final String vmName = "small";
         final String uuid = "206b21a7-c6ec-40e2-b5e2-f861b9612f04";
-        final String path = "/";
 
         final Connection conn = Mockito.mock(Connection.class);
         final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
@@ -128,12 +125,6 @@
         final Network networkForSm = Mockito.mock(Network.class);
         final XsHost xsHost = Mockito.mock(XsHost.class);
 
-        final SR sr1 = Mockito.mock(SR.class);
-        final SR sr2 = Mockito.mock(SR.class);
-
-        final VDI vdi1 = Mockito.mock(VDI.class);
-        final VDI vdi2 = Mockito.mock(VDI.class);
-
         final MigrateWithStorageCommand migrateStorageCommand = new MigrateWithStorageCommand(vmSpec, volumeToFiler);
 
         final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance();
@@ -143,17 +134,7 @@
         when(vmSpec.getName()).thenReturn(vmName);
         when(vmSpec.getNics()).thenReturn(nicTOs);
 
-        when(storage1.getUuid()).thenReturn(uuid);
-        when(storage2.getUuid()).thenReturn(uuid);
 
-        when(vol1.getPath()).thenReturn(path);
-        when(vol2.getPath()).thenReturn(path);
-
-        when(xenServer610Resource.getStorageRepository(conn, storage1.getUuid())).thenReturn(sr1);
-        when(xenServer610Resource.getStorageRepository(conn, storage2.getUuid())).thenReturn(sr2);
-
-        when(xenServer610Resource.getVDIbyUuid(conn, storage1.getPath())).thenReturn(vdi1);
-        when(xenServer610Resource.getVDIbyUuid(conn, storage2.getPath())).thenReturn(vdi2);
 
         try {
             when(xenServer610Resource.getNativeNetworkForTraffic(conn, TrafficType.Storage, null)).thenReturn(nativeNetworkForTraffic);
@@ -221,9 +202,6 @@
         final Network nw2 = Mockito.mock(Network.class);
         final Network nw3 = Mockito.mock(Network.class);
 
-        final SR sr1 = Mockito.mock(SR.class);
-        final SR sr2 = Mockito.mock(SR.class);
-
         final MigrateWithStorageReceiveCommand migrateStorageCommand = new MigrateWithStorageReceiveCommand(vmSpec, volumeToFiler);
 
         final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance();
@@ -233,14 +211,7 @@
         when(vmSpec.getName()).thenReturn(vmName);
         when(vmSpec.getNics()).thenReturn(nicTOs);
 
-        when(storage1.getUuid()).thenReturn(uuid);
-        when(storage2.getUuid()).thenReturn(uuid);
-
-        when(xenServer610Resource.getStorageRepository(conn, storage1.getUuid())).thenReturn(sr1);
-        when(xenServer610Resource.getStorageRepository(conn, storage2.getUuid())).thenReturn(sr2);
-
         try {
-
             when(xenServer610Resource.getNetwork(conn, nicTO1)).thenReturn(nw1);
             when(xenServer610Resource.getNetwork(conn, nicTO2)).thenReturn(nw2);
             when(xenServer610Resource.getNetwork(conn, nicTO3)).thenReturn(nw3);
@@ -288,8 +259,8 @@
         final Connection conn = Mockito.mock(Connection.class);
         final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
 
-        final VolumeTO volume1 = Mockito.mock(VolumeTO.class);
-        final VolumeTO volume2 = Mockito.mock(VolumeTO.class);
+        final VolumeTO volume1 = MockVolumeTO(path);
+        final VolumeTO volume2 = MockVolumeTO(path);
 
         final SR sr1 = Mockito.mock(SR.class);
         final SR sr2 = Mockito.mock(SR.class);
@@ -324,9 +295,6 @@
         when(xenServer610Resource.getConnection()).thenReturn(conn);
         when(vmSpec.getName()).thenReturn(vmName);
 
-        when(volume1.getPath()).thenReturn(path);
-        when(volume2.getPath()).thenReturn(path);
-
         when(nic1.getMac()).thenReturn(mac);
         when(nic2.getMac()).thenReturn(mac);
 
@@ -398,8 +366,8 @@
         final Connection conn = Mockito.mock(Connection.class);
         final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
 
-        final VolumeTO volume1 = Mockito.mock(VolumeTO.class);
-        final VolumeTO volume2 = Mockito.mock(VolumeTO.class);
+        final VolumeTO volume1 = MockVolumeTO(path);
+        final VolumeTO volume2 = MockVolumeTO(path);
 
         final SR sr1 = Mockito.mock(SR.class);
         final SR sr2 = Mockito.mock(SR.class);
@@ -410,7 +378,6 @@
         final NicTO nic1 = Mockito.mock(NicTO.class);
         final NicTO nic2 = Mockito.mock(NicTO.class);
 
-        Gson gson = new Gson();
         final List<Pair<VolumeTO, Object>> volumeToSr = new ArrayList<Pair<VolumeTO, Object>>();
         volumeToSr.add(new Pair<VolumeTO, Object>(volume1, sr1));
         volumeToSr.add(new Pair<VolumeTO, Object>(volume2, sr2));
@@ -429,9 +396,6 @@
         when(xenServer610Resource.getConnection()).thenReturn(conn);
         when(vmSpec.getName()).thenReturn(vmName);
 
-        when(volume1.getPath()).thenReturn(path);
-        when(volume2.getPath()).thenReturn(path);
-
         when(xenServer610Resource.getVDIbyUuid(conn, volume1.getPath())).thenReturn(vdi1);
         when(xenServer610Resource.getVDIbyUuid(conn, volume2.getPath())).thenReturn(vdi2);
 
@@ -471,12 +435,9 @@
 
     @Test
     public void testXenServer610MigrateVolumeCommandWrapper() {
-        final String uuid = "206b21a7-c6ec-40e2-b5e2-f861b9612f04";
 
         final Connection conn = Mockito.mock(Connection.class);
-        final SR destinationPool = Mockito.mock(SR.class);
         final VDI srcVolume = Mockito.mock(VDI.class);
-        final Task task = Mockito.mock(Task.class);
 
         final long volumeId = 1l;
         final String volumePath = "206b21a7-c6ec-40e2-b5e2-f861b9612f04";
@@ -492,19 +453,8 @@
         assertNotNull(wrapper);
 
         when(xenServer610Resource.getConnection()).thenReturn(conn);
-        when(pool.getUuid()).thenReturn(uuid);
-        when(xenServer610Resource.getStorageRepository(conn, uuid)).thenReturn(destinationPool);
         when(xenServer610Resource.getVDIbyUuid(conn, volumePath)).thenReturn(srcVolume);
 
-        try {
-            when(srcVolume.poolMigrateAsync(conn, destinationPool, other)).thenReturn(task);
-        } catch (final BadServerResponse e) {
-            fail(e.getMessage());
-        } catch (final XenAPIException e) {
-            fail(e.getMessage());
-        } catch (final XmlRpcException e) {
-            fail(e.getMessage());
-        }
 
         when(xenServer610Resource.getMigrateWait()).thenReturn(120);
 
@@ -525,4 +475,10 @@
 
         assertFalse(answer.getResult());
     }
+
+    VolumeTO MockVolumeTO(String path){
+        VolumeTO vol = Mockito.mock(VolumeTO.class);
+        when(vol.getPath()).thenReturn(path);
+        return vol;
+    }
 }
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620SP1WrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620SP1WrapperTest.java
index 2645229..495370d 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620SP1WrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620SP1WrapperTest.java
@@ -31,7 +31,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.GetGPUStatsCommand;
@@ -41,7 +41,7 @@
 import com.xensource.xenapi.Connection;
 import com.xensource.xenapi.Types.XenAPIException;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class XenServer620SP1WrapperTest {
 
     @Mock
diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620WrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620WrapperTest.java
index fc3d003..fe694a6 100644
--- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620WrapperTest.java
+++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620WrapperTest.java
@@ -23,14 +23,14 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.CheckNetworkCommand;
 import com.cloud.hypervisor.xenserver.resource.XenServer620Resource;
 import com.cloud.network.PhysicalNetworkSetupInfo;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class XenServer620WrapperTest {
 
     @Test
diff --git a/plugins/hypervisors/xenserver/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/hypervisors/xenserver/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/hypervisors/xenserver/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/integrations/cloudian/pom.xml b/plugins/integrations/cloudian/pom.xml
index 0ef4e0f..8df4daa 100644
--- a/plugins/integrations/cloudian/pom.xml
+++ b/plugins/integrations/cloudian/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/integrations/kubernetes-service/pom.xml b/plugins/integrations/kubernetes-service/pom.xml
index c7d3076..4e42c75 100644
--- a/plugins/integrations/kubernetes-service/pom.xml
+++ b/plugins/integrations/kubernetes-service/pom.xml
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java
index b8f399b..591da07 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java
@@ -32,6 +32,10 @@
  */
 public interface KubernetesCluster extends ControlledEntity, com.cloud.utils.fsm.StateObject<KubernetesCluster.State>, Identity, InternalIdentity, Displayable {
 
+    enum ClusterType {
+        CloudManaged, ExternalManaged;
+    };
+
     enum Event {
         StartRequested,
         StopRequested,
@@ -115,10 +119,10 @@
     String getName();
     String getDescription();
     long getZoneId();
-    long getKubernetesVersionId();
-    long getServiceOfferingId();
-    long getTemplateId();
-    long getNetworkId();
+    Long getKubernetesVersionId();
+    Long getServiceOfferingId();
+    Long getTemplateId();
+    Long getNetworkId();
     long getDomainId();
     long getAccountId();
     long getControlNodeCount();
@@ -137,4 +141,5 @@
     Long getMinSize();
     Long getMaxSize();
     Long getSecurityGroupId();
+    ClusterType getClusterType();
 }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
index 4bd6ec6..281fe84 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
@@ -17,6 +17,7 @@
 package com.cloud.kubernetes.cluster;
 
 import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+import static com.cloud.vm.UserVmManager.AllowUserExpungeRecoverVm;
 
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -25,8 +26,11 @@
 import java.util.Date;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
@@ -36,17 +40,22 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.uservm.UserVm;
+import com.cloud.vm.UserVmService;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiConstants.VMDetails;
+import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.CreateKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.DeleteKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.GetKubernetesClusterConfigCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.ListKubernetesClustersCmd;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMachinesFromKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.ScaleKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.StartKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd;
@@ -54,6 +63,7 @@
 import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse;
 import org.apache.cloudstack.api.response.KubernetesClusterResponse;
 import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.RemoveVirtualMachinesFromKubernetesClusterResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.config.ApiServiceConfiguration;
 import org.apache.cloudstack.context.CallContext;
@@ -247,6 +257,9 @@
     @Inject
     public NetworkHelper networkHelper;
 
+    @Inject
+    private UserVmService userVmService;
+
     private void logMessage(final Level logLevel, final String message, final Exception e) {
         if (logLevel == Level.WARN) {
             if (e != null) {
@@ -542,10 +555,14 @@
         response.setControlNodes(kubernetesCluster.getControlNodeCount());
         response.setClusterSize(kubernetesCluster.getNodeCount());
         VMTemplateVO template = ApiDBUtils.findTemplateById(kubernetesCluster.getTemplateId());
-        response.setTemplateId(template.getUuid());
+        if (template != null) {
+            response.setTemplateId(template.getUuid());
+        }
         ServiceOfferingVO offering = serviceOfferingDao.findById(kubernetesCluster.getServiceOfferingId());
-        response.setServiceOfferingId(offering.getUuid());
-        response.setServiceOfferingName(offering.getName());
+        if (offering != null) {
+            response.setServiceOfferingId(offering.getUuid());
+            response.setServiceOfferingName(offering.getName());
+        }
         KubernetesSupportedVersionVO version = kubernetesSupportedVersionDao.findById(kubernetesCluster.getKubernetesVersionId());
         if (version != null) {
             response.setKubernetesVersionId(version.getUuid());
@@ -568,13 +585,15 @@
         response.setMemory(String.valueOf(kubernetesCluster.getMemory()));
         NetworkVO ntwk = networkDao.findByIdIncludingRemoved(kubernetesCluster.getNetworkId());
         response.setEndpoint(kubernetesCluster.getEndpoint());
-        response.setNetworkId(ntwk.getUuid());
-        response.setAssociatedNetworkName(ntwk.getName());
-        if (ntwk.getGuestType() == Network.GuestType.Isolated) {
-            List<IPAddressVO> ipAddresses = ipAddressDao.listByAssociatedNetwork(ntwk.getId(), true);
-            if (ipAddresses != null && ipAddresses.size() == 1) {
-                response.setIpAddress(ipAddresses.get(0).getAddress().addr());
-                response.setIpAddressId(ipAddresses.get(0).getUuid());
+        if (ntwk != null) {
+            response.setNetworkId(ntwk.getUuid());
+            response.setAssociatedNetworkName(ntwk.getName());
+            if (ntwk.getGuestType() == Network.GuestType.Isolated) {
+                List<IPAddressVO> ipAddresses = ipAddressDao.listByAssociatedNetwork(ntwk.getId(), true);
+                if (ipAddresses != null && ipAddresses.size() == 1) {
+                    response.setIpAddress(ipAddresses.get(0).getAddress().addr());
+                    response.setIpAddressId(ipAddresses.get(0).getUuid());
+                }
             }
         }
 
@@ -602,6 +621,7 @@
         response.setAutoscalingEnabled(kubernetesCluster.getAutoscalingEnabled());
         response.setMinSize(kubernetesCluster.getMinSize());
         response.setMaxSize(kubernetesCluster.getMaxSize());
+        response.setClusterType(kubernetesCluster.getClusterType());
         response.setCreated(kubernetesCluster.getCreated());
         return response;
     }
@@ -615,7 +635,98 @@
         }
     }
 
-    private void validateKubernetesClusterCreateParameters(final CreateKubernetesClusterCmd cmd) throws CloudRuntimeException {
+    private DataCenter validateAndGetZoneForKubernetesCreateParameters(Long zoneId, Long networkId) {
+        DataCenter zone = dataCenterDao.findById(zoneId);
+        if (zone == null) {
+            throw new InvalidParameterValueException("Unable to find zone by ID: " + zoneId);
+        }
+        if (zone.getAllocationState() == Grouping.AllocationState.Disabled) {
+            throw new PermissionDeniedException(String.format("Cannot perform this operation, zone ID: %s is currently disabled", zone.getUuid()));
+        }
+        if (DataCenter.Type.Edge.equals(zone.getType()) && networkId == null) {
+            throw new PermissionDeniedException("Kubernetes clusters cannot be created on an edge zone without an existing network");
+        }
+        return zone;
+    }
+
+    private void validateSshKeyPairForKubernetesCreateParameters(String sshKeyPair, Account owner) {
+        if (!StringUtils.isBlank(sshKeyPair)) {
+            SSHKeyPairVO sshKeyPairVO = sshKeyPairDao.findByName(owner.getAccountId(), owner.getDomainId(), sshKeyPair);
+            if (sshKeyPairVO == null) {
+                throw new InvalidParameterValueException(String.format("Given SSH key pair with name: %s was not found for the account %s", sshKeyPair, owner.getAccountName()));
+            }
+        }
+    }
+
+    private Network validateAndGetNetworkForKubernetesCreateParameters(Long networkId) {
+        Network network = null;
+        if (networkId != null) {
+            network = networkService.getNetwork(networkId);
+            if (network == null) {
+                throw new InvalidParameterValueException("Unable to find network with given ID");
+            }
+        }
+        return network;
+    }
+
+    private void validateUnmanagedKubernetesClusterCreateParameters(final CreateKubernetesClusterCmd cmd) throws CloudRuntimeException {
+        final String name = cmd.getName();
+        final Long zoneId = cmd.getZoneId();
+        final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
+        final Long networkId = cmd.getNetworkId();
+        final String sshKeyPair = cmd.getSSHKeyPairName();
+        final String dockerRegistryUserName = cmd.getDockerRegistryUserName();
+        final String dockerRegistryPassword = cmd.getDockerRegistryPassword();
+        final String dockerRegistryUrl = cmd.getDockerRegistryUrl();
+        final Long nodeRootDiskSize = cmd.getNodeRootDiskSize();
+        final String externalLoadBalancerIpAddress = cmd.getExternalLoadBalancerIpAddress();
+
+        if (name == null || name.isEmpty()) {
+            throw new InvalidParameterValueException("Invalid name for the Kubernetes cluster name: " + name);
+        }
+
+        validateAndGetZoneForKubernetesCreateParameters(zoneId, networkId);
+        validateSshKeyPairForKubernetesCreateParameters(sshKeyPair, owner);
+
+        if (nodeRootDiskSize != null && nodeRootDiskSize <= 0) {
+            throw new InvalidParameterValueException(String.format("Invalid value for %s", ApiConstants.NODE_ROOT_DISK_SIZE));
+        }
+
+        validateDockerRegistryParams(dockerRegistryUserName, dockerRegistryPassword, dockerRegistryUrl);
+
+        validateAndGetNetworkForKubernetesCreateParameters(networkId);
+
+        if (StringUtils.isNotEmpty(externalLoadBalancerIpAddress) && (!NetUtils.isValidIp4(externalLoadBalancerIpAddress) && !NetUtils.isValidIp6(externalLoadBalancerIpAddress))) {
+            throw new InvalidParameterValueException("Invalid external load balancer IP address");
+        }
+    }
+
+    public boolean isCommandSupported(KubernetesCluster cluster, String cmdName) {
+        switch (cluster.getClusterType()) {
+            case CloudManaged:
+                return Arrays.asList(
+                        BaseCmd.getCommandNameByClass(CreateKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(ListKubernetesClustersCmd.class),
+                        BaseCmd.getCommandNameByClass(DeleteKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(ScaleKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(StartKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(StopKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(UpgradeKubernetesClusterCmd.class)
+                ).contains(cmdName);
+            case ExternalManaged:
+                return Arrays.asList(
+                        BaseCmd.getCommandNameByClass(CreateKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(ListKubernetesClustersCmd.class),
+                        BaseCmd.getCommandNameByClass(DeleteKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(AddVirtualMachinesToKubernetesClusterCmd.class),
+                        BaseCmd.getCommandNameByClass(RemoveVirtualMachinesFromKubernetesClusterCmd.class)
+                ).contains(cmdName);
+            default:
+                return false;
+        }
+    }
+
+    private void validateManagedKubernetesClusterCreateParameters(final CreateKubernetesClusterCmd cmd) throws CloudRuntimeException {
         validateEndpointUrl();
         final String name = cmd.getName();
         final Long zoneId = cmd.getZoneId();
@@ -626,7 +737,6 @@
         final String sshKeyPair = cmd.getSSHKeyPairName();
         final Long controlNodeCount = cmd.getControlNodes();
         final Long clusterSize = cmd.getClusterSize();
-        final long totalNodeCount = controlNodeCount + clusterSize;
         final String dockerRegistryUserName = cmd.getDockerRegistryUserName();
         final String dockerRegistryPassword = cmd.getDockerRegistryPassword();
         final String dockerRegistryUrl = cmd.getDockerRegistryUrl();
@@ -634,35 +744,24 @@
         final String externalLoadBalancerIpAddress = cmd.getExternalLoadBalancerIpAddress();
 
         if (name == null || name.isEmpty()) {
-            throw new InvalidParameterValueException("Invalid name for the Kubernetes cluster name:" + name);
+            throw new InvalidParameterValueException("Invalid name for the Kubernetes cluster name: " + name);
         }
 
         if (controlNodeCount < 1) {
             throw new InvalidParameterValueException("Invalid cluster control nodes count: " + controlNodeCount);
         }
-
-        if (clusterSize < 1) {
+        if (clusterSize == null || clusterSize < 1) {
             throw new InvalidParameterValueException("Invalid cluster size: " + clusterSize);
         }
 
         int maxClusterSize = KubernetesMaxClusterSize.valueIn(owner.getId());
+        final long totalNodeCount = controlNodeCount + clusterSize;
         if (totalNodeCount > maxClusterSize) {
             throw new InvalidParameterValueException(
                 String.format("Maximum cluster size can not exceed %d. Please contact your administrator", maxClusterSize));
         }
 
-        DataCenter zone = dataCenterDao.findById(zoneId);
-        if (zone == null) {
-            throw new InvalidParameterValueException("Unable to find zone by ID: " + zoneId);
-        }
-
-        if (Grouping.AllocationState.Disabled == zone.getAllocationState()) {
-            throw new PermissionDeniedException(String.format("Zone ID: %s is currently disabled", zone.getUuid()));
-        }
-
-        if (DataCenter.Type.Edge.equals(zone.getType()) && networkId == null) {
-            throw new PermissionDeniedException("Kubernetes clusters cannot be created on an edge zone without an existing network");
-        }
+        DataCenter zone = validateAndGetZoneForKubernetesCreateParameters(zoneId, networkId);
 
         if (!isKubernetesServiceConfigured(zone)) {
             throw new CloudRuntimeException("Kubernetes service has not been configured properly to provision Kubernetes clusters");
@@ -705,12 +804,7 @@
             throw new InvalidParameterValueException("No service offering with ID: " + serviceOfferingId);
         }
 
-        if (sshKeyPair != null && !sshKeyPair.isEmpty()) {
-            SSHKeyPairVO sshKeyPairVO = sshKeyPairDao.findByName(owner.getAccountId(), owner.getDomainId(), sshKeyPair);
-            if (sshKeyPairVO == null) {
-                throw new InvalidParameterValueException(String.format("Given SSH key pair with name: %s was not found for the account %s", sshKeyPair, owner.getAccountName()));
-            }
-        }
+        validateSshKeyPairForKubernetesCreateParameters(sshKeyPair, owner);
 
         if (nodeRootDiskSize != null && nodeRootDiskSize <= 0) {
             throw new InvalidParameterValueException(String.format("Invalid value for %s", ApiConstants.NODE_ROOT_DISK_SIZE));
@@ -722,13 +816,7 @@
 
         validateDockerRegistryParams(dockerRegistryUserName, dockerRegistryPassword, dockerRegistryUrl);
 
-        Network network = null;
-        if (networkId != null) {
-            network = networkService.getNetwork(networkId);
-            if (network == null) {
-                throw new InvalidParameterValueException("Unable to find network with given ID");
-            }
-        }
+        Network network = validateAndGetNetworkForKubernetesCreateParameters(networkId);
 
         if (StringUtils.isNotEmpty(externalLoadBalancerIpAddress)) {
             if (!NetUtils.isValidIp4(externalLoadBalancerIpAddress) && !NetUtils.isValidIp6(externalLoadBalancerIpAddress)) {
@@ -799,15 +887,16 @@
                 List<KubernetesClusterDetailsVO> details = new ArrayList<>();
                 long kubernetesClusterId = kubernetesCluster.getId();
 
-                if (Network.GuestType.Shared.equals(network.getGuestType())) {
+                if ((network != null && Network.GuestType.Shared.equals(network.getGuestType())) || kubernetesCluster.getClusterType() == KubernetesCluster.ClusterType.ExternalManaged) {
                     addKubernetesClusterDetailIfIsNotEmpty(details, kubernetesClusterId, ApiConstants.EXTERNAL_LOAD_BALANCER_IP_ADDRESS, externalLoadBalancerIpAddress, true);
                 }
 
                 addKubernetesClusterDetailIfIsNotEmpty(details, kubernetesClusterId, ApiConstants.DOCKER_REGISTRY_USER_NAME, dockerRegistryUserName, true);
                 addKubernetesClusterDetailIfIsNotEmpty(details, kubernetesClusterId, ApiConstants.DOCKER_REGISTRY_PASSWORD, dockerRegistryPassword, false);
                 addKubernetesClusterDetailIfIsNotEmpty(details, kubernetesClusterId, ApiConstants.DOCKER_REGISTRY_URL, dockerRegistryUrl, true);
-
-                details.add(new KubernetesClusterDetailsVO(kubernetesClusterId, "networkCleanup", String.valueOf(networkCleanup), true));
+                if (kubernetesCluster.getClusterType() == KubernetesCluster.ClusterType.CloudManaged) {
+                    details.add(new KubernetesClusterDetailsVO(kubernetesClusterId, "networkCleanup", String.valueOf(networkCleanup), true));
+                }
                 kubernetesClusterDetailsDao.saveDetails(details);
             }
         });
@@ -876,6 +965,9 @@
 
         Account caller = CallContext.current().getCallingAccount();
         accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, kubernetesCluster);
+        if (!isCommandSupported(kubernetesCluster, cmd.getActualCommandName())) {
+            throw new InvalidParameterValueException(String.format("Scale kubernetes cluster is not supported for an externally managed cluster (%s)", kubernetesCluster.getName()));
+        }
 
         final KubernetesSupportedVersion clusterVersion = kubernetesSupportedVersionDao.findById(kubernetesCluster.getKubernetesVersionId());
         if (clusterVersion == null) {
@@ -984,6 +1076,9 @@
         if (kubernetesCluster == null || kubernetesCluster.getRemoved() != null) {
             throw new InvalidParameterValueException("Invalid Kubernetes cluster ID");
         }
+        if (!isCommandSupported(kubernetesCluster, cmd.getActualCommandName())) {
+            throw new InvalidParameterValueException(String.format("Upgrade kubernetes cluster is not supported for an externally managed cluster (%s)", kubernetesCluster.getName()));
+        }
         accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, kubernetesCluster);
         if (!KubernetesCluster.State.Running.equals(kubernetesCluster.getState())) {
             throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s is not in running state", kubernetesCluster.getName()));
@@ -1043,12 +1138,62 @@
     }
 
     @Override
-    public KubernetesCluster createKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException {
+    public KubernetesCluster createUnmanagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException {
         if (!KubernetesServiceEnabled.value()) {
             logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
         }
 
-        validateKubernetesClusterCreateParameters(cmd);
+        validateUnmanagedKubernetesClusterCreateParameters(cmd);
+
+        final DataCenter zone = dataCenterDao.findById(cmd.getZoneId());
+        final long controlNodeCount = cmd.getControlNodes();
+        final long clusterSize = Objects.requireNonNullElse(cmd.getClusterSize(), 0L);
+        final ServiceOffering serviceOffering = serviceOfferingDao.findById(cmd.getServiceOfferingId());
+        final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
+        final KubernetesSupportedVersion clusterKubernetesVersion = kubernetesSupportedVersionDao.findById(cmd.getKubernetesVersionId());
+
+        final Network network = networkDao.findById(cmd.getNetworkId());
+        long cores = 0;
+        long memory = 0;
+        Long serviceOfferingId = null;
+        if (serviceOffering != null) {
+            serviceOfferingId = serviceOffering.getId();
+            cores = serviceOffering.getCpu() * (controlNodeCount + clusterSize);
+            memory = serviceOffering.getRamSize() * (controlNodeCount + clusterSize);
+        }
+
+        final Long finalServiceOfferingId = serviceOfferingId;
+        final Long defaultNetworkId = network == null ? null : network.getId();
+        final Long clusterKubernetesVersionId = clusterKubernetesVersion == null ? null : clusterKubernetesVersion.getId();
+        final long finalCores = cores;
+        final long finalMemory = memory;
+        final KubernetesClusterVO cluster = Transaction.execute(new TransactionCallback<KubernetesClusterVO>() {
+            @Override
+            public KubernetesClusterVO doInTransaction(TransactionStatus status) {
+                KubernetesClusterVO newCluster = new KubernetesClusterVO(cmd.getName(), cmd.getDisplayName(), zone.getId(), clusterKubernetesVersionId,
+                        finalServiceOfferingId, null, defaultNetworkId, owner.getDomainId(),
+                        owner.getAccountId(), controlNodeCount, clusterSize, KubernetesCluster.State.Running, cmd.getSSHKeyPairName(), finalCores, finalMemory,
+                        cmd.getNodeRootDiskSize(), "", KubernetesCluster.ClusterType.ExternalManaged);
+                kubernetesClusterDao.persist(newCluster);
+                return newCluster;
+            }
+        });
+
+        addKubernetesClusterDetails(cluster, network, cmd);
+
+        if (LOGGER.isInfoEnabled()) {
+            LOGGER.info(String.format("Kubernetes cluster with name: %s and ID: %s has been created", cluster.getName(), cluster.getUuid()));
+        }
+        return cluster;
+    }
+
+    @Override
+    public KubernetesCluster createManagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException {
+        if (!KubernetesServiceEnabled.value()) {
+            logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
+        }
+
+        validateManagedKubernetesClusterCreateParameters(cmd);
 
         final DataCenter zone = dataCenterDao.findById(cmd.getZoneId());
         final long controlNodeCount = cmd.getControlNodes();
@@ -1084,7 +1229,8 @@
             public KubernetesClusterVO doInTransaction(TransactionStatus status) {
                 KubernetesClusterVO newCluster = new KubernetesClusterVO(cmd.getName(), cmd.getDisplayName(), zone.getId(), clusterKubernetesVersion.getId(),
                         serviceOffering.getId(), finalTemplate.getId(), defaultNetwork.getId(), owner.getDomainId(),
-                        owner.getAccountId(), controlNodeCount, clusterSize, KubernetesCluster.State.Created, cmd.getSSHKeyPairName(), cores, memory, cmd.getNodeRootDiskSize(), "");
+                        owner.getAccountId(), controlNodeCount, clusterSize, KubernetesCluster.State.Created, cmd.getSSHKeyPairName(), cores, memory,
+                        cmd.getNodeRootDiskSize(), "", KubernetesCluster.ClusterType.CloudManaged);
                 if (zone.isSecurityGroupEnabled()) {
                     newCluster.setSecurityGroupId(finalSecurityGroup.getId());
                 }
@@ -1204,7 +1350,8 @@
     }
 
     @Override
-    public boolean stopKubernetesCluster(long kubernetesClusterId) throws CloudRuntimeException {
+    public boolean stopKubernetesCluster(StopKubernetesClusterCmd cmd) throws CloudRuntimeException {
+        long kubernetesClusterId = cmd.getId();
         if (!KubernetesServiceEnabled.value()) {
             logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
         }
@@ -1212,6 +1359,9 @@
         if (kubernetesCluster == null) {
             throw new InvalidParameterValueException("Failed to find Kubernetes cluster with given ID");
         }
+        if (!isCommandSupported(kubernetesCluster, cmd.getActualCommandName())) {
+            throw new InvalidParameterValueException(String.format("Stop kubernetes cluster is not supported for an externally managed cluster (%s)", kubernetesCluster.getName()));
+        }
         if (kubernetesCluster.getRemoved() != null) {
             throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s is already deleted", kubernetesCluster.getName()));
         }
@@ -1234,18 +1384,51 @@
     }
 
     @Override
-    public boolean deleteKubernetesCluster(Long kubernetesClusterId) throws CloudRuntimeException {
+    public boolean deleteKubernetesCluster(DeleteKubernetesClusterCmd cmd) throws CloudRuntimeException {
         if (!KubernetesServiceEnabled.value()) {
             logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
         }
+        Long kubernetesClusterId = cmd.getId();
         KubernetesClusterVO cluster = kubernetesClusterDao.findById(kubernetesClusterId);
         if (cluster == null) {
             throw new InvalidParameterValueException("Invalid cluster id specified");
         }
         accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, cluster);
-        KubernetesClusterDestroyWorker destroyWorker = new KubernetesClusterDestroyWorker(cluster, this);
-        destroyWorker = ComponentContext.inject(destroyWorker);
-        return destroyWorker.destroy();
+        if (cluster.getClusterType() == KubernetesCluster.ClusterType.CloudManaged) {
+            KubernetesClusterDestroyWorker destroyWorker = new KubernetesClusterDestroyWorker(cluster, this);
+            destroyWorker = ComponentContext.inject(destroyWorker);
+            return destroyWorker.destroy();
+        } else {
+            boolean cleanup = cmd.getCleanup();
+            boolean expunge = cmd.getExpunge();
+            if (cleanup || expunge) {
+                CallContext ctx = CallContext.current();
+
+                if (expunge && !accountManager.isAdmin(ctx.getCallingAccount().getId()) && !AllowUserExpungeRecoverVm.valueIn(cmd.getEntityOwnerId())) {
+                    throw new PermissionDeniedException("Parameter " + ApiConstants.EXPUNGE + " can be passed by Admin only. Or when the allow.user.expunge.recover.vm key is set.");
+                }
+
+                List<KubernetesClusterVmMapVO> vmMapList = kubernetesClusterVmMapDao.listByClusterId(kubernetesClusterId);
+                for (KubernetesClusterVmMapVO vmMap : vmMapList) {
+                    try {
+                        userVmService.destroyVm(vmMap.getVmId(), expunge);
+                        if (expunge) {
+                            userVmService.expungeVm(vmMap.getVmId());
+                        }
+                    } catch (Exception exception) {
+                        logMessage(Level.WARN, String.format("Failed to destroy vm %d", vmMap.getVmId()), exception);
+                    }
+                }
+            }
+            return Transaction.execute(new TransactionCallback<Boolean>() {
+                @Override
+                public Boolean doInTransaction(TransactionStatus status) {
+                    kubernetesClusterDetailsDao.removeDetails(kubernetesClusterId);
+                    kubernetesClusterVmMapDao.removeByClusterId(kubernetesClusterId);
+                    return kubernetesClusterDao.remove(kubernetesClusterId);
+                }
+            });
+        }
     }
 
     @Override
@@ -1259,6 +1442,7 @@
         final String state = cmd.getState();
         final String name = cmd.getName();
         final String keyword = cmd.getKeyword();
+        final String cmdClusterType = cmd.getClusterType();
         List<KubernetesClusterResponse> responsesList = new ArrayList<KubernetesClusterResponse>();
         List<Long> permittedAccounts = new ArrayList<Long>();
         Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, Project.ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
@@ -1266,6 +1450,17 @@
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
+
+        KubernetesCluster.ClusterType clusterType = null;
+
+        if (cmdClusterType != null) {
+            try {
+             clusterType = KubernetesCluster.ClusterType.valueOf(cmdClusterType);
+            } catch (IllegalArgumentException exception) {
+                throw new InvalidParameterValueException("Unable to resolve cluster type " + cmdClusterType + " to a supported value (CloudManaged, ExternalManaged)");
+            }
+        }
+
         Filter searchFilter = new Filter(KubernetesClusterVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
         SearchBuilder<KubernetesClusterVO> sb = kubernetesClusterDao.createSearchBuilder();
         accountManager.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
@@ -1273,6 +1468,7 @@
         sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
         sb.and("keyword", sb.entity().getName(), SearchCriteria.Op.LIKE);
         sb.and("state", sb.entity().getState(), SearchCriteria.Op.IN);
+        sb.and("cluster_type", sb.entity().getClusterType(), SearchCriteria.Op.EQ);
         SearchCriteria<KubernetesClusterVO> sc = sb.create();
         accountManager.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
         if (state != null) {
@@ -1287,6 +1483,9 @@
         if (name != null) {
             sc.setParameters("name", name);
         }
+        if (clusterType != null) {
+            sc.setParameters("cluster_type", clusterType);
+        }
         List<KubernetesClusterVO> kubernetesClusters = kubernetesClusterDao.search(sc, searchFilter);
         for (KubernetesClusterVO cluster : kubernetesClusters) {
             KubernetesClusterResponse clusterResponse = createKubernetesClusterResponse(cluster.getId());
@@ -1365,6 +1564,114 @@
         return upgradeWorker.upgradeCluster();
     }
 
+    private void updateNodeCount(KubernetesClusterVO kubernetesCluster) {
+        List<KubernetesClusterVmMapVO> nodeList = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
+        kubernetesCluster.setControlNodeCount(nodeList.stream().filter(KubernetesClusterVmMapVO::isControlNode).count());
+        kubernetesCluster.setNodeCount(nodeList.size());
+        kubernetesCluster.setNodeCount(nodeList.size());
+        kubernetesClusterDao.persist(kubernetesCluster);
+    }
+
+    @Override
+    public boolean addVmsToCluster(AddVirtualMachinesToKubernetesClusterCmd cmd) {
+        if (!KubernetesServiceEnabled.value()) {
+            logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
+        }
+        List<Long> vmIds = cmd.getVmIds();
+        Long clusterId = cmd.getId();
+
+        KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(clusterId);
+        if (kubernetesCluster == null) {
+            throw new InvalidParameterValueException("Invalid Kubernetes cluster ID specified");
+        }
+        if (!isCommandSupported(kubernetesCluster, cmd.getActualCommandName())) {
+            throw new InvalidParameterValueException("VM cannot be added to a CloudStack managed Kubernetes cluster");
+        }
+
+        // User should have access to both VM and Kubernetes cluster
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, kubernetesCluster);
+
+        for (Long vmId : vmIds) {
+            VMInstanceVO vmInstance = vmInstanceDao.findById(vmId);
+            if (vmInstance == null) {
+                throw new InvalidParameterValueException("Invalid VM ID specified");
+            }
+            accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, vmInstance);
+        }
+
+        KubernetesClusterVmMapVO clusterVmMap = null;
+        List<KubernetesClusterVmMapVO> clusterVmMapList = kubernetesClusterVmMapDao.listByClusterIdAndVmIdsIn(clusterId, vmIds);
+        ArrayList<Long> alreadyExistingVmIds = new ArrayList<>();
+        for (KubernetesClusterVmMapVO clusterVmMapVO : clusterVmMapList) {
+            alreadyExistingVmIds.add(clusterVmMapVO.getVmId());
+        }
+        vmIds.removeAll(alreadyExistingVmIds);
+        for (Long vmId : vmIds) {
+            clusterVmMap = new KubernetesClusterVmMapVO(clusterId, vmId, cmd.isControlNode());
+            kubernetesClusterVmMapDao.persist(clusterVmMap);
+        }
+        updateNodeCount(kubernetesCluster);
+        return true;
+    }
+
+    @Override
+    public List<RemoveVirtualMachinesFromKubernetesClusterResponse> removeVmsFromCluster(RemoveVirtualMachinesFromKubernetesClusterCmd cmd) {
+        if (!KubernetesServiceEnabled.value()) {
+            logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
+        }
+        List<Long> vmIds = cmd.getVmIds();
+        Long clusterId = cmd.getId();
+
+        KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(clusterId);
+        if (kubernetesCluster == null) {
+            throw new InvalidParameterValueException("Invalid Kubernetes cluster ID specified");
+        }
+        if (!isCommandSupported(kubernetesCluster, cmd.getActualCommandName())) {
+            throw new InvalidParameterValueException("VM cannot be removed from a CloudStack Managed Kubernetes cluster");
+        }
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, kubernetesCluster);
+
+        List<KubernetesClusterVmMapVO> kubernetesClusterVmMap = kubernetesClusterVmMapDao.listByClusterIdAndVmIdsIn(clusterId, vmIds);
+        List<RemoveVirtualMachinesFromKubernetesClusterResponse> responseList = new ArrayList<>();
+
+        Set<Long> vmIdsRemoved = new HashSet<>();
+
+        for (KubernetesClusterVmMapVO clusterVmMap : kubernetesClusterVmMap) {
+            RemoveVirtualMachinesFromKubernetesClusterResponse response = new RemoveVirtualMachinesFromKubernetesClusterResponse();
+            UserVm vm = userVmService.getUserVm(clusterVmMap.getVmId());
+            response.setVmId(vm.getUuid());
+            response.setSuccess(kubernetesClusterVmMapDao.remove(clusterVmMap.getId()));
+            response.setObjectName(cmd.getCommandName());
+            responseList.add(response);
+            vmIdsRemoved.add(clusterVmMap.getVmId());
+        }
+
+        for (Long vmId : vmIds) {
+            if (!vmIdsRemoved.contains(vmId)) {
+                RemoveVirtualMachinesFromKubernetesClusterResponse response = new RemoveVirtualMachinesFromKubernetesClusterResponse();
+                VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId);
+                if (vm == null) {
+                    response.setVmId(vmId.toString());
+                    response.setDisplayText("Not a valid vm id");
+                    vmIdsRemoved.add(vmId);
+                } else {
+                    response.setVmId(vm.getUuid());
+                    vmIdsRemoved.add(vmId);
+                    if (vm.isRemoved()) {
+                        response.setDisplayText("VM is already removed");
+                    } else {
+                        response.setDisplayText("VM is not part of the cluster");
+                    }
+                }
+                response.setObjectName(cmd.getCommandName());
+                response.setSuccess(false);
+                responseList.add(response);
+            }
+        }
+        updateNodeCount(kubernetesCluster);
+        return responseList;
+    }
+
     @Override
     public List<Class<?>> getCommands() {
         List<Class<?>> cmdList = new ArrayList<Class<?>>();
@@ -1379,6 +1686,8 @@
         cmdList.add(GetKubernetesClusterConfigCmd.class);
         cmdList.add(ScaleKubernetesClusterCmd.class);
         cmdList.add(UpgradeKubernetesClusterCmd.class);
+        cmdList.add(AddVirtualMachinesToKubernetesClusterCmd.class);
+        cmdList.add(RemoveVirtualMachinesFromKubernetesClusterCmd.class);
         return cmdList;
     }
 
@@ -1465,7 +1774,7 @@
         public void reallyRun() {
             try {
                 // run through Kubernetes clusters in 'Running' state and ensure all the VM's are Running in the cluster
-                List<KubernetesClusterVO> runningKubernetesClusters = kubernetesClusterDao.findKubernetesClustersInState(KubernetesCluster.State.Running);
+                List<KubernetesClusterVO> runningKubernetesClusters = kubernetesClusterDao.findManagedKubernetesClustersInState(KubernetesCluster.State.Running);
                 for (KubernetesCluster kubernetesCluster : runningKubernetesClusters) {
                     if (LOGGER.isInfoEnabled()) {
                         LOGGER.info(String.format("Running Kubernetes cluster state scanner on Kubernetes cluster : %s", kubernetesCluster.getName()));
@@ -1480,7 +1789,7 @@
                 }
 
                 // run through Kubernetes clusters in 'Stopped' state and ensure all the VM's are Stopped in the cluster
-                List<KubernetesClusterVO> stoppedKubernetesClusters = kubernetesClusterDao.findKubernetesClustersInState(KubernetesCluster.State.Stopped);
+                List<KubernetesClusterVO> stoppedKubernetesClusters = kubernetesClusterDao.findManagedKubernetesClustersInState(KubernetesCluster.State.Stopped);
                 for (KubernetesCluster kubernetesCluster : stoppedKubernetesClusters) {
                     if (LOGGER.isInfoEnabled()) {
                         LOGGER.info(String.format("Running Kubernetes cluster state scanner on Kubernetes cluster : %s for state: %s", kubernetesCluster.getName(), KubernetesCluster.State.Stopped.toString()));
@@ -1495,7 +1804,7 @@
                 }
 
                 // run through Kubernetes clusters in 'Alert' state and reconcile state as 'Running' if the VM's are running or 'Stopped' if VM's are stopped
-                List<KubernetesClusterVO> alertKubernetesClusters = kubernetesClusterDao.findKubernetesClustersInState(KubernetesCluster.State.Alert);
+                List<KubernetesClusterVO> alertKubernetesClusters = kubernetesClusterDao.findManagedKubernetesClustersInState(KubernetesCluster.State.Alert);
                 for (KubernetesClusterVO kubernetesCluster : alertKubernetesClusters) {
                     if (LOGGER.isInfoEnabled()) {
                         LOGGER.info(String.format("Running Kubernetes cluster state scanner on Kubernetes cluster : %s for state: %s", kubernetesCluster.getName(), KubernetesCluster.State.Alert.toString()));
@@ -1518,7 +1827,7 @@
 
                 if (firstRun) {
                     // run through Kubernetes clusters in 'Starting' state and reconcile state as 'Alert' or 'Error' if the VM's are running
-                    List<KubernetesClusterVO> startingKubernetesClusters = kubernetesClusterDao.findKubernetesClustersInState(KubernetesCluster.State.Starting);
+                    List<KubernetesClusterVO> startingKubernetesClusters = kubernetesClusterDao.findManagedKubernetesClustersInState(KubernetesCluster.State.Starting);
                     for (KubernetesCluster kubernetesCluster : startingKubernetesClusters) {
                         if ((new Date()).getTime() - kubernetesCluster.getCreated().getTime() < 10*60*1000) {
                             continue;
@@ -1536,7 +1845,7 @@
                             LOGGER.warn(String.format("Failed to run Kubernetes cluster Starting state scanner on Kubernetes cluster : %s status scanner", kubernetesCluster.getName()), e);
                         }
                     }
-                    List<KubernetesClusterVO> destroyingKubernetesClusters = kubernetesClusterDao.findKubernetesClustersInState(KubernetesCluster.State.Destroying);
+                    List<KubernetesClusterVO> destroyingKubernetesClusters = kubernetesClusterDao.findManagedKubernetesClustersInState(KubernetesCluster.State.Destroying);
                     for (KubernetesCluster kubernetesCluster : destroyingKubernetesClusters) {
                         if (LOGGER.isInfoEnabled()) {
                             LOGGER.info(String.format("Running Kubernetes cluster state scanner on Kubernetes cluster : %s for state: %s", kubernetesCluster.getName(), KubernetesCluster.State.Destroying.toString()));
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java
index 12240a4..39b9265 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java
@@ -16,20 +16,27 @@
 // under the License.
 package com.cloud.kubernetes.cluster;
 
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.CreateKubernetesClusterCmd;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.DeleteKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.GetKubernetesClusterConfigCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.ListKubernetesClustersCmd;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMachinesFromKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.ScaleKubernetesClusterCmd;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd;
 import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse;
 import org.apache.cloudstack.api.response.KubernetesClusterResponse;
 import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.RemoveVirtualMachinesFromKubernetesClusterResponse;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 
 import com.cloud.utils.component.PluggableService;
 import com.cloud.utils.exception.CloudRuntimeException;
 
+import java.util.List;
+
 public interface KubernetesClusterService extends PluggableService, Configurable {
     static final String MIN_KUBERNETES_VERSION_HA_SUPPORT = "1.16.0";
     static final int MIN_KUBERNETES_CLUSTER_NODE_CPU = 2;
@@ -38,7 +45,7 @@
 
     static final ConfigKey<Boolean> KubernetesServiceEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class,
             "cloud.kubernetes.service.enabled",
-            "false",
+            "true",
             "Indicates whether Kubernetes Service plugin is enabled or not. Management server restart needed on change",
             false);
     static final ConfigKey<String> KubernetesClusterNetworkOffering = new ConfigKey<String>("Advanced", String.class,
@@ -87,13 +94,17 @@
 
     KubernetesCluster findById(final Long id);
 
-    KubernetesCluster createKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException;
+    KubernetesCluster createUnmanagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException;
+
+    KubernetesCluster createManagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException;
 
     boolean startKubernetesCluster(long kubernetesClusterId, boolean onCreate) throws CloudRuntimeException;
 
-    boolean stopKubernetesCluster(long kubernetesClusterId) throws CloudRuntimeException;
+    boolean stopKubernetesCluster(StopKubernetesClusterCmd cmd) throws CloudRuntimeException;
 
-    boolean deleteKubernetesCluster(Long kubernetesClusterId) throws CloudRuntimeException;
+    boolean deleteKubernetesCluster(DeleteKubernetesClusterCmd cmd) throws CloudRuntimeException;
+
+    boolean isCommandSupported(KubernetesCluster cluster, String cmdName);
 
     ListResponse<KubernetesClusterResponse> listKubernetesClusters(ListKubernetesClustersCmd cmd);
 
@@ -104,4 +115,8 @@
     boolean scaleKubernetesCluster(ScaleKubernetesClusterCmd cmd) throws CloudRuntimeException;
 
     boolean upgradeKubernetesCluster(UpgradeKubernetesClusterCmd cmd) throws CloudRuntimeException;
+
+    boolean addVmsToCluster(AddVirtualMachinesToKubernetesClusterCmd cmd);
+
+    List<RemoveVirtualMachinesFromKubernetesClusterResponse> removeVmsFromCluster(RemoveVirtualMachinesFromKubernetesClusterCmd cmd);
 }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java
index 1b30b1b..270916a 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterVO.java
@@ -52,16 +52,16 @@
     private long zoneId;
 
     @Column(name = "kubernetes_version_id")
-    private long kubernetesVersionId;
+    private Long kubernetesVersionId;
 
     @Column(name = "service_offering_id")
-    private long serviceOfferingId;
+    private Long serviceOfferingId;
 
     @Column(name = "template_id")
-    private long templateId;
+    private Long templateId;
 
     @Column(name = "network_id")
-    private long networkId;
+    private Long networkId;
 
     @Column(name = "domain_id")
     private long domainId;
@@ -114,6 +114,9 @@
     @Column(name = "security_group_id")
     private Long securityGroupId;
 
+    @Column(name = "cluster_type")
+    private ClusterType clusterType;
+
     @Override
     public long getId() {
         return id;
@@ -160,7 +163,7 @@
     }
 
     @Override
-    public long getKubernetesVersionId() {
+    public Long getKubernetesVersionId() {
         return kubernetesVersionId;
     }
 
@@ -169,7 +172,7 @@
     }
 
     @Override
-    public long getServiceOfferingId() {
+    public Long getServiceOfferingId() {
         return serviceOfferingId;
     }
 
@@ -178,7 +181,7 @@
     }
 
     @Override
-    public long getTemplateId() {
+    public Long getTemplateId() {
         return templateId;
     }
 
@@ -187,7 +190,7 @@
     }
 
     @Override
-    public long getNetworkId() {
+    public Long getNetworkId() {
         return networkId;
     }
 
@@ -350,13 +353,21 @@
         return securityGroupId;
     }
 
+    public ClusterType getClusterType() {
+        return clusterType;
+    }
+
+    public void setClusterType(ClusterType clusterType) {
+        this.clusterType = clusterType;
+    }
+
     public KubernetesClusterVO() {
         this.uuid = UUID.randomUUID().toString();
     }
 
-    public KubernetesClusterVO(String name, String description, long zoneId, long kubernetesVersionId, long serviceOfferingId, long templateId,
-                               long networkId, long domainId, long accountId, long controlNodeCount, long nodeCount, State state,
-                               String keyPair, long cores, long memory, Long nodeRootDiskSize, String endpoint) {
+    public KubernetesClusterVO(String name, String description, long zoneId, Long kubernetesVersionId, Long serviceOfferingId, Long templateId,
+                               Long networkId, long domainId, long accountId, long controlNodeCount, long nodeCount, State state,
+                               String keyPair, long cores, long memory, Long nodeRootDiskSize, String endpoint, ClusterType clusterType) {
         this.uuid = UUID.randomUUID().toString();
         this.name = name;
         this.description = description;
@@ -377,14 +388,15 @@
             this.nodeRootDiskSize = nodeRootDiskSize;
         }
         this.endpoint = endpoint;
+        this.clusterType = clusterType;
         this.checkForGc = false;
     }
 
     public KubernetesClusterVO(String name, String description, long zoneId, long kubernetesVersionId, long serviceOfferingId, long templateId,
         long networkId, long domainId, long accountId, long controlNodeCount, long nodeCount, State state, String keyPair, long cores,
-        long memory, Long nodeRootDiskSize, String endpoint, boolean autoscalingEnabled, Long minSize, Long maxSize) {
+        long memory, Long nodeRootDiskSize, String endpoint, ClusterType clusterType, boolean autoscalingEnabled, Long minSize, Long maxSize) {
         this(name, description, zoneId, kubernetesVersionId, serviceOfferingId, templateId, networkId, domainId, accountId, controlNodeCount,
-            nodeCount, state, keyPair, cores, memory, nodeRootDiskSize, endpoint);
+            nodeCount, state, keyPair, cores, memory, nodeRootDiskSize, endpoint, clusterType);
         this.autoscalingEnabled = autoscalingEnabled;
         this.minSize = minSize;
         this.maxSize = maxSize;
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java
index 6d44565..a84320e 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java
@@ -412,7 +412,7 @@
                 return new Pair<>(address.getAddress().addr(), port);
             }
         }
-        LOGGER.warn(String.format("No public IP found for the the VPC tier: %s, Kubernetes cluster : %s", network, kubernetesCluster.getName()));
+        LOGGER.warn(String.format("No public IP found for the VPC tier: %s, Kubernetes cluster : %s", network, kubernetesCluster.getName()));
         return new Pair<>(null, port);
     }
 
@@ -585,7 +585,7 @@
             writer.write(data);
             writer.close();
         } catch (IOException e) {
-            logAndThrow(Level.ERROR, String.format("Kubernetes Cluster %s : Failed to to fetch script %s",
+            logAndThrow(Level.ERROR, String.format("Kubernetes Cluster %s : Failed to fetch script %s",
                 kubernetesCluster.getName(), filename), e);
         }
         return file;
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDao.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDao.java
index fe67323..9341912 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDao.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDao.java
@@ -28,7 +28,7 @@
 
     List<KubernetesClusterVO> listByAccount(long accountId);
     List<KubernetesClusterVO> findKubernetesClustersToGarbageCollect();
-    List<KubernetesClusterVO> findKubernetesClustersInState(KubernetesCluster.State state);
+    List<KubernetesClusterVO> findManagedKubernetesClustersInState(KubernetesCluster.State state);
     List<KubernetesClusterVO> listByNetworkId(long networkId);
     List<KubernetesClusterVO> listAllByKubernetesVersion(long kubernetesVersionId);
 }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDaoImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDaoImpl.java
index 003286c..63cca35 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDaoImpl.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterDaoImpl.java
@@ -32,7 +32,7 @@
 
     private final SearchBuilder<KubernetesClusterVO> AccountIdSearch;
     private final SearchBuilder<KubernetesClusterVO> GarbageCollectedSearch;
-    private final SearchBuilder<KubernetesClusterVO> StateSearch;
+    private final SearchBuilder<KubernetesClusterVO> ManagedStateSearch;
     private final SearchBuilder<KubernetesClusterVO> SameNetworkSearch;
     private final SearchBuilder<KubernetesClusterVO> KubernetesVersionSearch;
 
@@ -44,11 +44,13 @@
         GarbageCollectedSearch = createSearchBuilder();
         GarbageCollectedSearch.and("gc", GarbageCollectedSearch.entity().isCheckForGc(), SearchCriteria.Op.EQ);
         GarbageCollectedSearch.and("state", GarbageCollectedSearch.entity().getState(), SearchCriteria.Op.EQ);
+        GarbageCollectedSearch.and("cluster_type", GarbageCollectedSearch.entity().getClusterType(), SearchCriteria.Op.EQ);
         GarbageCollectedSearch.done();
 
-        StateSearch = createSearchBuilder();
-        StateSearch.and("state", StateSearch.entity().getState(), SearchCriteria.Op.EQ);
-        StateSearch.done();
+        ManagedStateSearch = createSearchBuilder();
+        ManagedStateSearch.and("state", ManagedStateSearch.entity().getState(), SearchCriteria.Op.EQ);
+        ManagedStateSearch.and("cluster_type", ManagedStateSearch.entity().getClusterType(), SearchCriteria.Op.EQ);
+        ManagedStateSearch.done();
 
         SameNetworkSearch = createSearchBuilder();
         SameNetworkSearch.and("network_id", SameNetworkSearch.entity().getNetworkId(), SearchCriteria.Op.EQ);
@@ -71,13 +73,15 @@
         SearchCriteria<KubernetesClusterVO> sc = GarbageCollectedSearch.create();
         sc.setParameters("gc", true);
         sc.setParameters("state", KubernetesCluster.State.Destroying);
+        sc.setParameters("cluster_type", KubernetesCluster.ClusterType.CloudManaged);
         return listBy(sc);
     }
 
     @Override
-    public List<KubernetesClusterVO> findKubernetesClustersInState(KubernetesCluster.State state) {
-        SearchCriteria<KubernetesClusterVO> sc = StateSearch.create();
+    public List<KubernetesClusterVO> findManagedKubernetesClustersInState(KubernetesCluster.State state) {
+        SearchCriteria<KubernetesClusterVO> sc = ManagedStateSearch.create();
         sc.setParameters("state", state);
+        sc.setParameters("cluster_type", KubernetesCluster.ClusterType.CloudManaged);
         return listBy(sc);
     }
 
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java
index 42061cd..688a611 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java
@@ -24,4 +24,8 @@
 public interface KubernetesClusterVmMapDao extends GenericDao<KubernetesClusterVmMapVO, Long> {
     public List<KubernetesClusterVmMapVO> listByClusterId(long clusterId);
     public List<KubernetesClusterVmMapVO> listByClusterIdAndVmIdsIn(long clusterId, List<Long> vmIds);
+
+    int removeByClusterIdAndVmIdsIn(long clusterId, List<Long> vmIds);
+
+    public int removeByClusterId(long clusterId);
 }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java
index c5a9ad4..b9f2ec9 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java
@@ -54,4 +54,19 @@
         sc.setParameters("vmIdsIN", vmIds.toArray());
         return listBy(sc);
     }
+
+    @Override
+    public int removeByClusterIdAndVmIdsIn(long clusterId, List<Long> vmIds) {
+        SearchCriteria<KubernetesClusterVmMapVO> sc = clusterIdSearch.create();
+        sc.setParameters("clusterId", clusterId);
+        sc.setParameters("vmIdsIN", vmIds.toArray());
+        return remove(sc);
+    }
+
+    @Override
+    public int removeByClusterId(long clusterId) {
+        SearchCriteria<KubernetesClusterVmMapVO> sc = clusterIdSearch.create();
+        sc.setParameters("clusterId", clusterId);
+        return remove(sc);
+    }
 }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/AddVirtualMachinesToKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/AddVirtualMachinesToKubernetesClusterCmd.java
new file mode 100644
index 0000000..a7134f5
--- /dev/null
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/AddVirtualMachinesToKubernetesClusterCmd.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
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.kubernetes.cluster;
+
+import com.cloud.kubernetes.cluster.KubernetesClusterService;
+import com.cloud.utils.exception.CloudRuntimeException;
+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.KubernetesClusterResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(name = "addVirtualMachinesToKubernetesCluster",
+        description = "Add VMs to an ExternalManaged kubernetes cluster. Not applicable for CloudManaged kubernetes clusters.",
+        responseObject = SuccessResponse.class,
+        since = "4.19.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class AddVirtualMachinesToKubernetesClusterCmd extends BaseCmd {
+    public static final Logger LOGGER = Logger.getLogger(AddVirtualMachinesToKubernetesClusterCmd.class.getName());
+
+    @Inject
+    public KubernetesClusterService kubernetesClusterService;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID,
+            entityType = KubernetesClusterResponse.class,
+            required = true,
+            description = "the ID of the Kubernetes cluster")
+    private Long id;
+
+    @Parameter(name = ApiConstants.VIRTUAL_MACHINE_IDS, type = CommandType.LIST,
+            collectionType=CommandType.UUID,
+            entityType = UserVmResponse.class,
+            required = true,
+            description = "the IDs of the VMs to add to the cluster")
+    private List<Long> vmIds;
+
+    @Parameter(name = ApiConstants.IS_CONTROL_NODE, type = CommandType.BOOLEAN,
+            description = "Is control node or not? Defaults to false.")
+    private Boolean isControlNode;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public List<Long> getVmIds() {
+        return vmIds;
+    }
+
+    public boolean isControlNode() {
+        return (isControlNode != null) && isControlNode;
+    }
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public void execute() throws ServerApiException {
+        try {
+            if (!kubernetesClusterService.addVmsToCluster(this)) {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add VMs to cluster");
+            }
+            final SuccessResponse response = new SuccessResponse();
+            response.setResponseName(getCommandName());
+            setResponseObject(response);
+        } catch (CloudRuntimeException e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+        }
+    }
+}
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
index 5e4bd39..12a50c9 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java
@@ -20,6 +20,7 @@
 
 import javax.inject.Inject;
 
+import com.cloud.exception.InvalidParameterValueException;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.api.ACL;
@@ -39,6 +40,7 @@
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.kubernetes.cluster.KubernetesCluster;
@@ -68,7 +70,7 @@
     @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "name for the Kubernetes cluster")
     private String name;
 
-    @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, description = "description for the Kubernetes cluster")
+    @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "description for the Kubernetes cluster")
     private String description;
 
     @ACL(accessType = AccessType.UseEntry)
@@ -76,13 +78,13 @@
             description = "availability zone in which Kubernetes cluster to be launched")
     private Long zoneId;
 
-    @Parameter(name = ApiConstants.KUBERNETES_VERSION_ID, type = CommandType.UUID, entityType = KubernetesSupportedVersionResponse.class, required = true,
+    @Parameter(name = ApiConstants.KUBERNETES_VERSION_ID, type = CommandType.UUID, entityType = KubernetesSupportedVersionResponse.class,
             description = "Kubernetes version with which cluster to be launched")
     private Long kubernetesVersionId;
 
     @ACL(accessType = AccessType.UseEntry)
     @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class,
-            required = true, description = "the ID of the service offering for the virtual machines in the cluster.")
+            description = "the ID of the service offering for the virtual machines in the cluster.")
     private Long serviceOfferingId;
 
     @ACL(accessType = AccessType.UseEntry)
@@ -124,7 +126,7 @@
     private String externalLoadBalancerIpAddress;
 
     @Parameter(name=ApiConstants.SIZE, type = CommandType.LONG,
-            required = true, description = "number of Kubernetes cluster worker nodes")
+            description = "number of Kubernetes cluster worker nodes")
     private Long clusterSize;
 
     @Parameter(name = ApiConstants.DOCKER_REGISTRY_USER_NAME, type = CommandType.STRING,
@@ -143,6 +145,9 @@
             description = "root disk size in GB for each node")
     private Long nodeRootDiskSize;
 
+    @Parameter(name = ApiConstants.CLUSTER_TYPE, type = CommandType.STRING, description = "type of the cluster: CloudManaged, ExternalManaged. The default value is CloudManaged.", since="4.19.0")
+    private String clusterType;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -155,7 +160,7 @@
     }
 
     public String getDisplayName() {
-        return description;
+        return StringUtils.firstNonEmpty(description, name);
     }
 
     public Long getDomainId() {
@@ -232,6 +237,13 @@
         }
     }
 
+    public String getClusterType() {
+        if (clusterType == null) {
+            return KubernetesCluster.ClusterType.CloudManaged.toString();
+        }
+        return clusterType;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -278,7 +290,8 @@
     @Override
     public void execute() {
         try {
-            if (!kubernetesClusterService.startKubernetesCluster(getEntityId(), true)) {
+            if (KubernetesCluster.ClusterType.valueOf(getClusterType()) == KubernetesCluster.ClusterType.CloudManaged
+                    && !kubernetesClusterService.startKubernetesCluster(getEntityId(), true)) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to start Kubernetes cluster");
             }
             KubernetesClusterResponse response = kubernetesClusterService.createKubernetesClusterResponse(getEntityId());
@@ -291,8 +304,20 @@
 
     @Override
     public void create() throws CloudRuntimeException {
+        KubernetesCluster cluster;
+        KubernetesCluster.ClusterType type;
         try {
-            KubernetesCluster cluster = kubernetesClusterService.createKubernetesCluster(this);
+            type = KubernetesCluster.ClusterType.valueOf(getClusterType());
+        } catch (IllegalArgumentException e) {
+            throw new InvalidParameterValueException("Unable to resolve cluster type " + getClusterType() + " to a supported value (CloudManaged, ExternalManaged)");
+        }
+
+        try {
+            if (type == KubernetesCluster.ClusterType.CloudManaged) {
+                cluster = kubernetesClusterService.createManagedKubernetesCluster(this);
+            } else {
+                cluster = kubernetesClusterService.createUnmanagedKubernetesCluster(this);
+            }
             if (cluster == null) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Kubernetes cluster");
             }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java
index 564ae78..2b4a128 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java
@@ -58,6 +58,18 @@
             description = "the ID of the Kubernetes cluster")
     private Long id;
 
+    @Parameter(name = ApiConstants.CLEANUP,
+            type = CommandType.BOOLEAN,
+            since = "4.19.0",
+            description = "Destroy attached instances of the ExternalManaged Cluster. Default: false")
+    private Boolean cleanup;
+
+    @Parameter(name = ApiConstants.EXPUNGE,
+            type = CommandType.BOOLEAN,
+            since = "4.19.0",
+            description = "Expunge attached instances of the ExternalManaged Cluster. If true, value of cleanup is ignored. Default: false")
+    private Boolean expunge;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -66,6 +78,14 @@
         return id;
     }
 
+    public Boolean getCleanup() {
+        return cleanup != null && cleanup;
+    }
+
+    public Boolean getExpunge() {
+        return expunge != null && expunge;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -73,7 +93,7 @@
     @Override
     public void execute() throws ServerApiException, ConcurrentOperationException {
         try {
-            if (!kubernetesClusterService.deleteKubernetesCluster(id)) {
+            if (!kubernetesClusterService.deleteKubernetesCluster(this)) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to delete Kubernetes cluster ID: %d", getId()));
             }
             SuccessResponse response = new SuccessResponse(getCommandName());
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ListKubernetesClustersCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ListKubernetesClustersCmd.java
index 692b934..33eab2c 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ListKubernetesClustersCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ListKubernetesClustersCmd.java
@@ -61,6 +61,10 @@
             " (a substring match is made against the parameter value, data for all matching Kubernetes clusters will be returned)")
     private String name;
 
+    @Parameter(name = ApiConstants.CLUSTER_TYPE, type = CommandType.STRING, since = "4.19.0",
+            description = "type of the cluster: CloudManaged, ExternalManaged")
+    private String clusterType;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -77,6 +81,10 @@
         return name;
     }
 
+    public String getClusterType() {
+        return clusterType;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java
new file mode 100644
index 0000000..704d0b2
--- /dev/null
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java
@@ -0,0 +1,103 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command.user.kubernetes.cluster;
+
+import com.cloud.kubernetes.cluster.KubernetesClusterService;
+import com.cloud.utils.exception.CloudRuntimeException;
+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.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.KubernetesClusterResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.RemoveVirtualMachinesFromKubernetesClusterResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(name = "removeVirtualMachinesFromKubernetesCluster",
+        description = "Remove VMs from an ExternalManaged kubernetes cluster. Not applicable for CloudManaged kubernetes clusters.",
+        responseObject = RemoveVirtualMachinesFromKubernetesClusterResponse.class,
+        since = "4.19.0",
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
+public class RemoveVirtualMachinesFromKubernetesClusterCmd extends BaseListCmd {
+    public static final Logger LOGGER = Logger.getLogger(RemoveVirtualMachinesFromKubernetesClusterCmd.class.getName());
+
+    @Inject
+    public KubernetesClusterService kubernetesClusterService;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID,
+            entityType = KubernetesClusterResponse.class,
+            required = true,
+            description = "the ID of the Kubernetes cluster")
+    private Long id;
+
+    @Parameter(name = ApiConstants.VIRTUAL_MACHINE_IDS, type = CommandType.LIST,
+            collectionType=CommandType.UUID,
+            entityType = UserVmResponse.class,
+            required = true,
+            description = "the IDs of the VMs to remove from the cluster")
+    private List<Long> vmIds;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public List<Long> getVmIds() {
+        return vmIds;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public void execute() throws ServerApiException {
+        try {
+            List<RemoveVirtualMachinesFromKubernetesClusterResponse> responseList = kubernetesClusterService.removeVmsFromCluster(this);
+            if (responseList.size() < 1) {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Provided VMs are not part of the CKS cluster");
+            }
+            ListResponse<RemoveVirtualMachinesFromKubernetesClusterResponse> listResponse = new ListResponse<>();
+            listResponse.setResponseName(getCommandName());
+            listResponse.setResponses(responseList);
+            setResponseObject(listResponse);
+        } catch (CloudRuntimeException e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+        }
+    }
+}
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
index f3c9939..e5a5c90 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java
@@ -43,7 +43,7 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 
 @APICommand(name = "scaleKubernetesCluster",
-        description = "Scales a created, running or stopped Kubernetes cluster",
+        description = "Scales a created, running or stopped CloudManaged Kubernetes cluster",
         responseObject = KubernetesClusterResponse.class,
         responseView = ResponseObject.ResponseView.Restricted,
         entityType = {KubernetesCluster.class},
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java
index 1140cac..7a7c1e8 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java
@@ -36,7 +36,7 @@
 import com.cloud.kubernetes.cluster.KubernetesClusterService;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-@APICommand(name = "startKubernetesCluster", description = "Starts a stopped Kubernetes cluster",
+@APICommand(name = "startKubernetesCluster", description = "Starts a stopped CloudManaged Kubernetes cluster",
         responseObject = KubernetesClusterResponse.class,
         responseView = ResponseObject.ResponseView.Restricted,
         entityType = {KubernetesCluster.class},
@@ -99,6 +99,10 @@
         if (kubernetesCluster == null) {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Given Kubernetes cluster was not found");
         }
+        if (!kubernetesClusterService.isCommandSupported(kubernetesCluster, getActualCommandName())) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    String.format("Start kubernetes cluster is not supported for an externally managed cluster (%s)", kubernetesCluster.getName()));
+        }
         return kubernetesCluster;
     }
 
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java
index 393786f..866a7a8 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java
@@ -37,7 +37,7 @@
 import com.cloud.kubernetes.cluster.KubernetesClusterService;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-@APICommand(name = "stopKubernetesCluster", description = "Stops a running Kubernetes cluster",
+@APICommand(name = "stopKubernetesCluster", description = "Stops a running CloudManaged Kubernetes cluster",
         responseObject = SuccessResponse.class,
         responseView = ResponseObject.ResponseView.Restricted,
         entityType = {KubernetesCluster.class},
@@ -95,7 +95,7 @@
     @Override
     public void execute() throws ServerApiException, ConcurrentOperationException {
         try {
-            if (!kubernetesClusterService.stopKubernetesCluster(getId())) {
+            if (!kubernetesClusterService.stopKubernetesCluster(this)) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to start Kubernetes cluster ID: %d", getId()));
             }
             final SuccessResponse response = new SuccessResponse(getCommandName());
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java
index 3283ea0..2cbedf5 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java
@@ -38,7 +38,7 @@
 import com.cloud.kubernetes.cluster.KubernetesClusterService;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-@APICommand(name = "upgradeKubernetesCluster", description = "Upgrades a running Kubernetes cluster",
+@APICommand(name = "upgradeKubernetesCluster", description = "Upgrades a running CloudManaged Kubernetes cluster",
         responseObject = KubernetesClusterResponse.class,
         responseView = ResponseObject.ResponseView.Restricted,
         entityType = {KubernetesCluster.class},
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java
index a67d41a..168dfaf 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java
@@ -159,6 +159,10 @@
     @Param(description = "Maximum size of the cluster")
     private Long maxSize;
 
+    @SerializedName(ApiConstants.CLUSTER_TYPE)
+    @Param(description = "the type of the cluster")
+    private KubernetesCluster.ClusterType clusterType;
+
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "the date when this Kubernetes cluster was created")
     private Date created;
@@ -386,4 +390,12 @@
     public void setCreated(Date created) {
         this.created = created;
     }
+
+    public KubernetesCluster.ClusterType getClusterType() {
+        return clusterType;
+    }
+
+    public void setClusterType(KubernetesCluster.ClusterType clusterType) {
+        this.clusterType = clusterType;
+    }
 }
diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/RemoveVirtualMachinesFromKubernetesClusterResponse.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/RemoveVirtualMachinesFromKubernetesClusterResponse.java
new file mode 100644
index 0000000..eb2dfce
--- /dev/null
+++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/RemoveVirtualMachinesFromKubernetesClusterResponse.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 org.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+
+public class RemoveVirtualMachinesFromKubernetesClusterResponse extends SuccessResponse {
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "the id of the Kubernetes cluster")
+    private String vmId;
+
+    public RemoveVirtualMachinesFromKubernetesClusterResponse() {
+    }
+
+    public void setVmId(String vmId) {
+        this.vmId = vmId;
+    }
+}
diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
index 52e555c..a6d46ff 100644
--- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
+++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java
@@ -1,25 +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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES 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.kubernetes.cluster;
 
-
-import java.util.ArrayList;
-import java.util.List;
-
+import com.cloud.api.query.dao.TemplateJoinDao;
+import com.cloud.api.query.vo.TemplateJoinVO;
+import com.cloud.dc.DataCenter;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterActionWorker;
+import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
+import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
+import com.cloud.network.Network;
+import com.cloud.network.dao.FirewallRulesDao;
+import com.cloud.network.rules.FirewallRule;
+import com.cloud.network.rules.FirewallRuleVO;
+import com.cloud.network.vpc.NetworkACL;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.User;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd;
+import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMachinesFromKubernetesClusterCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+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;
@@ -28,19 +55,11 @@
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.api.query.dao.TemplateJoinDao;
-import com.cloud.api.query.vo.TemplateJoinVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterActionWorker;
-import com.cloud.network.Network;
-import com.cloud.network.dao.FirewallRulesDao;
-import com.cloud.network.rules.FirewallRule;
-import com.cloud.network.rules.FirewallRuleVO;
-import com.cloud.network.vpc.NetworkACL;
-import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.dao.VMTemplateDao;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 @RunWith(MockitoJUnitRunner.class)
 public class KubernetesClusterManagerImplTest {
@@ -54,6 +73,18 @@
     @Mock
     TemplateJoinDao templateJoinDao;
 
+    @Mock
+    KubernetesClusterDao kubernetesClusterDao;
+
+    @Mock
+    KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
+
+    @Mock
+    VMInstanceDao vmInstanceDao;
+
+    @Mock
+    private AccountManager accountManager;
+
     @Spy
     @InjectMocks
     KubernetesClusterManagerImpl kubernetesClusterManager;
@@ -171,7 +202,7 @@
     }
 
     @Test
-    public void testValidateKubernetesClusterScaleSizeDownsacaleNoError() {
+    public void testValidateKubernetesClusterScaleSizeDownscaleNoError() {
         KubernetesClusterVO clusterVO = Mockito.mock(KubernetesClusterVO.class);
         Mockito.when(clusterVO.getState()).thenReturn(KubernetesCluster.State.Running);
         Mockito.when(clusterVO.getControlNodeCount()).thenReturn(1L);
@@ -209,5 +240,56 @@
         Mockito.when(templateDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(VMTemplateVO.class));
         Mockito.when(templateJoinDao.newTemplateView(Mockito.any(VMTemplateVO.class), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(Mockito.mock(TemplateJoinVO.class)));
         kubernetesClusterManager.validateKubernetesClusterScaleSize(clusterVO, 4L, 10, Mockito.mock(DataCenter.class));
+
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
+        overrideDefaultConfigValue(KubernetesClusterService.KubernetesServiceEnabled, "_defaultValue", "true");
+        Mockito.doNothing().when(accountManager).checkAccess(
+                Mockito.any(Account.class), Mockito.any(), Mockito.anyBoolean(), Mockito.any());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        CallContext.unregister();
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
+    @Test
+    public void addVmsToCluster() {
+        KubernetesClusterVO cluster = Mockito.mock(KubernetesClusterVO.class);
+        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+        AddVirtualMachinesToKubernetesClusterCmd cmd = Mockito.mock(AddVirtualMachinesToKubernetesClusterCmd.class);
+        List<Long> vmIds = Arrays.asList(1L, 2L, 3L);
+
+        Mockito.when(cmd.getId()).thenReturn(1L);
+        Mockito.when(cmd.getVmIds()).thenReturn(vmIds);
+        Mockito.when(cmd.getActualCommandName()).thenReturn(BaseCmd.getCommandNameByClass(RemoveVirtualMachinesFromKubernetesClusterCmd.class));
+        Mockito.when(cluster.getClusterType()).thenReturn(KubernetesCluster.ClusterType.ExternalManaged);
+        Mockito.when(vmInstanceDao.findById(Mockito.anyLong())).thenReturn(vm);
+        Mockito.when(kubernetesClusterDao.findById(Mockito.anyLong())).thenReturn(cluster);
+        Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmIdsIn(1L, vmIds)).thenReturn(Collections.emptyList());
+        Assert.assertTrue(kubernetesClusterManager.addVmsToCluster(cmd));
+    }
+
+    @Test
+    public void removeVmsFromCluster() {
+        KubernetesClusterVO cluster = Mockito.mock(KubernetesClusterVO.class);
+        RemoveVirtualMachinesFromKubernetesClusterCmd cmd = Mockito.mock(RemoveVirtualMachinesFromKubernetesClusterCmd.class);
+        List<Long> vmIds = Arrays.asList(1L, 2L, 3L);
+
+        Mockito.when(cmd.getId()).thenReturn(1L);
+        Mockito.when(cmd.getVmIds()).thenReturn(vmIds);
+        Mockito.when(cmd.getActualCommandName()).thenReturn(BaseCmd.getCommandNameByClass(RemoveVirtualMachinesFromKubernetesClusterCmd.class));
+        Mockito.when(cluster.getClusterType()).thenReturn(KubernetesCluster.ClusterType.ExternalManaged);
+        Mockito.when(kubernetesClusterDao.findById(Mockito.anyLong())).thenReturn(cluster);
+        Assert.assertTrue(kubernetesClusterManager.removeVmsFromCluster(cmd).size() > 0);
     }
 }
diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtilTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtilTest.java
index 53bc1a3..31363db 100644
--- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtilTest.java
+++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtilTest.java
@@ -16,35 +16,22 @@
 // under the License.
 package com.cloud.kubernetes.cluster.utils;
 
-import java.io.File;
-
+import com.cloud.utils.Pair;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.utils.Pair;
-import com.cloud.utils.ssh.SshHelper;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(SshHelper.class)
+@RunWith(MockitoJUnitRunner.class)
 public class KubernetesClusterUtilTest {
-    String ipAddress = "10.1.1.1";
-    int port = 2222;
-    String user = "user";
-    File sshKeyFile = Mockito.mock(File.class);
-    String hostName = "host";
 
-    private void mockSshHelperExecuteThrowAndTestVersionMatch() {
+    private void executeThrowAndTestVersionMatch() {
         Pair<Boolean, String> resultPair = null;
         boolean result = KubernetesClusterUtil.clusterNodeVersionMatches(resultPair, "1.24.0");
         Assert.assertFalse(result);
     }
 
-    private void mockSshHelperExecuteAndTestVersionMatch(boolean status, String response, boolean expectedResult) {
+    private void executeAndTestVersionMatch(boolean status, String response, boolean expectedResult) {
         Pair<Boolean, String> resultPair = new Pair<>(status, response);
         boolean result = KubernetesClusterUtil.clusterNodeVersionMatches(resultPair, "1.24.0");
         Assert.assertEquals(expectedResult, result);
@@ -52,17 +39,17 @@
 
     @Test
     public void testClusterNodeVersionMatches() {
-        PowerMockito.mockStatic(SshHelper.class);
         String v1233WorkerNodeOutput = "v1.23.3";
         String v1240WorkerNodeOutput = "v1.24.0";
-        mockSshHelperExecuteAndTestVersionMatch(true, v1240WorkerNodeOutput, true);
 
-        mockSshHelperExecuteAndTestVersionMatch(true, v1233WorkerNodeOutput, false);
+        executeAndTestVersionMatch(true, v1240WorkerNodeOutput, true);
 
-        mockSshHelperExecuteAndTestVersionMatch(false, v1240WorkerNodeOutput, false);
+        executeAndTestVersionMatch(true, v1233WorkerNodeOutput, false);
 
-        mockSshHelperExecuteAndTestVersionMatch(false, v1233WorkerNodeOutput, false);
+        executeAndTestVersionMatch(false, v1240WorkerNodeOutput, false);
 
-        mockSshHelperExecuteThrowAndTestVersionMatch();
+        executeAndTestVersionMatch(false, v1233WorkerNodeOutput, false);
+
+        executeThrowAndTestVersionMatch();
     }
 }
diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java
index 0427c35..d92b2c4 100644
--- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java
+++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java
@@ -30,20 +30,22 @@
 import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd;
 import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd;
 import org.apache.cloudstack.api.command.user.kubernetes.version.ListKubernetesSupportedVersionsCmd;
+import org.apache.cloudstack.api.response.KubernetesSupportedVersionResponse;
+import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.framework.config.ConfigKey;
 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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.api.query.dao.TemplateJoinDao;
 import com.cloud.api.query.vo.TemplateJoinVO;
@@ -70,8 +72,7 @@
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ComponentContext.class})
+@RunWith(MockitoJUnitRunner.class)
 public class KubernetesVersionServiceTest {
 
     @InjectMocks
@@ -92,6 +93,9 @@
     @Mock
     private TemplateApiService templateService;
 
+    AutoCloseable closeable;
+
+
     private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
         Field f = ConfigKey.class.getDeclaredField(name);
         f.setAccessible(true);
@@ -100,7 +104,7 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
 
         overrideDefaultConfigValue(KubernetesClusterService.KubernetesServiceEnabled, "_defaultValue", "true");
 
@@ -114,12 +118,9 @@
         when(kubernetesSupportedVersionDao.createSearchCriteria()).thenReturn(versionSearchCriteria);
 
         DataCenterVO zone = Mockito.mock(DataCenterVO.class);
-        when(zone.getId()).thenReturn(1L);
         when(dataCenterDao.findById(Mockito.anyLong())).thenReturn(zone);
 
         TemplateJoinVO templateJoinVO = Mockito.mock(TemplateJoinVO.class);
-        when(templateJoinVO.getId()).thenReturn(1L);
-        when(templateJoinVO.getUrl()).thenReturn("https://download.cloudstack.com");
         when(templateJoinVO.getState()).thenReturn(ObjectInDataStoreStateMachine.State.Ready);
         when(templateJoinDao.findById(Mockito.anyLong())).thenReturn(templateJoinVO);
 
@@ -130,6 +131,7 @@
 
     @After
     public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -141,7 +143,13 @@
         versionVOs.add(versionVO);
         when(kubernetesSupportedVersionDao.findById(Mockito.anyLong())).thenReturn(versionVO);
         when(kubernetesSupportedVersionDao.search(Mockito.any(SearchCriteria.class), Mockito.any(Filter.class))).thenReturn(versionVOs);
-        kubernetesVersionService.listKubernetesSupportedVersions(cmd);
+        ListResponse<KubernetesSupportedVersionResponse> response =
+                kubernetesVersionService.listKubernetesSupportedVersions(
+                cmd);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(Integer.valueOf(1), response.getCount());
+        Assert.assertEquals(1, response.getResponses().size());
+        Assert.assertEquals(KubernetesVersionService.MIN_KUBERNETES_VERSION, response.getResponses().get(0).getSemanticVersion());
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -206,13 +214,19 @@
         when(cmd.getMinimumRamSize()).thenReturn(KubernetesClusterService.MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE);
         Account systemAccount =  new AccountVO("system", 1L, "", Account.Type.ADMIN, "uuid");
         when(accountManager.getSystemAccount()).thenReturn(systemAccount);
-        PowerMockito.mockStatic(ComponentContext.class);
-        when(ComponentContext.inject(Mockito.any(RegisterIsoCmd.class))).thenReturn(new RegisterIsoCmd());
-        when(templateService.registerIso(Mockito.any(RegisterIsoCmd.class))).thenReturn(Mockito.mock(VirtualMachineTemplate.class));
-        VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
-        when(templateVO.getId()).thenReturn(1L);
-        when(templateDao.findById(Mockito.anyLong())).thenReturn(templateVO);
-        kubernetesVersionService.addKubernetesSupportedVersion(cmd);
+        try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
+            mockedComponentContext.when(() -> ComponentContext.inject(Mockito.any(RegisterIsoCmd.class))).thenReturn(
+                    new RegisterIsoCmd());
+
+            when(templateService.registerIso(Mockito.any(RegisterIsoCmd.class))).thenReturn(
+                    Mockito.mock(VirtualMachineTemplate.class));
+            VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
+            when(templateVO.getId()).thenReturn(1L);
+            when(templateDao.findById(Mockito.anyLong())).thenReturn(templateVO);
+            KubernetesSupportedVersionResponse response = kubernetesVersionService.addKubernetesSupportedVersion(cmd);
+            Assert.assertNotNull(response);
+            Mockito.verify(kubernetesSupportedVersionDao, Mockito.times(1)).persist(Mockito.any(KubernetesSupportedVersionVO.class));
+        }
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -237,12 +251,11 @@
         when(kubernetesSupportedVersionDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(KubernetesSupportedVersionVO.class));
         List<KubernetesClusterVO> clusters = new ArrayList<>();
         when(kubernetesClusterDao.listAllByKubernetesVersion(Mockito.anyLong())).thenReturn(clusters);
-        when(templateDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(VMTemplateVO.class));
-        PowerMockito.mockStatic(ComponentContext.class);
-        when(ComponentContext.inject(Mockito.any(DeleteIsoCmd.class))).thenReturn(new DeleteIsoCmd());
-        when(templateService.deleteIso(Mockito.any(DeleteIsoCmd.class))).thenReturn(true);
-        when(kubernetesClusterDao.remove(Mockito.anyLong())).thenReturn(true);
-        kubernetesVersionService.deleteKubernetesSupportedVersion(cmd);
+        try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
+            mockedComponentContext.when(() -> ComponentContext.inject(Mockito.any(DeleteIsoCmd.class))).thenReturn(new DeleteIsoCmd());
+            kubernetesVersionService.deleteKubernetesSupportedVersion(cmd);
+            Mockito.verify(kubernetesSupportedVersionDao).remove(Mockito.anyLong());
+        }
     }
 
     @Test
@@ -254,11 +267,12 @@
         CallContext.register(user, account);
         when(kubernetesSupportedVersionDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(KubernetesSupportedVersionVO.class));
         KubernetesSupportedVersionVO version = Mockito.mock(KubernetesSupportedVersionVO.class);
-        when(kubernetesSupportedVersionDao.createForUpdate(Mockito.anyLong())).thenReturn(version);
-        when(kubernetesSupportedVersionDao.update(Mockito.anyLong(), Mockito.any(KubernetesSupportedVersionVO.class))).thenReturn(true);
         when(version.getState()).thenReturn(KubernetesSupportedVersion.State.Disabled);
         when(version.getSemanticVersion()).thenReturn(KubernetesVersionService.MIN_KUBERNETES_VERSION);
         when(kubernetesSupportedVersionDao.findById(Mockito.anyLong())).thenReturn(version);
-        kubernetesVersionService.updateKubernetesSupportedVersion(cmd);
+        KubernetesSupportedVersionResponse response = kubernetesVersionService.updateKubernetesSupportedVersion(cmd);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(KubernetesSupportedVersion.State.Disabled.toString(), response.getState());
+        Assert.assertEquals(KubernetesVersionService.MIN_KUBERNETES_VERSION, response.getSemanticVersion());
     }
 }
diff --git a/plugins/integrations/kubernetes-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/integrations/kubernetes-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/integrations/kubernetes-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/integrations/prometheus/pom.xml b/plugins/integrations/prometheus/pom.xml
index 73349f5..917c597 100644
--- a/plugins/integrations/prometheus/pom.xml
+++ b/plugins/integrations/prometheus/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterImpl.java b/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterImpl.java
index b44b888..17fbd48 100644
--- a/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterImpl.java
+++ b/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterImpl.java
@@ -29,6 +29,7 @@
 import com.cloud.dc.DedicatedResourceVO;
 import com.cloud.dc.dao.DedicatedResourceDao;
 import com.cloud.host.HostStats;
+import com.cloud.host.HostTagVO;
 import com.cloud.user.Account;
 import com.cloud.user.dao.AccountDao;
 import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
@@ -81,6 +82,24 @@
     private static final String ONLINE = "online";
     private static final String OFFLINE = "offline";
 
+    enum MissingInfoFilter {
+        Host_Stats("hostStats"),
+        CPU_CAPACITY("cpuCapacity"),
+        MEM_CAPACITY("memCapacity"),
+        CORE_CAPACITY("coreCapacity");
+
+        private final String name;
+
+        MissingInfoFilter(String name){
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
     private static List<Item> metricsItems = new ArrayList<>();
 
     @Inject
@@ -128,8 +147,6 @@
         Map<String, Integer> upHosts = new HashMap<>();
         Map<String, Integer> downHosts = new HashMap<>();
 
-        HostStats hostStats;
-
         for (final HostVO host : hostDao.listAll()) {
             if (host == null || host.getType() != Host.Type.Routing || host.getDataCenterId() != dcId) {
                 continue;
@@ -146,8 +163,6 @@
             int isDedicated = (dr != null) ? 1 : 0;
             metricsList.add(new ItemHostIsDedicated(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), isDedicated));
 
-            String hostTags = markTagMaps(host, totalHosts, upHosts,  downHosts);
-            hostStats = ApiDBUtils.getHostStatistics(host.getId());
 
             // Get account, domain details for dedicated hosts
             if (isDedicated == 1) {
@@ -159,16 +174,22 @@
                 metricsList.add(new ItemHostDedicatedToAccount(zoneName, host.getName(), accountName, domain.getPath(), isDedicated));
             }
 
+            String hostTags = markTagMaps(host, totalHosts, upHosts,  downHosts);
+            HostStats hostStats = ApiDBUtils.getHostStatistics(host.getId());
+
+            if (hostStats == null){
+                metricsList.add(new MissingHostInfo(zoneName, host.getName(), MissingInfoFilter.Host_Stats));
+            }
+
             final String cpuFactor = String.valueOf(CapacityManager.CpuOverprovisioningFactor.valueIn(host.getClusterId()));
             final CapacityVO cpuCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_CPU);
-            final double cpuUsedMhz = hostStats.getCpuUtilization() * host.getCpus() * host.getSpeed() / 100.0 ;
 
-            if (host.isInMaintenanceStates()) {
-                metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, ALLOCATED, 0L, isDedicated, hostTags));
-                metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, USED, 0L, isDedicated, hostTags));
-                metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, TOTAL, 0L, isDedicated, hostTags));
+            if (cpuCapacity == null && !host.isInMaintenanceStates()){
+                metricsList.add(new MissingHostInfo(zoneName, host.getName(), MissingInfoFilter.CPU_CAPACITY));
             }
-            else if (cpuCapacity != null && cpuCapacity.getCapacityState() == CapacityState.Enabled) {
+
+            if (hostStats != null && cpuCapacity != null && cpuCapacity.getCapacityState() == CapacityState.Enabled) {
+                final double cpuUsedMhz = hostStats.getCpuUtilization() * host.getCpus() * host.getSpeed() / 100.0 ;
                 metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, ALLOCATED, cpuCapacity.getUsedCapacity(), isDedicated, hostTags));
                 metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, USED, cpuUsedMhz, isDedicated, hostTags));
                 metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, TOTAL, cpuCapacity.getTotalCapacity(), isDedicated, hostTags));
@@ -180,12 +201,12 @@
 
             final String memoryFactor = String.valueOf(CapacityManager.MemOverprovisioningFactor.valueIn(host.getClusterId()));
             final CapacityVO memCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_MEMORY);
-            if (host.isInMaintenanceStates()) {
-                metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, ALLOCATED, 0L, isDedicated, hostTags));
-                metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, USED, 0, isDedicated, hostTags));
-                metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, TOTAL, 0L, isDedicated, hostTags));
+
+            if (memCapacity == null && !host.isInMaintenanceStates()){
+                metricsList.add(new MissingHostInfo(zoneName, host.getName(), MissingInfoFilter.MEM_CAPACITY));
             }
-            else if (memCapacity != null && memCapacity.getCapacityState() == CapacityState.Enabled) {
+
+            if (hostStats != null && memCapacity != null && memCapacity.getCapacityState() == CapacityState.Enabled) {
                 metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, ALLOCATED, memCapacity.getUsedCapacity(), isDedicated, hostTags));
                 metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, USED, hostStats.getUsedMemory(), isDedicated, hostTags));
                 metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, TOTAL, memCapacity.getTotalCapacity(), isDedicated, hostTags));
@@ -196,13 +217,13 @@
             }
 
             metricsList.add(new ItemHostVM(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), vmDao.listByHostId(host.getId()).size()));
-
             final CapacityVO coreCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_CPU_CORE);
-            if (host.isInMaintenanceStates()) {
-                metricsList.add(new ItemVMCore(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), USED, 0L, isDedicated, hostTags));
-                metricsList.add(new ItemVMCore(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), TOTAL, 0L, isDedicated, hostTags));
+
+            if (coreCapacity == null && !host.isInMaintenanceStates()){
+                metricsList.add(new MissingHostInfo(zoneName, host.getName(), MissingInfoFilter.CORE_CAPACITY));
             }
-            else if (coreCapacity != null && coreCapacity.getCapacityState() == CapacityState.Enabled) {
+
+            if (hostStats != null && coreCapacity != null && coreCapacity.getCapacityState() == CapacityState.Enabled) {
                 metricsList.add(new ItemVMCore(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), USED, coreCapacity.getUsedCapacity(), isDedicated, hostTags));
                 metricsList.add(new ItemVMCore(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), TOTAL, coreCapacity.getTotalCapacity(), isDedicated, hostTags));
             } else {
@@ -212,17 +233,17 @@
         }
 
         final List<CapacityDaoImpl.SummedCapacity> cpuCapacity = capacityDao.findCapacityBy((int) Capacity.CAPACITY_TYPE_CPU, dcId, null, null);
-        if (cpuCapacity != null && cpuCapacity.size() > 0) {
+        if (cpuCapacity != null && !cpuCapacity.isEmpty()) {
             metricsList.add(new ItemHostCpu(zoneName, zoneUuid, null, null, null, null, ALLOCATED, cpuCapacity.get(0).getAllocatedCapacity() != null ? cpuCapacity.get(0).getAllocatedCapacity() : 0, 0, ""));
         }
 
         final List<CapacityDaoImpl.SummedCapacity> memCapacity = capacityDao.findCapacityBy((int) Capacity.CAPACITY_TYPE_MEMORY, dcId, null, null);
-        if (memCapacity != null && memCapacity.size() > 0) {
+        if (memCapacity != null && !memCapacity.isEmpty()) {
             metricsList.add(new ItemHostMemory(zoneName, zoneUuid, null, null, null, null, ALLOCATED, memCapacity.get(0).getAllocatedCapacity() != null ? memCapacity.get(0).getAllocatedCapacity() : 0, 0, ""));
         }
 
         final List<CapacityDaoImpl.SummedCapacity> coreCapacity = capacityDao.findCapacityBy((int) Capacity.CAPACITY_TYPE_CPU_CORE, dcId, null, null);
-        if (coreCapacity != null && coreCapacity.size() > 0) {
+        if (coreCapacity != null && !coreCapacity.isEmpty()) {
             metricsList.add(new ItemVMCore(zoneName, zoneUuid, null, null, null, ALLOCATED, coreCapacity.get(0).getAllocatedCapacity() != null ? coreCapacity.get(0).getAllocatedCapacity() : 0, 0, ""));
         }
 
@@ -234,11 +255,13 @@
     }
 
     private String markTagMaps(HostVO host, Map<String, Integer> totalHosts, Map<String, Integer> upHosts, Map<String, Integer> downHosts) {
-        List<String> hostTags = _hostTagsDao.getHostTags(host.getId());
+        List<HostTagVO> hostTagVOS = _hostTagsDao.getHostTags(host.getId());
+        List<String> hostTags = new ArrayList<>();
+        hostTagVOS.forEach(hostTagVO -> hostTags.add(hostTagVO.getTag()));
         markTags(hostTags,totalHosts);
-        if (host.getStatus() == Status.Up) {
+        if (host.getStatus() == Status.Up && !host.isInMaintenanceStates()) {
             markTags(hostTags, upHosts);
-        } else if (host.getStatus() == Status.Disconnected || host.getStatus() == Status.Down) {
+        } else if (host.getStatus() == Status.Disconnected || host.getStatus() == Status.Down || host.isInMaintenanceStates()) {
             markTags(hostTags, downHosts);
         }
         return StringUtils.join(hostTags, ",");
@@ -277,10 +300,12 @@
                     metricsList.add(new ItemHostMemory(zoneName, zoneUuid, null, null, null, null, ALLOCATED, allocatedCapacityByTag.third(), 0, tag));
                 });
 
-        List<String> allHostTags = hostDao.listAll().stream()
+        List<HostTagVO> allHostTagVOS = hostDao.listAll().stream()
                 .flatMap( h -> _hostTagsDao.getHostTags(h.getId()).stream())
                 .distinct()
                 .collect(Collectors.toList());
+        List<String> allHostTags = new ArrayList<>();
+        allHostTagVOS.forEach(hostTagVO -> allHostTags.add(hostTagVO.getTag()));
 
         for (final State state : State.values()) {
             for (final String hostTag : allHostTags) {
@@ -621,6 +646,25 @@
         }
     }
 
+    class MissingHostInfo extends Item {
+
+        String zoneName;
+        String hostName;
+        MissingInfoFilter filter;
+
+        public MissingHostInfo(String zoneName, String hostname, MissingInfoFilter filter) {
+            super("cloudstack_host_missing_info");
+            this.zoneName = zoneName;
+            this.hostName = hostname;
+            this.filter = filter;
+        }
+
+        @Override
+        public String toMetricsString() {
+            return String.format("%s{zone=\"%s\",hostname=\"%s\",filter=\"%s\"} -1", name, zoneName, hostName, filter);
+        }
+    }
+
     class ItemHostCpu extends Item {
         String zoneName;
         String zoneUuid;
diff --git a/plugins/metrics/pom.xml b/plugins/metrics/pom.xml
index aebd267..863bada 100644
--- a/plugins/metrics/pom.xml
+++ b/plugins/metrics/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
index 979e4aa..136976c 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
@@ -27,9 +27,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.naming.ConfigurationException;
 
+import com.cloud.dc.ClusterVO;
+import com.cloud.utils.Ternary;
 import org.apache.cloudstack.api.ApiErrorCode;
 import org.apache.cloudstack.api.ListClustersMetricsCmd;
 import org.apache.cloudstack.api.ListDbMetricsCmd;
@@ -55,6 +59,7 @@
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.cluster.ClusterDrsAlgorithm;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.management.ManagementServerHost.State;
 import org.apache.cloudstack.response.ClusterMetricsResponse;
@@ -71,6 +76,7 @@
 import org.apache.cloudstack.response.VolumeMetricsStatsResponse;
 import org.apache.cloudstack.response.ZoneMetricsResponse;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
 import org.apache.commons.beanutils.BeanUtils;
@@ -176,6 +182,9 @@
     @Inject
     private VolumeStatsDao volumeStatsDao;
 
+    @Inject
+    private ObjectStoreDao objectStoreDao;
+
     private static Gson gson = new Gson();
 
     protected MetricsServiceImpl() {
@@ -557,6 +566,7 @@
         response.setHosts(hostDao.countAllByType(Host.Type.Routing));
         response.setStoragePools(storagePoolDao.countAll());
         response.setImageStores(imageStoreDao.countAllImageStores());
+        response.setObjectStores(objectStoreDao.countAllObjectStores());
         response.setSystemvms(vmInstanceDao.listByTypes(VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm).size());
         response.setRouters(domainRouterDao.countAllByRole(VirtualRouter.Role.VIRTUAL_ROUTER));
         response.setInternalLbs(domainRouterDao.countAllByRole(VirtualRouter.Role.INTERNAL_LB_VM));
@@ -626,6 +636,7 @@
     @Override
     public List<StoragePoolMetricsResponse> listStoragePoolMetrics(List<StoragePoolResponse> poolResponses) {
         final List<StoragePoolMetricsResponse> metricsResponses = new ArrayList<>();
+        Map<String, Long> clusterUuidToIdMap = clusterDao.findByUuids(poolResponses.stream().map(StoragePoolResponse::getClusterId).toArray(String[]::new)).stream().collect(Collectors.toMap(ClusterVO::getUuid, ClusterVO::getId));
         for (final StoragePoolResponse poolResponse: poolResponses) {
             StoragePoolMetricsResponse metricsResponse = new StoragePoolMetricsResponse();
 
@@ -635,11 +646,7 @@
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to generate storagepool metrics response");
             }
 
-            Long poolClusterId = null;
-            final Cluster cluster = clusterDao.findByUuid(poolResponse.getClusterId());
-            if (cluster != null) {
-                poolClusterId = cluster.getId();
-            }
+            Long poolClusterId = clusterUuidToIdMap.get(poolResponse.getClusterId());
             final Double storageThreshold = AlertManager.StorageCapacityThreshold.valueIn(poolClusterId);
             final Double storageDisableThreshold = CapacityManager.StorageCapacityDisableThreshold.valueIn(poolClusterId);
 
@@ -682,8 +689,10 @@
             final Float cpuDisableThreshold = DeploymentClusterPlanner.ClusterCPUCapacityDisableThreshold.valueIn(clusterId);
             final Float memoryDisableThreshold = DeploymentClusterPlanner.ClusterMemoryCapacityDisableThreshold.valueIn(clusterId);
 
-            Long upInstances = 0L;
-            Long totalInstances = 0L;
+            long upInstances = 0L;
+            long totalInstances = 0L;
+            long upSystemInstances = 0L;
+            long totalSystemInstances = 0L;
             for (final VMInstanceVO instance: vmInstanceDao.listByHostId(hostId)) {
                 if (instance == null) {
                     continue;
@@ -693,10 +702,16 @@
                     if (instance.getState() == VirtualMachine.State.Running) {
                         upInstances++;
                     }
+                } else if (instance.getType().isUsedBySystem()) {
+                    totalSystemInstances++;
+                    if (instance.getState() == VirtualMachine.State.Running) {
+                        upSystemInstances++;
+                    }
                 }
             }
             metricsResponse.setPowerState(hostResponse.getOutOfBandManagementResponse().getPowerState());
             metricsResponse.setInstances(upInstances, totalInstances);
+            metricsResponse.setSystemInstances(upSystemInstances, totalSystemInstances);
             metricsResponse.setCpuTotal(hostResponse.getCpuNumber(), hostResponse.getCpuSpeed());
             metricsResponse.setCpuUsed(hostResponse.getCpuUsed(), hostResponse.getCpuNumber(), hostResponse.getCpuSpeed());
             metricsResponse.setCpuAllocated(hostResponse.getCpuAllocated(), hostResponse.getCpuNumber(), hostResponse.getCpuSpeed());
@@ -749,10 +764,13 @@
             final Long clusterId = cluster.getId();
 
             // CPU and memory capacities
-            final CapacityDaoImpl.SummedCapacity cpuCapacity = getCapacity((int) Capacity.CAPACITY_TYPE_CPU, null, clusterId);
-            final CapacityDaoImpl.SummedCapacity memoryCapacity = getCapacity((int) Capacity.CAPACITY_TYPE_MEMORY, null, clusterId);
+            final CapacityDaoImpl.SummedCapacity cpuCapacity = getCapacity(Capacity.CAPACITY_TYPE_CPU, null, clusterId);
+            final CapacityDaoImpl.SummedCapacity memoryCapacity = getCapacity(Capacity.CAPACITY_TYPE_MEMORY, null, clusterId);
             final HostMetrics hostMetrics = new HostMetrics(cpuCapacity, memoryCapacity);
 
+            List<Ternary<Long, Long, Long>> cpuList = new ArrayList<>();
+            List<Ternary<Long, Long, Long>> memoryList = new ArrayList<>();
+
             for (final Host host: hostDao.findByClusterId(clusterId)) {
                 if (host == null || host.getType() != Host.Type.Routing) {
                     continue;
@@ -761,7 +779,18 @@
                     hostMetrics.incrUpResources();
                 }
                 hostMetrics.incrTotalResources();
-                updateHostMetrics(hostMetrics, hostJoinDao.findById(host.getId()));
+                HostJoinVO hostJoin = hostJoinDao.findById(host.getId());
+                updateHostMetrics(hostMetrics, hostJoin);
+
+                cpuList.add(new Ternary<>(hostJoin.getCpuUsedCapacity(), hostJoin.getCpuReservedCapacity(), hostJoin.getCpus() * hostJoin.getSpeed()));
+                memoryList.add(new Ternary<>(hostJoin.getMemUsedCapacity(), hostJoin.getMemReservedCapacity(), hostJoin.getTotalMemory()));
+            }
+
+            try {
+                Double imbalance = ClusterDrsAlgorithm.getClusterImbalance(clusterId, cpuList, memoryList, null);
+                metricsResponse.setDrsImbalance(imbalance.isNaN() ? null : 100.0 * imbalance);
+            } catch (ConfigurationException e) {
+                LOGGER.warn("Failed to get cluster imbalance for cluster " + clusterId, e);
             }
 
             metricsResponse.setState(clusterResponse.getAllocationState(), clusterResponse.getManagedState());
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
index 18ea57d..5c25021 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/ClusterMetricsResponse.java
@@ -94,6 +94,10 @@
     @Param(description = "memory allocated disable threshold exceeded")
     private Boolean memoryAllocatedDisableThresholdExceeded;
 
+    @SerializedName("drsimbalance")
+    @Param(description = "DRS imbalance for the cluster")
+    private String drsImbalance;
+
     public void setState(final String allocationState, final String managedState) {
         this.state = allocationState;
         if (managedState.equals("Unmanaged")) {
@@ -208,4 +212,12 @@
             this.memoryAllocatedDisableThresholdExceeded = (1.0 * memAllocated / memTotal) > threshold;
         }
     }
+
+    public void setDrsImbalance(Double drsImbalance) {
+        if (drsImbalance != null) {
+            this.drsImbalance = String.format("%.2f%%", drsImbalance);
+        } else {
+            this.drsImbalance = null;
+        }
+    }
 }
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java
index 30f7e06..8e12d2c 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java
@@ -36,6 +36,10 @@
     @Param(description = "instances on the host")
     private String instances;
 
+    @SerializedName("systeminstances")
+    @Param(description = "system vm instances on the host")
+    private String systemInstances;
+
     @SerializedName("cputotalghz")
     @Param(description = "the total cpu capacity in Ghz")
     private String cpuTotal;
@@ -108,10 +112,12 @@
         this.powerState = powerState;
     }
 
-    public void setInstances(final Long running, final Long total) {
-        if (running != null && total != null) {
-            this.instances = String.format("%d / %d", running, total);
-        }
+    public void setSystemInstances(final long running, final long total) {
+        this.systemInstances = String.format("%d / %d", running, total);
+    }
+
+    public void setInstances(final long running, final long total) {
+        this.instances = String.format("%d / %d", running, total);
     }
 
     public void setCpuTotal(final Integer cpuNumber, final Long cpuSpeed) {
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java
index 280b799..cb1faf2 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java
@@ -47,6 +47,10 @@
     @Param(description = "Number of images stores")
     private Integer imageStores;
 
+    @SerializedName("objectstores")
+    @Param(description = "Number of object stores")
+    private Integer objectStores;
+
     @SerializedName("systemvms")
     @Param(description = "Number of systemvms")
     private Integer systemvms;
@@ -118,4 +122,8 @@
     public void setAlerts(Integer alerts) { this.alerts = alerts; }
 
     public void setInternalLbs(Integer internalLbs) { this.internalLbs = internalLbs; }
+
+    public void setObjectStores(Integer objectStores) {
+        this.objectStores = objectStores;
+    }
 }
diff --git a/plugins/network-elements/bigswitch/pom.xml b/plugins/network-elements/bigswitch/pom.xml
index 9e1697d..955602f 100644
--- a/plugins/network-elements/bigswitch/pom.xml
+++ b/plugins/network-elements/bigswitch/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/brocade-vcs/pom.xml b/plugins/network-elements/brocade-vcs/pom.xml
index 47d70bf..f5ff4bb 100644
--- a/plugins/network-elements/brocade-vcs/pom.xml
+++ b/plugins/network-elements/brocade-vcs/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <build>
diff --git a/plugins/network-elements/brocade-vcs/src/main/resources/META-INF/cloudstack/vcs/module.properties b/plugins/network-elements/brocade-vcs/src/main/resources/META-INF/cloudstack/vcs/module.properties
index db2c80d..b010fba 100644
--- a/plugins/network-elements/brocade-vcs/src/main/resources/META-INF/cloudstack/vcs/module.properties
+++ b/plugins/network-elements/brocade-vcs/src/main/resources/META-INF/cloudstack/vcs/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vcs
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/cisco-vnmc/pom.xml b/plugins/network-elements/cisco-vnmc/pom.xml
index 6d1cc2d..117c411 100644
--- a/plugins/network-elements/cisco-vnmc/pom.xml
+++ b/plugins/network-elements/cisco-vnmc/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/network-elements/cisco-vnmc/src/main/resources/META-INF/cloudstack/cisco-vnmc/module.properties b/plugins/network-elements/cisco-vnmc/src/main/resources/META-INF/cloudstack/cisco-vnmc/module.properties
index 69ffb6f..abaf575 100644
--- a/plugins/network-elements/cisco-vnmc/src/main/resources/META-INF/cloudstack/cisco-vnmc/module.properties
+++ b/plugins/network-elements/cisco-vnmc/src/main/resources/META-INF/cloudstack/cisco-vnmc/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=cisco-vnmc
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-acl-policy.xml b/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-acl-policy.xml
index c6f7d37..fc9e805 100755
--- a/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-acl-policy.xml
+++ b/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-acl-policy.xml
@@ -32,4 +32,4 @@
 <!--
     aclpolicydn="org-root/org-vlan-123/org-VDC-vlan-123/pol-test_policy"
     aclpolicyname="test_policy"
--->
\ No newline at end of file
+-->
diff --git a/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-ip-pool.xml b/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-ip-pool.xml
index 876fa21..0698c99 100755
--- a/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-ip-pool.xml
+++ b/plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/create-ip-pool.xml
@@ -55,4 +55,4 @@
     ippooldn="org-root/org-vlan-123/org-VDC-vlan-123/objgrp-ccc"
     ippoolname="ccc"
     ipvalue="10.1.1.20"
--->
\ No newline at end of file
+-->
diff --git a/plugins/network-elements/dns-notifier/pom.xml b/plugins/network-elements/dns-notifier/pom.xml
index 0006985..89084e2d 100644
--- a/plugins/network-elements/dns-notifier/pom.xml
+++ b/plugins/network-elements/dns-notifier/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>cloud-plugin-example-dns-notifier</artifactId>
diff --git a/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml b/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
index c53c0b1..76a6cad 100755
--- a/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
+++ b/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml
@@ -111,6 +111,7 @@
             <adapter name="XCP Agent" class="com.cloud.hypervisor.xenserver.discoverer.XcpServerDiscoverer"/>
             <adapter name="SecondaryStorage" class="com.cloud.storage.secondary.SecondaryStorageDiscoverer"/>
             <adapter name="KVM Agent" class="com.cloud.hypervisor.kvm.discoverer.KvmServerDiscoverer"/>
+            <adapter name="CustomHW Agent" class="com.cloud.hypervisor.discoverer.CustomServerDiscoverer"/>
             <adapter name="Bare Metal Agent" class="com.cloud.baremetal.BareMetalDiscoverer"/>
 			<adapter name="Ovm Discover" class="com.cloud.ovm.hypervisor.OvmDiscoverer" />
         </adapters>
diff --git a/plugins/network-elements/elastic-loadbalancer/pom.xml b/plugins/network-elements/elastic-loadbalancer/pom.xml
index 32a7fd2..c9b118e 100644
--- a/plugins/network-elements/elastic-loadbalancer/pom.xml
+++ b/plugins/network-elements/elastic-loadbalancer/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/resources/META-INF/cloudstack/elb/module.properties b/plugins/network-elements/elastic-loadbalancer/src/main/resources/META-INF/cloudstack/elb/module.properties
index a8e3b9c..559245d 100644
--- a/plugins/network-elements/elastic-loadbalancer/src/main/resources/META-INF/cloudstack/elb/module.properties
+++ b/plugins/network-elements/elastic-loadbalancer/src/main/resources/META-INF/cloudstack/elb/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=elb
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/ElasticLoadBalancerManagerImplTest.java b/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/ElasticLoadBalancerManagerImplTest.java
index 04f59bb..064bb3e 100644
--- a/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/ElasticLoadBalancerManagerImplTest.java
+++ b/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/ElasticLoadBalancerManagerImplTest.java
@@ -25,14 +25,14 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.agent.api.check.CheckSshAnswer;
 import com.cloud.agent.manager.Commands;
 import com.cloud.network.lb.dao.ElasticLbVmMapDao;
 import com.cloud.vm.ReservationContext;
 import com.cloud.vm.VirtualMachineProfile;
+import org.springframework.test.util.ReflectionTestUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ElasticLoadBalancerManagerImplTest {
@@ -90,7 +90,7 @@
     public void testGarbageCollectUnusedElbVmsWhenVariableUnusedElbVmsIsNull() throws Exception {
         ElasticLbVmMapDao elasticLbVmMapDaoMock = mock(ElasticLbVmMapDao.class);
         when(elasticLbVmMapDaoMock.listUnusedElbVms()).thenReturn(null);
-        Whitebox.setInternalState(elasticLoadBalancerManagerImpl, "_elbVmMapDao", elasticLbVmMapDaoMock);
+        ReflectionTestUtils.setField(elasticLoadBalancerManagerImpl, "_elbVmMapDao", elasticLbVmMapDaoMock);
 
         try {
             elasticLoadBalancerManagerImpl.garbageCollectUnusedElbVms();
diff --git a/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/LoadBalanceRuleHandlerTest.java b/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/LoadBalanceRuleHandlerTest.java
index cde56c1..18cdab9 100644
--- a/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/LoadBalanceRuleHandlerTest.java
+++ b/plugins/network-elements/elastic-loadbalancer/src/test/java/com/cloud/network/lb/LoadBalanceRuleHandlerTest.java
@@ -36,8 +36,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.powermock.reflect.Whitebox;
 
 import com.cloud.dc.PodVlanMapVO;
 import com.cloud.dc.dao.PodVlanMapDao;
@@ -47,6 +45,8 @@
 import com.cloud.vm.VirtualMachineManager;
 import com.cloud.vm.VirtualMachineProfile.Param;
 import com.cloud.vm.dao.DomainRouterDao;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 @RunWith(MockitoJUnitRunner.class)
 public class LoadBalanceRuleHandlerTest {
@@ -68,10 +68,10 @@
 
     @Before
     public void setup() {
-        Whitebox.setInternalState(loadBalanceRuleHandler, "_itMgr", virtualMachineManagerMock);
-        Whitebox.setInternalState(loadBalanceRuleHandler, "_routerDao", domainRouterDaoMock);
-        Whitebox.setInternalState(loadBalanceRuleHandler, "_elbVmMapDao", elasticLbVmMapDao);
-        Whitebox.setInternalState(loadBalanceRuleHandler, "_podVlanMapDao", podVlanMapDao);
+        ReflectionTestUtils.setField(loadBalanceRuleHandler, "_itMgr", virtualMachineManagerMock);
+        ReflectionTestUtils.setField(loadBalanceRuleHandler, "_routerDao", domainRouterDaoMock);
+        ReflectionTestUtils.setField(loadBalanceRuleHandler, "_elbVmMapDao", elasticLbVmMapDao);
+        ReflectionTestUtils.setField(loadBalanceRuleHandler, "_podVlanMapDao", podVlanMapDao);
     }
 
     @Test
diff --git a/plugins/network-elements/elastic-loadbalancer/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/network-elements/elastic-loadbalancer/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/network-elements/elastic-loadbalancer/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/network-elements/globodns/pom.xml b/plugins/network-elements/globodns/pom.xml
index 3dac61d..f535b1d 100644
--- a/plugins/network-elements/globodns/pom.xml
+++ b/plugins/network-elements/globodns/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/network-elements/globodns/src/main/resources/META-INF/cloudstack/globodns/module.properties b/plugins/network-elements/globodns/src/main/resources/META-INF/cloudstack/globodns/module.properties
index 6c74bd2..cd6bc77 100644
--- a/plugins/network-elements/globodns/src/main/resources/META-INF/cloudstack/globodns/module.properties
+++ b/plugins/network-elements/globodns/src/main/resources/META-INF/cloudstack/globodns/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=globodns
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/internal-loadbalancer/pom.xml b/plugins/network-elements/internal-loadbalancer/pom.xml
index f985cb9..828b1df 100644
--- a/plugins/network-elements/internal-loadbalancer/pom.xml
+++ b/plugins/network-elements/internal-loadbalancer/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/juniper-contrail/pom.xml b/plugins/network-elements/juniper-contrail/pom.xml
index f3b2283..9eb6138 100644
--- a/plugins/network-elements/juniper-contrail/pom.xml
+++ b/plugins/network-elements/juniper-contrail/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <repositories>
@@ -120,8 +120,8 @@
             </exclusions>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/plugins/network-elements/juniper-contrail/src/main/resources/META-INF/cloudstack/contrail/module.properties b/plugins/network-elements/juniper-contrail/src/main/resources/META-INF/cloudstack/contrail/module.properties
index ced0f3a..0c3091d 100644
--- a/plugins/network-elements/juniper-contrail/src/main/resources/META-INF/cloudstack/contrail/module.properties
+++ b/plugins/network-elements/juniper-contrail/src/main/resources/META-INF/cloudstack/contrail/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=contrail
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
index bf0b94a..67cfe1d 100644
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
@@ -177,6 +177,11 @@
     }
 
     @Override
+    public List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId) {
+        return null;
+    }
+
+    @Override
     public User getActiveUser(long arg0) {
         return _systemUser;
     }
diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/InstanceIpModelTest.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/InstanceIpModelTest.java
index 38b8ea6..06ea2d9 100644
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/InstanceIpModelTest.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/InstanceIpModelTest.java
@@ -16,6 +16,7 @@
 // under the License.
 
 package org.apache.cloudstack.network.contrail.model;
+
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
@@ -44,26 +45,19 @@
 
 public class InstanceIpModelTest extends TestCase {
     private static final Logger s_logger =
-        Logger.getLogger(InstanceIpModelTest.class);
+            Logger.getLogger(InstanceIpModelTest.class);
 
     @Test
     public void testCreateInstanceIp() throws IOException {
 
         ContrailManagerImpl contrailMgr = mock(ContrailManagerImpl.class);
-        ModelController controller      = mock(ModelController.class);
+        ModelController controller = mock(ModelController.class);
         ApiConnector api = new ApiConnectorMock(null, 0);
         when(controller.getApiAccessor()).thenReturn(api);
         when(controller.getManager()).thenReturn(contrailMgr);
 
         // Create Virtual-Network (VN)
-        NetworkVO network = mock(NetworkVO.class);
-        when(network.getName()).thenReturn("testnetwork");
-        when(network.getState()).thenReturn(Network.State.Implemented);
-        when(network.getGateway()).thenReturn("10.1.1.1");
-        when(network.getCidr()).thenReturn("10.1.1.0/24");
-        when(network.getPhysicalNetworkId()).thenReturn(42L);
-        when(network.getDomainId()).thenReturn(10L);
-        when(network.getAccountId()).thenReturn(42L);
+        NetworkVO network = MockNetworkVO.getNetwork(Network.State.Implemented);
         NetworkDao networkDao = mock(NetworkDao.class);
         when(networkDao.findById(anyLong())).thenReturn(network);
         when(controller.getNetworkDao()).thenReturn(networkDao);
@@ -86,7 +80,7 @@
         when(vm.getState()).thenReturn(VirtualMachine.State.Running);
         when(vm.getDomainId()).thenReturn(10L);
         when(vm.getAccountId()).thenReturn(42L);
-        UserVmDao VmDao      = mock(UserVmDao.class);
+        UserVmDao VmDao = mock(UserVmDao.class);
         when(VmDao.findById(anyLong())).thenReturn(null);
         when(controller.getVmDao()).thenReturn(VmDao);
 
diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/MockNetworkVO.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/MockNetworkVO.java
new file mode 100644
index 0000000..d7d774a
--- /dev/null
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/MockNetworkVO.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.network.contrail.model;
+
+import com.cloud.network.Network;
+import com.cloud.network.dao.NetworkVO;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MockNetworkVO {
+
+    private NetworkVO network;
+
+    MockNetworkVO(Network.State state) {
+        network = mock(NetworkVO.class);
+        when(network.getName()).thenReturn("testnetwork");
+        when(network.getState()).thenReturn(state);
+        when(network.getGateway()).thenReturn("10.1.1.1");
+        when(network.getCidr()).thenReturn("10.1.1.0/24");
+        when(network.getPhysicalNetworkId()).thenReturn(42L);
+        when(network.getDomainId()).thenReturn(10L);
+        when(network.getAccountId()).thenReturn(42L);
+    }
+
+    public NetworkVO getNetwork() {
+        return network;
+    }
+
+    public static NetworkVO getNetwork(Network.State state) {
+        MockNetworkVO mockNetwork = new MockNetworkVO(state);
+        return mockNetwork.getNetwork();
+    }
+
+}
diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VMInterfaceModelTest.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VMInterfaceModelTest.java
index 391ada5..71238a9 100644
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VMInterfaceModelTest.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VMInterfaceModelTest.java
@@ -16,6 +16,7 @@
 // under the License.
 
 package org.apache.cloudstack.network.contrail.model;
+
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
@@ -45,27 +46,20 @@
 
 public class VMInterfaceModelTest extends TestCase {
     private static final Logger s_logger =
-        Logger.getLogger(VMInterfaceModelTest.class);
+            Logger.getLogger(VMInterfaceModelTest.class);
 
     @Test
     public void testCreateVMInterface() throws IOException {
 
         String uuid;
         ContrailManagerImpl contrailMgr = mock(ContrailManagerImpl.class);
-        ModelController controller      = mock(ModelController.class);
+        ModelController controller = mock(ModelController.class);
         ApiConnector api = new ApiConnectorMock(null, 0);
         when(controller.getManager()).thenReturn(contrailMgr);
         when(controller.getApiAccessor()).thenReturn(api);
 
         // Create Virtual-Network (VN)
-        NetworkVO network = mock(NetworkVO.class);
-        when(network.getName()).thenReturn("testnetwork");
-        when(network.getState()).thenReturn(Network.State.Implemented);
-        when(network.getGateway()).thenReturn("10.1.1.1");
-        when(network.getCidr()).thenReturn("10.1.1.0/24");
-        when(network.getPhysicalNetworkId()).thenReturn(42L);
-        when(network.getDomainId()).thenReturn(10L);
-        when(network.getAccountId()).thenReturn(42L);
+        NetworkVO network = MockNetworkVO.getNetwork(Network.State.Implemented);
         NetworkDao networkDao = mock(NetworkDao.class);
         when(networkDao.findById(anyLong())).thenReturn(network);
         when(controller.getNetworkDao()).thenReturn(networkDao);
@@ -88,7 +82,7 @@
         when(vm.getState()).thenReturn(VirtualMachine.State.Running);
         when(vm.getDomainId()).thenReturn(10L);
         when(vm.getAccountId()).thenReturn(42L);
-        UserVmDao VmDao      = mock(UserVmDao.class);
+        UserVmDao VmDao = mock(UserVmDao.class);
         when(VmDao.findById(anyLong())).thenReturn(null);
         when(controller.getVmDao()).thenReturn(VmDao);
 
diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualMachineModelTest.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualMachineModelTest.java
index 893ca85..dec4a40 100644
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualMachineModelTest.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualMachineModelTest.java
@@ -46,18 +46,18 @@
     @Test
     public void testVirtualMachineDBLookup() {
         ModelDatabase db = new ModelDatabase();
-        VMInstanceVO vm  = mock(VMInstanceVO.class);
+        VMInstanceVO vm = mock(VMInstanceVO.class);
 
         // Create 3 dummy Virtual Machine model objects
         // Add these models to database.
         // Each VM is identified by unique UUId.
-        VirtualMachineModel  vm0 = new VirtualMachineModel(vm, "fbc1f8fa-4b78-45ee-bba0-b551dbf72353");
+        VirtualMachineModel vm0 = new VirtualMachineModel(vm, "fbc1f8fa-4b78-45ee-bba0-b551dbf72353");
         db.getVirtualMachines().add(vm0);
 
-        VirtualMachineModel  vm1 = new VirtualMachineModel(vm, "fbc1f8fa-4b78-45ee-bba0-b551dbf83464");
+        VirtualMachineModel vm1 = new VirtualMachineModel(vm, "fbc1f8fa-4b78-45ee-bba0-b551dbf83464");
         db.getVirtualMachines().add(vm1);
 
-        VirtualMachineModel  vm2 = new VirtualMachineModel(vm, "fbc1f8fa-4b78-45ee-bba0-b551dbf94575");
+        VirtualMachineModel vm2 = new VirtualMachineModel(vm, "fbc1f8fa-4b78-45ee-bba0-b551dbf94575");
         db.getVirtualMachines().add(vm2);
 
         s_logger.debug("No of Vitual Machines added to database : " + db.getVirtualMachines().size());
@@ -74,20 +74,13 @@
 
         String uuid = UUID.randomUUID().toString();
         ContrailManagerImpl contrailMgr = mock(ContrailManagerImpl.class);
-        ModelController controller      = mock(ModelController.class);
+        ModelController controller = mock(ModelController.class);
         ApiConnector api = new ApiConnectorMock(null, 0);
         when(controller.getManager()).thenReturn(contrailMgr);
         when(controller.getApiAccessor()).thenReturn(api);
 
         // Create Virtual-Network (VN)
-        NetworkVO network = mock(NetworkVO.class);
-        when(network.getName()).thenReturn("testnetwork");
-        when(network.getState()).thenReturn(Network.State.Allocated);
-        when(network.getGateway()).thenReturn("10.1.1.1");
-        when(network.getCidr()).thenReturn("10.1.1.0/24");
-        when(network.getPhysicalNetworkId()).thenReturn(42L);
-        when(network.getDomainId()).thenReturn(10L);
-        when(network.getAccountId()).thenReturn(42L);
+        NetworkVO network = MockNetworkVO.getNetwork(Network.State.Allocated);
 
         when(contrailMgr.getCanonicalName(network)).thenReturn("testnetwork");
         when(contrailMgr.getProjectId(network.getDomainId(), network.getAccountId())).thenReturn("testProjectId");
@@ -99,7 +92,7 @@
         when(vm.getDomainId()).thenReturn(10L);
         when(vm.getAccountId()).thenReturn(42L);
 
-        UserVmDao VmDao      = mock(UserVmDao.class);
+        UserVmDao VmDao = mock(UserVmDao.class);
         when(VmDao.findById(anyLong())).thenReturn(null);
         when(controller.getVmDao()).thenReturn(VmDao);
 
diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualNetworkModelTest.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualNetworkModelTest.java
index 4790933..e4abfc9 100644
--- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualNetworkModelTest.java
+++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/model/VirtualNetworkModelTest.java
@@ -115,34 +115,13 @@
         when(vn3.getNetworkPolicy()).thenReturn(policyRefs3);
 
         //Virtual-Network 1
-        NetworkVO network1 = mock(NetworkVO.class);
-        when(network1.getName()).thenReturn("testnetwork");
-        when(network1.getState()).thenReturn(State.Allocated);
-        when(network1.getGateway()).thenReturn("10.1.1.1");
-        when(network1.getCidr()).thenReturn("10.1.1.0/24");
-        when(network1.getPhysicalNetworkId()).thenReturn(42L);
-        when(network1.getDomainId()).thenReturn(10L);
-        when(network1.getAccountId()).thenReturn(42L);
+        NetworkVO network1 = MockNetworkVO.getNetwork(State.Allocated);
 
         //Virtual-Network 2
-        NetworkVO network2 = mock(NetworkVO.class);
-        when(network2.getName()).thenReturn("Testnetwork");
-        when(network2.getState()).thenReturn(State.Allocated);
-        when(network2.getGateway()).thenReturn("10.1.1.1");
-        when(network2.getCidr()).thenReturn("10.1.1.0/24");
-        when(network2.getPhysicalNetworkId()).thenReturn(42L);
-        when(network2.getDomainId()).thenReturn(10L);
-        when(network2.getAccountId()).thenReturn(42L);
+        NetworkVO network2 = MockNetworkVO.getNetwork(State.Allocated);
 
         //Virtual-Network 3
-        NetworkVO network3 = mock(NetworkVO.class);
-        when(network3.getName()).thenReturn("Testnetwork");
-        when(network3.getState()).thenReturn(State.Allocated);
-        when(network3.getGateway()).thenReturn("10.1.1.1");
-        when(network3.getCidr()).thenReturn("10.1.1.0/24");
-        when(network3.getPhysicalNetworkId()).thenReturn(42L);
-        when(network3.getDomainId()).thenReturn(10L);
-        when(network3.getAccountId()).thenReturn(42L);
+        NetworkVO network3 = MockNetworkVO.getNetwork(State.Allocated);
 
         when(contrailMgr.getCanonicalName(network1)).thenReturn("testnetwork");
         when(contrailMgr.getProjectId(network1.getDomainId(), network1.getAccountId())).thenReturn("testProjectId");
@@ -179,20 +158,13 @@
 
         String uuid = UUID.randomUUID().toString();
         ContrailManagerImpl contrailMgr = mock(ContrailManagerImpl.class);
-        ModelController controller      = mock(ModelController.class);
+        ModelController controller = mock(ModelController.class);
         ApiConnector api = new ApiConnectorMock(null, 0);
         when(controller.getManager()).thenReturn(contrailMgr);
         when(controller.getApiAccessor()).thenReturn(api);
 
         // Create Virtual-Network (VN)
-        NetworkVO network = mock(NetworkVO.class);
-        when(network.getName()).thenReturn("testnetwork");
-        when(network.getState()).thenReturn(State.Allocated);
-        when(network.getGateway()).thenReturn("10.1.1.1");
-        when(network.getCidr()).thenReturn("10.1.1.0/24");
-        when(network.getPhysicalNetworkId()).thenReturn(42L);
-        when(network.getDomainId()).thenReturn(10L);
-        when(network.getAccountId()).thenReturn(42L);
+        NetworkVO network = MockNetworkVO.getNetwork(State.Allocated);
 
         when(contrailMgr.getCanonicalName(network)).thenReturn("testnetwork");
         when(contrailMgr.getProjectId(network.getDomainId(), network.getAccountId())).thenReturn("testProjectId");
diff --git a/plugins/network-elements/juniper-contrail/src/test/resources/log4j.properties b/plugins/network-elements/juniper-contrail/src/test/resources/log4j.properties
index 8c012b1..27276fc 100644
--- a/plugins/network-elements/juniper-contrail/src/test/resources/log4j.properties
+++ b/plugins/network-elements/juniper-contrail/src/test/resources/log4j.properties
@@ -32,4 +32,3 @@
 #log4j.category.com.cloud.utils.db.Transaction=ALL
 log4j.category.org.apache.cloudstack.network.contrail=ALL
 log4j.category.com.cloud.network=ALL
-
diff --git a/plugins/network-elements/juniper-contrail/src/test/resources/mysql_db_stop.sh b/plugins/network-elements/juniper-contrail/src/test/resources/mysql_db_stop.sh
index 62d70d3..7ac304f 100644
--- a/plugins/network-elements/juniper-contrail/src/test/resources/mysql_db_stop.sh
+++ b/plugins/network-elements/juniper-contrail/src/test/resources/mysql_db_stop.sh
@@ -27,5 +27,3 @@
 rm -rf /tmp/mysql$1
 
 echo "Deleting db directories"
-
-
diff --git a/plugins/network-elements/juniper-contrail/src/test/resources/providerContext.xml b/plugins/network-elements/juniper-contrail/src/test/resources/providerContext.xml
index 704466f..fc7e10b 100644
--- a/plugins/network-elements/juniper-contrail/src/test/resources/providerContext.xml
+++ b/plugins/network-elements/juniper-contrail/src/test/resources/providerContext.xml
@@ -31,4 +31,4 @@
     <import resource="commonContext.xml"/>
     <bean id="ProviderTestConfiguration"
     	class="org.apache.cloudstack.network.contrail.management.ProviderTestConfiguration"/>
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/network-elements/juniper-contrail/src/test/resources/publicNetworkContext.xml b/plugins/network-elements/juniper-contrail/src/test/resources/publicNetworkContext.xml
index d1c5f40..b2c28d2 100644
--- a/plugins/network-elements/juniper-contrail/src/test/resources/publicNetworkContext.xml
+++ b/plugins/network-elements/juniper-contrail/src/test/resources/publicNetworkContext.xml
@@ -23,4 +23,4 @@
     <import resource="commonContext.xml"/>
     <bean id="PublicNetworkTestConfiguration"
     	class="org.apache.cloudstack.network.contrail.management.PublicNetworkTestConfiguration"/>
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/network-elements/netscaler/pom.xml b/plugins/network-elements/netscaler/pom.xml
index 5a73ff6..14b986b 100644
--- a/plugins/network-elements/netscaler/pom.xml
+++ b/plugins/network-elements/netscaler/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/network-elements/netscaler/src/main/java/com/cloud/network/resource/NetscalerResource.java b/plugins/network-elements/netscaler/src/main/java/com/cloud/network/resource/NetscalerResource.java
index d17525d..99f7102 100644
--- a/plugins/network-elements/netscaler/src/main/java/com/cloud/network/resource/NetscalerResource.java
+++ b/plugins/network-elements/netscaler/src/main/java/com/cloud/network/resource/NetscalerResource.java
@@ -248,7 +248,7 @@
             enableLoadBalancingFeature();
             SSL.enableSslFeature(_netscalerService, _isSdx);
 
-            //if the the device is cloud stack provisioned then make it part of the public network
+            //if the device is cloud stack provisioned then make it part of the public network
             if (_cloudManaged) {
                 _publicIP = (String)params.get("publicip");
                 _publicIPNetmask = (String)params.get("publicipnetmask");
@@ -1856,7 +1856,7 @@
         public static void linkCerts(final nitro_service ns, final String userCertName, final String caCertName) throws ExecutionException {
             try {
 
-                // the assumption is that that both userCertName and caCertName are present on NS
+                // the assumption is that both userCertName and caCertName are present on NS
 
                 final sslcertkey caCert = sslcertkey.get(ns, caCertName);
                 final sslcertkey userCert = sslcertkey.get(ns, userCertName);
diff --git a/plugins/network-elements/netscaler/src/main/resources/META-INF/cloudstack/netscaler/module.properties b/plugins/network-elements/netscaler/src/main/resources/META-INF/cloudstack/netscaler/module.properties
index 2f1b641..8bf4e6a 100644
--- a/plugins/network-elements/netscaler/src/main/resources/META-INF/cloudstack/netscaler/module.properties
+++ b/plugins/network-elements/netscaler/src/main/resources/META-INF/cloudstack/netscaler/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=netscaler
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/nicira-nvp/pom.xml b/plugins/network-elements/nicira-nvp/pom.xml
index a3b3812..6ce1645 100644
--- a/plugins/network-elements/nicira-nvp/pom.xml
+++ b/plugins/network-elements/nicira-nvp/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java b/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java
index 73d08a0..aa428b0 100644
--- a/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java
+++ b/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java
@@ -20,7 +20,7 @@
 package com.cloud.network.nicira;
 
 import java.io.IOException;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.http.HttpEntity;
@@ -76,7 +76,7 @@
         return execute(request, 0);
     }
 
-    private CloseableHttpResponse execute(final HttpUriRequest request, final int previousStatusCode) throws CloudstackRESTException {
+    CloseableHttpResponse execute(final HttpUriRequest request, final int previousStatusCode) throws CloudstackRESTException {
         if (counter.hasReachedExecutionLimit()) {
             throw new CloudstackRESTException("Reached max executions limit of " + executionLimit);
         }
@@ -120,7 +120,7 @@
     }
 
     private HttpUriRequest createAuthenticationRequest() {
-        final Map<String, String> parameters = new HashMap<>();
+        final Map<String, String> parameters = new LinkedHashMap<>();
         parameters.put("username", username);
         parameters.put("password", password);
         return HttpUriRequestBuilder.create()
diff --git a/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureSharedNetworkUuidCommandWrapper.java b/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureSharedNetworkUuidCommandWrapper.java
index 3a1d2cb..5f3198a 100644
--- a/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureSharedNetworkUuidCommandWrapper.java
+++ b/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureSharedNetworkUuidCommandWrapper.java
@@ -140,7 +140,7 @@
             cleanupLSwitchPort(logicalSwitchUuid, lSwitchPort, niciraNvpApi);
             return handleException(e, command, niciraNvpResource);
         }
-        s_logger.debug("Logical Router Port " + lRouterPort.getUuid() + " (" + lRouterPort.getDisplayName() + ") successfully attached to to Logical Switch Port " + lSwitchPort.getUuid() + " (" + lSwitchPort.getDisplayName() + ") with a PatchAttachment");
+        s_logger.debug("Logical Router Port " + lRouterPort.getUuid() + " (" + lRouterPort.getDisplayName() + ") successfully attached to Logical Switch Port " + lSwitchPort.getUuid() + " (" + lSwitchPort.getDisplayName() + ") with a PatchAttachment");
 
 
         //Step 5: Attach lSwitchPort to lRouterPort with a PatchAttachment
@@ -154,7 +154,7 @@
             cleanupLSwitchPort(logicalSwitchUuid, lSwitchPort, niciraNvpApi);
             return handleException(e, command, niciraNvpResource);
         }
-        s_logger.debug("Logical Switch Port " + lSwitchPort.getUuid() + " (" + lSwitchPort.getDisplayName() + ") successfully attached to to Logical Router Port " + lRouterPort.getUuid() + " (" + lRouterPort.getDisplayName() + ") with a PatchAttachment");
+        s_logger.debug("Logical Switch Port " + lSwitchPort.getUuid() + " (" + lSwitchPort.getDisplayName() + ") successfully attached to Logical Router Port " + lRouterPort.getUuid() + " (" + lRouterPort.getDisplayName() + ") with a PatchAttachment");
 
         s_logger.info("Successfully attached Logical Switch " + logicalSwitchUuid + " on Logical Router " + logicalRouterUuid + " for Shared Network " + networkId);
         return new ConfigureSharedNetworkUuidAnswer(command, true, "OK");
diff --git a/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties b/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties
index 5085fce..11bc471 100644
--- a/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties
+++ b/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties
@@ -18,4 +18,4 @@
 #
 
 name=nvp
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java b/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java
index 60c521a..318b95a 100644
--- a/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java
+++ b/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -104,36 +105,14 @@
     }
 
     @Test
-    public void testCRUDAcl() {
-        Acl acl = new Acl();
-        acl.setDisplayName("Acl" + timestamp);
-
-        // Note that if the protocol is 6 (TCP) then you cannot put ICMP code and type
-        // Note that if the protocol is 1 (ICMP) then you cannot put ports
-        final List<AclRule> egressRules = new ArrayList<AclRule>();
-        acl.setLogicalPortEgressRules(egressRules);
-        egressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 1, "allow", null, null, "1.10.10.0", "1.10.10.1", null, null, null, null, 0, 0, 5));
-        egressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 6, "allow", null, null, "1.10.10.6", "1.10.10.7", 80, 80, 80, 80, 1, null, null));
-
-        final List<AclRule> ingressRules = new ArrayList<AclRule>();
-        acl.setLogicalPortIngressRules(ingressRules);
-        ingressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 1, "allow", null, null, "1.10.10.0", "1.10.10.1", null, null, null, null, 0, 0, 5));
-        ingressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 6, "allow", null, null, "1.10.10.6", "1.10.10.7", 80, 80, 80, 80, 1, null, null));
-
-        final List<NiciraNvpTag> tags = new ArrayList<NiciraNvpTag>();
-        acl.setTags(tags);
-        tags.add(new NiciraNvpTag("nvp", "MyTag1"));
-        tags.add(new NiciraNvpTag("nicira", "MyTag2"));
-        // In the creation we don't get to specify UUID, href or schema: they don't exist yet
-
+    public void testCRUDAclReadAll() {
         try {
-            acl = api.createAcl(acl);
+            Acl acl = setupAcl();
 
-            // We can now update the new entity
+            acl = api.createAcl(acl);
             acl.setDisplayName("UpdatedAcl" + timestamp);
             api.updateAcl(acl, acl.getUuid());
 
-            // Read them all
             List<Acl> acls = api.findAcl();
             Acl scInList = null;
             for (final Acl iAcl : acls) {
@@ -143,16 +122,30 @@
             }
             assertEquals("Read a ACL different from the one just created and updated", acl, scInList);
 
-            // Read them filtered by uuid (get one)
-            acls = api.findAcl(acl.getUuid());
-            assertEquals("Read a ACL different from the one just created and updated", acl, acls.get(0));
-            assertEquals("Read a ACL filtered by unique id (UUID) with more than one item", 1, acls.size());
-
-            // We can now delete the new entity
             api.deleteAcl(acl.getUuid());
         } catch (final NiciraNvpApiException e) {
             e.printStackTrace();
-            assertTrue("Errors in ACL CRUD", false);
+            fail("Errors in ACL CRUD");
+        }
+    }
+
+    @Test
+    public void testCRUDAclReadOne() {
+        try {
+            Acl acl = setupAcl();
+
+            acl = api.createAcl(acl);
+            acl.setDisplayName("UpdatedAcl" + timestamp);
+            api.updateAcl(acl, acl.getUuid());
+
+            List<Acl> acls = api.findAcl(acl.getUuid());
+            assertEquals("Read a ACL different from the one just created and updated", acl, acls.get(0));
+            assertEquals("Read a ACL filtered by unique id (UUID) with more than one item", 1, acls.size());
+
+            api.deleteAcl(acl.getUuid());
+        } catch (final NiciraNvpApiException e) {
+            e.printStackTrace();
+            fail("Errors in ACL CRUD");
         }
     }
 
@@ -316,4 +309,29 @@
         assertTrue("Not recognizable cluster status", correctStatus);
     }
 
+    private Acl setupAcl() {
+        Acl acl = new Acl();
+        acl.setDisplayName("Acl" + timestamp);
+
+        // Note that if the protocol is 6 (TCP) then you cannot put ICMP code and type
+        // Note that if the protocol is 1 (ICMP) then you cannot put ports
+        final List<AclRule> egressRules = new ArrayList<>();
+        acl.setLogicalPortEgressRules(egressRules);
+        egressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 1, "allow", null, null, "1.10.10.0", "1.10.10.1", null, null, null, null, 0, 0, 5));
+        egressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 6, "allow", null, null, "1.10.10.6", "1.10.10.7", 80, 80, 80, 80, 1, null, null));
+
+        final List<AclRule> ingressRules = new ArrayList<>();
+        acl.setLogicalPortIngressRules(ingressRules);
+        ingressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 1, "allow", null, null, "1.10.10.0", "1.10.10.1", null, null, null, null, 0, 0, 5));
+        ingressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 6, "allow", null, null, "1.10.10.6", "1.10.10.7", 80, 80, 80, 80, 1, null, null));
+
+        final List<NiciraNvpTag> tags = new ArrayList<>();
+        acl.setTags(tags);
+        tags.add(new NiciraNvpTag("nvp", "MyTag1"));
+        tags.add(new NiciraNvpTag("nicira", "MyTag2"));
+        // In the creation we don't get to specify UUID, href or schema: they don't exist yet
+
+        return acl;
+    }
+
 }
diff --git a/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java b/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java
index d23c4dc..bae11c7 100644
--- a/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java
+++ b/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java
@@ -26,13 +26,13 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
 
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.http.HttpHost;
@@ -47,18 +47,14 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import com.cloud.utils.rest.CloudstackRESTException;
 import com.cloud.utils.rest.HttpMethods;
 import com.cloud.utils.rest.HttpRequestMatcher;
 import com.cloud.utils.rest.HttpUriRequestBuilder;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(NiciraRestClient.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.apache.log4j.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class NiciraRestClientTest {
 
     private static final int HTTPS_PORT = 443;
@@ -74,7 +70,7 @@
     private static final StatusLine HTTP_200_STATUSLINE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 200, "OK");
     private static final StatusLine HTTP_401_STATUSLINE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 401, "Unauthorized");
 
-    private static final Map<String, String> loginParameters = new HashMap<String, String>();
+    private static final Map<String, String> loginParameters = new LinkedHashMap<String, String>();
     private static HttpUriRequest request;
     private static HttpUriRequest loginRequest;
     private final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
@@ -120,7 +116,7 @@
 
         assertThat(response, notNullValue());
         assertThat(response, sameInstance(mockResponse));
-        verifyPrivate(client).invoke("execute", request, 0);
+        verify(client).execute(request, 0);
     }
 
     @Test
@@ -139,9 +135,9 @@
 
         assertThat(response, notNullValue());
         assertThat(response, sameInstance(mockResponse));
-        verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(request), eq(0));
-        verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(loginRequest), eq(401));
-        verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(request), eq(200));
+        verify(client).execute((HttpUriRequest)HttpRequestMatcher.eq(request), eq(0));
+        verify(client).execute((HttpUriRequest)HttpRequestMatcher.eq(request), eq(200));
+        verify(client).execute((HttpUriRequest)HttpRequestMatcher.eq(loginRequest), eq(401));
     }
 
     @Test
@@ -168,8 +164,8 @@
             fail("Expected CloudstackRESTException exception");
         } catch (final CloudstackRESTException e) {
             assertThat(e.getMessage(), not(isEmptyOrNullString()));
-            verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(request), eq(0));
-            verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(loginRequest), eq(401));
+            verify(client).execute((HttpUriRequest)HttpRequestMatcher.eq(request), eq(0));
+            verify(client).execute((HttpUriRequest)HttpRequestMatcher.eq(loginRequest), eq(401));
         }
     }
 
diff --git a/plugins/network-elements/nicira-nvp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/network-elements/nicira-nvp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/network-elements/nicira-nvp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/network-elements/opendaylight/pom.xml b/plugins/network-elements/opendaylight/pom.xml
index 9f4641a..374e199 100644
--- a/plugins/network-elements/opendaylight/pom.xml
+++ b/plugins/network-elements/opendaylight/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <profiles>
diff --git a/plugins/network-elements/opendaylight/src/main/resources/META-INF/cloudstack/opendaylight/spring-opendaylight-context.xml b/plugins/network-elements/opendaylight/src/main/resources/META-INF/cloudstack/opendaylight/spring-opendaylight-context.xml
index 244ded8..4e9c705 100644
--- a/plugins/network-elements/opendaylight/src/main/resources/META-INF/cloudstack/opendaylight/spring-opendaylight-context.xml
+++ b/plugins/network-elements/opendaylight/src/main/resources/META-INF/cloudstack/opendaylight/spring-opendaylight-context.xml
@@ -39,4 +39,3 @@
     <bean id="OpenDaylightControllerMappingDao" class="org.apache.cloudstack.network.opendaylight.dao.OpenDaylightControllerMappingDaoImpl" />
 
 </beans>
-
diff --git a/plugins/network-elements/ovs/pom.xml b/plugins/network-elements/ovs/pom.xml
index 7f25e38..df0f282 100644
--- a/plugins/network-elements/ovs/pom.xml
+++ b/plugins/network-elements/ovs/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/palo-alto/pom.xml b/plugins/network-elements/palo-alto/pom.xml
index 8a4b176..5e0538c 100644
--- a/plugins/network-elements/palo-alto/pom.xml
+++ b/plugins/network-elements/palo-alto/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/module.properties b/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/module.properties
index 960fdba..28ef552 100644
--- a/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/module.properties
+++ b/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=paloalto
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/spring-paloalto-context.xml b/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/spring-paloalto-context.xml
index d812ec0..137d8c9 100644
--- a/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/spring-paloalto-context.xml
+++ b/plugins/network-elements/palo-alto/src/main/resources/META-INF/cloudstack/paloalto/spring-paloalto-context.xml
@@ -30,4 +30,4 @@
     <bean id="PaloAlto" class="com.cloud.network.element.PaloAltoExternalFirewallElement">
         <property name="name" value="PaloAlto" />
     </bean>
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/network-elements/stratosphere-ssp/pom.xml b/plugins/network-elements/stratosphere-ssp/pom.xml
index 017eb6f..5748b22 100644
--- a/plugins/network-elements/stratosphere-ssp/pom.xml
+++ b/plugins/network-elements/stratosphere-ssp/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/stratosphere-ssp/src/main/resources/META-INF/cloudstack/ssp/module.properties b/plugins/network-elements/stratosphere-ssp/src/main/resources/META-INF/cloudstack/ssp/module.properties
index 5a99e56..b4d6e1d 100644
--- a/plugins/network-elements/stratosphere-ssp/src/main/resources/META-INF/cloudstack/ssp/module.properties
+++ b/plugins/network-elements/stratosphere-ssp/src/main/resources/META-INF/cloudstack/ssp/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=ssp
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/tungsten/pom.xml b/plugins/network-elements/tungsten/pom.xml
index b833310..d04c050 100644
--- a/plugins/network-elements/tungsten/pom.xml
+++ b/plugins/network-elements/tungsten/pom.xml
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
@@ -44,4 +44,4 @@
             <version>2.0</version>
         </dependency>
     </dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/plugins/network-elements/tungsten/src/main/resources/META-INF/cloudstack/tungsten/module.properties b/plugins/network-elements/tungsten/src/main/resources/META-INF/cloudstack/tungsten/module.properties
index 72422a4..1e24c76 100644
--- a/plugins/network-elements/tungsten/src/main/resources/META-INF/cloudstack/tungsten/module.properties
+++ b/plugins/network-elements/tungsten/src/main/resources/META-INF/cloudstack/tungsten/module.properties
@@ -18,4 +18,4 @@
 #
 
 name=tungsten
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricNetworkGatewayToLogicalRouterCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricNetworkGatewayToLogicalRouterCmdTest.java
index 3e00748..b0ebb1e 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricNetworkGatewayToLogicalRouterCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricNetworkGatewayToLogicalRouterCmdTest.java
@@ -24,17 +24,21 @@
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.List;
 
+@RunWith(MockitoJUnitRunner.class)
 public class AddTungstenFabricNetworkGatewayToLogicalRouterCmdTest {
 
     @Mock
@@ -42,18 +46,25 @@
 
     AddTungstenFabricNetworkGatewayToLogicalRouterCmd addTungstenFabricNetworkGatewayToLogicalRouterCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         addTungstenFabricNetworkGatewayToLogicalRouterCmd = new AddTungstenFabricNetworkGatewayToLogicalRouterCmd();
         addTungstenFabricNetworkGatewayToLogicalRouterCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(addTungstenFabricNetworkGatewayToLogicalRouterCmd, "zoneId", 1L);
-        Whitebox.setInternalState(addTungstenFabricNetworkGatewayToLogicalRouterCmd, "networkUuid", "005f0dea-0196" +
+        ReflectionTestUtils.setField(addTungstenFabricNetworkGatewayToLogicalRouterCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(addTungstenFabricNetworkGatewayToLogicalRouterCmd, "networkUuid", "005f0dea-0196" +
                 "-11ec-a1ed-b42e99f6e187");
-        Whitebox.setInternalState(addTungstenFabricNetworkGatewayToLogicalRouterCmd, "logicalRouterUuid", "125f0dea" +
+        ReflectionTestUtils.setField(addTungstenFabricNetworkGatewayToLogicalRouterCmd, "logicalRouterUuid", "125f0dea" +
                 "-0196-11ec-a1ed-b42e99f6e187");
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void executeTest() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
             ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricPolicyRuleCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricPolicyRuleCmdTest.java
index 5fb4b25..20898e4 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricPolicyRuleCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/AddTungstenFabricPolicyRuleCmdTest.java
@@ -27,12 +27,15 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class AddTungstenFabricPolicyRuleCmdTest {
 
     @Mock
@@ -40,26 +43,32 @@
 
     AddTungstenFabricPolicyRuleCmd addTungstenFabricPolicyRuleCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         addTungstenFabricPolicyRuleCmd = new AddTungstenFabricPolicyRuleCmd();
         addTungstenFabricPolicyRuleCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "zoneId", 1L);
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "policyUuid", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "action", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "direction", "oneway");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "protocol", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "srcNetwork", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "srcIpPrefix", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "srcIpPrefixLen", 1);
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "srcStartPort", 1);
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "srcEndPort", 1);
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "destNetwork", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "destIpPrefix", "test");
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "destIpPrefixLen", 1);
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "destStartPort", 1);
-        Whitebox.setInternalState(addTungstenFabricPolicyRuleCmd, "destEndPort", 1);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "action", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "direction", "oneway");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "protocol", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "srcNetwork", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "srcIpPrefix", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "srcIpPrefixLen", 1);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "srcStartPort", 1);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "srcEndPort", 1);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "destNetwork", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "destIpPrefix", "test");
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "destIpPrefixLen", 1);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "destStartPort", 1);
+        ReflectionTestUtils.setField(addTungstenFabricPolicyRuleCmd, "destEndPort", 1);
+    }
+
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricPolicyCmdTest.java
index 432aad0..3618869 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricPolicyCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricPolicyResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class ApplyTungstenFabricPolicyCmdTest {
 
     @Mock
@@ -40,16 +44,23 @@
 
     ApplyTungstenFabricPolicyCmd applyTungstenFabricPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         applyTungstenFabricPolicyCmd = new ApplyTungstenFabricPolicyCmd();
         applyTungstenFabricPolicyCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(applyTungstenFabricPolicyCmd, "zoneId", 1L);
-        Whitebox.setInternalState(applyTungstenFabricPolicyCmd, "networkUuid", "test");
-        Whitebox.setInternalState(applyTungstenFabricPolicyCmd, "policyUuid", "test");
-        Whitebox.setInternalState(applyTungstenFabricPolicyCmd, "majorSequence", 1);
-        Whitebox.setInternalState(applyTungstenFabricPolicyCmd, "minorSequence", 1);
+        ReflectionTestUtils.setField(applyTungstenFabricPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(applyTungstenFabricPolicyCmd, "networkUuid", "test");
+        ReflectionTestUtils.setField(applyTungstenFabricPolicyCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(applyTungstenFabricPolicyCmd, "majorSequence", 1);
+        ReflectionTestUtils.setField(applyTungstenFabricPolicyCmd, "minorSequence", 1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricTagCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricTagCmdTest.java
index 4446356..a42cbdc 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricTagCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ApplyTungstenFabricTagCmdTest.java
@@ -24,17 +24,21 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricTagResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 
+@RunWith(MockitoJUnitRunner.class)
 public class ApplyTungstenFabricTagCmdTest {
 
     @Mock
@@ -42,18 +46,25 @@
 
     ApplyTungstenFabricTagCmd applyTungstenFabricTagCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         applyTungstenFabricTagCmd = new ApplyTungstenFabricTagCmd();
         applyTungstenFabricTagCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "zoneId", 1L);
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "networkUuids", Arrays.asList("test"));
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "vmUuids", Arrays.asList("test"));
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "nicUuids", Arrays.asList("test"));
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "policyUuid", "test");
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "applicationPolicySetUuid", "test");
-        Whitebox.setInternalState(applyTungstenFabricTagCmd, "tagUuid", "test");
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "networkUuids", Arrays.asList("test"));
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "vmUuids", Arrays.asList("test"));
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "nicUuids", Arrays.asList("test"));
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(applyTungstenFabricTagCmd, "tagUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ConfigTungstenFabricServiceCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ConfigTungstenFabricServiceCmdTest.java
index 057a490..4fea9dd 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ConfigTungstenFabricServiceCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ConfigTungstenFabricServiceCmdTest.java
@@ -28,27 +28,25 @@
 import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallbackNoReturn;
 import org.apache.cloudstack.api.response.SuccessResponse;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({Transaction.class, ConfigTungstenFabricServiceCmd.class})
+@RunWith(MockitoJUnitRunner.class)
 public class ConfigTungstenFabricServiceCmdTest {
     @Mock
     EntityManager entityManager;
@@ -65,9 +63,10 @@
 
     ConfigTungstenFabricServiceCmd configTungstenFabricServiceCmd;
 
+    AutoCloseable closeable;
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         configTungstenFabricServiceCmd = new ConfigTungstenFabricServiceCmd();
         configTungstenFabricServiceCmd._entityMgr = entityManager;
         configTungstenFabricServiceCmd.networkModel = networkModel;
@@ -75,8 +74,13 @@
         configTungstenFabricServiceCmd.networkOfferingServiceMapDao = networkOfferingServiceMapDao;
         configTungstenFabricServiceCmd.networkServiceMapDao = networkServiceMapDao;
         configTungstenFabricServiceCmd.physicalNetworkServiceProviderDao = physicalNetworkServiceProviderDao;
-        Whitebox.setInternalState(configTungstenFabricServiceCmd, "zoneId", 1L);
-        Whitebox.setInternalState(configTungstenFabricServiceCmd, "physicalNetworkId", 1L);
+        ReflectionTestUtils.setField(configTungstenFabricServiceCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(configTungstenFabricServiceCmd, "physicalNetworkId", 1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -86,16 +90,14 @@
         Network managementNetwork = Mockito.mock(Network.class);
         TransactionCallbackNoReturn transactionCallbackNoReturn = Mockito.mock(TransactionCallbackNoReturn.class);
         List<NetworkOfferingVO> systemNetworkOffering = Arrays.asList(Mockito.mock(NetworkOfferingVO.class));
-        mockStatic(Transaction.class);
         Mockito.when(entityManager.findById(ArgumentMatchers.any(), ArgumentMatchers.anyLong())).thenReturn(dataCenter);
         Mockito.when(dataCenter.isSecurityGroupEnabled()).thenReturn(true);
-        Mockito.when(networkModel.getSystemNetworkByZoneAndTrafficType(ArgumentMatchers.anyLong(),
-                ArgumentMatchers.any())).thenReturn(managementNetwork);
-        Mockito.when(networkOfferingDao.listSystemNetworkOfferings()).thenReturn(systemNetworkOffering);
-        PowerMockito.when(Transaction.execute(any(TransactionCallbackNoReturn.class))).thenReturn(transactionCallbackNoReturn);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
-        configTungstenFabricServiceCmd.execute();
-        Assert.assertEquals(successResponse, configTungstenFabricServiceCmd.getResponseObject());
+        try (MockedStatic<Transaction> transactionMocked = Mockito.mockStatic(Transaction.class)) {
+            transactionMocked.when(() -> Transaction.execute(any(TransactionCallbackNoReturn.class))).thenReturn(transactionCallbackNoReturn);
+            configTungstenFabricServiceCmd.execute();
+            SuccessResponse response = (SuccessResponse) configTungstenFabricServiceCmd.getResponseObject();
+            Assert.assertTrue(response.getSuccess());
+        }
     }
 
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricAddressGroupCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricAddressGroupCmdTest.java
index 2eb497e..79d8dc7 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricAddressGroupCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricAddressGroupCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricAddressGroupResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricAddressGroupCmdTest {
 
     @Mock
@@ -40,15 +44,22 @@
 
     CreateTungstenFabricAddressGroupCmd createTungstenFabricAddressGroupCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricAddressGroupCmd = new CreateTungstenFabricAddressGroupCmd();
         createTungstenFabricAddressGroupCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricAddressGroupCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricAddressGroupCmd, "name", "test");
-        Whitebox.setInternalState(createTungstenFabricAddressGroupCmd, "ipPrefix", "test");
-        Whitebox.setInternalState(createTungstenFabricAddressGroupCmd, "ipPrefixLen", 1);
+        ReflectionTestUtils.setField(createTungstenFabricAddressGroupCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricAddressGroupCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricAddressGroupCmd, "ipPrefix", "test");
+        ReflectionTestUtils.setField(createTungstenFabricAddressGroupCmd, "ipPrefixLen", 1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricApplicationPolicySetCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricApplicationPolicySetCmdTest.java
index 9f1439c..c02ba33 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricApplicationPolicySetCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricApplicationPolicySetCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricApplicationPolicySetResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricApplicationPolicySetCmdTest {
 
     @Mock
@@ -40,13 +44,20 @@
 
     CreateTungstenFabricApplicationPolicySetCmd createTungstenFabricApplicationPolicySetCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricApplicationPolicySetCmd = new CreateTungstenFabricApplicationPolicySetCmd();
         createTungstenFabricApplicationPolicySetCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricApplicationPolicySetCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricApplicationPolicySetCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricApplicationPolicySetCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricApplicationPolicySetCmd, "name", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallPolicyCmdTest.java
index 833cec0..7ea11b9 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallPolicyCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricFirewallPolicyResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricFirewallPolicyCmdTest {
 
     @Mock
@@ -40,15 +44,22 @@
 
     CreateTungstenFabricFirewallPolicyCmd createTungstenFabricFirewallPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricFirewallPolicyCmd = new CreateTungstenFabricFirewallPolicyCmd();
         createTungstenFabricFirewallPolicyCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricFirewallPolicyCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricFirewallPolicyCmd, "applicationPolicySetUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallPolicyCmd, "name", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallPolicyCmd, "sequence", 1);
+        ReflectionTestUtils.setField(createTungstenFabricFirewallPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricFirewallPolicyCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallPolicyCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallPolicyCmd, "sequence", 1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallRuleCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallRuleCmdTest.java
index 72db94f..3b946fa 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallRuleCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricFirewallRuleCmdTest.java
@@ -27,12 +27,15 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricFirewallRuleCmdTest {
 
     @Mock
@@ -40,25 +43,31 @@
 
     CreateTungstenFabricFirewallRuleCmd createTungstenFabricFirewallRuleCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricFirewallRuleCmd = new CreateTungstenFabricFirewallRuleCmd();
         createTungstenFabricFirewallRuleCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "firewallPolicyUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "name", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "action", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "serviceGroupUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "srcTagUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "srcAddressGroupUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "srcNetworkUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "direction", "oneway");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "destTagUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "destAddressGroupUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "destNetworkUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "tagTypeUuid", "test");
-        Whitebox.setInternalState(createTungstenFabricFirewallRuleCmd, "sequence", 1);
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "firewallPolicyUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "action", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "serviceGroupUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "srcTagUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "srcAddressGroupUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "srcNetworkUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "direction", "oneway");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "destTagUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "destAddressGroupUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "destNetworkUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "tagTypeUuid", "test");
+        ReflectionTestUtils.setField(createTungstenFabricFirewallRuleCmd, "sequence", 1);
+    }
+
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricLogicalRouterCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricLogicalRouterCmdTest.java
index a6f0b03..39b209f 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricLogicalRouterCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricLogicalRouterCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricLogicalRouterCmdTest {
 
     @Mock
@@ -40,13 +44,20 @@
 
     CreateTungstenFabricLogicalRouterCmd createTungstenFabricLogicalRouterCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricLogicalRouterCmd = new CreateTungstenFabricLogicalRouterCmd();
         createTungstenFabricLogicalRouterCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricLogicalRouterCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricLogicalRouterCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricLogicalRouterCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricLogicalRouterCmd, "name", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricManagementNetworkCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricManagementNetworkCmdTest.java
index 44ffe5d..17774e1 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricManagementNetworkCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricManagementNetworkCmdTest.java
@@ -20,6 +20,7 @@
 import com.cloud.dc.dao.HostPodDao;
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -28,13 +29,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CreateTungstenFabricManagementNetworkCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricManagementNetworkCmdTest {
 
     @Mock
@@ -44,24 +42,29 @@
 
     CreateTungstenFabricManagementNetworkCmd createTungstenFabricManagementNetworkCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricManagementNetworkCmd = new CreateTungstenFabricManagementNetworkCmd();
         createTungstenFabricManagementNetworkCmd.tungstenService = tungstenService;
         createTungstenFabricManagementNetworkCmd.podDao = podDao;
-        Whitebox.setInternalState(createTungstenFabricManagementNetworkCmd, "podId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricManagementNetworkCmd, "podId", 1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         HostPodVO pod = Mockito.mock(HostPodVO.class);
         Mockito.when(podDao.findById(ArgumentMatchers.anyLong())).thenReturn(pod);
         Mockito.when(tungstenService.createManagementNetwork(ArgumentMatchers.anyLong())).thenReturn(true);
         Mockito.when(tungstenService.addManagementNetworkSubnet(ArgumentMatchers.any())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         createTungstenFabricManagementNetworkCmd.execute();
-        Assert.assertEquals(successResponse, createTungstenFabricManagementNetworkCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse)createTungstenFabricManagementNetworkCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPolicyCmdTest.java
index f3e8399..94dc6dc 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPolicyCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricPolicyResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricPolicyCmdTest {
 
     @Mock
@@ -40,13 +44,20 @@
 
     CreateTungstenFabricPolicyCmd createTungstenFabricPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricPolicyCmd = new CreateTungstenFabricPolicyCmd();
         createTungstenFabricPolicyCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricPolicyCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricPolicyCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricPolicyCmd, "name", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricProviderCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricProviderCmdTest.java
index 335c5f0..ef56b73 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricProviderCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricProviderCmdTest.java
@@ -28,12 +28,15 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricProviderCmdTest {
 
     @Mock
@@ -41,18 +44,24 @@
 
     CreateTungstenFabricProviderCmd createTungstenFabricProviderCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricProviderCmd = new CreateTungstenFabricProviderCmd();
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "tungstenProviderService", tungstenProviderService);
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "name", "test");
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "hostname", "test");
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "port", "test");
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "gateway", "test");
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "vrouterPort", "test");
-        Whitebox.setInternalState(createTungstenFabricProviderCmd, "introspectPort", "test");
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "tungstenProviderService", tungstenProviderService);
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "hostname", "test");
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "port", "test");
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "gateway", "test");
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "vrouterPort", "test");
+        ReflectionTestUtils.setField(createTungstenFabricProviderCmd, "introspectPort", "test");
+    }
+
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPublicNetworkCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPublicNetworkCmdTest.java
index 3a98e61..c626f34 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPublicNetworkCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricPublicNetworkCmdTest.java
@@ -23,6 +23,7 @@
 import com.cloud.utils.db.SearchCriteria;
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -31,16 +32,13 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CreateTungstenFabricPublicNetworkCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricPublicNetworkCmdTest {
 
     @Mock
@@ -52,19 +50,25 @@
 
     CreateTungstenFabricPublicNetworkCmd createTungstenFabricPublicNetworkCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricPublicNetworkCmd = new CreateTungstenFabricPublicNetworkCmd();
         createTungstenFabricPublicNetworkCmd.tungstenService = tungstenService;
         createTungstenFabricPublicNetworkCmd.vlanDao = vlanDao;
         createTungstenFabricPublicNetworkCmd.networkModel = networkModel;
-        Whitebox.setInternalState(createTungstenFabricPublicNetworkCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricPublicNetworkCmd, "zoneId", 1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Network publicNetwork = Mockito.mock(Network.class);
         SearchCriteria<VlanVO> sc = Mockito.mock(SearchCriteria.class);
         List<VlanVO> pubVlanVOList = Arrays.asList(Mockito.mock(VlanVO.class));
@@ -75,8 +79,7 @@
 
         Mockito.when(tungstenService.createPublicNetwork(ArgumentMatchers.anyLong())).thenReturn(true);
         Mockito.when(tungstenService.addPublicNetworkSubnet(ArgumentMatchers.any())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         createTungstenFabricPublicNetworkCmd.execute();
-        Assert.assertEquals(successResponse, createTungstenFabricPublicNetworkCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) createTungstenFabricPublicNetworkCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricServiceGroupCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricServiceGroupCmdTest.java
index d80a6fc..7591c4c 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricServiceGroupCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricServiceGroupCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricServiceGroupResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricServiceGroupCmdTest {
 
     @Mock
@@ -40,16 +44,23 @@
 
     CreateTungstenFabricServiceGroupCmd createTungstenFabricServiceGroupCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricServiceGroupCmd = new CreateTungstenFabricServiceGroupCmd();
         createTungstenFabricServiceGroupCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricServiceGroupCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricServiceGroupCmd, "name", "test");
-        Whitebox.setInternalState(createTungstenFabricServiceGroupCmd, "protocol", "test");
-        Whitebox.setInternalState(createTungstenFabricServiceGroupCmd, "startPort", 1);
-        Whitebox.setInternalState(createTungstenFabricServiceGroupCmd, "endPort", 1);
+        ReflectionTestUtils.setField(createTungstenFabricServiceGroupCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricServiceGroupCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricServiceGroupCmd, "protocol", "test");
+        ReflectionTestUtils.setField(createTungstenFabricServiceGroupCmd, "startPort", 1);
+        ReflectionTestUtils.setField(createTungstenFabricServiceGroupCmd, "endPort", 1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagCmdTest.java
index 1b953f9..29340d5 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricTagResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricTagCmdTest {
 
     @Mock
@@ -40,14 +44,21 @@
 
     CreateTungstenFabricTagCmd createTungstenFabricTagCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricTagCmd = new CreateTungstenFabricTagCmd();
         createTungstenFabricTagCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricTagCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricTagCmd, "tagType", "test");
-        Whitebox.setInternalState(createTungstenFabricTagCmd, "tagValue", "test");
+        ReflectionTestUtils.setField(createTungstenFabricTagCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricTagCmd, "tagType", "test");
+        ReflectionTestUtils.setField(createTungstenFabricTagCmd, "tagValue", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagTypeCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagTypeCmdTest.java
index 81b32f0..2530ec6 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagTypeCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/CreateTungstenFabricTagTypeCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricTagTypeResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class CreateTungstenFabricTagTypeCmdTest {
 
     @Mock
@@ -40,13 +44,20 @@
 
     CreateTungstenFabricTagTypeCmd createTungstenFabricTagTypeCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         createTungstenFabricTagTypeCmd = new CreateTungstenFabricTagTypeCmd();
         createTungstenFabricTagTypeCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(createTungstenFabricTagTypeCmd, "zoneId", 1L);
-        Whitebox.setInternalState(createTungstenFabricTagTypeCmd, "name", "test");
+        ReflectionTestUtils.setField(createTungstenFabricTagTypeCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(createTungstenFabricTagTypeCmd, "name", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricAddressGroupCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricAddressGroupCmdTest.java
index 0e617ba..54b0a1a 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricAddressGroupCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricAddressGroupCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricAddressGroupCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricAddressGroupCmdTest {
 
     @Mock
@@ -40,13 +38,20 @@
 
     DeleteTungstenFabricAddressGroupCmd deleteTungstenFabricAddressGroupCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricAddressGroupCmd = new DeleteTungstenFabricAddressGroupCmd();
         deleteTungstenFabricAddressGroupCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricAddressGroupCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricAddressGroupCmd, "addressGroupUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricAddressGroupCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricAddressGroupCmd, "addressGroupUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -54,8 +59,7 @@
         SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenAddressGroup(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricAddressGroupCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricAddressGroupCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricAddressGroupCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricApplicationPolicySetCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricApplicationPolicySetCmdTest.java
index 0ff22ae..8ea3c09 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricApplicationPolicySetCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricApplicationPolicySetCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricApplicationPolicySetCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricApplicationPolicySetCmdTest {
 
     @Mock
@@ -40,22 +38,27 @@
 
     DeleteTungstenFabricApplicationPolicySetCmd deleteTungstenFabricApplicationPolicySetCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricApplicationPolicySetCmd = new DeleteTungstenFabricApplicationPolicySetCmd();
         deleteTungstenFabricApplicationPolicySetCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricApplicationPolicySetCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricApplicationPolicySetCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricApplicationPolicySetCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricApplicationPolicySetCmd, "applicationPolicySetUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenApplicationPolicySet(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricApplicationPolicySetCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricApplicationPolicySetCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricApplicationPolicySetCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallPolicyCmdTest.java
index 4b3ce89..e5a23f1 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallPolicyCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricFirewallPolicyCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricFirewallPolicyCmdTest {
 
     @Mock
@@ -40,22 +38,27 @@
 
     DeleteTungstenFabricFirewallPolicyCmd deleteTungstenFabricFirewallPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricFirewallPolicyCmd = new DeleteTungstenFabricFirewallPolicyCmd();
         deleteTungstenFabricFirewallPolicyCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricFirewallPolicyCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricFirewallPolicyCmd, "firewallPolicyUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricFirewallPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricFirewallPolicyCmd, "firewallPolicyUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenFirewallPolicy(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricFirewallPolicyCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricFirewallPolicyCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricFirewallPolicyCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallRuleCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallRuleCmdTest.java
index 612a0e9..fea20ce 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallRuleCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricFirewallRuleCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricFirewallRuleCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricFirewallRuleCmdTest {
 
     @Mock
@@ -40,22 +38,27 @@
 
     DeleteTungstenFabricFirewallRuleCmd deleteTungstenFabricFirewallRuleCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricFirewallRuleCmd = new DeleteTungstenFabricFirewallRuleCmd();
         deleteTungstenFabricFirewallRuleCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricFirewallRuleCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricFirewallRuleCmd, "firewallRuleUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricFirewallRuleCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricFirewallRuleCmd, "firewallRuleUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenFirewallRule(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricFirewallRuleCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricFirewallRuleCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricFirewallRuleCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricLogicalRouterCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricLogicalRouterCmdTest.java
index 710fea9..469fe2f 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricLogicalRouterCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricLogicalRouterCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,16 +27,13 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricLogicalRouterCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricLogicalRouterCmdTest {
 
     @Mock
@@ -43,25 +41,30 @@
 
     DeleteTungstenFabricLogicalRouterCmd deleteTungstenFabricLogicalRouterCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricLogicalRouterCmd = new DeleteTungstenFabricLogicalRouterCmd();
         deleteTungstenFabricLogicalRouterCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricLogicalRouterCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricLogicalRouterCmd, "logicalRouterUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricLogicalRouterCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricLogicalRouterCmd, "logicalRouterUuid", "test");
+    }
+
+    @After
+    public void close() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         List<String> networkList = new ArrayList<>();
         Mockito.when(tungstenService.listConnectedNetworkFromLogicalRouter(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(networkList);
         Mockito.when(tungstenService.deleteLogicalRouter(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricLogicalRouterCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricLogicalRouterCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricLogicalRouterCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricPolicyCmdTest.java
index 7f421dd..bce24ec 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricPolicyCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricPolicyCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricPolicyCmdTest {
 
     @Mock
@@ -40,22 +38,27 @@
 
     DeleteTungstenFabricPolicyCmd deleteTungstenFabricPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricPolicyCmd = new DeleteTungstenFabricPolicyCmd();
         deleteTungstenFabricPolicyCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricPolicyCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricPolicyCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricPolicyCmd, "policyUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenPolicy(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricPolicyCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricPolicyCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricPolicyCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricServiceGroupCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricServiceGroupCmdTest.java
index 2e32ced..08314be 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricServiceGroupCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricServiceGroupCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricServiceGroupCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricServiceGroupCmdTest {
 
     @Mock
@@ -40,22 +38,27 @@
 
     DeleteTungstenFabricServiceGroupCmd deleteTungstenFabricServiceGroupCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricServiceGroupCmd = new DeleteTungstenFabricServiceGroupCmd();
         deleteTungstenFabricServiceGroupCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricServiceGroupCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricServiceGroupCmd, "serviceGroupUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricServiceGroupCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricServiceGroupCmd, "serviceGroupUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenServiceGroup(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricServiceGroupCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricServiceGroupCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) deleteTungstenFabricServiceGroupCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagCmdTest.java
index 0bf3b09..c572fa1 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagCmdTest.java
@@ -26,13 +26,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricTagCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricTagCmdTest {
 
     @Mock
@@ -40,22 +37,25 @@
 
     DeleteTungstenFabricTagCmd deleteTungstenFabricTagCmd;
 
+    AutoCloseable closeable;
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricTagCmd = new DeleteTungstenFabricTagCmd();
         deleteTungstenFabricTagCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricTagCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricTagCmd, "tagUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricTagCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricTagCmd, "tagUuid", "test");
+    }
+
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenTag(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricTagCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricTagCmd.getResponseObject());
+        Assert.assertEquals(true, ((SuccessResponse) deleteTungstenFabricTagCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagTypeCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagTypeCmdTest.java
index 3a78370..81393af 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagTypeCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/DeleteTungstenFabricTagTypeCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DeleteTungstenFabricTagTypeCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class DeleteTungstenFabricTagTypeCmdTest {
 
     @Mock
@@ -40,13 +38,20 @@
 
     DeleteTungstenFabricTagTypeCmd deleteTungstenFabricTagTypeCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         deleteTungstenFabricTagTypeCmd = new DeleteTungstenFabricTagTypeCmd();
         deleteTungstenFabricTagTypeCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(deleteTungstenFabricTagTypeCmd, "zoneId", 1L);
-        Whitebox.setInternalState(deleteTungstenFabricTagTypeCmd, "tagTypeUuid", "test");
+        ReflectionTestUtils.setField(deleteTungstenFabricTagTypeCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(deleteTungstenFabricTagTypeCmd, "tagTypeUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -54,8 +59,7 @@
         SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.deleteTungstenTagType(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         deleteTungstenFabricTagTypeCmd.execute();
-        Assert.assertEquals(successResponse, deleteTungstenFabricTagTypeCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse)deleteTungstenFabricTagTypeCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/GetLoadBalancerSslCertificateCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/GetLoadBalancerSslCertificateCmdTest.java
index dcf0737..6130779 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/GetLoadBalancerSslCertificateCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/GetLoadBalancerSslCertificateCmdTest.java
@@ -19,6 +19,8 @@
 import com.cloud.network.lb.LoadBalancingRule;
 import com.cloud.network.lb.LoadBalancingRulesManager;
 import org.apache.cloudstack.network.tungsten.api.response.TlsDataResponse;
+import org.apache.commons.codec.binary.Base64;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -27,13 +29,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(GetLoadBalancerSslCertificateCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class GetLoadBalancerSslCertificateCmdTest {
 
     @Mock
@@ -41,24 +40,34 @@
 
     GetLoadBalancerSslCertificateCmd getLoadBalancerSslCertificateCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         getLoadBalancerSslCertificateCmd = new GetLoadBalancerSslCertificateCmd();
-        Whitebox.setInternalState(getLoadBalancerSslCertificateCmd, "lbMgr", loadBalancingRulesManager);
-        Whitebox.setInternalState(getLoadBalancerSslCertificateCmd, "id", 1L);
+        ReflectionTestUtils.setField(getLoadBalancerSslCertificateCmd, "lbMgr", loadBalancingRulesManager);
+        ReflectionTestUtils.setField(getLoadBalancerSslCertificateCmd, "id", 1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
         LoadBalancingRule.LbSslCert lbSslCert = Mockito.mock(LoadBalancingRule.LbSslCert.class);
-        TlsDataResponse tlsDataResponse = Mockito.mock(TlsDataResponse.class);
-        Mockito.when(lbSslCert.getCert()).thenReturn("test");
-        Mockito.when(lbSslCert.getKey()).thenReturn("test");
-        Mockito.when(lbSslCert.getChain()).thenReturn("test");
+        Mockito.when(lbSslCert.getCert()).thenReturn("testCrt");
+        Mockito.when(lbSslCert.getKey()).thenReturn("testKey");
+        Mockito.when(lbSslCert.getChain()).thenReturn("testChain");
         Mockito.when(loadBalancingRulesManager.getLbSslCert(ArgumentMatchers.anyLong())).thenReturn(lbSslCert);
-        PowerMockito.whenNew(TlsDataResponse.class).withAnyArguments().thenReturn(tlsDataResponse);
         getLoadBalancerSslCertificateCmd.execute();
-        Assert.assertEquals(tlsDataResponse, getLoadBalancerSslCertificateCmd.getResponseObject());
+        TlsDataResponse response = (TlsDataResponse) getLoadBalancerSslCertificateCmd.getResponseObject();
+
+        Assert.assertEquals(Base64.encodeBase64String("testCrt".getBytes()), response.getCrt());
+        Assert.assertEquals(Base64.encodeBase64String("testChain".getBytes()), response.getChain());
+        Assert.assertEquals(Base64.encodeBase64String("testKey".getBytes()), response.getKey());
+
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricAddressGroupCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricAddressGroupCmdTest.java
index f741580..d797d29 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricAddressGroupCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricAddressGroupCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,64 +29,63 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricAddressGroupCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricAddressGroupCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricAddressGroupCmd listTungstenFabricAddressGroupCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricAddressGroupCmd = new ListTungstenFabricAddressGroupCmd();
         listTungstenFabricAddressGroupCmd.tungstenService = tungstenService;
-        listTungstenFabricAddressGroupCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricAddressGroupCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricAddressGroupCmd, "addressGroupUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricAddressGroupCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricAddressGroupCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricAddressGroupCmd, "addressGroupUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricAddressGroupCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricAddressGroupCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricAddressGroupCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricAddressGroupCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricAddressGroupCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenAddressGroup(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricAddressGroupCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricAddressGroupCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricAddressGroupCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenAddressGroup(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricAddressGroupCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricAddressGroupCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricAddressGroupCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricApplicationPolicySetCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricApplicationPolicySetCmdTest.java
new file mode 100644
index 0000000..8d7fddb
--- /dev/null
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricApplicationPolicySetCmdTest.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.network.tungsten.api.command;
+
+import com.cloud.network.element.TungstenProviderVO;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ListTungstenFabricApplicationPolicySetCmdTest {
+
+    @Mock
+    TungstenService tungstenService;
+
+    ListTungstenFabricApplictionPolicySetCmd listTungstenFabricApplictionPolicySetCmd;
+
+    AutoCloseable closeable;
+
+    @Before
+    public void setup() {
+        closeable = MockitoAnnotations.openMocks(this);
+        listTungstenFabricApplictionPolicySetCmd = new ListTungstenFabricApplictionPolicySetCmd();
+        listTungstenFabricApplictionPolicySetCmd.tungstenService = tungstenService;
+        ReflectionTestUtils.setField(listTungstenFabricApplictionPolicySetCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricApplictionPolicySetCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricApplictionPolicySetCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricApplictionPolicySetCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void executeTest() throws Exception {
+        ReflectionTestUtils.setField(listTungstenFabricApplictionPolicySetCmd, "zoneId", 1L);
+        BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
+        List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
+        Mockito.when(tungstenService.listTungstenApplicationPolicySet(ArgumentMatchers.anyLong(),
+                ArgumentMatchers.anyString())).thenReturn(baseResponseList);
+        listTungstenFabricApplictionPolicySetCmd.execute();
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricApplictionPolicySetCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+    }
+
+    @Test
+    public void executeAllZoneTest() throws Exception {
+        BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
+        List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
+        TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
+        List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
+        Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
+        Mockito.when(tungstenService.listTungstenApplicationPolicySet(ArgumentMatchers.anyLong(),
+                ArgumentMatchers.anyString())).thenReturn(baseResponseList);
+        listTungstenFabricApplictionPolicySetCmd.execute();
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricApplictionPolicySetCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+    }
+}
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricApplictionPolicySetCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricApplictionPolicySetCmdTest.java
deleted file mode 100644
index eff717d..0000000
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricApplictionPolicySetCmdTest.java
+++ /dev/null
@@ -1,93 +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.network.tungsten.api.command;
-
-import com.cloud.configuration.ConfigurationService;
-import com.cloud.network.element.TungstenProviderVO;
-import org.apache.cloudstack.api.BaseResponse;
-import org.apache.cloudstack.api.response.ListResponse;
-import org.apache.cloudstack.network.tungsten.service.TungstenService;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricApplictionPolicySetCmd.class)
-public class ListTungstenFabricApplictionPolicySetCmdTest {
-
-    @Mock
-    TungstenService tungstenService;
-
-    @Mock
-    ConfigurationService configService;
-
-    ListTungstenFabricApplictionPolicySetCmd listTungstenFabricApplictionPolicySetCmd;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        listTungstenFabricApplictionPolicySetCmd = new ListTungstenFabricApplictionPolicySetCmd();
-        listTungstenFabricApplictionPolicySetCmd.tungstenService = tungstenService;
-        listTungstenFabricApplictionPolicySetCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricApplictionPolicySetCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricApplictionPolicySetCmd, "zoneId", 1L);
-        Whitebox.setInternalState(listTungstenFabricApplictionPolicySetCmd, "applicationPolicySetUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricApplictionPolicySetCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricApplictionPolicySetCmd, "pageSize", 10);
-    }
-
-    @Test
-    public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricApplictionPolicySetCmd, "zoneId", 1L);
-        BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
-        List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
-        Mockito.when(tungstenService.listTungstenApplicationPolicySet(ArgumentMatchers.anyLong(),
-                ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
-        listTungstenFabricApplictionPolicySetCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricApplictionPolicySetCmd.getResponseObject());
-    }
-
-    @Test
-    public void executeAllZoneTest() throws Exception {
-        BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
-        List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
-        TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
-        List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
-        Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
-        Mockito.when(tungstenService.listTungstenApplicationPolicySet(ArgumentMatchers.anyLong(),
-                ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
-        listTungstenFabricApplictionPolicySetCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricApplictionPolicySetCmd.getResponseObject());
-    }
-}
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallPolicyCmdTest.java
index ed3b68e..9764a54 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallPolicyCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,65 +29,64 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricFirewallPolicyCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricFirewallPolicyCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricFirewallPolicyCmd listTungstenFabricFirewallPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricFirewallPolicyCmd = new ListTungstenFabricFirewallPolicyCmd();
         listTungstenFabricFirewallPolicyCmd.tungstenService = tungstenService;
-        listTungstenFabricFirewallPolicyCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricFirewallPolicyCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricFirewallPolicyCmd, "applicationPolicySetUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricFirewallPolicyCmd, "firewallPolicyUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricFirewallPolicyCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricFirewallPolicyCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallPolicyCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricFirewallPolicyCmd, "firewallPolicyUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricFirewallPolicyCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallPolicyCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallPolicyCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricFirewallPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallPolicyCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenFirewallPolicy(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricFirewallPolicyCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricFirewallPolicyCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricFirewallPolicyCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenFirewallPolicy(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricFirewallPolicyCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricFirewallPolicyCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricFirewallPolicyCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallRuleCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallRuleCmdTest.java
index f267478..12fca75 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallRuleCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricFirewallRuleCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,65 +29,64 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricFirewallRuleCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricFirewallRuleCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricFirewallRuleCmd listTungstenFabricFirewallRuleCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricFirewallRuleCmd = new ListTungstenFabricFirewallRuleCmd();
         listTungstenFabricFirewallRuleCmd.tungstenService = tungstenService;
-        listTungstenFabricFirewallRuleCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricFirewallRuleCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricFirewallRuleCmd, "firewallPolicyUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricFirewallRuleCmd, "firewallRuleUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricFirewallRuleCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricFirewallRuleCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallRuleCmd, "firewallPolicyUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricFirewallRuleCmd, "firewallRuleUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricFirewallRuleCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallRuleCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallRuleCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricFirewallRuleCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricFirewallRuleCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenFirewallRule(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricFirewallRuleCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricFirewallRuleCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricFirewallRuleCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenFirewallRule(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricFirewallRuleCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricFirewallRuleCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricFirewallRuleCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLBHealthMonitorCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLBHealthMonitorCmdTest.java
index c9df935..c2ee9a9 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLBHealthMonitorCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLBHealthMonitorCmdTest.java
@@ -16,10 +16,10 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -28,47 +28,46 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricLBHealthMonitorCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricLBHealthMonitorCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricLBHealthMonitorCmd listTungstenFabricLBHealthMonitorCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricLBHealthMonitorCmd = new ListTungstenFabricLBHealthMonitorCmd();
         listTungstenFabricLBHealthMonitorCmd.tungstenService = tungstenService;
-        listTungstenFabricLBHealthMonitorCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricLBHealthMonitorCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricLBHealthMonitorCmd, "lbID", 1L);
-        Whitebox.setInternalState(listTungstenFabricLBHealthMonitorCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricLBHealthMonitorCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricLBHealthMonitorCmd, "lbID", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricLBHealthMonitorCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricLBHealthMonitorCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricLBHealthMonitorCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenFabricLBHealthMonitor(ArgumentMatchers.anyLong())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricLBHealthMonitorCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricLBHealthMonitorCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricLBHealthMonitorCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLogicalRouterCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLogicalRouterCmdTest.java
index 34879d2..72cc0dd 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLogicalRouterCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricLogicalRouterCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,65 +29,64 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricLogicalRouterCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricLogicalRouterCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricLogicalRouterCmd listTungstenFabricLogicalRouterCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricLogicalRouterCmd = new ListTungstenFabricLogicalRouterCmd();
         listTungstenFabricLogicalRouterCmd.tungstenService = tungstenService;
-        listTungstenFabricLogicalRouterCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricLogicalRouterCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricLogicalRouterCmd, "networkUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricLogicalRouterCmd, "logicalRouterUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricLogicalRouterCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricLogicalRouterCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricLogicalRouterCmd, "networkUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricLogicalRouterCmd, "logicalRouterUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricLogicalRouterCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricLogicalRouterCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricLogicalRouterCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricLogicalRouterCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricLogicalRouterCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listRoutingLogicalRouter(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricLogicalRouterCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricLogicalRouterCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricLogicalRouterCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listRoutingLogicalRouter(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricLogicalRouterCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricLogicalRouterCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricLogicalRouterCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNetworkCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNetworkCmdTest.java
index dbb3b60..a8eebe8 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNetworkCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNetworkCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,66 +29,67 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricNetworkCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricNetworkCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricNetworkCmd listTungstenFabricNetworkCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricNetworkCmd = new ListTungstenFabricNetworkCmd();
         listTungstenFabricNetworkCmd.tungstenService = tungstenService;
-        listTungstenFabricNetworkCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricNetworkCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricNetworkCmd, "networkUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricNetworkCmd, "listAll", true);
-        Whitebox.setInternalState(listTungstenFabricNetworkCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricNetworkCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricNetworkCmd, "networkUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricNetworkCmd, "listAll", true);
+        ReflectionTestUtils.setField(listTungstenFabricNetworkCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricNetworkCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricNetworkCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricNetworkCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricNetworkCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
+
         Mockito.when(tungstenService.listTungstenNetwork(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(BaseResponse.class).withAnyArguments().thenReturn(baseResponse);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
+
         listTungstenFabricNetworkCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricNetworkCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricNetworkCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenNetwork(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricNetworkCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricNetworkCmd.getResponseObject());
+
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricNetworkCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNicCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNicCmdTest.java
index 8f43c25..64bc950 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNicCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricNicCmdTest.java
@@ -16,77 +16,78 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 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.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricNicCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricNicCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
+    @InjectMocks
     ListTungstenFabricNicCmd listTungstenFabricNicCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricNicCmd = new ListTungstenFabricNicCmd();
         listTungstenFabricNicCmd.tungstenService = tungstenService;
-        listTungstenFabricNicCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricNicCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricNicCmd, "nicUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricNicCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricNicCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricNicCmd, "nicUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricNicCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricNicCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricNicCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricNicCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricNicCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenNic(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricNicCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricNicCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricNicCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenNic(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricNicCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricNicCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricNicCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyCmdTest.java
index eddb6b9..b7348d1 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,61 +29,58 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricPolicyCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricPolicyCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricPolicyCmd listTungstenFabricPolicyCmd;
 
+    AutoCloseable closeable;
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricPolicyCmd = new ListTungstenFabricPolicyCmd();
         listTungstenFabricPolicyCmd.tungstenService = tungstenService;
-        listTungstenFabricPolicyCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricPolicyCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricPolicyCmd, "networkId", 1L);
-        Whitebox.setInternalState(listTungstenFabricPolicyCmd, "addressId", 1L);
-        Whitebox.setInternalState(listTungstenFabricPolicyCmd, "policyUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricPolicyCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricPolicyCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "networkId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "addressId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenPolicy(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricPolicyCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricPolicyCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricPolicyCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
@@ -91,8 +88,9 @@
                 ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricPolicyCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricPolicyCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricPolicyCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyRuleCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyRuleCmdTest.java
index e76b8a7..d90f9e5 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyRuleCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricPolicyRuleCmdTest.java
@@ -16,80 +16,81 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 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.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricPolicyRuleCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricPolicyRuleCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
+    @InjectMocks
     ListTungstenFabricPolicyRuleCmd listTungstenFabricPolicyRuleCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricPolicyRuleCmd = new ListTungstenFabricPolicyRuleCmd();
         listTungstenFabricPolicyRuleCmd.tungstenService = tungstenService;
-        listTungstenFabricPolicyRuleCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricPolicyRuleCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricPolicyRuleCmd, "policyUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricPolicyRuleCmd, "ruleUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricPolicyRuleCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricPolicyRuleCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyRuleCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricPolicyRuleCmd, "ruleUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricPolicyRuleCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyRuleCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyRuleCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricPolicyRuleCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricPolicyRuleCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenPolicyRule(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricPolicyRuleCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricPolicyRuleCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricPolicyRuleCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenPolicyRule(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricPolicyRuleCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricPolicyRuleCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricPolicyRuleCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricProvidersCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricProvidersCmdTest.java
index 83eb037..4a3c088 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricProvidersCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricProvidersCmdTest.java
@@ -16,10 +16,10 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenProviderService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -28,47 +28,45 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricProvidersCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricProvidersCmdTest {
 
     @Mock
     TungstenProviderService tungstenProviderService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricProvidersCmd listTungstenFabricProvidersCmd;
 
+    AutoCloseable closeable;
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricProvidersCmd = new ListTungstenFabricProvidersCmd();
-        listTungstenFabricProvidersCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricProvidersCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricProvidersCmd, "tungstenProviderService", tungstenProviderService);
-        Whitebox.setInternalState(listTungstenFabricProvidersCmd, "zoneId", 1L);
-        Whitebox.setInternalState(listTungstenFabricProvidersCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricProvidersCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricProvidersCmd, "tungstenProviderService", tungstenProviderService);
+        ReflectionTestUtils.setField(listTungstenFabricProvidersCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricProvidersCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricProvidersCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricProvidersCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenProviderService.listTungstenProvider(ArgumentMatchers.anyLong())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricProvidersCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricProvidersCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricProvidersCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricServiceGroupCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricServiceGroupCmdTest.java
index 2aacd6d..493e024 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricServiceGroupCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricServiceGroupCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,64 +29,64 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricServiceGroupCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricServiceGroupCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricServiceGroupCmd listTungstenFabricServiceGroupCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricServiceGroupCmd = new ListTungstenFabricServiceGroupCmd();
         listTungstenFabricServiceGroupCmd.tungstenService = tungstenService;
-        listTungstenFabricServiceGroupCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricServiceGroupCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricServiceGroupCmd, "serviceGroupUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricServiceGroupCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricServiceGroupCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricServiceGroupCmd, "serviceGroupUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricServiceGroupCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricServiceGroupCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricServiceGroupCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricServiceGroupCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricServiceGroupCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenServiceGroup(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricServiceGroupCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricServiceGroupCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricServiceGroupCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenServiceGroup(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricServiceGroupCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricServiceGroupCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricServiceGroupCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagCmdTest.java
index a80fc70..afc473c 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,50 +29,48 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricTagCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricTagCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricTagCmd listTungstenFabricTagCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricTagCmd = new ListTungstenFabricTagCmd();
         listTungstenFabricTagCmd.tungstenService = tungstenService;
-        listTungstenFabricTagCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricTagCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "networkUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "vmUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "nicUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "policyUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "applicationPolicySetUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "tagUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "networkUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "vmUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "nicUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "tagUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricTagCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricTagCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenTags(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString(),
@@ -80,16 +78,17 @@
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricTagCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricTagCmd.getResponseObject());
+        listTungstenFabricTagCmd.execute();
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricTagCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
@@ -100,8 +99,9 @@
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricTagCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricTagCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricTagCmd.getResponseObject();
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagTypeCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagTypeCmdTest.java
index 1eeb867..943ac18 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagTypeCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricTagTypeCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,64 +29,63 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricTagTypeCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricTagTypeCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricTagTypeCmd listTungstenFabricTagTypeCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricTagTypeCmd = new ListTungstenFabricTagTypeCmd();
         listTungstenFabricTagTypeCmd.tungstenService = tungstenService;
-        listTungstenFabricTagTypeCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricTagTypeCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricTagTypeCmd, "tagTypeUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricTagTypeCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricTagTypeCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricTagTypeCmd, "tagTypeUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricTagTypeCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricTagTypeCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricTagTypeCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricTagTypeCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricTagTypeCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenTagTypes(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricTagTypeCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricTagTypeCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricTagTypeCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenTagTypes(ArgumentMatchers.anyLong(),
                 ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricTagTypeCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricTagTypeCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricTagTypeCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricVmCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricVmCmdTest.java
index a347ca8..0e85a47 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricVmCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/ListTungstenFabricVmCmdTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package org.apache.cloudstack.network.tungsten.api.command;
 
-import com.cloud.configuration.ConfigurationService;
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,62 +29,61 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ListTungstenFabricVmCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ListTungstenFabricVmCmdTest {
 
     @Mock
     TungstenService tungstenService;
 
-    @Mock
-    ConfigurationService configService;
-
     ListTungstenFabricVmCmd listTungstenFabricVmCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         listTungstenFabricVmCmd = new ListTungstenFabricVmCmd();
         listTungstenFabricVmCmd.tungstenService = tungstenService;
-        listTungstenFabricVmCmd._configService = configService;
-        Mockito.when(configService.getDefaultPageSize()).thenReturn(-1L);
-        listTungstenFabricVmCmd.configure();
-        Whitebox.setInternalState(listTungstenFabricVmCmd, "vmUuid", "test");
-        Whitebox.setInternalState(listTungstenFabricVmCmd, "page", 1);
-        Whitebox.setInternalState(listTungstenFabricVmCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricVmCmd, "vmUuid", "test");
+        ReflectionTestUtils.setField(listTungstenFabricVmCmd, "page", 1);
+        ReflectionTestUtils.setField(listTungstenFabricVmCmd, "pageSize", 10);
+        ReflectionTestUtils.setField(listTungstenFabricVmCmd, "s_maxPageSize", -1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        Whitebox.setInternalState(listTungstenFabricVmCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(listTungstenFabricVmCmd, "zoneId", 1L);
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         Mockito.when(tungstenService.listTungstenVm(ArgumentMatchers.anyLong(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricVmCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricVmCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricVmCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 
     @Test
     public void executeAllZoneTest() throws Exception {
         BaseResponse baseResponse = Mockito.mock(BaseResponse.class);
         List<BaseResponse> baseResponseList = Arrays.asList(baseResponse);
-        ListResponse<BaseResponse> responseList = Mockito.mock(ListResponse.class);
         TungstenProviderVO tungstenProviderVO = Mockito.mock(TungstenProviderVO.class);
         List<TungstenProviderVO> tungstenProviderVOList = Arrays.asList(tungstenProviderVO);
         Mockito.when(tungstenService.getTungstenProviders()).thenReturn(tungstenProviderVOList);
         Mockito.when(tungstenService.listTungstenVm(ArgumentMatchers.anyLong(), ArgumentMatchers.anyString())).thenReturn(baseResponseList);
-        PowerMockito.whenNew(ListResponse.class).withAnyArguments().thenReturn(responseList);
         listTungstenFabricVmCmd.execute();
-        Assert.assertEquals(responseList, listTungstenFabricVmCmd.getResponseObject());
+        ListResponse<BaseResponse> responseList = (ListResponse<BaseResponse>) listTungstenFabricVmCmd.getResponseObject();
+        Assert.assertEquals(baseResponseList, responseList.getResponses());
+        Assert.assertEquals(Integer.valueOf(1), responseList.getCount());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmdTest.java
index cb7aa9b..6598a3a 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmdTest.java
@@ -24,18 +24,22 @@
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
+@RunWith(MockitoJUnitRunner.class)
 public class RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmdTest {
 
     @Mock
@@ -43,15 +47,22 @@
 
     RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmd removeTungstenFabricNetworkGatewayFromLogicalRouterCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         removeTungstenFabricNetworkGatewayFromLogicalRouterCmd =
                 new RemoveTungstenFabricNetworkGatewayFromLogicalRouterCmd();
         removeTungstenFabricNetworkGatewayFromLogicalRouterCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(removeTungstenFabricNetworkGatewayFromLogicalRouterCmd, "zoneId", 1L);
-        Whitebox.setInternalState(removeTungstenFabricNetworkGatewayFromLogicalRouterCmd, "networkUuid", "test");
-        Whitebox.setInternalState(removeTungstenFabricNetworkGatewayFromLogicalRouterCmd, "logicalRouterUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricNetworkGatewayFromLogicalRouterCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(removeTungstenFabricNetworkGatewayFromLogicalRouterCmd, "networkUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricNetworkGatewayFromLogicalRouterCmd, "logicalRouterUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyCmdTest.java
index f449104..abd72d3 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricPolicyResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class RemoveTungstenFabricPolicyCmdTest {
 
     @Mock
@@ -40,14 +44,21 @@
 
     RemoveTungstenFabricPolicyCmd removeTungstenFabricPolicyCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         removeTungstenFabricPolicyCmd = new RemoveTungstenFabricPolicyCmd();
         removeTungstenFabricPolicyCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(removeTungstenFabricPolicyCmd, "zoneId", 1L);
-        Whitebox.setInternalState(removeTungstenFabricPolicyCmd, "networkUuid", "test");
-        Whitebox.setInternalState(removeTungstenFabricPolicyCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricPolicyCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(removeTungstenFabricPolicyCmd, "networkUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricPolicyCmd, "policyUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyRuleCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyRuleCmdTest.java
index bb9acee..633c13f 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyRuleCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricPolicyRuleCmdTest.java
@@ -24,15 +24,19 @@
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricPolicyResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+@RunWith(MockitoJUnitRunner.class)
 public class RemoveTungstenFabricPolicyRuleCmdTest {
 
     @Mock
@@ -40,14 +44,21 @@
 
     RemoveTungstenFabricPolicyRuleCmd removeTungstenFabricPolicyRuleCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         removeTungstenFabricPolicyRuleCmd = new RemoveTungstenFabricPolicyRuleCmd();
         removeTungstenFabricPolicyRuleCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(removeTungstenFabricPolicyRuleCmd, "zoneId", 1L);
-        Whitebox.setInternalState(removeTungstenFabricPolicyRuleCmd, "policyUuid", "test");
-        Whitebox.setInternalState(removeTungstenFabricPolicyRuleCmd, "ruleUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricPolicyRuleCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(removeTungstenFabricPolicyRuleCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricPolicyRuleCmd, "ruleUuid", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricTagCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricTagCmdTest.java
index 399f80c..c03eab4 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricTagCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/RemoveTungstenFabricTagCmdTest.java
@@ -27,14 +27,17 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 
+@RunWith(MockitoJUnitRunner.class)
 public class RemoveTungstenFabricTagCmdTest {
 
     @Mock
@@ -42,18 +45,24 @@
 
     RemoveTungstenFabricTagCmd removeTungstenFabricTagCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         removeTungstenFabricTagCmd = new RemoveTungstenFabricTagCmd();
         removeTungstenFabricTagCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "zoneId", 1L);
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "networkUuids", Arrays.asList("test"));
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "vmUuids", Arrays.asList("test"));
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "nicUuids", Arrays.asList("test"));
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "policyUuid", "test");
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "applicationPolicySetUuid", "test");
-        Whitebox.setInternalState(removeTungstenFabricTagCmd, "tagUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "zoneId", 1L);
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "networkUuids", Arrays.asList("test"));
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "vmUuids", Arrays.asList("test"));
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "nicUuids", Arrays.asList("test"));
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "policyUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "applicationPolicySetUuid", "test");
+        ReflectionTestUtils.setField(removeTungstenFabricTagCmd, "tagUuid", "test");
+    }
+
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/SynchronizeTungstenFabricDataCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/SynchronizeTungstenFabricDataCmdTest.java
index dfd6477..ac7b1cd 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/SynchronizeTungstenFabricDataCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/SynchronizeTungstenFabricDataCmdTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.cloudstack.api.response.SuccessResponse;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -26,13 +27,10 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(SynchronizeTungstenFabricDataCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class SynchronizeTungstenFabricDataCmdTest {
 
     @Mock
@@ -40,20 +38,25 @@
 
     SynchronizeTungstenFabricDataCmd synchronizeTungstenFabricDataCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         synchronizeTungstenFabricDataCmd = new SynchronizeTungstenFabricDataCmd();
         synchronizeTungstenFabricDataCmd.tungstenService = tungstenService;
-        Whitebox.setInternalState(synchronizeTungstenFabricDataCmd, "tungstenProviderId", 1L);
+        ReflectionTestUtils.setField(synchronizeTungstenFabricDataCmd, "tungstenProviderId", 1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void executeTest() throws Exception {
-        SuccessResponse successResponse = Mockito.mock(SuccessResponse.class);
         Mockito.when(tungstenService.synchronizeTungstenData(ArgumentMatchers.anyLong())).thenReturn(true);
-        PowerMockito.whenNew(SuccessResponse.class).withAnyArguments().thenReturn(successResponse);
         synchronizeTungstenFabricDataCmd.execute();
-        Assert.assertEquals(successResponse, synchronizeTungstenFabricDataCmd.getResponseObject());
+        Assert.assertTrue(((SuccessResponse) synchronizeTungstenFabricDataCmd.getResponseObject()).getSuccess());
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/UpdateTungstenFabricLBHealthMonitorCmdTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/UpdateTungstenFabricLBHealthMonitorCmdTest.java
index 7f0919f..f5c2e5f 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/UpdateTungstenFabricLBHealthMonitorCmdTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/api/command/UpdateTungstenFabricLBHealthMonitorCmdTest.java
@@ -24,6 +24,7 @@
 import org.apache.cloudstack.network.tungsten.api.response.TungstenFabricLBHealthMonitorResponse;
 import org.apache.cloudstack.network.tungsten.dao.TungstenFabricLBHealthMonitorVO;
 import org.apache.cloudstack.network.tungsten.service.TungstenService;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -32,15 +33,12 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Optional;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(UpdateTungstenFabricLBHealthMonitorCmd.class)
+@RunWith(MockitoJUnitRunner.class)
 public class UpdateTungstenFabricLBHealthMonitorCmdTest {
     @Mock
     EntityManager entityManager;
@@ -49,20 +47,27 @@
 
     UpdateTungstenFabricLBHealthMonitorCmd updateTungstenFabricLBHealthMonitorCmd;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         updateTungstenFabricLBHealthMonitorCmd = new UpdateTungstenFabricLBHealthMonitorCmd();
         updateTungstenFabricLBHealthMonitorCmd.tungstenService = tungstenService;
         updateTungstenFabricLBHealthMonitorCmd._entityMgr = entityManager;
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "lbId", 1L);
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "type", "HTTP");
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "retry", 1);
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "timeout", 1);
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "interval", 1);
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "httpMethod", "GET");
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "expectedCode", "test");
-        Whitebox.setInternalState(updateTungstenFabricLBHealthMonitorCmd, "urlPath", "test");
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "lbId", 1L);
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "type", "HTTP");
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "retry", 1);
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "timeout", 1);
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "interval", 1);
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "httpMethod", "GET");
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "expectedCode", "test");
+        ReflectionTestUtils.setField(updateTungstenFabricLBHealthMonitorCmd, "urlPath", "test");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -83,13 +88,11 @@
     @Test
     public void executeTest() throws Exception {
         updateTungstenFabricLBHealthMonitorCmd.setEntityId(1L);
-        TungstenFabricLBHealthMonitorResponse tungstenFabricLBHealthMonitorResponse =
-                Mockito.mock(TungstenFabricLBHealthMonitorResponse.class);
         TungstenFabricLBHealthMonitorVO tungstenFabricLBHealthMonitorVO =
                 Mockito.mock(TungstenFabricLBHealthMonitorVO.class);
         tungstenFabricLBHealthMonitorVO.setType("test");
-        Whitebox.setInternalState(tungstenFabricLBHealthMonitorVO, "id", 1L);
-        Whitebox.setInternalState(tungstenFabricLBHealthMonitorVO, "uuid", "test");
+        ReflectionTestUtils.setField(tungstenFabricLBHealthMonitorVO, "id", 1L);
+        ReflectionTestUtils.setField(tungstenFabricLBHealthMonitorVO, "uuid", "test");
         tungstenFabricLBHealthMonitorVO.setRetry(1);
         tungstenFabricLBHealthMonitorVO.setTimeout(1);
         tungstenFabricLBHealthMonitorVO.setInterval(1);
@@ -106,9 +109,22 @@
         Mockito.when(entityManager.findById(ArgumentMatchers.eq(Network.class), ArgumentMatchers.anyLong())).thenReturn(network);
         Mockito.when(entityManager.findById(ArgumentMatchers.eq(DataCenter.class), ArgumentMatchers.anyLong())).thenReturn(dataCenter);
         Mockito.when(tungstenService.applyLBHealthMonitor(ArgumentMatchers.anyLong())).thenReturn(true);
-        PowerMockito.whenNew(TungstenFabricLBHealthMonitorResponse.class).withAnyArguments().thenReturn(tungstenFabricLBHealthMonitorResponse);
         updateTungstenFabricLBHealthMonitorCmd.execute();
-        Assert.assertEquals(tungstenFabricLBHealthMonitorResponse,
-                updateTungstenFabricLBHealthMonitorCmd.getResponseObject());
+        TungstenFabricLBHealthMonitorResponse tungstenFabricLBHealthMonitorResponse =
+                (TungstenFabricLBHealthMonitorResponse) updateTungstenFabricLBHealthMonitorCmd.getResponseObject();
+
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getType(), tungstenFabricLBHealthMonitorResponse.getType());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getId(), tungstenFabricLBHealthMonitorResponse.getId());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getUuid(), tungstenFabricLBHealthMonitorResponse.getUuid());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getRetry(), tungstenFabricLBHealthMonitorResponse.getRetry());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getTimeout(), tungstenFabricLBHealthMonitorResponse.getTimeout());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getInterval(), tungstenFabricLBHealthMonitorResponse.getInterval());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getHttpMethod(), tungstenFabricLBHealthMonitorResponse.getHttpMethod());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getExpectedCode(), tungstenFabricLBHealthMonitorResponse.getExpectedCode());
+        Assert.assertEquals(tungstenFabricLBHealthMonitorVO.getUrlPath(), tungstenFabricLBHealthMonitorResponse.getUrlPath());
+
+        Assert.assertEquals(dataCenter.getId(), tungstenFabricLBHealthMonitorResponse.getZoneId());
+        Assert.assertEquals(dataCenter.getName(), tungstenFabricLBHealthMonitorResponse.getZoneName());
+
     }
 }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/resource/TungstenResourceTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/resource/TungstenResourceTest.java
index bd0b335..af46146 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/resource/TungstenResourceTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/resource/TungstenResourceTest.java
@@ -31,8 +31,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
+
 
 import net.juniper.tungsten.api.ApiObjectBase;
 import net.juniper.tungsten.api.ApiPropertyBase;
@@ -159,15 +158,17 @@
 import org.apache.cloudstack.network.tungsten.service.TungstenApi;
 import org.apache.cloudstack.network.tungsten.service.TungstenVRouterApi;
 import org.apache.cloudstack.network.tungsten.vrouter.Port;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -177,27 +178,36 @@
 
 import javax.naming.ConfigurationException;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({TungstenVRouterApi.class, TungstenResource.class, TungstenNetworkPolicy.class})
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenResourceTest {
     @Mock
     TungstenApi tungstenApi;
 
     TungstenResource tungstenResource;
 
+    MockedStatic<TungstenVRouterApi> tungstenVRouterApiMocked;
+
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         tungstenResource = new TungstenResource();
         tungstenResource.tungstenApi = tungstenApi;
-        Whitebox.setInternalState(tungstenResource, "vrouterPort", "9091");
-        mockStatic(TungstenVRouterApi.class);
+        ReflectionTestUtils.setField(tungstenResource, "vrouterPort", "9091");
+        tungstenVRouterApiMocked = Mockito.mockStatic(TungstenVRouterApi.class);
 
         Project project = mock(Project.class);
         when(project.getUuid()).thenReturn("065eab99-b819-4f3f-8e97-99c2ab22e6ed");
         when(tungstenApi.getTungstenProjectByFqn(any())).thenReturn(project);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+        tungstenVRouterApiMocked.close();
+    }
+
     @Test
     public void configureTest() throws ConfigurationException {
         Map<String, Object> map = new HashMap<>();
@@ -307,7 +317,9 @@
             anyString(), anyString(), anyBoolean())).thenReturn(virtualMachineInterface);
         when(tungstenApi.createTungstenInstanceIp(anyString(), anyString(), anyString(), anyString())).thenReturn(
             instanceIp);
-        when(TungstenVRouterApi.addTungstenVrouterPort(anyString(), anyString(), any(Port.class))).thenReturn(true);
+        tungstenVRouterApiMocked.when(
+                () -> TungstenVRouterApi.addTungstenVrouterPort(anyString(), anyString(), any(Port.class))
+                                     ).thenReturn(true);
 
         TungstenAnswer answer = (TungstenAnswer) tungstenResource.executeRequest(command);
         assertTrue(answer.getResult());
@@ -444,7 +456,9 @@
         TungstenCommand command = new DeleteTungstenVRouterPortCommand("10.0.0.10",
             "b604c7f7-1dbc-42d8-bceb-2c0898034a7a");
 
-        when(TungstenVRouterApi.deleteTungstenVrouterPort(anyString(), anyString(), anyString())).thenReturn(true);
+        tungstenVRouterApiMocked.when(
+                () -> TungstenVRouterApi.deleteTungstenVrouterPort(anyString(), anyString(), anyString())
+                                     ).thenReturn(true);
 
         TungstenAnswer answer = (TungstenAnswer) tungstenResource.executeRequest(command);
         assertTrue(answer.getResult());
@@ -543,10 +557,7 @@
         VirtualNetwork virtualNetwork1 = mock(VirtualNetwork.class);
         VirtualNetwork virtualNetwork2 = mock(VirtualNetwork.class);
         List<VirtualNetwork> virtualNetworkList = Arrays.asList(virtualNetwork1, virtualNetwork2);
-        TungstenNetworkPolicy tungstenNetworkPolicy = mock(TungstenNetworkPolicy.class);
 
-        whenNew(TungstenNetworkPolicy.class).withArguments(networkPolicy, virtualNetworkList)
-            .thenReturn(tungstenNetworkPolicy);
         when(networkPolicy.getUuid()).thenReturn("ac617be6-bf80-4086-9d6a-c05ff78e2264");
         when(tungstenApi.getTungstenObjectByName(eq(NetworkPolicy.class), any(), anyString())).thenReturn(
             networkPolicy);
@@ -556,7 +567,7 @@
 
         TungstenAnswer answer = (TungstenAnswer) tungstenResource.executeRequest(command);
         assertTrue(answer.getResult());
-        assertEquals(tungstenNetworkPolicy, answer.getTungstenModel());
+        assertEquals(networkPolicy, ((TungstenNetworkPolicy) answer.getTungstenModel()).getNetworkPolicy());
     }
 
     @Test
@@ -647,8 +658,6 @@
         when(loadbalancerListener.getUuid()).thenReturn("c877d37a-9ad4-4188-a09a-fb13f57f9be0");
         when(loadbalancerPool.getUuid()).thenReturn("baf714fa-80a1-454f-9c32-c4d4a6f5c5a4");
         when(tungstenApi.getTungstenObject(eq(VirtualNetwork.class), anyString())).thenReturn(virtualNetwork);
-        when(tungstenApi.getTungstenObjectByName(eq(FloatingIpPool.class), any(), anyString())).thenReturn(
-            floatingIpPool);
         when(tungstenApi.getSubnetUuid(anyString())).thenReturn("b604c7f7-1dbc-42d8-bceb-2c0898034a7a");
         when(tungstenApi.createTungstenLbVmi(anyString(), anyString(), anyString())).thenReturn(
             virtualMachineInterface);
@@ -952,8 +961,6 @@
         VirtualNetwork virtualNetwork1 = mock(VirtualNetwork.class);
         VirtualNetwork virtualNetwork2 = mock(VirtualNetwork.class);
         List<VirtualNetwork> virtualNetworks = Arrays.asList(virtualNetwork1, virtualNetwork2);
-        when(tungstenApi.listTungstenNetworkPolicy(anyString(), anyString())).thenAnswer(networkPoliciesAnswer);
-        when(tungstenApi.getNetworksFromNetworkPolicy(any(NetworkPolicy.class))).thenReturn(virtualNetworks);
         TungstenAnswer answer = (TungstenAnswer) tungstenResource.executeRequest(command);
         assertTrue(answer.getResult());
         assertNotNull(answer.getTungstenModelList());
@@ -1383,10 +1390,6 @@
         VirtualNetwork virtualNetwork1 = mock(VirtualNetwork.class);
         VirtualNetwork virtualNetwork2 = mock(VirtualNetwork.class);
         Answer<List<ApiObjectBase>> virtualNetworksAnswer = setupApiObjectBaseListAnswer(virtualNetwork1, virtualNetwork2);
-        when(virtualNetwork1.getUuid()).thenReturn("fe79e06a-1142-11ec-82a8-0242ac130003");
-        when(virtualNetwork2.getUuid()).thenReturn("efffa88c-1145-11ec-82a8-0242ac130003");
-        when(tungstenApi.getTungstenObject(eq(LogicalRouter.class), anyString())).thenReturn(logicalRouter);
-        when(tungstenApi.listConnectedNetworkFromLogicalRouter(any(LogicalRouter.class))).thenAnswer(virtualNetworksAnswer);
         when(tungstenApi.addNetworkGatewayToLogicalRouter(anyString(), anyString(), anyString())).thenReturn(logicalRouter);
         when(tungstenApi.listConnectedNetworkFromLogicalRouter(any(LogicalRouter.class))).thenAnswer(virtualNetworksAnswer);
         TungstenAnswer answer = (TungstenAnswer) tungstenResource.executeRequest(command);
@@ -1445,10 +1448,6 @@
         VirtualMachineInterface virtualMachineInterface = mock(VirtualMachineInterface.class);
 
         when(tungstenApi.getTungstenObject(eq(LogicalRouter.class), anyString())).thenReturn(logicalRouter);
-        when(logicalRouter.getVirtualMachineInterface()).thenReturn(List.of(objectReference));
-        when(tungstenApi.updateTungstenObject(any(LogicalRouter.class))).thenReturn(true);
-        when(tungstenApi.getTungstenObject(eq(VirtualMachineInterface.class), anyString())).thenReturn(virtualMachineInterface);
-        when(tungstenApi.deleteTungstenVmInterface(any(VirtualMachineInterface.class))).thenReturn(true);
         when(tungstenApi.deleteTungstenObject(any(LogicalRouter.class))).thenReturn(true);
 
         TungstenAnswer answer = (TungstenAnswer) tungstenResource.executeRequest(command);
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/MockTungstenAnswerFactory.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/MockTungstenAnswerFactory.java
new file mode 100644
index 0000000..587a01d
--- /dev/null
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/MockTungstenAnswerFactory.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.network.tungsten.service;
+
+import org.apache.cloudstack.network.tungsten.agent.api.TungstenAnswer;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MockTungstenAnswerFactory {
+
+    TungstenAnswer tungstenAnswer;
+    MockTungstenAnswerFactory(boolean returnResult){
+        tungstenAnswer=mock(TungstenAnswer.class);
+        when(tungstenAnswer.getResult()).thenReturn(returnResult);
+    }
+    TungstenAnswer get() {
+        return tungstenAnswer;
+    }
+    public static TungstenAnswer get(boolean returnResult){
+        TungstenAnswer tungstenAnswers = new MockTungstenAnswerFactory(returnResult).get();
+        return tungstenAnswers;
+    }
+}
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenApiTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenApiTest.java
index 5bd9221..580bea0 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenApiTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenApiTest.java
@@ -58,6 +58,8 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -66,6 +68,7 @@
 import java.util.List;
 import java.util.UUID;
 
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenApiTest {
 
     private static final Logger s_logger = Logger.getLogger(TungstenApiTest.class);
@@ -1357,6 +1360,7 @@
 
         s_logger.debug("Check if policy was listed all in Tungsten-Fabric");
         List<? extends ApiObjectBase> policyList3 = tungstenApi.listTungstenPolicy(projectUuid, null);
+        policyList3.sort(comparator);
         assertEquals(policyList1, policyList3);
 
         s_logger.debug("Check if policy was listed with uuid in Tungsten-Fabric");
@@ -1380,6 +1384,7 @@
 
         s_logger.debug("Check if network was listed all in Tungsten-Fabric");
         List<? extends ApiObjectBase> networkList3 = tungstenApi.listTungstenNetwork(projectUuid, null);
+        networkList3.sort(comparator);
         assertEquals(networkList1, networkList3);
 
         s_logger.debug("Check if network policy was listed with uuid in Tungsten-Fabric");
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenElementTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenElementTest.java
index bad5669..58084d3 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenElementTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenElementTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.api.ApiDBUtils;
@@ -110,24 +109,24 @@
 import org.apache.cloudstack.network.tungsten.agent.api.SetupTungstenVRouterCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.TungstenAnswer;
 import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerHealthMonitorCommand;
-import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerListenerCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerMemberCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerPoolCommand;
 import org.apache.cloudstack.network.tungsten.dao.TungstenFabricLBHealthMonitorDao;
 import org.apache.cloudstack.network.tungsten.dao.TungstenFabricLBHealthMonitorVO;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ApiDBUtils.class, EncryptionUtil.class})
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenElementTest {
     @Mock
     TungstenFabricUtils tungstenFabricUtils;
@@ -182,9 +181,15 @@
 
     TungstenElement tungstenElement;
 
+    MockedStatic<ApiDBUtils> apiDBUtilsMocked;
+
+    MockedStatic<EncryptionUtil> encryptionUtilMocked;
+
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         tungstenElement = new TungstenElement();
         tungstenElement.tungstenFabricUtils = tungstenFabricUtils;
         tungstenElement.networkModel = networkModel;
@@ -212,12 +217,19 @@
         tungstenElement.tungstenFabricLBHealthMonitorDao = tungstenFabricLBHealthMonitorDao;
         tungstenElement.loadBalancerCertMapDao = loadBalancerCertMapDao;
 
-        mockStatic(ApiDBUtils.class);
-        mockStatic(EncryptionUtil.class);
+        apiDBUtilsMocked = Mockito.mockStatic(ApiDBUtils.class);
+        encryptionUtilMocked = Mockito.mockStatic(EncryptionUtil.class);
 
         when(tungstenService.getTungstenProjectFqn(any())).thenReturn("default-domain:default-project");
     }
 
+    @After
+    public void tearDown() throws Exception {
+        apiDBUtilsMocked.close();
+        encryptionUtilMocked.close();
+        closeable.close();
+    }
+
     @Test
     public void canHandleSuccessTest() {
         Network network = mock(Network.class);
@@ -244,13 +256,12 @@
         StaticNatImpl staticNat = mock(StaticNatImpl.class);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
-        TungstenAnswer assignFloatingIpAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer assignFloatingIpAnswer = MockTungstenAnswerFactory.get(true);
         Nic nic = mock(Nic.class);
         Network publicNetwork = mock(Network.class);
         List<StaticNatImpl> staticNatList = List.of(staticNat);
 
         when(staticNat.isForRevoke()).thenReturn(false);
-        when(assignFloatingIpAnswer.getResult()).thenReturn(true);
         when(ipAddressDao.findByIdIncludingRemoved(anyLong())).thenReturn(ipAddressVO);
         when(vmInstanceDao.findByIdIncludingRemoved(anyLong())).thenReturn(vmInstanceVO);
         when(networkModel.getNicInNetworkIncludingRemoved(anyLong(), anyLong())).thenReturn(nic);
@@ -266,13 +277,12 @@
         StaticNatImpl staticNat = mock(StaticNatImpl.class);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
-        TungstenAnswer assignFloatingIpAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer assignFloatingIpAnswer = MockTungstenAnswerFactory.get(false);
         Nic nic = mock(Nic.class);
         Network publicNetwork = mock(Network.class);
         List<StaticNatImpl> staticNatList = List.of(staticNat);
 
         when(staticNat.isForRevoke()).thenReturn(false);
-        when(assignFloatingIpAnswer.getResult()).thenReturn(false);
         when(ipAddressDao.findByIdIncludingRemoved(anyLong())).thenReturn(ipAddressVO);
         when(vmInstanceDao.findByIdIncludingRemoved(anyLong())).thenReturn(vmInstanceVO);
         when(networkModel.getNicInNetworkIncludingRemoved(anyLong(), anyLong())).thenReturn(nic);
@@ -288,13 +298,12 @@
         StaticNatImpl staticNat = mock(StaticNatImpl.class);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
-        TungstenAnswer releaseFloatingIpAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer releaseFloatingIpAnswer = MockTungstenAnswerFactory.get(true);
         Nic nic = mock(Nic.class);
         Network publicNetwork = mock(Network.class);
         List<StaticNatImpl> staticNatList = List.of(staticNat);
 
         when(staticNat.isForRevoke()).thenReturn(true);
-        when(releaseFloatingIpAnswer.getResult()).thenReturn(true);
         when(ipAddressDao.findByIdIncludingRemoved(anyLong())).thenReturn(ipAddressVO);
         when(vmInstanceDao.findByIdIncludingRemoved(anyLong())).thenReturn(vmInstanceVO);
         when(networkModel.getNicInNetworkIncludingRemoved(anyLong(), anyLong())).thenReturn(nic);
@@ -310,13 +319,12 @@
         StaticNatImpl staticNat = mock(StaticNatImpl.class);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
-        TungstenAnswer releaseFloatingIpAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer releaseFloatingIpAnswer = MockTungstenAnswerFactory.get(false);
         Nic nic = mock(Nic.class);
         Network publicNetwork = mock(Network.class);
         List<StaticNatImpl> staticNatList = List.of(staticNat);
 
         when(staticNat.isForRevoke()).thenReturn(true);
-        when(releaseFloatingIpAnswer.getResult()).thenReturn(false);
         when(ipAddressDao.findByIdIncludingRemoved(anyLong())).thenReturn(ipAddressVO);
         when(vmInstanceDao.findByIdIncludingRemoved(anyLong())).thenReturn(vmInstanceVO);
         when(networkModel.getNicInNetworkIncludingRemoved(anyLong(), anyLong())).thenReturn(nic);
@@ -343,18 +351,15 @@
         TungstenFabricLBHealthMonitorVO tungstenFabricLBHealthMonitorVO = mock(TungstenFabricLBHealthMonitorVO.class);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
-        TungstenAnswer createTungstenNetworkLoadbalancerAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenLoadBalancerPoolAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenLoadBalancerMemberAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenLoadBalancerListenerAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenHealthMonitorAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenNetworkLoadbalancerAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenLoadBalancerPoolAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenLoadBalancerMemberAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenLoadBalancerListenerAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenHealthMonitorAnswer = MockTungstenAnswerFactory.get(true);
         LoadBalancingRule.LbSslCert lbSslCert = mock(LoadBalancingRule.LbSslCert.class);
         when(lbStickinessPolicy.getMethodName()).thenReturn("AppCookie");
         List<Pair<String, String>> pairList = List.of(new Pair<>("cookieName", "cookieValue"));
 
-        when(accountMgr.getActiveUser(anyLong())).thenReturn(caller);
-        when(caller.getApiKey()).thenReturn("apikey");
-        when(caller.getSecretKey()).thenReturn("secreatekey");
         when(lbStickinessPolicy.getParams()).thenReturn(pairList);
         when(loadBalancingRule1.getId()).thenReturn(1L);
         when(loadBalancingRule1.getState()).thenReturn(FirewallRule.State.Add);
@@ -363,8 +368,6 @@
         when(loadBalancingRule1.getDefaultPortStart()).thenReturn(443);
         when(loadBalancingRule1.getStickinessPolicies()).thenReturn(lbStickinessPolicyList);
         when(loadBalancingRule1.getSourceIp()).thenReturn(ip);
-        when(loadBalancingRule1.getLbSslCert()).thenReturn(lbSslCert);
-        when(loadBalancingRule1.getUuid()).thenReturn("loadbalancingruleuuid");
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), any())).thenReturn(publicNetwork);
         when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
         when(ipAddressVO.getAddress()).thenReturn(ip);
@@ -372,19 +375,11 @@
         when(tungstenGuestNetworkIpAddressVO.getGuestIpAddress()).thenReturn(ip);
         when(ip.addr()).thenReturn("10.10.10.10");
         when(tungstenGuestNetworkIpAddressDao.findByNetworkIdAndPublicIp(anyLong(), anyString())).thenReturn(tungstenGuestNetworkIpAddressVO);
-        when(ipAddressMgr.acquireGuestIpAddress(any(), any())).thenReturn("192.168.100.100");
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkLoadbalancerCommand.class), anyLong())).thenReturn(createTungstenNetworkLoadbalancerAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerPoolCommand.class), anyLong())).thenReturn(updateTungstenLoadBalancerPoolAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerMemberCommand.class), anyLong())).thenReturn(updateTungstenLoadBalancerMemberAnswer);
-        when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerListenerCommand.class), anyLong())).thenReturn(updateTungstenLoadBalancerListenerAnswer);
-        when(createTungstenNetworkLoadbalancerAnswer.getResult()).thenReturn(true);
-        when(updateTungstenLoadBalancerPoolAnswer.getResult()).thenReturn(true);
-        when(updateTungstenLoadBalancerMemberAnswer.getResult()).thenReturn(true);
-        when(updateTungstenLoadBalancerListenerAnswer.getResult()).thenReturn(true);
-        when(updateTungstenHealthMonitorAnswer.getResult()).thenReturn(true);
         when(configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key())).thenReturn("enabled");
         when(tungstenService.updateLoadBalancer(any(), any())).thenReturn(true);
-        when(lbDao.listByIpAddress(anyLong())).thenReturn(loadBalancerVOList);
         when(EncryptionUtil.generateSignature(anyString(), anyString())).thenReturn("generatedString");
         when(tungstenFabricLBHealthMonitorDao.findByLbId(anyLong())).thenReturn(tungstenFabricLBHealthMonitorVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerHealthMonitorCommand.class), anyLong())).thenReturn(updateTungstenHealthMonitorAnswer);
@@ -407,10 +402,10 @@
         List<LoadBalancerVO> loadBalancerVOList = List.of(loadBalancerVO);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
-        TungstenAnswer createTungstenNetworkLoadbalancerAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenLoadBalancerPoolAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenLoadBalancerMemberAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenHealthMonitorAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenNetworkLoadbalancerAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenLoadBalancerPoolAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenLoadBalancerMemberAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenHealthMonitorAnswer = MockTungstenAnswerFactory.get(true);
         List<Pair<String, String>> pairList = List.of(new Pair<>("cookieName", "cookieValue"));
         TungstenFabricLBHealthMonitorVO tungstenFabricLBHealthMonitorVO = mock(TungstenFabricLBHealthMonitorVO.class);
 
@@ -427,20 +422,13 @@
         when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
         when(ipAddressVO.getAddress()).thenReturn(ip);
         when(lbVmMapDao.listByLoadBalancerId(anyLong(), anyBoolean())).thenReturn(loadBalancerVMMapVOList);
-        when(tungstenGuestNetworkIpAddressVO.getGuestIpAddress()).thenReturn(ip);
         when(ip.addr()).thenReturn("10.10.10.10");
         when(ipAddressMgr.acquireGuestIpAddress(any(), any())).thenReturn("192.168.100.100");
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkLoadbalancerCommand.class), anyLong())).thenReturn(createTungstenNetworkLoadbalancerAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerPoolCommand.class), anyLong())).thenReturn(updateTungstenLoadBalancerPoolAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerMemberCommand.class), anyLong())).thenReturn(updateTungstenLoadBalancerMemberAnswer);
-        when(createTungstenNetworkLoadbalancerAnswer.getResult()).thenReturn(true);
-        when(updateTungstenLoadBalancerPoolAnswer.getResult()).thenReturn(true);
-        when(updateTungstenLoadBalancerMemberAnswer.getResult()).thenReturn(true);
         when(configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key())).thenReturn("disabled");
-        when(tungstenService.updateLoadBalancerSsl(any(), any())).thenReturn(false);
-        when(lbDao.listByIpAddress(anyLong())).thenReturn(loadBalancerVOList);
         when(tungstenFabricLBHealthMonitorDao.findByLbId(anyLong())).thenReturn(tungstenFabricLBHealthMonitorVO);
-        when(updateTungstenHealthMonitorAnswer.getResult()).thenReturn(true);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenLoadBalancerHealthMonitorCommand.class), anyLong())).thenReturn(updateTungstenHealthMonitorAnswer);
 
         assertFalse(tungstenElement.applyLBRules(network, loadBalancingRuleList1));
@@ -457,8 +445,8 @@
         List<LoadBalancingRule> loadBalancingRuleList1 = List.of(loadBalancingRule1);
         List<LoadBalancerVO> loadBalancerVOList1 = List.of(loadBalancerVO1);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
-        TungstenAnswer deleteTungstenLoadBalancerListenerAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer deleteTungstenLoadBalancerCommand = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenLoadBalancerListenerAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer deleteTungstenLoadBalancerCommand = MockTungstenAnswerFactory.get(true);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), any())).thenReturn(publicNetwork);
         when(loadBalancingRule1.getSourceIp()).thenReturn(ip1);
@@ -466,11 +454,7 @@
         when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
         when(ipAddressVO.getAddress()).thenReturn(ip1);
         when(ip1.addr()).thenReturn("10.10.10.10");
-        when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenLoadBalancerListenerCommand.class), anyLong())).thenReturn(deleteTungstenLoadBalancerListenerAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenLoadBalancerCommand.class), anyLong())).thenReturn(deleteTungstenLoadBalancerCommand);
-        when(deleteTungstenLoadBalancerListenerAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenLoadBalancerCommand.getResult()).thenReturn(true);
-        when(tungstenService.updateLoadBalancerSsl(any(), any())).thenReturn(false);
         when(lbDao.listByIpAddress(anyLong())).thenReturn(loadBalancerVOList1);
         when(tungstenGuestNetworkIpAddressDao.findByNetworkIdAndPublicIp(anyLong(),anyString())).thenReturn(tungstenGuestNetworkIpAddressVO);
         when(tungstenGuestNetworkIpAddressDao.remove(anyLong())).thenReturn(false);
@@ -488,8 +472,8 @@
         LoadBalancerVO loadBalancerVO1 = mock(LoadBalancerVO.class);
         LoadBalancerVO loadBalancerVO2 = mock(LoadBalancerVO.class);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
-        TungstenAnswer deleteTungstenLoadBalancerListenerAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer deleteTungstenLoadBalancerCommand = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenLoadBalancerListenerAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer deleteTungstenLoadBalancerCommand = MockTungstenAnswerFactory.get(true);
         List<LoadBalancingRule> loadBalancingRuleList = List.of(loadBalancingRule);
         List<LoadBalancerVO> loadBalancerVOList = Arrays.asList(loadBalancerVO1, loadBalancerVO2);
 
@@ -497,15 +481,10 @@
         when(loadBalancingRule.getSourceIp()).thenReturn(ip);
         when(loadBalancingRule.getState()).thenReturn(FirewallRule.State.Revoke);
         when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
-        when(ipAddressVO.getAddress()).thenReturn(ip);
         when(ip.addr()).thenReturn("10.10.10.10");
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenLoadBalancerListenerCommand.class), anyLong())).thenReturn(deleteTungstenLoadBalancerListenerAnswer);
-        when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenLoadBalancerCommand.class), anyLong())).thenReturn(deleteTungstenLoadBalancerCommand);
-        when(deleteTungstenLoadBalancerListenerAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenLoadBalancerCommand.getResult()).thenReturn(true);
         when(tungstenService.updateLoadBalancer(any(), any())).thenReturn(true);
         when(lbDao.listByIpAddress(anyLong())).thenReturn(loadBalancerVOList);
-        when(tungstenGuestNetworkIpAddressDao.findByNetworkIdAndPublicIp(anyLong(),anyString())).thenReturn(tungstenGuestNetworkIpAddressVO);
 
         assertTrue(tungstenElement.applyLBRules(network, loadBalancingRuleList));
     }
@@ -518,7 +497,7 @@
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         UserVm userVm = mock(UserVm.class);
         Nic nic = mock(Nic.class);
-        TungstenAnswer applyTungstenPortForwardingAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer applyTungstenPortForwardingAnswer = MockTungstenAnswerFactory.get(true);
         List<PortForwardingRule> portForwardingRuleList = List.of(portForwardingRule);
 
         when(portForwardingRule.getState()).thenReturn(FirewallRule.State.Add);
@@ -527,7 +506,6 @@
         when(ApiDBUtils.findUserVmById(anyLong())).thenReturn(userVm);
         when(networkModel.getNicInNetwork(anyLong(), anyLong())).thenReturn(nic);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenPortForwardingCommand.class), anyLong())).thenReturn(applyTungstenPortForwardingAnswer);
-        when(applyTungstenPortForwardingAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.applyPFRules(network, portForwardingRuleList));
     }
@@ -540,7 +518,7 @@
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         UserVm userVm = mock(UserVm.class);
         Nic nic = mock(Nic.class);
-        TungstenAnswer applyTungstenPortForwardingAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer applyTungstenPortForwardingAnswer = MockTungstenAnswerFactory.get(false);
         List<PortForwardingRule> portForwardingRuleList = List.of(portForwardingRule);
 
         when(portForwardingRule.getState()).thenReturn(FirewallRule.State.Add);
@@ -549,7 +527,6 @@
         when(ApiDBUtils.findUserVmById(anyLong())).thenReturn(userVm);
         when(networkModel.getNicInNetwork(anyLong(), anyLong())).thenReturn(nic);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenPortForwardingCommand.class), anyLong())).thenReturn(applyTungstenPortForwardingAnswer);
-        when(applyTungstenPortForwardingAnswer.getResult()).thenReturn(false);
 
         assertFalse(tungstenElement.applyPFRules(network, portForwardingRuleList));
     }
@@ -562,7 +539,7 @@
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         UserVm userVm = mock(UserVm.class);
         Nic nic = mock(Nic.class);
-        TungstenAnswer applyTungstenPortForwardingAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer applyTungstenPortForwardingAnswer = MockTungstenAnswerFactory.get(true);
         List<PortForwardingRule> portForwardingRuleList = List.of(portForwardingRule);
 
         when(portForwardingRule.getState()).thenReturn(FirewallRule.State.Revoke);
@@ -571,7 +548,6 @@
         when(ApiDBUtils.findUserVmById(anyLong())).thenReturn(userVm);
         when(networkModel.getNicInNetwork(anyLong(), anyLong())).thenReturn(nic);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenPortForwardingCommand.class), anyLong())).thenReturn(applyTungstenPortForwardingAnswer);
-        when(applyTungstenPortForwardingAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.applyPFRules(network, portForwardingRuleList));
     }
@@ -584,7 +560,7 @@
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         UserVm userVm = mock(UserVm.class);
         Nic nic = mock(Nic.class);
-        TungstenAnswer applyTungstenPortForwardingAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer applyTungstenPortForwardingAnswer = MockTungstenAnswerFactory.get(false);
         List<PortForwardingRule> portForwardingRuleList = List.of(portForwardingRule);
 
         when(portForwardingRule.getState()).thenReturn(FirewallRule.State.Revoke);
@@ -593,7 +569,6 @@
         when(ApiDBUtils.findUserVmById(anyLong())).thenReturn(userVm);
         when(networkModel.getNicInNetwork(anyLong(), anyLong())).thenReturn(nic);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenPortForwardingCommand.class), anyLong())).thenReturn(applyTungstenPortForwardingAnswer);
-        when(applyTungstenPortForwardingAnswer.getResult()).thenReturn(false);
 
         assertFalse(tungstenElement.applyPFRules(network, portForwardingRuleList));
     }
@@ -607,9 +582,9 @@
         ReservationContext reservationContext = mock(ReservationContext.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
         HostVO host = mock(HostVO.class);
-        TungstenAnswer createTungstenVMAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenVMAnswer = MockTungstenAnswerFactory.get(true);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
-        TungstenAnswer createTungstenNetworkPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
 
         nicProfile.setIPv4Address("192.168.100.100");
         when(network.getTrafficType()).thenReturn(Networks.TrafficType.Public);
@@ -619,8 +594,6 @@
         when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(createTungstenNetworkPolicyAnswer);
         when(virtualMachineProfile.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
-        when(createTungstenVMAnswer.getResult()).thenReturn(true);
-        when(createTungstenNetworkPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.prepare(network, nicProfile, virtualMachineProfile, deployDestination, reservationContext));
         assertEquals(Nic.ReservationStrategy.Create, nicProfile.getReservationStrategy());
@@ -638,7 +611,7 @@
         ReservationContext reservationContext = mock(ReservationContext.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
         HostVO host = mock(HostVO.class);
-        TungstenAnswer createTungstenVMAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenVMAnswer = MockTungstenAnswerFactory.get(true);
 
         nicProfile.setIPv4Address("192.168.100.100");
         when(network.getTrafficType()).thenReturn(Networks.TrafficType.Management);
@@ -646,7 +619,6 @@
         when(hostDao.findById(anyLong())).thenReturn(host);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenVirtualMachineCommand.class), anyLong())).thenReturn(createTungstenVMAnswer);
         when(virtualMachineProfile.getType()).thenReturn(VirtualMachine.Type.SecondaryStorageVm);
-        when(createTungstenVMAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.prepare(network, nicProfile, virtualMachineProfile, deployDestination, reservationContext));
         assertEquals(Nic.ReservationStrategy.Create, nicProfile.getReservationStrategy());
@@ -700,10 +672,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVmInterfaceCommand.class), anyLong())).thenReturn(deleteVmiAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVmCommand.class), anyLong())).thenReturn(deleteVmAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(deleteTungstenNetworkPolicyAnswer);
-        when(deleteTungstenVRouterPortAnswer.getResult()).thenReturn(true);
-        when(deleteVmiAnswer.getResult()).thenReturn(true);
-        when(deleteVmAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenNetworkPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.release(network, nicProfile, virtualMachineProfile, reservationContext));
     }
@@ -720,7 +688,6 @@
         TungstenAnswer deleteVmiAnswer = mock(TungstenAnswer.class);
         TungstenAnswer deleteVmAnswer = mock(TungstenAnswer.class);
 
-        when(nicProfile.getIPv4Address()).thenReturn("192.168.100.100");
         when(network.getTrafficType()).thenReturn(Networks.TrafficType.Management);
         when(vmInstanceDao.findById(anyLong())).thenReturn(vmInstanceVO);
         when(hostDao.findById(anyLong())).thenReturn(host);
@@ -728,9 +695,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVRouterPortCommand.class), anyLong())).thenReturn(deleteTungstenVRouterPortAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVmInterfaceCommand.class), anyLong())).thenReturn(deleteVmiAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVmCommand.class), anyLong())).thenReturn(deleteVmAnswer);
-        when(deleteTungstenVRouterPortAnswer.getResult()).thenReturn(true);
-        when(deleteVmiAnswer.getResult()).thenReturn(true);
-        when(deleteVmAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.release(network, nicProfile, virtualMachineProfile, reservationContext));
     }
@@ -746,7 +710,6 @@
         TungstenAnswer deleteTungstenVRouterPortAnswer = mock(TungstenAnswer.class);
         TungstenAnswer deleteVmiAnswer = mock(TungstenAnswer.class);
 
-        when(nicProfile.getIPv4Address()).thenReturn("192.168.100.100");
         when(network.getTrafficType()).thenReturn(Networks.TrafficType.Management);
         when(vmInstanceDao.findById(anyLong())).thenReturn(vmInstanceVO);
         when(hostDao.findById(anyLong())).thenReturn(host);
@@ -754,8 +717,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVRouterPortCommand.class), anyLong())).thenReturn(deleteTungstenVRouterPortAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVmInterfaceCommand.class), anyLong())).thenReturn(deleteVmiAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVmCommand.class), anyLong())).thenThrow(IllegalArgumentException.class);
-        when(deleteTungstenVRouterPortAnswer.getResult()).thenReturn(true);
-        when(deleteVmiAnswer.getResult()).thenReturn(true);
 
         tungstenElement.release(network, nicProfile, virtualMachineProfile, reservationContext);
     }
@@ -767,14 +728,12 @@
         Network network = mock(Network.class);
         ReservationContext reservationContext = mock(ReservationContext.class);
         List<IPAddressVO> ipAddressVOList = Arrays.asList(ipAddressVO1, ipAddressVO2);
-        TungstenAnswer tungstenDeleteFIPAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer tungstenDeleteNPAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenDeleteFIPAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer tungstenDeleteNPAnswer = MockTungstenAnswerFactory.get(true);
 
         when(ipAddressDao.listByAssociatedNetwork(anyLong(), anyBoolean())).thenReturn(ipAddressVOList);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenFloatingIpCommand.class), anyLong())).thenReturn(tungstenDeleteFIPAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(tungstenDeleteNPAnswer);
-        when(tungstenDeleteFIPAnswer.getResult()).thenReturn(true);
-        when(tungstenDeleteNPAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.destroy(network, reservationContext));
     }
@@ -916,8 +875,8 @@
         Network network = mock(Network.class);
         FirewallRuleVO firewallRuleVO = mock(FirewallRuleVO.class);
         Network publicNetwork = mock(Network.class);
-        TungstenAnswer createNetworkPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer applyNetworkPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer applyNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
 
         when(firewallRuleVO.getState()).thenReturn(FirewallRule.State.Add);
         when(firewallRuleVO.getSourceCidrList()).thenReturn(List.of("192.168.100.0/24"));
@@ -927,8 +886,6 @@
         when(firewallRuleVO.getTrafficType()).thenReturn(FirewallRule.TrafficType.Egress);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(createNetworkPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(applyNetworkPolicyAnswer);
-        when(createNetworkPolicyAnswer.getResult()).thenReturn(true);
-        when(applyNetworkPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.applyFWRules(network, List.of(firewallRuleVO)));
     }
@@ -938,8 +895,8 @@
         Network network = mock(Network.class);
         FirewallRuleVO firewallRuleVO = mock(FirewallRuleVO.class);
         Network publicNetwork = mock(Network.class);
-        TungstenAnswer createNetworkPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer applyNetworkPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer applyNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         Ip ip = mock(Ip.class);
 
@@ -953,8 +910,6 @@
         when(firewallRuleVO.getTrafficType()).thenReturn(FirewallRule.TrafficType.Ingress);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(createNetworkPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(applyNetworkPolicyAnswer);
-        when(createNetworkPolicyAnswer.getResult()).thenReturn(true);
-        when(applyNetworkPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.applyFWRules(network, List.of(firewallRuleVO)));
     }
@@ -971,7 +926,6 @@
         when(firewallRuleVO.getPurpose()).thenReturn(FirewallRule.Purpose.Firewall);
         when(firewallRuleVO.getTrafficType()).thenReturn(FirewallRule.TrafficType.Ingress);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(deleteNetworkPolicyAnswer);
-        when(deleteNetworkPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.applyFWRules(network, List.of(firewallRuleVO)));
     }
@@ -1011,14 +965,13 @@
         ReservationContext context = mock(ReservationContext.class);
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
         HostVO hostVO = mock(HostVO.class);
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(vm.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
         when(network.getTrafficType()).thenReturn(Networks.TrafficType.Public);
         when(vmInstanceDao.findById(anyLong())).thenReturn(vmInstanceVO);
         when(hostDao.findById(anyLong())).thenReturn(hostVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenVirtualMachineCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenElement.prepareMigration(nic, network, vm, dest, context));
     }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenFabricUtilsTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenFabricUtilsTest.java
index 2961826..2764872 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenFabricUtilsTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenFabricUtilsTest.java
@@ -28,11 +28,15 @@
 import com.cloud.network.element.TungstenProviderVO;
 import org.apache.cloudstack.network.tungsten.agent.api.TungstenAnswer;
 import org.apache.cloudstack.network.tungsten.agent.api.TungstenCommand;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenFabricUtilsTest {
     @Mock
     AgentManager agentMgr;
@@ -41,23 +45,29 @@
 
     TungstenFabricUtils tungstenFabricUtils;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         tungstenFabricUtils = new TungstenFabricUtils();
         tungstenFabricUtils.agentMgr = agentMgr;
         tungstenFabricUtils.tungstenProviderDao = tungstenProviderDao;
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void sendTungstenCommandSuccessTest() {
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
         TungstenCommand tungstenCommand = mock(TungstenCommand.class);
 
         when(tungstenProviderDao.findByZoneId(anyLong())).thenReturn(tungstenProviderVO);
         when(agentMgr.easySend(anyLong(), any(TungstenCommand.class))).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertEquals(tungstenAnswer, tungstenFabricUtils.sendTungstenCommand(tungstenCommand, anyLong()));
     }
@@ -82,13 +92,11 @@
     @Test(expected = InvalidParameterValueException.class)
     public void sendTungstenCommandWithFalseAnswer() {
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(false);
         TungstenCommand tungstenCommand = mock(TungstenCommand.class);
 
         when(tungstenProviderDao.findByZoneId(anyLong())).thenReturn(tungstenProviderVO);
         when(agentMgr.easySend(anyLong(), any(TungstenCommand.class))).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(false);
-        when(tungstenAnswer.getDetails()).thenReturn("");
 
         tungstenFabricUtils.sendTungstenCommand(tungstenCommand, anyLong());
     }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenGuestNetworkGuruTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenGuestNetworkGuruTest.java
index 810ba64..6a5a013 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenGuestNetworkGuruTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenGuestNetworkGuruTest.java
@@ -85,27 +85,25 @@
 import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenVmCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenVmInterfaceCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.GetTungstenNatIpCommand;
-import org.apache.cloudstack.network.tungsten.agent.api.ReleaseTungstenFloatingIpCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.SetTungstenNetworkGatewayCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.SetupTungstenVRouterCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.TungstenAnswer;
 import org.apache.cloudstack.network.tungsten.agent.api.TungstenCommand;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(TungstenGuestNetworkGuru.class)
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenGuestNetworkGuruTest {
 
     @Mock
@@ -155,14 +153,16 @@
 
     TungstenGuestNetworkGuru guru;
 
+    AutoCloseable closeable;
+
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         guru = new TungstenGuestNetworkGuru();
-        Whitebox.setInternalState(guru, "_physicalNetworkDao", physicalNetworkDao);
-        Whitebox.setInternalState(guru, "_dcDao", dcDao);
-        Whitebox.setInternalState(guru, "_networkModel", networkModel);
-        Whitebox.setInternalState(guru, "_nicDao", nicDao);
+        ReflectionTestUtils.setField(guru, "_physicalNetworkDao", physicalNetworkDao);
+        ReflectionTestUtils.setField(guru, "_dcDao", dcDao);
+        ReflectionTestUtils.setField(guru, "_networkModel", networkModel);
+        ReflectionTestUtils.setField(guru, "_nicDao", nicDao);
         guru.networkOfferingServiceMapDao = ntwkOfferingSrvcDao;
         guru.tungstenFabricUtils = tungstenFabricUtils;
         guru.tungstenService = tungstenService;
@@ -180,10 +180,8 @@
 
         when(dc.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
         when(dc.getGuestNetworkCidr()).thenReturn("10.1.1.1/24");
-        when(dc.getId()).thenReturn(1L);
         when(dcDao.findById(anyLong())).thenReturn(dc);
 
-        when(physicalNetwork.getId()).thenReturn(1L);
         when(physicalNetwork.getIsolationMethods()).thenReturn(List.of("TF"));
         when(physicalNetworkDao.findById(anyLong())).thenReturn(physicalNetwork);
 
@@ -198,6 +196,11 @@
         when(plan.getPhysicalNetworkId()).thenReturn(1L);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void testIsMyIsolationMethod() {
         assertTrue(guru.isMyIsolationMethod(physicalNetwork));
@@ -401,13 +404,7 @@
         final VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
         final HostVO host = mock(HostVO.class);
 
-        when(vm.getType()).thenReturn(VirtualMachine.Type.User);
         when(hostDao.findById(anyLong())).thenReturn(host);
-        when(ipAddressDao.findByAssociatedVmId(anyLong())).thenReturn(ipAddressVO);
-        when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), any())).thenReturn(new NetworkVO());
-        when(
-            tungstenFabricUtils.sendTungstenCommand(any(ReleaseTungstenFloatingIpCommand.class), anyLong())).thenReturn(
-            new TungstenAnswer(new TungstenCommand(), true, ""));
         when(
             tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenVRouterPortCommand.class), anyLong())).thenReturn(
             new TungstenAnswer(new TungstenCommand(), true, ""));
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenIntrospectApiTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenIntrospectApiTest.java
index b893a7d..3eaa1a8 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenIntrospectApiTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenIntrospectApiTest.java
@@ -18,27 +18,34 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.powermock.api.mockito.PowerMockito.mock;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 
 import org.apache.cloudstack.network.tungsten.vrouter.IntrospectApiConnector;
 import org.apache.cloudstack.network.tungsten.vrouter.IntrospectApiConnectorFactory;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedStatic;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(IntrospectApiConnectorFactory.class)
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenIntrospectApiTest {
+    MockedStatic<IntrospectApiConnectorFactory> introspectApiConnectorFactoryMocked;
+
     @Before
     public void setup() {
-        mockStatic(IntrospectApiConnectorFactory.class);
+        introspectApiConnectorFactoryMocked = mockStatic(IntrospectApiConnectorFactory.class);
+    }
+
+    @After
+    public void tearDown() {
+        introspectApiConnectorFactoryMocked.close();
     }
 
     @Test
@@ -48,7 +55,7 @@
         NodeList nodeList = mock(NodeList.class);
         Node node = mock(Node.class);
 
-        when(IntrospectApiConnectorFactory.getInstance(anyString(), anyString())).thenReturn(introspectApiConnector);
+        introspectApiConnectorFactoryMocked.when(() -> IntrospectApiConnectorFactory.getInstance(anyString(), anyString())).thenReturn(introspectApiConnector);
         when(introspectApiConnector.getSnhItfReq(anyString())).thenReturn(document);
         when(document.getElementsByTagName(anyString())).thenReturn(nodeList);
         when(nodeList.getLength()).thenReturn(1);
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenProviderServiceTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenProviderServiceTest.java
index 0a62a22..6654718 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenProviderServiceTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenProviderServiceTest.java
@@ -38,15 +38,19 @@
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
 import org.apache.cloudstack.network.tungsten.api.command.CreateTungstenFabricProviderCmd;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Arrays;
 import java.util.List;
 
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenProviderServiceTest {
 
     @Mock
@@ -70,9 +74,11 @@
 
     TungstenProviderServiceImpl tungstenProviderService;
 
+    AutoCloseable closeable;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         tungstenProviderService = new TungstenProviderServiceImpl();
         tungstenProviderService.zoneDao = dcDao;
         tungstenProviderService.resourceMgr = resourceMgr;
@@ -86,8 +92,11 @@
         when(zone.getName()).thenReturn("ZoneName");
         when(resourceMgr.addHost(anyLong(), any(), any(), anyMap())).thenReturn(host);
         when(host.getId()).thenReturn(1L);
-        when(domainDao.listAll()).thenReturn(null);
-        when(projectDao.listAll()).thenReturn(null);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImplTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImplTest.java
index 6b199e2..38539d8 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImplTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImplTest.java
@@ -27,8 +27,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
@@ -193,7 +191,6 @@
 import org.apache.cloudstack.network.tungsten.agent.api.RemoveTungstenSecondaryIpAddressCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.RemoveTungstenSecurityGroupRuleCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.RemoveTungstenTagCommand;
-import org.apache.cloudstack.network.tungsten.agent.api.RemoveTungstenVmFromSecurityGroupCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.SetupTungstenVRouterCommand;
 import org.apache.cloudstack.network.tungsten.agent.api.TungstenAnswer;
 import org.apache.cloudstack.network.tungsten.agent.api.UpdateLoadBalancerServiceInstanceCommand;
@@ -205,21 +202,22 @@
 import org.apache.cloudstack.network.tungsten.model.TungstenNetworkPolicy;
 import org.apache.cloudstack.network.tungsten.model.TungstenRule;
 import org.apache.cloudstack.network.tungsten.model.TungstenTag;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({Transaction.class, TungstenServiceImpl.class})
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenServiceImplTest {
     @Mock
     MessageBus messageBus;
@@ -278,9 +276,11 @@
 
     TungstenServiceImpl tungstenService;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         tungstenService = new TungstenServiceImpl();
         tungstenService.projectDao = projectDao;
         tungstenService.tungstenProviderDao = tungstenProviderDao;
@@ -311,75 +311,74 @@
         tungstenService.messageBus = messageBus;
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void createTungstenFloatingIpTest() throws Exception {
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer createTungstenFloatingIpAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenFloatingIpAnswer = MockTungstenAnswerFactory.get(true);
         Ip ip = mock(Ip.class);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), eq(Networks.TrafficType.Public))).thenReturn(networkVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenFloatingIpCommand.class), anyLong())).thenReturn(createTungstenFloatingIpAnswer);
-        when(createTungstenFloatingIpAnswer.getResult()).thenReturn(true);
         when(ipAddressVO.getAddress()).thenReturn(ip);
 
-        assertTrue(Whitebox.invokeMethod(tungstenService, "createTungstenFloatingIp", 1L, ipAddressVO));
+        assertTrue(ReflectionTestUtils.invokeMethod(tungstenService, "createTungstenFloatingIp", 1L, ipAddressVO));
     }
 
     @Test
     public void deleteTungstenFloatingIpWithIpAddressTest() throws Exception {
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer deleteTungstenFloatingIpAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenFloatingIpAnswer = MockTungstenAnswerFactory.get(true);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), eq(Networks.TrafficType.Public))).thenReturn(networkVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenFloatingIpCommand.class), anyLong())).thenReturn(deleteTungstenFloatingIpAnswer);
-        when(deleteTungstenFloatingIpAnswer.getResult()).thenReturn(true);
 
-        assertTrue(Whitebox.invokeMethod(tungstenService, "deleteTungstenFloatingIp", 1L, ipAddressVO));
+        assertTrue(ReflectionTestUtils.invokeMethod(tungstenService, "deleteTungstenFloatingIp", 1L, ipAddressVO));
     }
 
     @Test
     public void deleteTungstenDomainTest() throws Exception {
         DomainVO domainVO = mock(DomainVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer deleteTungstenDomainAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenDomainAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenDomainCommand.class), anyLong())).thenReturn(deleteTungstenDomainAnswer);
-        when(deleteTungstenDomainAnswer.getResult()).thenReturn(true);
 
-        assertTrue(Whitebox.invokeMethod(tungstenService, "deleteTungstenDomain", domainVO));
+        assertTrue(ReflectionTestUtils.invokeMethod(tungstenService, "deleteTungstenDomain", domainVO));
     }
 
     @Test
     public void deleteTungstenProjectTest() throws Exception {
         ProjectVO projectVO = mock(ProjectVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer deleteTungstenProjectAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenProjectAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenProjectCommand.class), anyLong())).thenReturn(deleteTungstenProjectAnswer);
-        when(deleteTungstenProjectAnswer.getResult()).thenReturn(true);
 
-        assertTrue(Whitebox.invokeMethod(tungstenService, "deleteTungstenProject", projectVO));
+        assertTrue(ReflectionTestUtils.invokeMethod(tungstenService, "deleteTungstenProject", projectVO));
     }
 
     @Test
     public void addTungstenDefaultNetworkPolicyTest() {
         TungstenRule tungstenRule = mock(TungstenRule.class);
-        TungstenAnswer createTungstenPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer applyTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer applyTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(createTungstenPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(applyTungstenPolicyAnswer);
         when(createTungstenPolicyAnswer.getApiObjectBase()).thenReturn(networkPolicy);
-        when(createTungstenPolicyAnswer.getResult()).thenReturn(true);
-        when(applyTungstenPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.addTungstenDefaultNetworkPolicy(1L, "default-domain:default-project", "policyName", "7279ed91-314e-45be-81b4-b10395fd2ae3"
-            , List.of(tungstenRule), 1, 1));
+                , List.of(tungstenRule), 1, 1));
     }
 
     @Test
@@ -388,30 +387,25 @@
         VirtualNetwork managementVirtualNetwork = mock(VirtualNetwork.class);
         VirtualNetwork fabricVirtualNetwork = mock(VirtualNetwork.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
-        TungstenAnswer createTungstenNetworkAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenDefaultSecurityGroupAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenGlobalVrouterConfigAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer getTungstenFabricNetworkAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer createTungstenPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer applyTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenNetworkAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenDefaultSecurityGroupAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenGlobalVrouterConfigAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer getTungstenFabricNetworkAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer createTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer applyTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(),
-            eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
+                eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkCommand.class), anyLong())).thenReturn(createTungstenNetworkAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenDefaultSecurityGroupCommand.class), anyLong())).thenReturn(updateTungstenDefaultSecurityGroupAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenVrouterConfigCommand.class), anyLong())).thenReturn(updateTungstenGlobalVrouterConfigAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenFabricNetworkCommand.class), anyLong())).thenReturn(getTungstenFabricNetworkAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(createTungstenPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(applyTungstenPolicyAnswer);
-        when(createTungstenNetworkAnswer.getResult()).thenReturn(true);
+
         when(createTungstenNetworkAnswer.getApiObjectBase()).thenReturn(managementVirtualNetwork);
         when(createTungstenPolicyAnswer.getApiObjectBase()).thenReturn(networkPolicy);
         when(getTungstenFabricNetworkAnswer.getApiObjectBase()).thenReturn(fabricVirtualNetwork);
-        when(updateTungstenDefaultSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(updateTungstenGlobalVrouterConfigAnswer.getResult()).thenReturn(true);
-        when(getTungstenFabricNetworkAnswer.getResult()).thenReturn(true);
-        when(createTungstenPolicyAnswer.getResult()).thenReturn(true);
-        when(applyTungstenPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.createManagementNetwork(1L));
     }
@@ -420,17 +414,15 @@
     public void addManagementNetworkSubnetTest() {
         HostPodVO hostPodVO = mock(HostPodVO.class);
         Network managementNetwork = mock(Network.class);
-        TungstenAnswer addTungstenNetworkSubnetAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenNetworkSubnetAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         DataCenterIpAddressVO dataCenterIpAddressVO = mock(DataCenterIpAddressVO.class);
 
         when(hostPodVO.getDescription()).thenReturn("192.168.100.100-192.168.100.200");
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(),
-            eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
+                eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenNetworkSubnetCommand.class), anyLong())).thenReturn(addTungstenNetworkSubnetAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenNetworkDnsCommand.class), anyLong())).thenReturn(getTungstenNetworkDnsAnswer);
-        when(addTungstenNetworkSubnetAnswer.getResult()).thenReturn(true);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
         when(getTungstenNetworkDnsAnswer.getDetails()).thenReturn("192.168.100.150");
         when(managementNetwork.getCidr()).thenReturn("192.168.100.0/24");
         when(managementNetwork.getTrafficType()).thenReturn(Networks.TrafficType.Management);
@@ -443,23 +435,18 @@
     @Test
     public void deleteManagementNetworkTest() {
         Network managementNetwork = mock(Network.class);
-        TungstenAnswer deleteTungstenManagementPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer getTungstenFabricNetworkAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer deleteTungstenFabricPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer deleteTungstenNetworkAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenManagementPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer getTungstenFabricNetworkAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer deleteTungstenFabricPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer deleteTungstenNetworkAnswer = MockTungstenAnswerFactory.get(true);
         VirtualNetwork fabricVirtualNetwork = mock(VirtualNetwork.class);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(),
-            eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
-        when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(deleteTungstenManagementPolicyAnswer);
+                eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenFabricNetworkCommand.class), anyLong())).thenReturn(getTungstenFabricNetworkAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(deleteTungstenFabricPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkCommand.class), anyLong())).thenReturn(deleteTungstenNetworkAnswer);
         when(getTungstenFabricNetworkAnswer.getApiObjectBase()).thenReturn(fabricVirtualNetwork);
-        when(deleteTungstenManagementPolicyAnswer.getResult()).thenReturn(true);
-        when(getTungstenFabricNetworkAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenFabricPolicyAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenNetworkAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteManagementNetwork(1L));
     }
@@ -468,17 +455,14 @@
     public void removeManagementNetworkSubnetTest() {
         HostPodVO hostPodVO = mock(HostPodVO.class);
         Network managementNetwork = mock(Network.class);
-        TungstenAnswer removeTungstenNetworkSubnetAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenNetworkSubnetAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         DataCenterIpAddressVO dataCenterIpAddressVO = mock(DataCenterIpAddressVO.class);
 
-        when(hostPodVO.getDescription()).thenReturn("192.168.100.100-192.168.100.200");
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(),
-            eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
+                eq(Networks.TrafficType.Management))).thenReturn(managementNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenNetworkSubnetCommand.class), anyLong())).thenReturn(removeTungstenNetworkSubnetAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenNetworkDnsCommand.class), anyLong())).thenReturn(getTungstenNetworkDnsAnswer);
-        when(removeTungstenNetworkSubnetAnswer.getResult()).thenReturn(true);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
         when(getTungstenNetworkDnsAnswer.getDetails()).thenReturn("192.168.100.150");
         when(managementNetwork.getTrafficType()).thenReturn(Networks.TrafficType.Management);
         when(dataCenterIpAddressDao.listByPodIdDcIdIpAddress(anyLong(), anyLong(), anyString())).thenReturn(List.of(dataCenterIpAddressVO));
@@ -493,20 +477,16 @@
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
         NetworkDetailVO networkDetailVO = mock(NetworkDetailVO.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
-        TungstenAnswer createPublicNetworkAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer createFloatingIpPoolAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer createTungstenPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer applyTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createPublicNetworkAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer createFloatingIpPoolAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer createTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer applyTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), eq(Networks.TrafficType.Public))).thenReturn(publicNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkCommand.class), anyLong())).thenReturn(createPublicNetworkAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenFloatingIpPoolCommand.class), anyLong())).thenReturn(createFloatingIpPoolAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(createTungstenPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(applyTungstenPolicyAnswer);
-        when(createPublicNetworkAnswer.getResult()).thenReturn(true);
-        when(createFloatingIpPoolAnswer.getResult()).thenReturn(true);
-        when(createTungstenPolicyAnswer.getResult()).thenReturn(true);
-        when(applyTungstenPolicyAnswer.getResult()).thenReturn(true);
         when(createPublicNetworkAnswer.getApiObjectBase()).thenReturn(virtualNetwork);
         when(createTungstenPolicyAnswer.getApiObjectBase()).thenReturn(networkPolicy);
         when(networkDetailsDao.persist(any(NetworkDetailVO.class))).thenReturn(networkDetailVO);
@@ -519,11 +499,11 @@
     public void addPublicNetworkSubnetTest() {
         VlanVO vlanVO = mock(VlanVO.class);
         Network publicNetwork = mock(Network.class);
-        TungstenAnswer addTungstenNetworkSubnetAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer createTungstenPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer applyTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenNetworkSubnetAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer createTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer applyTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), eq(Networks.TrafficType.Public))).thenReturn(publicNetwork);
@@ -536,14 +516,8 @@
         when(vlanVO.getVlanGateway()).thenReturn("192.168.100.1");
         when(vlanVO.getVlanNetmask()).thenReturn("255.255.255.0");
         when(publicNetwork.getCidr()).thenReturn("192.168.100.0/24");
-        when(addTungstenNetworkSubnetAnswer.getResult()).thenReturn(true);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
-        when(addTungstenNetworkSubnetAnswer.getResult()).thenReturn(true);
-        when(createTungstenPolicyAnswer.getResult()).thenReturn(true);
-        when(applyTungstenPolicyAnswer.getResult()).thenReturn(true);
         when(publicNetwork.getTrafficType()).thenReturn(Networks.TrafficType.Public);
         when(createTungstenPolicyAnswer.getApiObjectBase()).thenReturn(networkPolicy);
-        when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
         when(ipAddressDao.mark(anyLong(), any(Ip.class))).thenReturn(true);
 
         assertTrue(tungstenService.addPublicNetworkSubnet(vlanVO));
@@ -552,17 +526,14 @@
     @Test
     public void deletePublicNetworkTest() {
         Network publicNetwork = mock(Network.class);
-        TungstenAnswer deleteTungstenNetworkPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer deleteTungstenFloatingIpPoolAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer deleteTungstenNetworkAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer deleteTungstenFloatingIpPoolAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer deleteTungstenNetworkAnswer = MockTungstenAnswerFactory.get(true);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), eq(Networks.TrafficType.Public))).thenReturn(publicNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(deleteTungstenNetworkPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenFloatingIpPoolCommand.class), anyLong())).thenReturn(deleteTungstenFloatingIpPoolAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkCommand.class), anyLong())).thenReturn(deleteTungstenNetworkAnswer);
-        when(deleteTungstenNetworkPolicyAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenFloatingIpPoolAnswer.getResult()).thenReturn(true);
-        when(deleteTungstenNetworkAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deletePublicNetwork(1L));
     }
@@ -571,18 +542,15 @@
     public void removePublicNetworkSubnetTest() {
         VlanVO vlanVO = mock(VlanVO.class);
         Network publicNetwork = mock(Network.class);
-        TungstenAnswer deleteTungstenNetworkPolicyAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer removeTungstenNetworkSubnetAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenNetworkPolicyAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer removeTungstenNetworkSubnetAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
 
         when(networkModel.getSystemNetworkByZoneAndTrafficType(anyLong(), eq(Networks.TrafficType.Public))).thenReturn(publicNetwork);
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(deleteTungstenNetworkPolicyAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenNetworkSubnetCommand.class), anyLong())).thenReturn(removeTungstenNetworkSubnetAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenNetworkDnsCommand.class), anyLong())).thenReturn(getTungstenNetworkDnsAnswer);
-        when(deleteTungstenNetworkPolicyAnswer.getResult()).thenReturn(true);
-        when(removeTungstenNetworkSubnetAnswer.getResult()).thenReturn(true);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
         when(getTungstenNetworkDnsAnswer.getDetails()).thenReturn("192.168.100.150");
         when(publicNetwork.getTrafficType()).thenReturn(Networks.TrafficType.Public);
         when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
@@ -594,11 +562,10 @@
     @Test
     public void allocateDnsIpAddressTest() {
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenNetworkDnsCommand.class), anyLong())).thenReturn(getTungstenNetworkDnsAnswer);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
         when(networkVO.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
         when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated);
         when(tungstenGuestNetworkIpAddressDao.persist(any(TungstenGuestNetworkIpAddressVO.class))).thenReturn(tungstenGuestNetworkIpAddressVO);
@@ -611,11 +578,10 @@
     @Test
     public void deallocateDnsIpAddressTest() {
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenNetworkDnsCommand.class), anyLong())).thenReturn(getTungstenNetworkDnsAnswer);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
         when(networkVO.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
         when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated);
         when(tungstenGuestNetworkIpAddressDao.findByNetworkAndGuestIpAddress(anyLong(), anyString())).thenReturn(tungstenGuestNetworkIpAddressVO);
@@ -654,22 +620,18 @@
         DomainVO domainVO = mock(DomainVO.class);
         ProjectVO projectVO = mock(ProjectVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer createTungstenDomainAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer createTungstenProjectAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer createTungstenDefaultProjectAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateTungstenDefaultSecurityGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenDomainAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer createTungstenProjectAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer createTungstenDefaultProjectAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateTungstenDefaultSecurityGroupAnswer = MockTungstenAnswerFactory.get(true);
 
         when(domainDao.listAll()).thenReturn(List.of(domainVO));
         when(projectDao.listAll()).thenReturn(List.of(projectVO));
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenDomainCommand.class), anyLong())).thenReturn(createTungstenDomainAnswer);
-        when(createTungstenDomainAnswer.getResult()).thenReturn(true);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenProjectCommand.class), anyLong())).thenReturn(createTungstenProjectAnswer);
-        when(createTungstenProjectAnswer.getResult()).thenReturn(true);
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateTungstenDefaultSecurityGroupCommand.class), anyLong())).thenReturn(updateTungstenDefaultSecurityGroupAnswer);
-        when(updateTungstenDefaultSecurityGroupAnswer.getResult()).thenReturn(true);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenDefaultProjectCommand.class), anyLong())).thenReturn(createTungstenDefaultProjectAnswer);
-        when(createTungstenDefaultProjectAnswer.getResult()).thenReturn(true);
         when(domainDao.findById(anyLong())).thenReturn(domainVO);
 
         assertTrue(tungstenService.syncTungstenDbWithCloudstackProjectsAndDomains());
@@ -682,8 +644,8 @@
         Network publicNetwork = mock(Network.class);
         IPAddressVO ipAddressVO = mock(IPAddressVO.class);
         HostVO hostVO = mock(HostVO.class);
-        TungstenAnswer getTungstenLoadBalancerAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer updateLoadBalancerServiceInstanceAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer getTungstenLoadBalancerAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer updateLoadBalancerServiceInstanceAnswer = MockTungstenAnswerFactory.get(true);
         Answer updateTungstenLoadbalancerStatsAnswer = mock(Answer.class);
         Answer updateTungstenLoadbalancerSslAnswer = mock(Answer.class);
         FirewallRuleVO firewallRuleVO = mock(FirewallRuleVO.class);
@@ -701,8 +663,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(UpdateLoadBalancerServiceInstanceCommand.class), anyLong())).thenReturn(updateLoadBalancerServiceInstanceAnswer);
         when(agentMgr.easySend(anyLong(), any(UpdateTungstenLoadbalancerStatsCommand.class))).thenReturn(updateTungstenLoadbalancerStatsAnswer);
         when(agentMgr.easySend(anyLong(), any(UpdateTungstenLoadbalancerSslCommand.class))).thenReturn(updateTungstenLoadbalancerSslAnswer);
-        when(getTungstenLoadBalancerAnswer.getResult()).thenReturn(true);
-        when(updateLoadBalancerServiceInstanceAnswer.getResult()).thenReturn(true);
         when(updateTungstenLoadbalancerStatsAnswer.getResult()).thenReturn(true);
         when(updateTungstenLoadbalancerSslAnswer.getResult()).thenReturn(true);
         when(configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key())).thenReturn("enabled");
@@ -727,32 +687,29 @@
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
         DomainVO domainVO = mock(DomainVO.class);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
-        TungstenAnswer createTungstenSecurityGroupAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer addTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
-        mockStatic(Transaction.class);
+        TungstenAnswer createTungstenSecurityGroupAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer addTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
 
         when(projectDao.findByProjectAccountId(anyLong())).thenReturn(projectVO);
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
         when(domainDao.findById(anyLong())).thenReturn(domainVO);
-        when(projectDao.findByUuid(anyString())).thenReturn(projectVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenSecurityGroupCommand.class), anyLong())).thenReturn(createTungstenSecurityGroupAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(addTungstenSecurityGroupRuleAnswer);
-        when(createTungstenSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(addTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
-        PowerMockito.when(Transaction.execute(any(TransactionCallback.class))).thenReturn(List.of(tungstenSecurityGroupRuleVO));
 
-        assertTrue(tungstenService.createTungstenSecurityGroup(securityGroup));
+        try (MockedStatic<Transaction> transactionMocked = Mockito.mockStatic(Transaction.class)) {
+            transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(List.of(tungstenSecurityGroupRuleVO));
+            assertTrue(tungstenService.createTungstenSecurityGroup(securityGroup));
+        }
     }
 
     @Test
     public void deleteTungstenSecurityGroupTest() {
         SecurityGroup securityGroup = mock(SecurityGroup.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer deleteTungstenSecurityGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenSecurityGroupAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenSecurityGroupCommand.class), anyLong())).thenReturn(deleteTungstenSecurityGroupAnswer);
-        when(deleteTungstenSecurityGroupAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenSecurityGroup(securityGroup));
     }
@@ -762,8 +719,8 @@
         SecurityRule securityRule = mock(SecurityRule.class);
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
         TungstenAnswer getTungstenSecurityGroupAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer addTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer addTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
         net.juniper.tungsten.api.types.SecurityGroup securityGroup = mock(net.juniper.tungsten.api.types.SecurityGroup.class);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
@@ -774,9 +731,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenSecurityGroupCommand.class), anyLong())).thenReturn(getTungstenSecurityGroupAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(removeTungstenSecurityGroupRuleAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(addTungstenSecurityGroupRuleAnswer);
-        when(getTungstenSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(removeTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
-        when(addTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
         when(getTungstenSecurityGroupAnswer.getApiObjectBase()).thenReturn(securityGroup);
         when(securityRule.getRuleType()).thenReturn(SecurityRule.SecurityRuleType.EgressRule);
         when(tungstenSecurityGroupRuleDao.findDefaultSecurityRule(anyLong(), anyString(), anyString())).thenReturn(tungstenSecurityGroupRuleVO);
@@ -799,7 +753,7 @@
         SecurityRule securityRule = mock(SecurityRule.class);
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
         TungstenAnswer getTungstenSecurityGroupAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer addTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
         net.juniper.tungsten.api.types.SecurityGroup securityGroup = mock(net.juniper.tungsten.api.types.SecurityGroup.class);
 
@@ -807,8 +761,6 @@
         when(securityGroupDao.findById(anyLong())).thenReturn(securityGroupVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenSecurityGroupCommand.class), anyLong())).thenReturn(getTungstenSecurityGroupAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(addTungstenSecurityGroupRuleAnswer);
-        when(getTungstenSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(addTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
         when(getTungstenSecurityGroupAnswer.getApiObjectBase()).thenReturn(securityGroup);
         when(securityRule.getRuleType()).thenReturn(SecurityRule.SecurityRuleType.IngressRule);
         when(securityRule.getProtocol()).thenReturn(NetUtils.ALL_PROTO);
@@ -824,8 +776,8 @@
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
-        TungstenAnswer addTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
         NicVO nicVO = mock(NicVO.class);
 
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
@@ -835,8 +787,6 @@
         when(tungstenSecurityGroupRuleDao.persist(any(TungstenSecurityGroupRuleVO.class))).thenReturn(tungstenSecurityGroupRuleVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(addTungstenSecurityGroupRuleAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(removeTungstenSecurityGroupRuleAnswer);
-        when(addTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
-        when(removeTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
         when(securityRule.getAllowedNetworkId()).thenReturn(1L);
         when(securityGroupVMMapDao.listVmIdsBySecurityGroup(anyLong())).thenReturn(List.of(1L));
         when(nicDao.findDefaultNicForVM(anyLong())).thenReturn(nicVO);
@@ -855,14 +805,12 @@
         SecurityRule securityRule = mock(SecurityRule.class);
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenProviderDao.findAll()).thenReturn(List.of(tungstenProviderVO));
         when(securityGroupDao.findById(anyLong())).thenReturn(securityGroupVO);
         when(securityRule.getRuleType()).thenReturn(SecurityRule.SecurityRuleType.IngressRule);
-        when(securityRule.getType()).thenReturn("ingress");
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(removeTungstenSecurityGroupRuleAnswer);
-        when(removeTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
         when(securityRule.getAllowedNetworkId()).thenReturn(null);
 
         assertTrue(tungstenService.removeTungstenSecurityGroupRule(securityRule));
@@ -878,8 +826,8 @@
         SecurityGroupRuleVO securityGroupRuleVO = mock(SecurityGroupRuleVO.class);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
-        TungstenAnswer addTungstenSecondaryIpAddressAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer addTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenSecondaryIpAddressAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer addTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
 
         when(entityMgr.findById(eq(NicSecondaryIp.class), anyLong())).thenReturn(nicSecondaryIp);
         when(entityMgr.findById(eq(Network.class), anyLong())).thenReturn(network);
@@ -888,8 +836,6 @@
         when(nicSecondaryIp.getIp4Address()).thenReturn("192.168.100.100");
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecondaryIpAddressCommand.class), anyLong())).thenReturn(addTungstenSecondaryIpAddressAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(addTungstenSecurityGroupRuleAnswer);
-        when(addTungstenSecondaryIpAddressAnswer.getResult()).thenReturn(true);
-        when(addTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
         when(dataCenter.isSecurityGroupEnabled()).thenReturn(true);
         when(network.getGuestType()).thenReturn(Network.GuestType.Shared);
         when(securityGroupManager.getSecurityGroupsForVm(anyLong())).thenReturn(List.of(securityGroupVO));
@@ -906,17 +852,16 @@
         NicSecondaryIpVO nicSecondaryIpVO = mock(NicSecondaryIpVO.class);
         Network network = mock(Network.class);
         DataCenter dataCenter = mock(DataCenter.class);
-        TungstenAnswer removeTungstenSecondaryIpAddressAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenSecondaryIpAddressAnswer = MockTungstenAnswerFactory.get(true);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
-        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
 
         when(entityMgr.findById(eq(Network.class), anyLong())).thenReturn(network);
         when(entityMgr.findById(eq(DataCenter.class), anyLong())).thenReturn(dataCenter);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenSecondaryIpAddressCommand.class), anyLong())).thenReturn(removeTungstenSecondaryIpAddressAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(removeTungstenSecurityGroupRuleAnswer);
-        when(removeTungstenSecondaryIpAddressAnswer.getResult()).thenReturn(true);
-        when(removeTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
+
         when(dataCenter.isSecurityGroupEnabled()).thenReturn(true);
         when(network.getGuestType()).thenReturn(Network.GuestType.Shared);
         when(tungstenSecurityGroupRuleDao.listByRuleTarget(anyString())).thenReturn(List.of(tungstenSecurityGroupRuleVO));
@@ -929,14 +874,13 @@
 
     @Test
     public void createTungstenPolicyTest() {
-        TungstenAnswer createTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
         TungstenNetworkPolicy tungstenNetworkPolicy = mock(TungstenNetworkPolicy.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenPolicyCommand.class), anyLong())).thenReturn(createTungstenPolicyAnswer);
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
-        when(createTungstenPolicyAnswer.getResult()).thenReturn(true);
         when(createTungstenPolicyAnswer.getTungstenModel()).thenReturn(tungstenNetworkPolicy);
         when(tungstenNetworkPolicy.getNetworkPolicy()).thenReturn(networkPolicy);
 
@@ -945,8 +889,7 @@
 
     @Test
     public void addTungstenPolicyRuleTest() throws Exception {
-        AddTungstenPolicyRuleCommand addTungstenPolicyRuleCommand = mock(AddTungstenPolicyRuleCommand.class);
-        TungstenAnswer addTungstenPolicyRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenPolicyRuleAnswer = MockTungstenAnswerFactory.get(true);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         PolicyEntriesType policyEntriesType = mock(PolicyEntriesType.class);
         PolicyRuleType policyRuleType = mock(PolicyRuleType.class);
@@ -958,12 +901,10 @@
 
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenPolicyRuleCommand.class), anyLong())).thenReturn(addTungstenPolicyRuleAnswer);
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
-        when(addTungstenPolicyRuleAnswer.getResult()).thenReturn(true);
         when(addTungstenPolicyRuleAnswer.getApiObjectBase()).thenReturn(networkPolicy);
         when(networkPolicy.getEntries()).thenReturn(policyEntriesType);
         when(policyEntriesType.getPolicyRule()).thenReturn(List.of(policyRuleType));
-        whenNew(AddTungstenPolicyRuleCommand.class).withAnyArguments().thenReturn(addTungstenPolicyRuleCommand);
-        PowerMockito.when(addTungstenPolicyRuleCommand, "getUuid").thenReturn("8b4637b6-5629-46de-8fb2-d0b0502bfa85");
+
         when(policyRuleType.getRuleUuid()).thenReturn("8b4637b6-5629-46de-8fb2-d0b0502bfa85");
         when(policyRuleType.getActionList()).thenReturn(actionListType);
         when(actionListType.getSimpleAction()).thenReturn("pass");
@@ -973,15 +914,21 @@
         when(policyRuleType.getDstAddresses()).thenReturn(List.of(addressType));
         when(policyRuleType.getDstPorts()).thenReturn(List.of(portType));
 
-        assertNotNull(tungstenService.addTungstenPolicyRule(1L, "948f421c-edde-4518-a391-09299cc25dc2", "pass",
-            "<>", "tcp", "network1", "192.168.100.100", 32, 80, 80,
-            "network2", "192.168.200.200", 32, 80, 80));
+        try (MockedConstruction<AddTungstenPolicyRuleCommand> ignored =
+                     Mockito.mockConstruction(AddTungstenPolicyRuleCommand.class, (mock, context) -> {
+                         when(mock.getUuid()).thenReturn("8b4637b6-5629-46de-8fb2-d0b0502bfa85");
+                     })) {
+
+            assertNotNull(tungstenService.addTungstenPolicyRule(1L, "948f421c-edde-4518-a391-09299cc25dc2", "pass",
+                    "<>", "tcp", "network1", "192.168.100.100", 32, 80, 80,
+                    "network2", "192.168.200.200", 32, 80, 80));
+        }
     }
 
     @Test
     public void listTungstenPolicyTest() {
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer listTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
         TungstenNetworkPolicy tungstenNetworkPolicy = mock(TungstenNetworkPolicy.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -990,7 +937,6 @@
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(networkDao.findById(anyLong())).thenReturn(networkVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenPolicyCommand.class), anyLong())).thenReturn(listTungstenPolicyAnswer);
-        when(listTungstenPolicyAnswer.getResult()).thenReturn(true);
         when(listTungstenPolicyAnswer.getTungstenModelList()).thenReturn(List.of(tungstenNetworkPolicy));
         when(tungstenNetworkPolicy.getNetworkPolicy()).thenReturn(networkPolicy);
         when(tungstenNetworkPolicy.getVirtualNetworkList()).thenReturn(
@@ -1001,13 +947,12 @@
 
     @Test
     public void listTungstenNetworkTest() {
-        TungstenAnswer listTungstenNetworkAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenNetworkAnswer = MockTungstenAnswerFactory.get(true);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenNetworkCommand.class), anyLong())).thenReturn(listTungstenNetworkAnswer);
-        when(listTungstenNetworkAnswer.getResult()).thenReturn(true);
         doReturn(List.of(virtualNetwork)).when(listTungstenNetworkAnswer).getApiObjectBaseList();
         when((virtualNetwork.getName())).thenReturn("guestNetwork1");
 
@@ -1016,13 +961,12 @@
 
     @Test
     public void listTungstenNicTest() {
-        TungstenAnswer listTungstenNicAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenNicAnswer = MockTungstenAnswerFactory.get(true);
         VirtualMachineInterface virtualMachineInterface = mock(VirtualMachineInterface.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenNicCommand.class), anyLong())).thenReturn(listTungstenNicAnswer);
-        when(listTungstenNicAnswer.getResult()).thenReturn(true);
         doReturn(List.of(virtualMachineInterface)).when(listTungstenNicAnswer).getApiObjectBaseList();
 
         assertNotNull(tungstenService.listTungstenNic(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
@@ -1030,13 +974,12 @@
 
     @Test
     public void listTungstenVmTest() {
-        TungstenAnswer listTungstenVmAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenVmAnswer = MockTungstenAnswerFactory.get(true);
         VirtualMachine virtualMachine = mock(VirtualMachine.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenVmCommand.class), anyLong())).thenReturn(listTungstenVmAnswer);
-        when(listTungstenVmAnswer.getResult()).thenReturn(true);
         doReturn(List.of(virtualMachine)).when(listTungstenVmAnswer).getApiObjectBaseList();
 
         assertNotNull(tungstenService.listTungstenVm(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
@@ -1044,17 +987,16 @@
 
     @Test
     public void deleteTungstenPolicyTest() {
-        TungstenAnswer deleteTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenPolicyCommand.class), anyLong())).thenReturn(deleteTungstenPolicyAnswer);
-        when(deleteTungstenPolicyAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenPolicy(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void listTungstenPolicyRuleWithRuleUuidTest() {
-        TungstenAnswer listTungstenPolicyRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenPolicyRuleAnswer = MockTungstenAnswerFactory.get(true);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         PolicyEntriesType policyEntriesType = mock(PolicyEntriesType.class);
         PolicyRuleType policyRuleType = mock(PolicyRuleType.class);
@@ -1066,7 +1008,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenPolicyRuleCommand.class), anyLong())).thenReturn(listTungstenPolicyRuleAnswer);
-        when(listTungstenPolicyRuleAnswer.getResult()).thenReturn(true);
         doReturn(networkPolicy).when(listTungstenPolicyRuleAnswer).getApiObjectBase();
         when(networkPolicy.getEntries()).thenReturn(policyEntriesType);
         when(policyEntriesType.getPolicyRule()).thenReturn(List.of(policyRuleType));
@@ -1084,7 +1025,7 @@
 
     @Test
     public void listTungstenPolicyRuleWithAllRuleTest() {
-        TungstenAnswer listTungstenPolicyRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenPolicyRuleAnswer = MockTungstenAnswerFactory.get(true);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         PolicyEntriesType policyEntriesType = mock(PolicyEntriesType.class);
         PolicyRuleType policyRuleType = mock(PolicyRuleType.class);
@@ -1096,7 +1037,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenPolicyRuleCommand.class), anyLong())).thenReturn(listTungstenPolicyRuleAnswer);
-        when(listTungstenPolicyRuleAnswer.getResult()).thenReturn(true);
         doReturn(networkPolicy).when(listTungstenPolicyRuleAnswer).getApiObjectBase();
         when(networkPolicy.getEntries()).thenReturn(policyEntriesType);
         when(policyEntriesType.getPolicyRule()).thenReturn(List.of(policyRuleType));
@@ -1114,7 +1054,7 @@
 
     @Test
     public void removeTungstenPolicyRuleTest() {
-        TungstenAnswer removeTungstenPolicyRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenPolicyRuleAnswer = MockTungstenAnswerFactory.get(true);
         TungstenNetworkPolicy tungstenNetworkPolicy = mock(TungstenNetworkPolicy.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1122,7 +1062,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenPolicyRuleCommand.class), anyLong())).thenReturn(removeTungstenPolicyRuleAnswer);
-        when(removeTungstenPolicyRuleAnswer.getResult()).thenReturn(true);
         when(removeTungstenPolicyRuleAnswer.getTungstenModel()).thenReturn(tungstenNetworkPolicy);
         when(tungstenNetworkPolicy.getNetworkPolicy()).thenReturn(networkPolicy);
         when(tungstenNetworkPolicy.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1132,7 +1071,7 @@
 
     @Test
     public void createTungstenTagTest() {
-        TungstenAnswer createTungstenTagAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenTagAnswer = MockTungstenAnswerFactory.get(true);
         TungstenTag tungstenTag = mock(TungstenTag.class);
         Tag tag = mock(Tag.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1143,7 +1082,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenTagCommand.class), anyLong())).thenReturn(createTungstenTagAnswer);
-        when(createTungstenTagAnswer.getResult()).thenReturn(true);
         when(createTungstenTagAnswer.getTungstenModel()).thenReturn(tungstenTag);
         when(tungstenTag.getTag()).thenReturn(tag);
         doReturn(List.of(virtualNetwork)).when(tungstenTag).getVirtualNetworkList();
@@ -1156,13 +1094,12 @@
 
     @Test
     public void createTungstenTagTypeTest() {
-        TungstenAnswer createTungstenTagTypeAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenTagTypeAnswer = MockTungstenAnswerFactory.get(true);
         TagType tagtype = mock(TagType.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenTagTypeCommand.class), anyLong())).thenReturn(createTungstenTagTypeAnswer);
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
-        when(createTungstenTagTypeAnswer.getResult()).thenReturn(true);
         when(createTungstenTagTypeAnswer.getApiObjectBase()).thenReturn(tagtype);
 
         assertNotNull(tungstenService.createTungstenTagType(1L, "testTagType"));
@@ -1170,7 +1107,7 @@
 
     @Test
     public void listTungstenTagsTest() {
-        TungstenAnswer listTungstenTagAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenTagAnswer = MockTungstenAnswerFactory.get(true);
         TungstenTag tungstenTag = mock(TungstenTag.class);
         Tag tag = mock(Tag.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1182,29 +1119,26 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenTagCommand.class), anyLong())).thenReturn(listTungstenTagAnswer);
-        when(listTungstenTagAnswer.getResult()).thenReturn(true);
         when(listTungstenTagAnswer.getTungstenModelList()).thenReturn(List.of(tungstenTag));
         when(tungstenTag.getTag()).thenReturn(tag);
         doReturn(List.of(virtualNetwork)).when(tungstenTag).getVirtualNetworkList();
         doReturn(List.of(virtualMachine)).when(tungstenTag).getVirtualMachineList();
         doReturn(List.of(virtualMachineInterface)).when(tungstenTag).getVirtualMachineInterfaceList();
         doReturn(List.of(networkPolicy)).when(tungstenTag).getNetworkPolicyList();
-        doReturn(List.of(applicationPolicySet)).when(tungstenTag).getApplicationPolicySetList();
 
         assertNotNull(tungstenService.listTungstenTags(1L, "948f421c-edde-4518-a391-09299cc25dc2"
-        , "8b4637b6-5629-46de-8fb2-d0b0502bfa85", "8d097a79-a38d-4db4-8a41-16f15d9c5afa", "a329662e-1805-4a89-9b05-2b818ea35978",
-            "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb", "f5ba12c8-d4c5-4c20-a57d-67a9b6fca652"));
+                , "8b4637b6-5629-46de-8fb2-d0b0502bfa85", "8d097a79-a38d-4db4-8a41-16f15d9c5afa", "a329662e-1805-4a89-9b05-2b818ea35978",
+                "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb", "f5ba12c8-d4c5-4c20-a57d-67a9b6fca652"));
     }
 
     @Test
     public void listTungstenTagTypesTest() {
-        TungstenAnswer listTungstenTagTypeAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenTagTypeAnswer = MockTungstenAnswerFactory.get(true);
         TagType tagtype = mock(TagType.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenTagTypeCommand.class), anyLong())).thenReturn(listTungstenTagTypeAnswer);
-        when(listTungstenTagTypeAnswer.getResult()).thenReturn(true);
         doReturn(List.of(tagtype)).when(listTungstenTagTypeAnswer).getApiObjectBaseList();
 
         assertNotNull(tungstenService.listTungstenTagTypes(1L, "testTagType"));
@@ -1212,27 +1146,25 @@
 
     @Test
     public void deleteTungstenTagTest() {
-        TungstenAnswer deleteTungstenTagAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenTagAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenTagCommand.class), anyLong())).thenReturn(deleteTungstenTagAnswer);
-        when(deleteTungstenTagAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenTag(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void deleteTungstenTagTypeTest() {
-        TungstenAnswer deleteTungstenTagTypeAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer deleteTungstenTagTypeAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenTagTypeCommand.class), anyLong())).thenReturn(deleteTungstenTagTypeAnswer);
-        when(deleteTungstenTagTypeAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenTagType(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void applyTungstenPolicyTest() {
-        TungstenAnswer applyTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer applyTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
         TungstenNetworkPolicy tungstenNetworkPolicy = mock(TungstenNetworkPolicy.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1240,7 +1172,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenNetworkPolicyCommand.class), anyLong())).thenReturn(applyTungstenPolicyAnswer);
-        when(applyTungstenPolicyAnswer.getResult()).thenReturn(true);
         when(applyTungstenPolicyAnswer.getTungstenModel()).thenReturn(tungstenNetworkPolicy);
         when(tungstenNetworkPolicy.getNetworkPolicy()).thenReturn(networkPolicy);
         when(tungstenNetworkPolicy.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1250,7 +1181,7 @@
 
     @Test
     public void applyTungstenTagTest() {
-        TungstenAnswer applyTungstenTagAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer applyTungstenTagAnswer = MockTungstenAnswerFactory.get(true);
         TungstenTag tungstenTag = mock(TungstenTag.class);
         Tag tag = mock(Tag.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
@@ -1261,7 +1192,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ApplyTungstenTagCommand.class), anyLong())).thenReturn(applyTungstenTagAnswer);
-        when(applyTungstenTagAnswer.getResult()).thenReturn(true);
         when(applyTungstenTagAnswer.getTungstenModel()).thenReturn(tungstenTag);
         when(tungstenTag.getTag()).thenReturn(tag);
         when(tungstenTag.getNetworkPolicyList()).thenReturn(List.of(networkPolicy));
@@ -1270,13 +1200,13 @@
         when(tungstenTag.getVirtualMachineInterfaceList()).thenReturn(List.of(virtualMachineInterface));
 
         assertNotNull(tungstenService.applyTungstenTag(1L, List.of("948f421c-edde-4518-a391-09299cc25dc2"), List.of("8b4637b6-5629-46de-8fb2-d0b0502bfa85")
-            , List.of("8d097a79-a38d-4db4-8a41-16f15d9c5afa"), "a329662e-1805-4a89-9b05-2b818ea35978", "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb"
-        , "f5ba12c8-d4c5-4c20-a57d-67a9b6fca652"));
+                , List.of("8d097a79-a38d-4db4-8a41-16f15d9c5afa"), "a329662e-1805-4a89-9b05-2b818ea35978", "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb"
+                , "f5ba12c8-d4c5-4c20-a57d-67a9b6fca652"));
     }
 
     @Test
     public void removeTungstenPolicyTest() {
-        TungstenAnswer removeTungstenPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenPolicyAnswer = MockTungstenAnswerFactory.get(true);
         TungstenNetworkPolicy tungstenNetworkPolicy = mock(TungstenNetworkPolicy.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1284,7 +1214,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenPolicyCommand.class), anyLong())).thenReturn(removeTungstenPolicyAnswer);
-        when(removeTungstenPolicyAnswer.getResult()).thenReturn(true);
         when(removeTungstenPolicyAnswer.getTungstenModel()).thenReturn(tungstenNetworkPolicy);
         when(tungstenNetworkPolicy.getNetworkPolicy()).thenReturn(networkPolicy);
         when(tungstenNetworkPolicy.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1294,7 +1223,7 @@
 
     @Test
     public void removeTungstenTagTest() {
-        TungstenAnswer removeTungstenTagAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenTagAnswer = MockTungstenAnswerFactory.get(true);
         TungstenTag tungstenTag = mock(TungstenTag.class);
         Tag tag = mock(Tag.class);
         NetworkPolicy networkPolicy = mock(NetworkPolicy.class);
@@ -1305,7 +1234,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenTagCommand.class), anyLong())).thenReturn(removeTungstenTagAnswer);
-        when(removeTungstenTagAnswer.getResult()).thenReturn(true);
         when(removeTungstenTagAnswer.getTungstenModel()).thenReturn(tungstenTag);
         when(tungstenTag.getTag()).thenReturn(tag);
         when(tungstenTag.getNetworkPolicyList()).thenReturn(List.of(networkPolicy));
@@ -1316,12 +1244,12 @@
         assertNotNull(tungstenService.removeTungstenTag(1L, List.of("948f421c-edde-4518-a391-09299cc25dc2"),
                 List.of("8b4637b6-5629-46de-8fb2-d0b0502bfa85"),
                 List.of("8d097a79-a38d-4db4-8a41-16f15d9c5afa"), "a329662e-1805-4a89-9b05-2b818ea35978", null,
-            "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb"));
+                "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb"));
     }
 
     @Test
     public void createTungstenAddressGroupTest() {
-        TungstenAnswer createTungstenAddressGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenAddressGroupAnswer = MockTungstenAnswerFactory.get(true);
         AddressGroup addressGroup = mock(AddressGroup.class);
         SubnetListType subnetListType = mock(SubnetListType.class);
         SubnetType subnetType = mock(SubnetType.class);
@@ -1329,7 +1257,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenAddressGroupCommand.class), anyLong())).thenReturn(createTungstenAddressGroupAnswer);
-        when(createTungstenAddressGroupAnswer.getResult()).thenReturn(true);
         when(createTungstenAddressGroupAnswer.getApiObjectBase()).thenReturn(addressGroup);
         when(addressGroup.getPrefix()).thenReturn(subnetListType);
         when(subnetListType.getSubnet()).thenReturn(List.of(subnetType));
@@ -1339,7 +1266,7 @@
 
     @Test
     public void createTungstenServiceGroupTest() {
-        TungstenAnswer createTungstenServiceGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenServiceGroupAnswer = MockTungstenAnswerFactory.get(true);
         ServiceGroup serviceGroup = mock(ServiceGroup.class);
         FirewallServiceGroupType firewallServiceGroupType = mock(FirewallServiceGroupType.class);
         FirewallServiceType firewallServiceType = mock(FirewallServiceType.class);
@@ -1348,7 +1275,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenServiceGroupCommand.class), anyLong())).thenReturn(createTungstenServiceGroupAnswer);
-        when(createTungstenServiceGroupAnswer.getResult()).thenReturn(true);
         when(createTungstenServiceGroupAnswer.getApiObjectBase()).thenReturn(serviceGroup);
         when(serviceGroup.getFirewallServiceList()).thenReturn(firewallServiceGroupType);
         when(firewallServiceGroupType.getFirewallService()).thenReturn(List.of(firewallServiceType));
@@ -1359,7 +1285,7 @@
 
     @Test
     public void createTungstenFirewallRuleTest() {
-        TungstenAnswer createTungstenFirewallRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenFirewallRuleAnswer = MockTungstenAnswerFactory.get(true);
         net.juniper.tungsten.api.types.FirewallRule firewallRule = mock(net.juniper.tungsten.api.types.FirewallRule.class);
         ActionListType actionListType = mock(ActionListType.class);
         ObjectReference<ApiPropertyBase> serviceGroup = mock(ObjectReference.class);
@@ -1370,7 +1296,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenFirewallRuleCommand.class), anyLong())).thenReturn(createTungstenFirewallRuleAnswer);
-        when(createTungstenFirewallRuleAnswer.getResult()).thenReturn(true);
         when(createTungstenFirewallRuleAnswer.getApiObjectBase()).thenReturn(firewallRule);
         when(firewallRule.getActionList()).thenReturn(actionListType);
         when(actionListType.getSimpleAction()).thenReturn("pass");
@@ -1384,20 +1309,19 @@
         when(firewallRule.getMatchTags()).thenReturn(firewallRuleMatchTagsType);
 
         assertNotNull(tungstenService.createTungstenFirewallRule(1L, "f5ba12c8-d4c5-4c20-a57d-67a9b6fca652", "test", "pass", "948f421c-edde-4518-a391-09299cc25dc2",
-            "8b4637b6-5629-46de-8fb2-d0b0502bfa85", "8d097a79-a38d-4db4-8a41-16f15d9c5afa", null, "<>", "a329662e-1805-4a89-9b05-2b818ea35978"
-        , "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb", null, "df8e4490-2a40-4d63-a6f3-1f829ffe4fc6", 1));
+                "8b4637b6-5629-46de-8fb2-d0b0502bfa85", "8d097a79-a38d-4db4-8a41-16f15d9c5afa", null, "<>", "a329662e-1805-4a89-9b05-2b818ea35978"
+                , "d5e3f5c5-97ed-41b6-9b6f-7f696b9eddeb", null, "df8e4490-2a40-4d63-a6f3-1f829ffe4fc6", 1));
     }
 
     @Test
     public void createTungstenFirewallPolicyTest() {
-        TungstenAnswer createTungstenFirewallPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenFirewallPolicyAnswer = MockTungstenAnswerFactory.get(true);
         FirewallPolicy firewallPolicy = mock(FirewallPolicy.class);
         ObjectReference<FirewallSequence> firewallSequenceObjectReference = mock(ObjectReference.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenFirewallPolicyCommand.class), anyLong())).thenReturn(createTungstenFirewallPolicyAnswer);
-        when(createTungstenFirewallPolicyAnswer.getResult()).thenReturn(true);
         when(createTungstenFirewallPolicyAnswer.getApiObjectBase()).thenReturn(firewallPolicy);
         when(firewallPolicy.getFirewallRule()).thenReturn(List.of(firewallSequenceObjectReference));
         when(firewallSequenceObjectReference.getReferredName()).thenReturn(List.of("firewallrule"));
@@ -1407,7 +1331,7 @@
 
     @Test
     public void createTungstenApplicationPolicySetTest() {
-        TungstenAnswer createTungstenApplicationPolicySetAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenApplicationPolicySetAnswer = MockTungstenAnswerFactory.get(true);
         ApplicationPolicySet applicationPolicySet = mock(ApplicationPolicySet.class);
         ObjectReference<ApiPropertyBase> objectReference = mock(ObjectReference.class);
         ObjectReference<FirewallSequence> firewallSequenceObjectReference = mock(ObjectReference.class);
@@ -1415,7 +1339,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenApplicationPolicySetCommand.class), anyLong())).thenReturn(createTungstenApplicationPolicySetAnswer);
-        when(createTungstenApplicationPolicySetAnswer.getResult()).thenReturn(true);
         when(createTungstenApplicationPolicySetAnswer.getApiObjectBase()).thenReturn(applicationPolicySet);
         when(applicationPolicySet.getTag()).thenReturn(List.of(objectReference));
         when(objectReference.getReferredName()).thenReturn(List.of("tag"));
@@ -1427,7 +1350,7 @@
 
     @Test
     public void listTungstenApplicationPolicySetTest() {
-        TungstenAnswer listTungstenApplicationPolicySetAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenApplicationPolicySetAnswer = MockTungstenAnswerFactory.get(true);
         ApplicationPolicySet applicationPolicySet = mock(ApplicationPolicySet.class);
         ObjectReference<ApiPropertyBase> objectReference = mock(ObjectReference.class);
         ObjectReference<FirewallSequence> firewallSequenceObjectReference = mock(ObjectReference.class);
@@ -1435,7 +1358,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenApplicationPolicySetCommand.class), anyLong())).thenReturn(listTungstenApplicationPolicySetAnswer);
-        when(listTungstenApplicationPolicySetAnswer.getResult()).thenReturn(true);
         doReturn(List.of(applicationPolicySet)).when(listTungstenApplicationPolicySetAnswer).getApiObjectBaseList();
         when(applicationPolicySet.getTag()).thenReturn(List.of(objectReference));
         when(objectReference.getReferredName()).thenReturn(List.of("tag"));
@@ -1447,14 +1369,13 @@
 
     @Test
     public void listTungstenFirewallPolicyTest() {
-        TungstenAnswer listTungstenFirewallPolicyAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenFirewallPolicyAnswer = MockTungstenAnswerFactory.get(true);
         FirewallPolicy firewallPolicy = mock(FirewallPolicy.class);
         ObjectReference<FirewallSequence> firewallSequenceObjectReference = mock(ObjectReference.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenFirewallPolicyCommand.class), anyLong())).thenReturn(listTungstenFirewallPolicyAnswer);
-        when(listTungstenFirewallPolicyAnswer.getResult()).thenReturn(true);
         doReturn(List.of(firewallPolicy)).when(listTungstenFirewallPolicyAnswer).getApiObjectBaseList();
         when(firewallPolicy.getFirewallRule()).thenReturn(List.of(firewallSequenceObjectReference));
         when(firewallSequenceObjectReference.getReferredName()).thenReturn(List.of("firewallrule"));
@@ -1464,7 +1385,7 @@
 
     @Test
     public void listTungstenFirewallRuleTest() {
-        TungstenAnswer listTungstenFirewallRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenFirewallRuleAnswer = MockTungstenAnswerFactory.get(true);
         net.juniper.tungsten.api.types.FirewallRule firewallRule = mock(net.juniper.tungsten.api.types.FirewallRule.class);
         ActionListType actionListType = mock(ActionListType.class);
         ObjectReference<ApiPropertyBase> serviceGroup = mock(ObjectReference.class);
@@ -1475,7 +1396,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenFirewallRuleCommand.class), anyLong())).thenReturn(listTungstenFirewallRuleAnswer);
-        when(listTungstenFirewallRuleAnswer.getResult()).thenReturn(true);
         doReturn(List.of(firewallRule)).when(listTungstenFirewallRuleAnswer).getApiObjectBaseList();
         when(firewallRule.getActionList()).thenReturn(actionListType);
         when(actionListType.getSimpleAction()).thenReturn("pass");
@@ -1489,12 +1409,12 @@
         when(firewallRule.getMatchTags()).thenReturn(firewallRuleMatchTagsType);
 
         assertNotNull(tungstenService.listTungstenFirewallRule(1L, "948f421c-edde-4518-a391-09299cc25dc2",
-            "8b4637b6-5629-46de-8fb2-d0b0502bfa85"));
+                "8b4637b6-5629-46de-8fb2-d0b0502bfa85"));
     }
 
     @Test
     public void listTungstenAddressGroupTest() {
-        TungstenAnswer listTungstenAddressGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenAddressGroupAnswer = MockTungstenAnswerFactory.get(true);
         AddressGroup addressGroup = mock(AddressGroup.class);
         SubnetListType subnetListType = mock(SubnetListType.class);
         SubnetType subnetType = mock(SubnetType.class);
@@ -1502,7 +1422,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenAddressGroupCommand.class), anyLong())).thenReturn(listTungstenAddressGroupAnswer);
-        when(listTungstenAddressGroupAnswer.getResult()).thenReturn(true);
         doReturn(List.of(addressGroup)).when(listTungstenAddressGroupAnswer).getApiObjectBaseList();
         when(addressGroup.getPrefix()).thenReturn(subnetListType);
         when(subnetListType.getSubnet()).thenReturn(
@@ -1513,7 +1432,7 @@
 
     @Test
     public void listTungstenServiceGroupTest() {
-        TungstenAnswer listTungstenServiceGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer listTungstenServiceGroupAnswer = MockTungstenAnswerFactory.get(true);
         ServiceGroup serviceGroup = mock(ServiceGroup.class);
         FirewallServiceGroupType firewallServiceGroupType = mock(FirewallServiceGroupType.class);
         FirewallServiceType firewallServiceType = mock(FirewallServiceType.class);
@@ -1522,7 +1441,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenServiceGroupCommand.class), anyLong())).thenReturn(listTungstenServiceGroupAnswer);
-        when(listTungstenServiceGroupAnswer.getResult()).thenReturn(true);
         doReturn(List.of(serviceGroup)).when(listTungstenServiceGroupAnswer).getApiObjectBaseList();
         when(serviceGroup.getFirewallServiceList()).thenReturn(firewallServiceGroupType);
         when(firewallServiceGroupType.getFirewallService()).thenReturn(List.of(firewallServiceType));
@@ -1533,50 +1451,45 @@
 
     @Test
     public void deleteTungstenApplicationPolicySetTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenApplicationPolicySetCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenApplicationPolicySet(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void deleteTungstenFirewallPolicyTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenFirewallPolicyCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenFirewallPolicy(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void deleteTungstenFirewallRuleTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenFirewallRuleCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenFirewallRule(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void deleteTungstenServiceGroupTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenServiceGroupCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenServiceGroup(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
 
     @Test
     public void deleteTungstenAddressGroupTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenAddressGroupCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteTungstenAddressGroup(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
@@ -1586,9 +1499,9 @@
         Network network = mock(Network.class);
         Vlan vlan = mock(Vlan.class);
         AccountVO accountVO = mock(AccountVO.class);
-        TungstenAnswer createTungstenSharedNetworkAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer addNetworkSubnetAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer getTungstenNetworkDnsAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer createTungstenSharedNetworkAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer addNetworkSubnetAnswer = MockTungstenAnswerFactory.get(true);
+        TungstenAnswer getTungstenNetworkDnsAnswer = MockTungstenAnswerFactory.get(true);
         NetworkDetailVO networkDetailVO = mock(NetworkDetailVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
         HostVO hostVO = mock(HostVO.class);
@@ -1600,9 +1513,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenNetworkCommand.class), anyLong())).thenReturn(createTungstenSharedNetworkAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenNetworkSubnetCommand.class), anyLong())).thenReturn(addNetworkSubnetAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenNetworkDnsCommand.class), anyLong())).thenReturn(getTungstenNetworkDnsAnswer);
-        when(createTungstenSharedNetworkAnswer.getResult()).thenReturn(true);
-        when(addNetworkSubnetAnswer.getResult()).thenReturn(true);
-        when(getTungstenNetworkDnsAnswer.getResult()).thenReturn(true);
         when(network.getMode()).thenReturn(Networks.Mode.Dhcp);
         when(network.getCidr()).thenReturn("192.168.100.0/24");
         when(vlan.getIpRange()).thenReturn("192.168.100.100-192.168.100.200");
@@ -1616,10 +1526,6 @@
         when(vlan.getIp6Cidr()).thenReturn("fd00::1/64");
         when(vlan.getIp6Range()).thenReturn("fd00::100-fd00::200");
         when(getTungstenNetworkDnsAnswer.getDetails()).thenReturn("192.168.1.150");
-        when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
-        when(network.getGuestType()).thenReturn(Network.GuestType.Shared);
-        when(ipAddressDao.findByIpAndDcId(anyLong(), anyString())).thenReturn(ipAddressVO);
-        when(ipAddressDao.mark(anyLong(), any(Ip.class))).thenReturn(true);
         when(createTungstenSharedNetworkAnswer.getApiObjectBase()).thenReturn(apiObjectBase);
         when(apiObjectBase.getQualifiedName()).thenReturn(List.of("network"));
         when(tungstenProviderVO.getGateway()).thenReturn("192.168.100.1");
@@ -1634,12 +1540,12 @@
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
         TungstenProviderVO tungstenProviderVO = mock(TungstenProviderVO.class);
         TungstenAnswer getTungstenSecurityGroupAnswer = mock(TungstenAnswer.class);
-        TungstenAnswer addTungstenVmToSecurityGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenVmToSecurityGroupAnswer = MockTungstenAnswerFactory.get(true);
         net.juniper.tungsten.api.types.SecurityGroup securityGroup = mock(net.juniper.tungsten.api.types.SecurityGroup.class);
         NicVO nicVO = mock(NicVO.class);
         SecurityGroupRuleVO securityGroupRuleVO = mock(SecurityGroupRuleVO.class);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
-        TungstenAnswer addTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer addTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(dataCenterVO.isSecurityGroupEnabled()).thenReturn(true);
@@ -1648,9 +1554,6 @@
         when(tungstenFabricUtils.sendTungstenCommand(any(GetTungstenSecurityGroupCommand.class), anyLong())).thenReturn(getTungstenSecurityGroupAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenVmToSecurityGroupCommand.class), anyLong())).thenReturn(addTungstenVmToSecurityGroupAnswer);
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(addTungstenSecurityGroupRuleAnswer);
-        when(getTungstenSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(addTungstenVmToSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(addTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
         when(getTungstenSecurityGroupAnswer.getApiObjectBase()).thenReturn(securityGroup);
         when(nicDao.findDefaultNicForVM(anyLong())).thenReturn(nicVO);
         when(nicVO.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.TUNGSTEN.toUri("tf"));
@@ -1670,37 +1573,21 @@
         VMInstanceVO vmInstanceVO = mock(VMInstanceVO.class);
         DataCenterVO dataCenterVO = mock(DataCenterVO.class);
         SecurityGroupVO securityGroupVO = mock(SecurityGroupVO.class);
-        TungstenAnswer removeTungstenVmFromSecurityGroupAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenVmFromSecurityGroupAnswer = MockTungstenAnswerFactory.get(true);
         NicVO nicVO = mock(NicVO.class);
         SecurityGroupRuleVO securityGroupRuleVO = mock(SecurityGroupRuleVO.class);
-        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer removeTungstenSecurityGroupRuleAnswer = MockTungstenAnswerFactory.get(true);
         TungstenSecurityGroupRuleVO tungstenSecurityGroupRuleVO = mock(TungstenSecurityGroupRuleVO.class);
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(dataCenterVO.isSecurityGroupEnabled()).thenReturn(true);
-        when(securityGroupManager.getSecurityGroupsForVm(anyLong())).thenReturn(List.of(securityGroupVO));
-        when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenVmFromSecurityGroupCommand.class), anyLong())).thenReturn(removeTungstenVmFromSecurityGroupAnswer);
-        when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenSecurityGroupRuleCommand.class), anyLong())).thenReturn(removeTungstenSecurityGroupRuleAnswer);
-        when(removeTungstenVmFromSecurityGroupAnswer.getResult()).thenReturn(true);
-        when(removeTungstenSecurityGroupRuleAnswer.getResult()).thenReturn(true);
-        when(nicDao.findDefaultNicForVM(anyLong())).thenReturn(nicVO);
-        when(nicVO.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.TUNGSTEN.toUri("tf"));
-        when(securityGroupRuleDao.listByAllowedSecurityGroupId(anyLong())).thenReturn(List.of(securityGroupRuleVO));
-        when(nicVO.getIPv4Address()).thenReturn("192.168.100.100");
-        when(nicVO.getIPv6Address()).thenReturn("fd00::1");
-        when(nicVO.getSecondaryIp()).thenReturn(true);
-        when(nicSecIpDao.getSecondaryIpAddressesForNic(anyLong())).thenReturn(List.of("192.168.100.200"));
-        when(securityGroupRuleVO.getProtocol()).thenReturn(NetUtils.ALL_PROTO);
-        when(tungstenSecurityGroupRuleDao.expunge(anyLong())).thenReturn(true);
-        when(tungstenSecurityGroupRuleDao.listByRuleTarget(anyString())).thenReturn(List.of(tungstenSecurityGroupRuleVO));
-        when(securityGroupDao.findById(anyLong())).thenReturn(securityGroupVO);
 
         assertTrue(tungstenService.removeTungstenVmSecurityGroup(vmInstanceVO));
     }
 
     @Test
     public void createRoutingLogicalRouterTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
         TungstenLogicalRouter tungstenLogicalRouter = mock(TungstenLogicalRouter.class);
         LogicalRouter logicalRouter = mock(LogicalRouter.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1708,7 +1595,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(CreateTungstenRoutingLogicalRouterCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
         when(tungstenAnswer.getTungstenModel()).thenReturn(tungstenLogicalRouter);
         when(tungstenLogicalRouter.getLogicalRouter()).thenReturn(logicalRouter);
         when(tungstenLogicalRouter.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1719,7 +1605,7 @@
     @Test
     public void addNetworkGatewayToLogicalRouterTest() {
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
         TungstenLogicalRouter tungstenLogicalRouter = mock(TungstenLogicalRouter.class);
         LogicalRouter logicalRouter = mock(LogicalRouter.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1729,7 +1615,6 @@
         when(networkDao.findByUuid(anyString())).thenReturn(networkVO);
         when(ipAddressManager.acquireLastGuestIpAddress(any(Network.class))).thenReturn("192.168.100.100");
         when(tungstenFabricUtils.sendTungstenCommand(any(AddTungstenNetworkGatewayToLogicalRouterCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
         when(tungstenAnswer.getTungstenModel()).thenReturn(tungstenLogicalRouter);
         when(tungstenLogicalRouter.getLogicalRouter()).thenReturn(logicalRouter);
         when(tungstenLogicalRouter.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1739,7 +1624,7 @@
 
     @Test
     public void listRoutingLogicalRouterTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
         TungstenLogicalRouter tungstenLogicalRouter = mock(TungstenLogicalRouter.class);
         LogicalRouter logicalRouter = mock(LogicalRouter.class);
         VirtualNetwork virtualNetwork = mock(VirtualNetwork.class);
@@ -1747,7 +1632,6 @@
 
         when(dataCenterDao.findById(anyLong())).thenReturn(dataCenterVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(ListTungstenRoutingLogicalRouterCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
         when(tungstenAnswer.getTungstenModelList()).thenReturn(List.of(tungstenLogicalRouter));
         when(tungstenLogicalRouter.getLogicalRouter()).thenReturn(logicalRouter);
         when(tungstenLogicalRouter.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1758,7 +1642,7 @@
     @Test
     public void removeNetworkGatewayFromLogicalRouterTest() {
         NetworkVO networkVO = mock(NetworkVO.class);
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
         TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = mock(TungstenGuestNetworkIpAddressVO.class);
         TungstenLogicalRouter tungstenLogicalRouter = mock(TungstenLogicalRouter.class);
         LogicalRouter logicalRouter = mock(LogicalRouter.class);
@@ -1769,7 +1653,6 @@
         when(networkDao.findByUuid(anyString())).thenReturn(networkVO);
         when(tungstenGuestNetworkIpAddressDao.findByNetworkAndLogicalRouter(anyLong(), anyString())).thenReturn(tungstenGuestNetworkIpAddressVO);
         when(tungstenFabricUtils.sendTungstenCommand(any(RemoveTungstenNetworkGatewayFromLogicalRouterCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
         when(tungstenAnswer.getTungstenModel()).thenReturn(tungstenLogicalRouter);
         when(tungstenLogicalRouter.getLogicalRouter()).thenReturn(logicalRouter);
         when(tungstenLogicalRouter.getVirtualNetworkList()).thenReturn(List.of(virtualNetwork));
@@ -1779,10 +1662,9 @@
 
     @Test
     public void deleteLogicalRouterTest() {
-        TungstenAnswer tungstenAnswer = mock(TungstenAnswer.class);
+        TungstenAnswer tungstenAnswer = MockTungstenAnswerFactory.get(true);
 
         when(tungstenFabricUtils.sendTungstenCommand(any(DeleteTungstenRoutingLogicalRouterCommand.class), anyLong())).thenReturn(tungstenAnswer);
-        when(tungstenAnswer.getResult()).thenReturn(true);
 
         assertTrue(tungstenService.deleteLogicalRouter(1L, "948f421c-edde-4518-a391-09299cc25dc2"));
     }
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenVRouterApiTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenVRouterApiTest.java
index e717fbd..094ad96 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenVRouterApiTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/service/TungstenVRouterApiTest.java
@@ -20,27 +20,34 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.powermock.api.mockito.PowerMockito.mock;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import org.apache.cloudstack.network.tungsten.vrouter.Port;
 import org.apache.cloudstack.network.tungsten.vrouter.VRouterApiConnector;
 import org.apache.cloudstack.network.tungsten.vrouter.VRouterApiConnectorFactory;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.IOException;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(VRouterApiConnectorFactory.class)
+@RunWith(MockitoJUnitRunner.class)
 public class TungstenVRouterApiTest {
+    MockedStatic<VRouterApiConnectorFactory> vRouterApiConnectorFactoryMocked;
+
     @Before
     public void setup() {
-        mockStatic(VRouterApiConnectorFactory.class);
+        vRouterApiConnectorFactoryMocked = Mockito.mockStatic(VRouterApiConnectorFactory.class);
+    }
+
+    @After
+    public void tearDown() {
+        vRouterApiConnectorFactoryMocked.close();
     }
 
     @Test
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/IntrospectApiConnectorImplTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/IntrospectApiConnectorImplTest.java
index cf1cd68..85625ac 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/IntrospectApiConnectorImplTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/IntrospectApiConnectorImplTest.java
@@ -27,12 +27,13 @@
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
@@ -43,17 +44,26 @@
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({HttpClients.class, DocumentBuilderFactory.class})
+@RunWith(MockitoJUnitRunner.class)
 public class IntrospectApiConnectorImplTest {
     IntrospectApiConnector introspectApiConnector;
 
+    MockedStatic<HttpClients> httpClientsMocked;
+
+    MockedStatic<DocumentBuilderFactory> documentBuilderFactoryMocked;
+
     @Before
     public void setup() {
         VRouter vRouter = mock(VRouter.class);
         introspectApiConnector = new IntrospectApiConnectorImpl(vRouter);
-        PowerMockito.mockStatic(HttpClients.class);
-        PowerMockito.mockStatic(DocumentBuilderFactory.class);
+        httpClientsMocked =  Mockito.mockStatic(HttpClients.class);
+        documentBuilderFactoryMocked = Mockito.mockStatic(DocumentBuilderFactory.class);
+    }
+
+    @After
+    public void tearDown() {
+        httpClientsMocked.close();
+        documentBuilderFactoryMocked.close();
     }
 
     @Test
@@ -66,11 +76,11 @@
         DocumentBuilderFactory documentBuilderFactory = mock(DocumentBuilderFactory.class);
         DocumentBuilder documentBuilder = mock(DocumentBuilder.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
         when(httpEntity.getContent()).thenReturn(inputStream);
-        when(DocumentBuilderFactory.newInstance()).thenReturn(documentBuilderFactory);
+        documentBuilderFactoryMocked.when(DocumentBuilderFactory::newInstance).thenReturn(documentBuilderFactory);
         when(documentBuilderFactory.newDocumentBuilder()).thenReturn(documentBuilder);
         when(documentBuilder.parse(any(InputStream.class))).thenReturn(document);
 
@@ -81,7 +91,7 @@
     public void getSnhItfReqWithIOExceptionTest() throws Exception {
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertNull(introspectApiConnector.getSnhItfReq("948f421c-edde-4518-a391-09299cc25dc2"));
@@ -95,11 +105,9 @@
         InputStream inputStream = mock(InputStream.class);
         DocumentBuilderFactory documentBuilderFactory = mock(DocumentBuilderFactory.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
-        when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(httpEntity.getContent()).thenReturn(inputStream);
-        when(DocumentBuilderFactory.newInstance()).thenReturn(documentBuilderFactory);
+        documentBuilderFactoryMocked.when(DocumentBuilderFactory::newInstance).thenReturn(documentBuilderFactory);
         when(documentBuilderFactory.newDocumentBuilder()).thenThrow(ParserConfigurationException.class);
 
         assertNull(introspectApiConnector.getSnhItfReq("948f421c-edde-4518-a391-09299cc25dc2"));
@@ -114,11 +122,11 @@
         DocumentBuilderFactory documentBuilderFactory = mock(DocumentBuilderFactory.class);
         DocumentBuilder documentBuilder = mock(DocumentBuilder.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
         when(httpEntity.getContent()).thenReturn(inputStream);
-        when(DocumentBuilderFactory.newInstance()).thenReturn(documentBuilderFactory);
+        documentBuilderFactoryMocked.when(DocumentBuilderFactory::newInstance).thenReturn(documentBuilderFactory);
         when(documentBuilderFactory.newDocumentBuilder()).thenReturn(documentBuilder);
         when(documentBuilder.parse(any(InputStream.class))).thenThrow(SAXException.class);
 
diff --git a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/VRouterApiConnectorImplTest.java b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/VRouterApiConnectorImplTest.java
index 11a2d9c..7006912 100644
--- a/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/VRouterApiConnectorImplTest.java
+++ b/plugins/network-elements/tungsten/src/test/java/org/apache/cloudstack/network/tungsten/vrouter/VRouterApiConnectorImplTest.java
@@ -28,27 +28,37 @@
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.IOException;
 import java.util.Arrays;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({HttpClients.class, EntityUtils.class})
+@RunWith(MockitoJUnitRunner.class)
 public class VRouterApiConnectorImplTest {
     VRouterApiConnector vRouterApiConnector;
 
+    MockedStatic<EntityUtils> entityUtilsMocked;
+
+    MockedStatic<HttpClients> httpClientsMocked;
+
     @Before
     public void setup() {
         VRouter vRouter = mock(VRouter.class);
         vRouterApiConnector = new VRouterApiConnectorImpl(vRouter);
-        PowerMockito.mockStatic(HttpClients.class);
-        PowerMockito.mockStatic(EntityUtils.class);
+        httpClientsMocked = Mockito.mockStatic(HttpClients.class);
+        entityUtilsMocked = Mockito.mockStatic(EntityUtils.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        httpClientsMocked.close();
+        entityUtilsMocked.close();
     }
 
     @Test
@@ -58,10 +68,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
 
         assertTrue(vRouterApiConnector.addPort(port));
     }
@@ -71,7 +81,7 @@
         Port port = mock(Port.class);
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertFalse(vRouterApiConnector.addPort(port));
@@ -84,10 +94,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{error:404}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{error:404}");
 
         assertFalse(vRouterApiConnector.addPort(port));
     }
@@ -98,10 +108,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
 
         assertTrue(vRouterApiConnector.deletePort("948f421c-edde-4518-a391-09299cc25dc2"));
     }
@@ -110,7 +120,7 @@
     public void deletePortWithExceptionTest() throws Exception {
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertFalse(vRouterApiConnector.deletePort("948f421c-edde-4518-a391-09299cc25dc2"));
@@ -122,10 +132,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
 
         assertTrue(vRouterApiConnector.enablePort("948f421c-edde-4518-a391-09299cc25dc2"));
     }
@@ -134,7 +144,7 @@
     public void enablePortWithExceptionTest() throws Exception {
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertFalse(vRouterApiConnector.enablePort("948f421c-edde-4518-a391-09299cc25dc2"));
@@ -146,10 +156,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
 
         assertTrue(vRouterApiConnector.disablePort("948f421c-edde-4518-a391-09299cc25dc2"));
     }
@@ -158,7 +168,7 @@
     public void disablePortWithExceptionTest() throws Exception {
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertFalse(vRouterApiConnector.disablePort("948f421c-edde-4518-a391-09299cc25dc2"));
@@ -172,10 +182,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
 
         assertTrue(vRouterApiConnector.addGateway(Arrays.asList(gateway1, gateway2)));
     }
@@ -186,7 +196,7 @@
         Gateway gateway2 = mock(Gateway.class);
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertFalse(vRouterApiConnector.addGateway(Arrays.asList(gateway1, gateway2)));
@@ -200,10 +210,10 @@
         CloseableHttpResponse closeableHttpResponse = mock(CloseableHttpResponse.class);
         HttpEntity httpEntity = mock(HttpEntity.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenReturn(closeableHttpResponse);
         when(closeableHttpResponse.getEntity()).thenReturn(httpEntity);
-        when(EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
+        entityUtilsMocked.when(()-> EntityUtils.toString(any(HttpEntity.class))).thenReturn("{}");
 
         assertTrue(vRouterApiConnector.deleteGateway(Arrays.asList(gateway1, gateway2)));
     }
@@ -214,7 +224,7 @@
         Gateway gateway2 = mock(Gateway.class);
         CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
 
-        when(HttpClients.createDefault()).thenReturn(httpClient);
+        httpClientsMocked.when(HttpClients::createDefault).thenReturn(httpClient);
         when(httpClient.execute(any(HttpUriRequest.class))).thenThrow(IOException.class);
 
         assertFalse(vRouterApiConnector.deleteGateway(Arrays.asList(gateway1, gateway2)));
diff --git a/plugins/network-elements/tungsten/src/test/resources/log4j.properties b/plugins/network-elements/tungsten/src/test/resources/log4j.properties
index 8c012b1..27276fc 100644
--- a/plugins/network-elements/tungsten/src/test/resources/log4j.properties
+++ b/plugins/network-elements/tungsten/src/test/resources/log4j.properties
@@ -32,4 +32,3 @@
 #log4j.category.com.cloud.utils.db.Transaction=ALL
 log4j.category.org.apache.cloudstack.network.contrail=ALL
 log4j.category.com.cloud.network=ALL
-
diff --git a/plugins/network-elements/tungsten/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/network-elements/tungsten/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/network-elements/tungsten/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/network-elements/vxlan/pom.xml b/plugins/network-elements/vxlan/pom.xml
index f74270a..78c5307 100644
--- a/plugins/network-elements/vxlan/pom.xml
+++ b/plugins/network-elements/vxlan/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/network-elements/vxlan/src/main/resources/META-INF/cloudstack/vxlan/module.properties b/plugins/network-elements/vxlan/src/main/resources/META-INF/cloudstack/vxlan/module.properties
index 4c2c7f7..04457d8 100644
--- a/plugins/network-elements/vxlan/src/main/resources/META-INF/cloudstack/vxlan/module.properties
+++ b/plugins/network-elements/vxlan/src/main/resources/META-INF/cloudstack/vxlan/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=vxlan
-parent=network
\ No newline at end of file
+parent=network
diff --git a/plugins/outofbandmanagement-drivers/ipmitool/pom.xml b/plugins/outofbandmanagement-drivers/ipmitool/pom.xml
index 1680af4..db6e8a4 100644
--- a/plugins/outofbandmanagement-drivers/ipmitool/pom.xml
+++ b/plugins/outofbandmanagement-drivers/ipmitool/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml
index 1ce3dd4..596d8a4 100644
--- a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml
+++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/outofbandmanagement-drivers/redfish/pom.xml b/plugins/outofbandmanagement-drivers/redfish/pom.xml
index 4c9f01a..1d1b035 100644
--- a/plugins/outofbandmanagement-drivers/redfish/pom.xml
+++ b/plugins/outofbandmanagement-drivers/redfish/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 1c961f7..e1aa0c4 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <build>
         <plugins>
@@ -73,6 +73,9 @@
         <module>deployment-planners/user-concentrated-pod</module>
         <module>deployment-planners/user-dispersing</module>
 
+        <module>drs/cluster/balanced</module>
+        <module>drs/cluster/condensed</module>
+
         <module>event-bus/inmemory</module>
         <module>event-bus/kafka</module>
         <module>event-bus/rabbitmq</module>
@@ -115,6 +118,8 @@
         <module>outofbandmanagement-drivers/nested-cloudstack</module>
         <module>outofbandmanagement-drivers/redfish</module>
 
+        <module>shutdown</module>
+
         <module>storage/image/default</module>
         <module>storage/image/s3</module>
         <module>storage/image/sample</module>
@@ -128,12 +133,18 @@
         <module>storage/volume/scaleio</module>
         <module>storage/volume/linstor</module>
         <module>storage/volume/storpool</module>
+        <module>storage/volume/adaptive</module>
+        <module>storage/volume/flasharray</module>
+        <module>storage/volume/primera</module>
+        <module>storage/object/minio</module>
+        <module>storage/object/simulator</module>
 
 
         <module>storage-allocators/random</module>
 
         <module>user-authenticators/ldap</module>
         <module>user-authenticators/md5</module>
+        <module>user-authenticators/oauth2</module>
         <module>user-authenticators/pbkdf2</module>
         <module>user-authenticators/plain-text</module>
         <module>user-authenticators/saml2</module>
@@ -181,6 +192,30 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.5.2</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio-admin</artifactId>
+            <version>8.5.2</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-object</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
     <profiles>
         <profile>
diff --git a/plugins/shutdown/pom.xml b/plugins/shutdown/pom.xml
new file mode 100644
index 0000000..052ebf0
--- /dev/null
+++ b/plugins/shutdown/pom.xml
@@ -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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-shutdown</artifactId>
+    <name>Apache CloudStack Plugin - Safe Shutdown</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-utils</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/BaseShutdownActionCmd.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/BaseShutdownActionCmd.java
new file mode 100644
index 0000000..d7f4953
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/BaseShutdownActionCmd.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.api.command;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+
+
+import org.apache.cloudstack.api.response.ManagementServerResponse;
+import org.apache.cloudstack.shutdown.ShutdownManager;
+
+public abstract class BaseShutdownActionCmd extends BaseCmd {
+
+    @Inject
+    protected ShutdownManager shutdownManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, description = "the uuid of the management server", required = true)
+    private Long managementServerId;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getManagementServerId() {
+        return managementServerId;
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/CancelShutdownCmd.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/CancelShutdownCmd.java
new file mode 100644
index 0000000..fe6204f
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/CancelShutdownCmd.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.api.command;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.log4j.Logger;
+
+import com.cloud.user.Account;
+
+import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
+import org.apache.cloudstack.acl.RoleType;
+
+@APICommand(name = CancelShutdownCmd.APINAME,
+            description = "Cancels a triggered shutdown",
+            since = "4.19.0",
+            responseObject = ReadyForShutdownResponse.class,
+            requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+            authorized = {RoleType.Admin})
+
+public class CancelShutdownCmd extends BaseShutdownActionCmd {
+
+    public static final Logger LOG = Logger.getLogger(CancelShutdownCmd.class);
+    public static final String APINAME = "cancelShutdown";
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        final ReadyForShutdownResponse response = shutdownManager.cancelShutdown(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName("cancelshutdown");
+        setResponseObject(response);
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/PrepareForShutdownCmd.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/PrepareForShutdownCmd.java
new file mode 100644
index 0000000..01ea179
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/PrepareForShutdownCmd.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;
+
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.log4j.Logger;
+
+import com.cloud.user.Account;
+
+import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
+import org.apache.cloudstack.acl.RoleType;
+
+@APICommand(name = PrepareForShutdownCmd.APINAME,
+            description = "Prepares CloudStack for a safe manual shutdown by preventing new jobs from being accepted",
+            since = "4.19.0",
+            responseObject = ReadyForShutdownResponse.class,
+            requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+            authorized = {RoleType.Admin})
+public class PrepareForShutdownCmd extends BaseShutdownActionCmd {
+    public static final Logger LOG = Logger.getLogger(PrepareForShutdownCmd.class);
+    public static final String APINAME = "prepareForShutdown";
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        final ReadyForShutdownResponse response = shutdownManager.prepareForShutdown(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName("prepareforshutdown");
+        setResponseObject(response);
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/ReadyForShutdownCmd.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/ReadyForShutdownCmd.java
new file mode 100644
index 0000000..1e6b3e1
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/ReadyForShutdownCmd.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;
+
+import javax.inject.Inject;
+
+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.ManagementServerResponse;
+import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
+import org.apache.cloudstack.shutdown.ShutdownManager;
+import org.apache.log4j.Logger;
+import com.cloud.user.Account;
+
+@APICommand(name = ReadyForShutdownCmd.APINAME,
+            description = "Returns the status of CloudStack, whether a shutdown has been triggered and if ready to shutdown",
+            since = "4.19.0",
+            responseObject = ReadyForShutdownResponse.class,
+            requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class ReadyForShutdownCmd extends BaseCmd {
+    public static final Logger LOG = Logger.getLogger(ReadyForShutdownCmd.class);
+    public static final String APINAME = "readyForShutdown";
+
+    @Inject
+    private ShutdownManager shutdownManager;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, description = "the uuid of the management server")
+    private Long managementServerId;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getManagementServerId() {
+        return managementServerId;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        final ReadyForShutdownResponse response = shutdownManager.readyForShutdown(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName("readyforshutdown");
+        setResponseObject(response);
+    }
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/TriggerShutdownCmd.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/TriggerShutdownCmd.java
new file mode 100644
index 0000000..3abde0b
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/command/TriggerShutdownCmd.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;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.log4j.Logger;
+
+import com.cloud.user.Account;
+
+import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
+import org.apache.cloudstack.acl.RoleType;
+
+@APICommand(name = TriggerShutdownCmd.APINAME,
+            description = "Triggers an automatic safe shutdown of CloudStack by not accepting new jobs and shutting down when all pending jobbs have been completed. Triggers an immediate shutdown if forced",
+            since = "4.19.0",
+            responseObject = ReadyForShutdownResponse.class,
+            requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+            authorized = {RoleType.Admin})
+public class TriggerShutdownCmd extends BaseShutdownActionCmd {
+    public static final Logger LOG = Logger.getLogger(TriggerShutdownCmd.class);
+    public static final String APINAME = "triggerShutdown";
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        final ReadyForShutdownResponse response = shutdownManager.triggerShutdown(this);
+        response.setResponseName(getCommandName());
+        response.setObjectName("triggershutdown");
+        setResponseObject(response);
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/api/response/ReadyForShutdownResponse.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/response/ReadyForShutdownResponse.java
new file mode 100644
index 0000000..d1b2353
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/api/response/ReadyForShutdownResponse.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.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 ReadyForShutdownResponse extends BaseResponse {
+    @SerializedName(ApiConstants.READY_FOR_SHUTDOWN)
+    @Param(description = "Indicates whether CloudStack is ready to shutdown")
+    private Boolean readyForShutdown;
+
+    @SerializedName(ApiConstants.SHUTDOWN_TRIGGERED)
+    @Param(description = "Indicates whether a shutdown has been triggered")
+    private Boolean shutdownTriggered;
+
+    @SerializedName(ApiConstants.PENDING_JOBS_COUNT)
+    @Param(description = "The number of jobs in progress")
+    private Long pendingJobsCount;
+
+    @SerializedName(ApiConstants.MANAGEMENT_SERVER_ID)
+    @Param(description = "The id of the management server")
+    private Long msId;
+
+    public ReadyForShutdownResponse(Long msId, Boolean shutdownTriggered, Boolean readyForShutdown, long pendingJobsCount) {
+        this.msId = msId;
+        this.shutdownTriggered = shutdownTriggered;
+        this.readyForShutdown = readyForShutdown;
+        this.pendingJobsCount = pendingJobsCount;
+    }
+
+    public Boolean getShutdownTriggered() {
+        return this.shutdownTriggered;
+    }
+
+    public void setShutdownTriggered(Boolean shutdownTriggered) {
+        this.shutdownTriggered = shutdownTriggered;
+    }
+
+    public Boolean getReadyForShutdown() {
+        return this.readyForShutdown;
+    }
+
+    public void setReadyForShutdown(Boolean readyForShutdown) {
+        this.readyForShutdown = readyForShutdown;
+    }
+
+    public Long getPendingJobsCount() {
+        return this.pendingJobsCount;
+    }
+
+    public void setPendingJobsCount(Long pendingJobsCount) {
+        this.pendingJobsCount = pendingJobsCount;
+    }
+
+    public Long getMsId() {
+        return msId;
+    }
+
+    public void setMsId(Long msId) {
+        this.msId = msId;
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/ShutdownManager.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/ShutdownManager.java
new file mode 100644
index 0000000..22f43cb
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/ShutdownManager.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.shutdown;
+
+import org.apache.cloudstack.api.command.CancelShutdownCmd;
+import org.apache.cloudstack.api.command.PrepareForShutdownCmd;
+import org.apache.cloudstack.api.command.ReadyForShutdownCmd;
+import org.apache.cloudstack.api.command.TriggerShutdownCmd;
+import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
+
+public interface ShutdownManager {
+    // Returns the number of pending jobs for the given Management server msids.
+    // NOTE: This is the msid and NOT the id
+    long countPendingJobs(Long... msIds);
+
+    // Indicates whether a shutdown has been triggered on the current management server
+    boolean isShutdownTriggered();
+
+    // Indicates whether the current management server is preparing to shutdown
+    boolean isPreparingForShutdown();
+
+    // Triggers a shutdown on the current management server by not accepting any more async jobs and shutting down when there are no pending jobs
+    void triggerShutdown();
+
+    // Prepares the current management server to shutdown by not accepting any more async jobs
+    void prepareForShutdown();
+
+    // Cancels the shutdown on the current management server
+    void cancelShutdown();
+
+    // Returns whether the given ms can be shut down
+    ReadyForShutdownResponse readyForShutdown(Long managementserverid);
+
+    // Returns whether the any of the ms can be shut down and if a shutdown has been triggered on any running ms
+    ReadyForShutdownResponse readyForShutdown(ReadyForShutdownCmd cmd);
+
+    // Prepares the specified management server to shutdown by not accepting any more async jobs
+    ReadyForShutdownResponse prepareForShutdown(PrepareForShutdownCmd cmd);
+
+    // Cancels the shutdown on the specified management server
+    ReadyForShutdownResponse cancelShutdown(CancelShutdownCmd cmd);
+
+    // Triggers a shutdown on the specified management server by not accepting any more async jobs and shutting down when there are no pending jobs
+    ReadyForShutdownResponse triggerShutdown(TriggerShutdownCmd cmd);
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/ShutdownManagerImpl.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/ShutdownManagerImpl.java
new file mode 100644
index 0000000..b8f5fb5
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/ShutdownManagerImpl.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 org.apache.cloudstack.shutdown;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.api.command.CancelShutdownCmd;
+import org.apache.cloudstack.api.command.PrepareForShutdownCmd;
+import org.apache.cloudstack.api.command.ReadyForShutdownCmd;
+import org.apache.cloudstack.api.command.TriggerShutdownCmd;
+import org.apache.cloudstack.api.response.ReadyForShutdownResponse;
+import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.management.ManagementServerHost.State;
+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.log4j.Logger;
+
+import com.cloud.agent.api.Command;
+import com.cloud.cluster.ClusterManager;
+import com.cloud.cluster.ManagementServerHostVO;
+import com.cloud.cluster.dao.ManagementServerHostDao;
+import com.cloud.serializer.GsonHelper;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.gson.Gson;
+
+public class ShutdownManagerImpl extends ManagerBase implements ShutdownManager, PluggableService{
+
+    private static Logger logger = Logger.getLogger(ShutdownManagerImpl.class);
+    Gson gson;
+
+    @Inject
+    private AsyncJobManager jobManager;
+    @Inject
+    private ManagementServerHostDao msHostDao;
+    @Inject
+    private ClusterManager clusterManager;
+
+    private boolean shutdownTriggered = false;
+    private boolean preparingForShutdown = false;
+
+    private Timer timer = new Timer();
+    private TimerTask shutdownTask;
+
+    protected ShutdownManagerImpl() {
+        super();
+        gson = GsonHelper.getGson();
+    }
+
+    @Override
+    public boolean isShutdownTriggered() {
+        return shutdownTriggered;
+    }
+
+    @Override
+    public boolean isPreparingForShutdown() {
+        return preparingForShutdown;
+    }
+
+    @Override
+    public long countPendingJobs(Long... msIds) {
+        return jobManager.countPendingNonPseudoJobs(msIds);
+    }
+
+    @Override
+    public void triggerShutdown() {
+        if (this.shutdownTriggered) {
+            throw new CloudRuntimeException("A shutdown has already been triggered");
+        }
+        this.shutdownTriggered = true;
+        prepareForShutdown(true);
+    }
+
+    private void prepareForShutdown(boolean postTrigger) {
+        // Ensure we don't throw an error if triggering a shutdown after just preparing for it
+        if (!postTrigger && this.preparingForShutdown) {
+            throw new CloudRuntimeException("A shutdown has already been triggered");
+        }
+        this.preparingForShutdown = true;
+        jobManager.disableAsyncJobs();
+        if (this.shutdownTask != null) {
+            this.shutdownTask.cancel();
+            this.shutdownTask = null;
+        }
+        this.shutdownTask = new ShutdownTask(this);
+        timer.scheduleAtFixedRate(shutdownTask, 0, 30L * 1000);
+    }
+
+    @Override
+    public void prepareForShutdown() {
+        prepareForShutdown(false);
+    }
+
+    @Override
+    public void cancelShutdown() {
+        if (!this.preparingForShutdown) {
+            throw new CloudRuntimeException("A shutdown has not been triggered");
+        }
+
+        this.preparingForShutdown = false;
+        this.shutdownTriggered = false;
+        jobManager.enableAsyncJobs();
+        if (shutdownTask != null) {
+            shutdownTask.cancel();
+        }
+        shutdownTask = null;
+    }
+
+    @Override
+    public ReadyForShutdownResponse readyForShutdown(Long managementserverid) {
+        Long[] msIds = null;
+        boolean shutdownTriggeredAnywhere = false;
+        State[] shutdownTriggeredStates = {State.ShuttingDown, State.PreparingToShutDown, State.ReadyToShutDown};
+        if (managementserverid == null) {
+            List<ManagementServerHostVO> msHosts = msHostDao.listBy(shutdownTriggeredStates);
+            if (msHosts != null && !msHosts.isEmpty()) {
+                msIds = new Long[msHosts.size()];
+                for (int i = 0; i < msHosts.size(); i++) {
+                    msIds[i] = msHosts.get(i).getMsid();
+                }
+                shutdownTriggeredAnywhere = !msHosts.isEmpty();
+            }
+        } else {
+            ManagementServerHostVO msHost = msHostDao.findById(managementserverid);
+            msIds = new Long[]{msHost.getMsid()};
+            shutdownTriggeredAnywhere = Arrays.asList(shutdownTriggeredStates).contains(msHost.getState());
+        }
+        long pendingJobCount = countPendingJobs(msIds);
+        return new ReadyForShutdownResponse(managementserverid, shutdownTriggeredAnywhere, pendingJobCount == 0, pendingJobCount);
+    }
+
+    @Override
+    public ReadyForShutdownResponse readyForShutdown(ReadyForShutdownCmd cmd) {
+        return readyForShutdown(cmd.getManagementServerId());
+    }
+
+    @Override
+    public ReadyForShutdownResponse prepareForShutdown(PrepareForShutdownCmd cmd) {
+        ManagementServerHostVO msHost = msHostDao.findById(cmd.getManagementServerId());
+        final Command[] cmds = new Command[1];
+        cmds[0] = new PrepareForShutdownManagementServerHostCommand(msHost.getMsid());
+        String result = clusterManager.execute(String.valueOf(msHost.getMsid()), 0, gson.toJson(cmds), true);
+        logger.info("PrepareForShutdownCmd result : " + result);
+        if (!result.contains("Success")) {
+            throw new CloudRuntimeException(result);
+        }
+
+        msHost.setState(State.PreparingToShutDown);
+        msHostDao.persist(msHost);
+
+        return readyForShutdown(cmd.getManagementServerId());
+    }
+
+    @Override
+    public ReadyForShutdownResponse triggerShutdown(TriggerShutdownCmd cmd) {
+        ManagementServerHostVO msHost = msHostDao.findById(cmd.getManagementServerId());
+        final Command[] cmds = new Command[1];
+        cmds[0] = new TriggerShutdownManagementServerHostCommand(msHost.getMsid());
+        String result = clusterManager.execute(String.valueOf(msHost.getMsid()), 0, gson.toJson(cmds), true);
+        logger.info("TriggerShutdownCmd result : " + result);
+        if (!result.contains("Success")) {
+            throw new CloudRuntimeException(result);
+        }
+
+        msHost.setState(State.ShuttingDown);
+        msHostDao.persist(msHost);
+
+        return readyForShutdown(cmd.getManagementServerId());
+    }
+
+    @Override
+    public ReadyForShutdownResponse cancelShutdown(CancelShutdownCmd cmd) {
+        ManagementServerHostVO msHost = msHostDao.findById(cmd.getManagementServerId());
+        final Command[] cmds = new Command[1];
+        cmds[0] = new CancelShutdownManagementServerHostCommand(msHost.getMsid());
+        String result = clusterManager.execute(String.valueOf(msHost.getMsid()), 0, gson.toJson(cmds), true);
+        logger.info("CancelShutdownCmd result : " + result);
+        if (!result.contains("Success")) {
+            throw new CloudRuntimeException(result);
+        }
+
+        msHost.setState(State.Up);
+        msHostDao.persist(msHost);
+
+        return readyForShutdown(cmd.getManagementServerId());
+    }
+
+    @Override
+    public List<Class<?>> getCommands() {
+        final List<Class<?>> cmdList = new ArrayList<>();
+        cmdList.add(CancelShutdownCmd.class);
+        cmdList.add(PrepareForShutdownCmd.class);
+        cmdList.add(ReadyForShutdownCmd.class);
+        cmdList.add(TriggerShutdownCmd.class);
+        return cmdList;
+    }
+
+    private final class ShutdownTask extends TimerTask {
+
+        private ShutdownManager shutdownManager;
+
+        public ShutdownTask(ShutdownManager shutdownManager) {
+            this.shutdownManager = shutdownManager;
+        }
+
+        @Override
+        public void run() {
+            try {
+                Long totalPendingJobs = shutdownManager.countPendingJobs(ManagementServerNode.getManagementServerId());
+                String msg = String.format("Checking for triggered shutdown... shutdownTriggered [%b] AllowAsyncJobs [%b] PendingJobCount [%d]",
+                    shutdownManager.isShutdownTriggered(), shutdownManager.isPreparingForShutdown(), totalPendingJobs);
+                logger.info(msg);
+
+                // If the shutdown has been cancelled
+                if (!shutdownManager.isPreparingForShutdown()) {
+                    logger.info("Shutdown cancelled. Terminating the shutdown timer task");
+                    this.cancel();
+                    return;
+                }
+
+                // No more pending jobs. Good to terminate
+                if (totalPendingJobs == 0) {
+                    if (shutdownManager.isShutdownTriggered()) {
+                        logger.info("Shutting down now");
+                        System.exit(0);
+                    }
+                    if (shutdownManager.isPreparingForShutdown()) {
+                        logger.info("Ready to shutdown");
+                        ManagementServerHostVO msHost = msHostDao.findByMsid(ManagementServerNode.getManagementServerId());
+                        msHost.setState(State.ReadyToShutDown);
+                        msHostDao.persist(msHost);
+                    }
+                }
+
+                logger.info("Pending jobs. Trying again later");
+            } catch (final Exception e) {
+                logger.error("Error trying to run shutdown task", e);
+            }
+        }
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/BaseShutdownManagementServerHostCommand.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/BaseShutdownManagementServerHostCommand.java
new file mode 100644
index 0000000..8fe3331
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/BaseShutdownManagementServerHostCommand.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.shutdown.command;
+
+import com.cloud.agent.api.Command;
+
+public class BaseShutdownManagementServerHostCommand extends Command {
+    long msId;
+
+    public BaseShutdownManagementServerHostCommand(long msId) {
+        this.msId = msId;
+    }
+
+    public long getMsId() {
+        return msId;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/CancelShutdownManagementServerHostCommand.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/CancelShutdownManagementServerHostCommand.java
new file mode 100644
index 0000000..eef4444
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/CancelShutdownManagementServerHostCommand.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.shutdown.command;
+
+public class CancelShutdownManagementServerHostCommand extends BaseShutdownManagementServerHostCommand {
+
+    public CancelShutdownManagementServerHostCommand(long msId) {
+        super(msId);
+    }
+
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/PrepareForShutdownManagementServerHostCommand.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/PrepareForShutdownManagementServerHostCommand.java
new file mode 100644
index 0000000..32a9201d
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/PrepareForShutdownManagementServerHostCommand.java
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+
+package org.apache.cloudstack.shutdown.command;
+
+public class PrepareForShutdownManagementServerHostCommand extends BaseShutdownManagementServerHostCommand {
+
+    public PrepareForShutdownManagementServerHostCommand(long msId) {
+        super(msId);
+    }
+}
diff --git a/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/TriggerShutdownManagementServerHostCommand.java b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/TriggerShutdownManagementServerHostCommand.java
new file mode 100644
index 0000000..e0d1879
--- /dev/null
+++ b/plugins/shutdown/src/main/java/org/apache/cloudstack/shutdown/command/TriggerShutdownManagementServerHostCommand.java
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+
+package org.apache.cloudstack.shutdown.command;
+
+public class TriggerShutdownManagementServerHostCommand extends BaseShutdownManagementServerHostCommand {
+
+    public TriggerShutdownManagementServerHostCommand(long msId) {
+        super(msId);
+    }
+}
diff --git a/plugins/shutdown/src/main/resources/META-INF/cloudstack/shutdown/module.properties b/plugins/shutdown/src/main/resources/META-INF/cloudstack/shutdown/module.properties
new file mode 100644
index 0000000..fd85c30
--- /dev/null
+++ b/plugins/shutdown/src/main/resources/META-INF/cloudstack/shutdown/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=shutdown
+parent=api
diff --git a/plugins/shutdown/src/main/resources/META-INF/cloudstack/shutdown/spring-shutdown-context.xml b/plugins/shutdown/src/main/resources/META-INF/cloudstack/shutdown/spring-shutdown-context.xml
new file mode 100644
index 0000000..5318b3b
--- /dev/null
+++ b/plugins/shutdown/src/main/resources/META-INF/cloudstack/shutdown/spring-shutdown-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd"
+>
+
+    <bean id="shutdownManager" class="org.apache.cloudstack.shutdown.ShutdownManagerImpl" >
+      <property name="name" value="shutdownManager" />
+    </bean>
+
+</beans>
diff --git a/plugins/shutdown/src/test/java/org/apache/cloudstack/shutdown/ShutdownManagerImplTest.java b/plugins/shutdown/src/test/java/org/apache/cloudstack/shutdown/ShutdownManagerImplTest.java
new file mode 100644
index 0000000..9f75251
--- /dev/null
+++ b/plugins/shutdown/src/test/java/org/apache/cloudstack/shutdown/ShutdownManagerImplTest.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.shutdown;
+
+import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+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.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class ShutdownManagerImplTest {
+
+    @Spy
+    @InjectMocks
+    ShutdownManagerImpl spy;
+
+    @Mock
+    AsyncJobManager jobManagerMock;
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    private long prepareCountPendingJobs() {
+        long expectedCount = 1L;
+        Mockito.doReturn(expectedCount).when(jobManagerMock).countPendingNonPseudoJobs(1L);
+        return expectedCount;
+    }
+
+    @Test
+    public void countPendingJobs() {
+        long expectedCount = prepareCountPendingJobs();
+        long count = spy.countPendingJobs(1L);
+        Assert.assertEquals(expectedCount, count);
+    }
+
+    @Test
+    public void cancelShutdown() {
+        Assert.assertThrows(CloudRuntimeException.class, () -> {
+            spy.cancelShutdown();
+        });
+    }
+
+    @Test
+    public void prepareForShutdown() {
+        Mockito.doNothing().when(jobManagerMock).disableAsyncJobs();
+        spy.prepareForShutdown();
+        Mockito.verify(jobManagerMock).disableAsyncJobs();
+
+        Assert.assertThrows(CloudRuntimeException.class, () -> {
+            spy.prepareForShutdown();
+        });
+
+
+        Mockito.doNothing().when(jobManagerMock).enableAsyncJobs();
+        spy.cancelShutdown();
+        Mockito.verify(jobManagerMock).enableAsyncJobs();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+}
diff --git a/plugins/storage-allocators/random/pom.xml b/plugins/storage-allocators/random/pom.xml
index 4b1bb2b..de2d5d1 100644
--- a/plugins/storage-allocators/random/pom.xml
+++ b/plugins/storage-allocators/random/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java b/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java
index 9787e61..87a6bf5 100644
--- a/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java
+++ b/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java
@@ -35,7 +35,7 @@
     private static final Logger s_logger = Logger.getLogger(RandomStoragePoolAllocator.class);
 
     @Override
-    public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck) {
+    public List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
         logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck);
 
         List<StoragePool> suitablePools = new ArrayList<StoragePool>();
diff --git a/plugins/storage/image/default/pom.xml b/plugins/storage/image/default/pom.xml
index 06f5460..0595968 100644
--- a/plugins/storage/image/default/pom.xml
+++ b/plugins/storage/image/default/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -53,9 +53,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java b/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java
index 8abf802..71fa2e9 100644
--- a/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java
+++ b/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java
@@ -70,7 +70,12 @@
         EndPoint ep = _epSelector.select(store);
         // Create Symlink at ssvm
         String path = installPath;
-        String uuid = UUID.randomUUID().toString() + "." + format.getFileExtension();
+        String uuid = UUID.randomUUID().toString();
+        if (format != null) {
+            uuid = uuid + "." + format.getFileExtension();
+        } else if (path.lastIndexOf(".") != -1) {
+            uuid = uuid + "." + path.substring(path.lastIndexOf(".") + 1);
+        }
         CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)store).getMountPoint(),
                                                                 path, uuid, dataObject == null ? null: dataObject.getTO());
         Answer ans = null;
@@ -82,7 +87,7 @@
             ans = ep.sendMessage(cmd);
         }
         if (ans == null || !ans.getResult()) {
-            String errorString = "Unable to create a link for entity at " + installPath + " on ssvm," + ans.getDetails();
+            String errorString = "Unable to create a link for entity at " + installPath + " on ssvm, " + ans.getDetails();
             s_logger.error(errorString);
             throw new CloudRuntimeException(errorString);
         }
diff --git a/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties b/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties
index 8381f6e..4bdbee2 100644
--- a/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties
+++ b/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-image-default
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/image/s3/pom.xml b/plugins/storage/image/s3/pom.xml
index 3021206..96ecab8 100644
--- a/plugins/storage/image/s3/pom.xml
+++ b/plugins/storage/image/s3/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties b/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties
index da571e2..6efe32c 100644
--- a/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties
+++ b/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-image-s3
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/image/sample/pom.xml b/plugins/storage/image/sample/pom.xml
index b5c31b6..ff979af 100644
--- a/plugins/storage/image/sample/pom.xml
+++ b/plugins/storage/image/sample/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -53,9 +53,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/image/swift/pom.xml b/plugins/storage/image/swift/pom.xml
index 296987c..db7c6b6 100644
--- a/plugins/storage/image/swift/pom.xml
+++ b/plugins/storage/image/swift/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -53,9 +53,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/image/swift/src/main/resources/META-INF/cloudstack/storage-image-swift/module.properties b/plugins/storage/image/swift/src/main/resources/META-INF/cloudstack/storage-image-swift/module.properties
index 1fa4be6..9cd56e7 100644
--- a/plugins/storage/image/swift/src/main/resources/META-INF/cloudstack/storage-image-swift/module.properties
+++ b/plugins/storage/image/swift/src/main/resources/META-INF/cloudstack/storage-image-swift/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-image-swift
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/object/minio/pom.xml b/plugins/storage/object/minio/pom.xml
new file mode 100644
index 0000000..74cc03c
--- /dev/null
+++ b/plugins/storage/object/minio/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-storage-object-minio</artifactId>
+    <name>Apache CloudStack Plugin - MinIO object storage provider</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-object</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-schema</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio-admin</artifactId>
+            <version>8.5.2</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java
new file mode 100644
index 0000000..b85383a
--- /dev/null
+++ b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImpl.java
@@ -0,0 +1,442 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.driver;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.inject.Inject;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.BaseObjectStoreDriverImpl;
+import org.apache.cloudstack.storage.object.Bucket;
+import org.apache.cloudstack.storage.object.BucketObject;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import com.amazonaws.services.s3.model.AccessControlList;
+import com.amazonaws.services.s3.model.BucketPolicy;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.storage.BucketVO;
+import com.cloud.storage.dao.BucketDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountDetailsDao;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+import io.minio.BucketExistsArgs;
+import io.minio.DeleteBucketEncryptionArgs;
+import io.minio.MakeBucketArgs;
+import io.minio.MinioClient;
+import io.minio.RemoveBucketArgs;
+import io.minio.SetBucketEncryptionArgs;
+import io.minio.SetBucketPolicyArgs;
+import io.minio.SetBucketVersioningArgs;
+import io.minio.admin.MinioAdminClient;
+import io.minio.admin.QuotaUnit;
+import io.minio.admin.UserInfo;
+import io.minio.admin.messages.DataUsageInfo;
+import io.minio.messages.SseConfiguration;
+import io.minio.messages.VersioningConfiguration;
+
+public class MinIOObjectStoreDriverImpl extends BaseObjectStoreDriverImpl {
+    private static final Logger s_logger = Logger.getLogger(MinIOObjectStoreDriverImpl.class);
+    protected static final String ACS_PREFIX = "acs";
+
+    @Inject
+    AccountDao _accountDao;
+
+    @Inject
+    AccountDetailsDao _accountDetailsDao;
+
+    @Inject
+    ObjectStoreDao _storeDao;
+
+    @Inject
+    BucketDao _bucketDao;
+
+    @Inject
+    ObjectStoreDetailsDao _storeDetailsDao;
+
+    private static final String ACCESS_KEY = "accesskey";
+    private static final String SECRET_KEY = "secretkey";
+
+    protected static final String MINIO_ACCESS_KEY = "minio-accesskey";
+    protected static final String MINIO_SECRET_KEY = "minio-secretkey";
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store) {
+        return null;
+    }
+
+    protected String getUserOrAccessKeyForAccount(Account account) {
+        return String.format("%s-%s", ACS_PREFIX, account.getUuid());
+    }
+
+    @Override
+    public Bucket createBucket(Bucket bucket, boolean objectLock) {
+        //ToDo Client pool mgmt
+        String bucketName = bucket.getName();
+        long storeId = bucket.getObjectStoreId();
+        long accountId = bucket.getAccountId();
+        MinioClient minioClient = getMinIOClient(storeId);
+        Account account = _accountDao.findById(accountId);
+
+        if ((_accountDetailsDao.findDetail(accountId, MINIO_ACCESS_KEY) == null)
+                || (_accountDetailsDao.findDetail(accountId, MINIO_SECRET_KEY) == null)) {
+            throw new CloudRuntimeException("Bucket access credentials unavailable for account: "+account.getAccountName());
+        }
+
+        try {
+            if(minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
+                throw new CloudRuntimeException("Bucket already exists with name "+ bucketName);
+            }
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        try {
+            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).objectLock(objectLock).build());
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+
+        List<BucketVO> buckets = _bucketDao.listByObjectStoreIdAndAccountId(storeId, accountId);
+        StringBuilder resources_builder = new StringBuilder();
+        for(BucketVO exitingBucket : buckets) {
+            resources_builder.append("\"arn:aws:s3:::"+exitingBucket.getName()+"/*\",\n");
+        }
+        resources_builder.append("\"arn:aws:s3:::"+bucketName+"/*\"\n");
+
+        String policy = " {\n" +
+                "     \"Statement\": [\n" +
+                "         {\n" +
+                "             \"Action\": \"s3:*\",\n" +
+                "             \"Effect\": \"Allow\",\n" +
+                "             \"Principal\": \"*\",\n" +
+                "             \"Resource\": ["+resources_builder+"]" +
+                "         }\n" +
+                "     ],\n" +
+                "     \"Version\": \"2012-10-17\"\n" +
+                " }";
+        MinioAdminClient minioAdminClient = getMinIOAdminClient(storeId);
+        String policyName = getUserOrAccessKeyForAccount(account) + "-policy";
+        String userName = getUserOrAccessKeyForAccount(account);
+        try {
+            minioAdminClient.addCannedPolicy(policyName, policy);
+            minioAdminClient.setPolicy(userName, false, policyName);
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        String accessKey = _accountDetailsDao.findDetail(accountId, MINIO_ACCESS_KEY).getValue();
+        String secretKey = _accountDetailsDao.findDetail(accountId, MINIO_SECRET_KEY).getValue();
+        ObjectStoreVO store = _storeDao.findById(storeId);
+        BucketVO bucketVO = _bucketDao.findById(bucket.getId());
+        bucketVO.setAccessKey(accessKey);
+        bucketVO.setSecretKey(secretKey);
+        bucketVO.setBucketURL(store.getUrl()+"/"+bucketName);
+        _bucketDao.update(bucket.getId(), bucketVO);
+        return bucket;
+    }
+
+    @Override
+    public List<Bucket> listBuckets(long storeId) {
+        MinioClient minioClient = getMinIOClient(storeId);
+        List<Bucket> bucketsList = new ArrayList<>();
+        try {
+            List<io.minio.messages.Bucket> minIOBuckets =  minioClient.listBuckets();
+            for(io.minio.messages.Bucket minIObucket : minIOBuckets) {
+                Bucket bucket = new BucketObject();
+                bucket.setName(minIObucket.name());
+                bucketsList.add(bucket);
+            }
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        return bucketsList;
+    }
+
+    @Override
+    public boolean deleteBucket(String bucketName, long storeId) {
+        MinioClient minioClient = getMinIOClient(storeId);
+        try {
+            if(!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
+                throw new CloudRuntimeException("Bucket doesn't exist: "+ bucketName);
+            }
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        //ToDo: check bucket empty
+        try {
+            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        return true;
+    }
+
+    @Override
+    public AccessControlList getBucketAcl(String bucketName, long storeId) {
+        return null;
+    }
+
+    @Override
+    public void setBucketAcl(String bucketName, AccessControlList acl, long storeId) {
+
+    }
+
+    @Override
+    public void setBucketPolicy(String bucketName, String policy, long storeId) {
+        String privatePolicy = "{\"Version\":\"2012-10-17\",\"Statement\":[]}";
+
+        StringBuilder builder = new StringBuilder();
+        builder.append("{\n");
+        builder.append("    \"Statement\": [\n");
+        builder.append("        {\n");
+        builder.append("            \"Action\": [\n");
+        builder.append("                \"s3:GetBucketLocation\",\n");
+        builder.append("                \"s3:ListBucket\"\n");
+        builder.append("            ],\n");
+        builder.append("            \"Effect\": \"Allow\",\n");
+        builder.append("            \"Principal\": \"*\",\n");
+        builder.append("            \"Resource\": \"arn:aws:s3:::"+bucketName+"\"\n");
+        builder.append("        },\n");
+        builder.append("        {\n");
+        builder.append("            \"Action\": \"s3:GetObject\",\n");
+        builder.append("            \"Effect\": \"Allow\",\n");
+        builder.append("            \"Principal\": \"*\",\n");
+        builder.append("            \"Resource\": \"arn:aws:s3:::"+bucketName+"/*\"\n");
+        builder.append("        }\n");
+        builder.append("    ],\n");
+        builder.append("    \"Version\": \"2012-10-17\"\n");
+        builder.append("}\n");
+
+        String publicPolicy = builder.toString();
+
+        //ToDo Support custom policy
+        String policyConfig = (policy.equalsIgnoreCase("public"))? publicPolicy : privatePolicy;
+
+        MinioClient minioClient = getMinIOClient(storeId);
+        try {
+            minioClient.setBucketPolicy(
+                    SetBucketPolicyArgs.builder().bucket(bucketName).config(policyConfig).build());
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+    }
+
+    @Override
+    public BucketPolicy getBucketPolicy(String bucketName, long storeId) {
+        return null;
+    }
+
+    @Override
+    public void deleteBucketPolicy(String bucketName, long storeId) {
+
+    }
+
+    protected void updateAccountCredentials(final long accountId, final String accessKey, final String secretKey, final boolean checkIfNotPresent) {
+        Map<String, String> details = _accountDetailsDao.findDetails(accountId);
+        boolean updateNeeded = false;
+        if (!checkIfNotPresent || StringUtils.isBlank(details.get(MINIO_ACCESS_KEY))) {
+            details.put(MINIO_ACCESS_KEY, accessKey);
+            updateNeeded = true;
+        }
+        if (StringUtils.isAllBlank(secretKey, details.get(MINIO_SECRET_KEY))) {
+            s_logger.error(String.format("Failed to retrieve secret key for MinIO user: %s from store and account details", accessKey));
+        }
+        if (StringUtils.isNotBlank(secretKey) && (!checkIfNotPresent || StringUtils.isBlank(details.get(MINIO_SECRET_KEY)))) {
+            details.put(MINIO_SECRET_KEY, secretKey);
+            updateNeeded = true;
+        }
+        if (!updateNeeded) {
+            return;
+        }
+        _accountDetailsDao.persist(accountId, details);
+    }
+
+    @Override
+    public boolean createUser(long accountId, long storeId) {
+        Account account = _accountDao.findById(accountId);
+        MinioAdminClient minioAdminClient = getMinIOAdminClient(storeId);
+        String accessKey = getUserOrAccessKeyForAccount(account);
+        // Check user exists
+        try {
+            UserInfo userInfo = minioAdminClient.getUserInfo(accessKey);
+            if(userInfo != null) {
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug(String.format("Skipping user creation as the user already exists in MinIO store: %s", accessKey));
+                }
+                updateAccountCredentials(accountId, accessKey, userInfo.secretKey(), true);
+                return true;
+            }
+        } catch (NoSuchAlgorithmException | IOException | InvalidKeyException e) {
+            s_logger.error(String.format("Error encountered while retrieving user: %s for existing MinIO store user check", accessKey), e);
+            return false;
+        } catch (RuntimeException e) { // MinIO lib may throw RuntimeException with code: XMinioAdminNoSuchUser
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug(String.format("Ignoring error encountered while retrieving user: %s for existing MinIO store user check", accessKey));
+            }
+            s_logger.trace("Exception during MinIO user check", e);
+        }
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("MinIO store user does not exist. Creating user: %s", accessKey));
+        }
+        KeyGenerator generator = null;
+        try {
+            generator = KeyGenerator.getInstance("HmacSHA1");
+        } catch (NoSuchAlgorithmException e) {
+            throw new CloudRuntimeException(e);
+        }
+        SecretKey key = generator.generateKey();
+        String secretKey = Base64.encodeBase64URLSafeString(key.getEncoded());
+        try {
+            minioAdminClient.addUser(accessKey, UserInfo.Status.ENABLED, secretKey, "", new ArrayList<String>());
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        // Store user credentials
+        updateAccountCredentials(accountId, accessKey, secretKey, false);
+        return true;
+    }
+
+    @Override
+    public boolean setBucketEncryption(String bucketName, long storeId) {
+        MinioClient minioClient = getMinIOClient(storeId);
+        try {
+            minioClient.setBucketEncryption(SetBucketEncryptionArgs.builder()
+                    .bucket(bucketName)
+                    .config(SseConfiguration.newConfigWithSseS3Rule())
+                    .build()
+            );
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean deleteBucketEncryption(String bucketName, long storeId) {
+        MinioClient minioClient = getMinIOClient(storeId);
+        try {
+            minioClient.deleteBucketEncryption(DeleteBucketEncryptionArgs.builder()
+                    .bucket(bucketName)
+                    .build()
+            );
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean setBucketVersioning(String bucketName, long storeId) {
+        MinioClient minioClient = getMinIOClient(storeId);
+        try {
+            minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
+                    .bucket(bucketName)
+                    .config(new VersioningConfiguration(VersioningConfiguration.Status.ENABLED, null))
+                    .build()
+            );
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean deleteBucketVersioning(String bucketName, long storeId) {
+        MinioClient minioClient = getMinIOClient(storeId);
+        try {
+            minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
+                    .bucket(bucketName)
+                    .config(new VersioningConfiguration(VersioningConfiguration.Status.SUSPENDED, null))
+                    .build()
+            );
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+        return true;
+    }
+
+    @Override
+    public void setBucketQuota(String bucketName, long storeId, long size) {
+
+        MinioAdminClient minioAdminClient = getMinIOAdminClient(storeId);
+        try {
+            minioAdminClient.setBucketQuota(bucketName, size, QuotaUnit.GB);
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+    }
+
+    @Override
+    public Map<String, Long> getAllBucketsUsage(long storeId) {
+        MinioAdminClient minioAdminClient = getMinIOAdminClient(storeId);
+        try {
+            DataUsageInfo dataUsageInfo = minioAdminClient.getDataUsageInfo();
+            return dataUsageInfo.bucketsSizes();
+        } catch (Exception e) {
+            throw new CloudRuntimeException(e);
+        }
+    }
+
+    protected MinioClient getMinIOClient(long storeId) {
+        ObjectStoreVO store = _storeDao.findById(storeId);
+        Map<String, String> storeDetails = _storeDetailsDao.getDetails(storeId);
+        String url = store.getUrl();
+        String accessKey = storeDetails.get(ACCESS_KEY);
+        String secretKey = storeDetails.get(SECRET_KEY);
+        MinioClient minioClient =
+                MinioClient.builder()
+                        .endpoint(url)
+                        .credentials(accessKey,secretKey)
+                        .build();
+        if(minioClient == null){
+            throw new CloudRuntimeException("Error while creating MinIO client");
+        }
+        return minioClient;
+    }
+
+    protected MinioAdminClient getMinIOAdminClient(long storeId) {
+        ObjectStoreVO store = _storeDao.findById(storeId);
+        Map<String, String> storeDetails = _storeDetailsDao.getDetails(storeId);
+        String url = store.getUrl();
+        String accessKey = storeDetails.get(ACCESS_KEY);
+        String secretKey = storeDetails.get(SECRET_KEY);
+        MinioAdminClient minioAdminClient =
+                MinioAdminClient.builder()
+                        .endpoint(url)
+                        .credentials(accessKey,secretKey)
+                        .build();
+        if(minioAdminClient == null){
+            throw new CloudRuntimeException("Error while creating MinIO client");
+        }
+        return minioAdminClient;
+    }
+}
diff --git a/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/MinIOObjectStoreLifeCycleImpl.java b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/MinIOObjectStoreLifeCycleImpl.java
new file mode 100644
index 0000000..fb7d1a6
--- /dev/null
+++ b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/MinIOObjectStoreLifeCycleImpl.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.storage.datastore.lifecycle;
+
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.utils.exception.CloudRuntimeException;
+import io.minio.MinioClient;
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
+import org.apache.cloudstack.storage.object.store.lifecycle.ObjectStoreLifeCycle;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MinIOObjectStoreLifeCycleImpl implements ObjectStoreLifeCycle {
+
+    private static final Logger s_logger = Logger.getLogger(MinIOObjectStoreLifeCycleImpl.class);
+
+    @Inject
+    ObjectStoreHelper objectStoreHelper;
+    @Inject
+    ObjectStoreProviderManager objectStoreMgr;
+
+    public MinIOObjectStoreLifeCycleImpl() {
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public DataStore initialize(Map<String, Object> dsInfos) {
+
+        String url = (String)dsInfos.get("url");
+        String name = (String)dsInfos.get("name");
+        String providerName = (String)dsInfos.get("providerName");
+        Map<String, String> details = (Map<String, String>)dsInfos.get("details");
+        if(details == null){
+            throw new CloudRuntimeException("MinIO credentials are missing");
+        }
+        String accessKey = details.get("accesskey");
+        String secretKey = details.get("secretkey");
+
+
+        Map<String, Object> objectStoreParameters = new HashMap();
+        objectStoreParameters.put("name", name);
+        objectStoreParameters.put("url", url);
+
+        objectStoreParameters.put("providerName", providerName);
+        objectStoreParameters.put("accesskey", accessKey);
+        objectStoreParameters.put("secretkey", secretKey);
+
+        //check credentials
+        MinioClient minioClient =
+                MinioClient.builder()
+                        .endpoint(url)
+                        .credentials(accessKey,secretKey)
+                        .build();
+        try {
+            // Test connection by listing buckets
+            minioClient.listBuckets();
+            s_logger.debug("Successfully connected to MinIO EndPoint: "+url);
+        } catch (Exception e) {
+            s_logger.debug("Error while initializing MinIO Object Store: "+e.getMessage());
+            throw new RuntimeException("Error while initializing MinIO Object Store. Invalid credentials or URL");
+        }
+
+        ObjectStoreVO objectStore = objectStoreHelper.createObjectStore(objectStoreParameters, details);
+        return objectStoreMgr.getObjectStore(objectStore.getId());
+    }
+
+    @Override
+    public boolean attachCluster(DataStore store, ClusterScope scope) {
+        return false;
+    }
+
+    @Override
+    public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
+        return false;
+    }
+
+    @Override
+    public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
+        return false;
+    }
+
+    @Override
+    public boolean maintain(DataStore store) {
+        return false;
+    }
+
+    @Override
+    public boolean cancelMaintain(DataStore store) {
+        return false;
+    }
+
+    @Override
+    public boolean deleteDataStore(DataStore store) {
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle#migrateToObjectStore(org.apache.cloudstack.engine.subsystem.api.storage.DataStore)
+     */
+    @Override
+    public boolean migrateToObjectStore(DataStore store) {
+        return false;
+    }
+
+}
diff --git a/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/provider/MinIOObjectStoreProviderImpl.java b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/provider/MinIOObjectStoreProviderImpl.java
new file mode 100644
index 0000000..abcc499
--- /dev/null
+++ b/plugins/storage/object/minio/src/main/java/org/apache/cloudstack/storage/datastore/provider/MinIOObjectStoreProviderImpl.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.storage.datastore.provider;
+
+import com.cloud.utils.component.ComponentContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectStoreProvider;
+import org.apache.cloudstack.storage.datastore.driver.MinIOObjectStoreDriverImpl;
+import org.apache.cloudstack.storage.datastore.lifecycle.MinIOObjectStoreLifeCycleImpl;
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
+import org.apache.cloudstack.storage.object.store.lifecycle.ObjectStoreLifeCycle;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@Component
+public class MinIOObjectStoreProviderImpl implements ObjectStoreProvider {
+
+    @Inject
+    ObjectStoreProviderManager storeMgr;
+    @Inject
+    ObjectStoreHelper helper;
+
+    private final String providerName = "MinIO";
+    protected ObjectStoreLifeCycle lifeCycle;
+    protected ObjectStoreDriver driver;
+
+    @Override
+    public DataStoreLifeCycle getDataStoreLifeCycle() {
+        return lifeCycle;
+    }
+
+    @Override
+    public String getName() {
+        return this.providerName;
+    }
+
+    @Override
+    public boolean configure(Map<String, Object> params) {
+        lifeCycle = ComponentContext.inject(MinIOObjectStoreLifeCycleImpl.class);
+        driver = ComponentContext.inject(MinIOObjectStoreDriverImpl.class);
+        storeMgr.registerDriver(this.getName(), driver);
+        return true;
+    }
+
+    @Override
+    public DataStoreDriver getDataStoreDriver() {
+        return this.driver;
+    }
+
+    @Override
+    public HypervisorHostListener getHostListener() {
+        return null;
+    }
+
+    @Override
+    public Set<DataStoreProviderType> getTypes() {
+        Set<DataStoreProviderType> types = new HashSet<DataStoreProviderType>();
+        types.add(DataStoreProviderType.OBJECT);
+        return types;
+    }
+}
diff --git a/plugins/storage/object/minio/src/main/resources/META-INF/cloudstack/storage-object-minio/module.properties b/plugins/storage/object/minio/src/main/resources/META-INF/cloudstack/storage-object-minio/module.properties
new file mode 100644
index 0000000..828454e
--- /dev/null
+++ b/plugins/storage/object/minio/src/main/resources/META-INF/cloudstack/storage-object-minio/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=storage-object-minio
+parent=storage
diff --git a/plugins/storage/object/minio/src/main/resources/META-INF/cloudstack/storage-object-minio/spring-storage-object-minio-context.xml b/plugins/storage/object/minio/src/main/resources/META-INF/cloudstack/storage-object-minio/spring-storage-object-minio-context.xml
new file mode 100644
index 0000000..30876de
--- /dev/null
+++ b/plugins/storage/object/minio/src/main/resources/META-INF/cloudstack/storage-object-minio/spring-storage-object-minio-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+    <bean id="minioStoreProviderImpl"
+        class="org.apache.cloudstack.storage.datastore.provider.MinIOObjectStoreProviderImpl" />
+</beans>
diff --git a/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.java b/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.java
new file mode 100644
index 0000000..ac88a0d
--- /dev/null
+++ b/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/driver/MinIOObjectStoreDriverImplTest.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.storage.datastore.driver;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.Bucket;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import com.cloud.storage.BucketVO;
+import com.cloud.storage.dao.BucketDao;
+import com.cloud.user.AccountDetailVO;
+import com.cloud.user.AccountDetailsDao;
+import com.cloud.user.AccountVO;
+import com.cloud.user.dao.AccountDao;
+
+import io.minio.BucketExistsArgs;
+import io.minio.MinioClient;
+import io.minio.RemoveBucketArgs;
+import io.minio.admin.MinioAdminClient;
+import io.minio.admin.UserInfo;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MinIOObjectStoreDriverImplTest {
+
+    @Spy
+    MinIOObjectStoreDriverImpl minioObjectStoreDriverImpl = new MinIOObjectStoreDriverImpl();
+
+    @Mock
+    MinioClient minioClient;
+    @Mock
+    MinioAdminClient minioAdminClient;
+    @Mock
+    ObjectStoreDao objectStoreDao;
+    @Mock
+    ObjectStoreVO objectStoreVO;
+    @Mock
+    ObjectStoreDetailsDao objectStoreDetailsDao;
+    @Mock
+    AccountDao accountDao;
+    @Mock
+    BucketDao bucketDao;
+    @Mock
+    AccountVO account;
+    @Mock
+    AccountDetailsDao accountDetailsDao;
+
+    Bucket bucket;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        minioObjectStoreDriverImpl._storeDao = objectStoreDao;
+        minioObjectStoreDriverImpl._storeDetailsDao = objectStoreDetailsDao;
+        minioObjectStoreDriverImpl._accountDao = accountDao;
+        minioObjectStoreDriverImpl._bucketDao = bucketDao;
+        minioObjectStoreDriverImpl._accountDetailsDao = accountDetailsDao;
+        bucket = new BucketVO();
+        bucket.setName("test-bucket");
+        when(objectStoreVO.getUrl()).thenReturn("http://localhost:9000");
+        when(objectStoreDao.findById(any())).thenReturn(objectStoreVO);
+    }
+
+    @Test
+    public void testCreateBucket() throws Exception {
+        doReturn(minioClient).when(minioObjectStoreDriverImpl).getMinIOClient(anyLong());
+        doReturn(minioAdminClient).when(minioObjectStoreDriverImpl).getMinIOAdminClient(anyLong());
+        when(bucketDao.listByObjectStoreIdAndAccountId(anyLong(), anyLong())).thenReturn(new ArrayList<BucketVO>());
+        when(account.getUuid()).thenReturn(UUID.randomUUID().toString());
+        when(accountDao.findById(anyLong())).thenReturn(account);
+        when(accountDetailsDao.findDetail(anyLong(),anyString())).
+                thenReturn(new AccountDetailVO(1L, "abc","def"));
+        when(bucketDao.findById(anyLong())).thenReturn(new BucketVO());
+        Bucket bucketRet = minioObjectStoreDriverImpl.createBucket(bucket, false);
+        assertEquals(bucketRet.getName(), bucket.getName());
+        verify(minioClient, times(1)).bucketExists(any());
+        verify(minioClient, times(1)).makeBucket(any());
+    }
+
+    @Test
+    public void testDeleteBucket() throws Exception {
+        String bucketName = "test-bucket";
+        doReturn(minioClient).when(minioObjectStoreDriverImpl).getMinIOClient(anyLong());
+        when(minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())).thenReturn(true);
+        doNothing().when(minioClient).removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
+        boolean success = minioObjectStoreDriverImpl.deleteBucket(bucketName, 1L);
+        assertTrue(success);
+        verify(minioClient, times(1)).bucketExists(any());
+        verify(minioClient, times(1)).removeBucket(any());
+    }
+
+    @Test
+    public void testCreateUserExisting() throws Exception {
+        String uuid = "uuid";
+        String accessKey = MinIOObjectStoreDriverImpl.ACS_PREFIX + "-" + uuid;
+        String secretKey = "secret";
+
+        doReturn(minioAdminClient).when(minioObjectStoreDriverImpl).getMinIOAdminClient(anyLong());
+        when(accountDao.findById(anyLong())).thenReturn(account);
+        when(account.getUuid()).thenReturn(uuid);
+        UserInfo info = mock(UserInfo.class);
+        when(info.secretKey()).thenReturn(secretKey);
+        when(minioAdminClient.getUserInfo(accessKey)).thenReturn(info);
+        final Map<String, String> persistedMap = new HashMap<>();
+        Mockito.doAnswer((Answer<Void>) invocation -> {
+            persistedMap.putAll((Map<String, String>)invocation.getArguments()[1]);
+            return null;
+        }).when(accountDetailsDao).persist(Mockito.anyLong(), Mockito.anyMap());
+        boolean result = minioObjectStoreDriverImpl.createUser(1L, 1L);
+        assertTrue(result);
+        assertEquals(accessKey, persistedMap.get(MinIOObjectStoreDriverImpl.MINIO_ACCESS_KEY));
+        assertEquals(secretKey, persistedMap.get(MinIOObjectStoreDriverImpl.MINIO_SECRET_KEY));
+    }
+}
diff --git a/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/provider/MinIOObjectStoreProviderImplTest.java b/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/provider/MinIOObjectStoreProviderImplTest.java
new file mode 100644
index 0000000..8651e00
--- /dev/null
+++ b/plugins/storage/object/minio/src/test/java/org/apache/cloudstack/storage/datastore/provider/MinIOObjectStoreProviderImplTest.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.storage.datastore.provider;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.DataStoreProviderType;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+public class MinIOObjectStoreProviderImplTest {
+
+    private MinIOObjectStoreProviderImpl minioObjectStoreProviderImpl;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        minioObjectStoreProviderImpl = new MinIOObjectStoreProviderImpl();
+    }
+
+    @Test
+    public void testGetName() {
+        String name = minioObjectStoreProviderImpl.getName();
+        assertEquals("MinIO", name);
+    }
+
+    @Test
+    public void testGetTypes() {
+        Set<DataStoreProviderType> types = minioObjectStoreProviderImpl.getTypes();
+        assertEquals(1, types.size());
+        assertEquals("OBJECT", types.toArray()[0].toString());
+    }
+}
diff --git a/plugins/storage/object/simulator/pom.xml b/plugins/storage/object/simulator/pom.xml
new file mode 100644
index 0000000..4c2f3ee
--- /dev/null
+++ b/plugins/storage/object/simulator/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-storage-object-simulator</artifactId>
+    <name>Apache CloudStack Plugin - Simulator object storage provider</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-object</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-schema</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio-admin</artifactId>
+            <version>8.5.2</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/driver/SimulatorObjectStoreDriverImpl.java b/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/driver/SimulatorObjectStoreDriverImpl.java
new file mode 100644
index 0000000..5f25a60
--- /dev/null
+++ b/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/driver/SimulatorObjectStoreDriverImpl.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.storage.datastore.driver;
+
+import com.amazonaws.services.s3.model.AccessControlList;
+import com.amazonaws.services.s3.model.BucketPolicy;
+import com.cloud.agent.api.to.DataStoreTO;
+import org.apache.cloudstack.storage.object.Bucket;
+import com.cloud.storage.BucketVO;
+import com.cloud.storage.dao.BucketDao;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.BaseObjectStoreDriverImpl;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SimulatorObjectStoreDriverImpl extends BaseObjectStoreDriverImpl {
+    private static final Logger s_logger = Logger.getLogger(SimulatorObjectStoreDriverImpl.class);
+
+    @Inject
+    ObjectStoreDao _storeDao;
+
+    @Inject
+    BucketDao _bucketDao;
+
+    private static final String ACCESS_KEY = "accesskey";
+    private static final String SECRET_KEY = "secretkey";
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store) {
+        return null;
+    }
+
+    @Override
+    public Bucket createBucket(Bucket bucket, boolean objectLock) {
+        String bucketName = bucket.getName();
+        long storeId = bucket.getObjectStoreId();
+        ObjectStoreVO store = _storeDao.findById(storeId);
+        BucketVO bucketVO = _bucketDao.findById(bucket.getId());
+        bucketVO.setAccessKey(ACCESS_KEY);
+        bucketVO.setSecretKey(SECRET_KEY);
+        bucketVO.setBucketURL(store.getUrl()+"/"+bucketName);
+        _bucketDao.update(bucket.getId(), bucketVO);
+        return bucket;
+    }
+
+    @Override
+    public List<Bucket> listBuckets(long storeId) {
+        List<Bucket> bucketsList = new ArrayList<>();
+        return bucketsList;
+    }
+
+    @Override
+    public boolean deleteBucket(String bucketName, long storeId) {
+        return true;
+    }
+
+    @Override
+    public AccessControlList getBucketAcl(String bucketName, long storeId) {
+        return null;
+    }
+
+    @Override
+    public void setBucketAcl(String bucketName, AccessControlList acl, long storeId) {
+
+    }
+
+    @Override
+    public void setBucketPolicy(String bucketName, String policy, long storeId) {
+
+    }
+
+    @Override
+    public BucketPolicy getBucketPolicy(String bucketName, long storeId) {
+        return null;
+    }
+
+    @Override
+    public void deleteBucketPolicy(String bucketName, long storeId) {
+
+    }
+
+    @Override
+    public boolean createUser(long accountId, long storeId) {
+        return true;
+    }
+
+    @Override
+    public boolean setBucketEncryption(String bucketName, long storeId) {
+        return true;
+    }
+
+    @Override
+    public boolean deleteBucketEncryption(String bucketName, long storeId) {
+        return true;
+    }
+
+    @Override
+    public boolean setBucketVersioning(String bucketName, long storeId) {
+        return true;
+    }
+
+    @Override
+    public boolean deleteBucketVersioning(String bucketName, long storeId) {
+        return true;
+    }
+
+    @Override
+    public void setBucketQuota(String bucketName, long storeId, long size) {
+
+    }
+
+    @Override
+    public Map<String, Long> getAllBucketsUsage(long storeId) {
+        return new HashMap<String, Long>();
+    }
+}
diff --git a/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SimulatorObjectStoreLifeCycleImpl.java b/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SimulatorObjectStoreLifeCycleImpl.java
new file mode 100644
index 0000000..34e928c
--- /dev/null
+++ b/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SimulatorObjectStoreLifeCycleImpl.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
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.lifecycle;
+
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.resource.Discoverer;
+import com.cloud.resource.ResourceManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
+import org.apache.cloudstack.storage.object.store.lifecycle.ObjectStoreLifeCycle;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SimulatorObjectStoreLifeCycleImpl implements ObjectStoreLifeCycle {
+
+    private static final Logger s_logger = Logger.getLogger(SimulatorObjectStoreLifeCycleImpl.class);
+    @Inject
+    protected ResourceManager _resourceMgr;
+    @Inject
+    protected ObjectStoreDao objectStoreDao;
+    @Inject
+    ObjectStoreHelper objectStoreHelper;
+    @Inject
+    ObjectStoreProviderManager objectStoreMgr;
+
+    protected List<? extends Discoverer> _discoverers;
+
+    public List<? extends Discoverer> getDiscoverers() {
+        return _discoverers;
+    }
+
+    public void setDiscoverers(List<? extends Discoverer> discoverers) {
+        this._discoverers = discoverers;
+    }
+
+    public SimulatorObjectStoreLifeCycleImpl() {
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public DataStore initialize(Map<String, Object> dsInfos) {
+
+        String url = (String)dsInfos.get("url");
+        String name = (String)dsInfos.get("name");
+        String providerName = (String)dsInfos.get("providerName");
+        Map<String, String> details = (Map<String, String>)dsInfos.get("details");
+
+        Map<String, Object> objectStoreParameters = new HashMap();
+        objectStoreParameters.put("name", name);
+        objectStoreParameters.put("url", url);
+        objectStoreParameters.put("providerName", providerName);
+
+        ObjectStoreVO ids = objectStoreHelper.createObjectStore(objectStoreParameters, details);
+        return objectStoreMgr.getObjectStore(ids.getId());
+    }
+
+    @Override
+    public boolean attachCluster(DataStore store, ClusterScope scope) {
+        return false;
+    }
+
+    @Override
+    public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
+        return false;
+    }
+
+    @Override
+    public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
+        return false;
+    }
+
+    @Override
+    public boolean maintain(DataStore store) {
+        return false;
+    }
+
+    @Override
+    public boolean cancelMaintain(DataStore store) {
+        return false;
+    }
+
+    @Override
+    public boolean deleteDataStore(DataStore store) {
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle#migrateToObjectStore(org.apache.cloudstack.engine.subsystem.api.storage.DataStore)
+     */
+    @Override
+    public boolean migrateToObjectStore(DataStore store) {
+        return false;
+    }
+
+}
diff --git a/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/provider/SimulatorObjectStoreProviderImpl.java b/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/provider/SimulatorObjectStoreProviderImpl.java
new file mode 100644
index 0000000..651fac4
--- /dev/null
+++ b/plugins/storage/object/simulator/src/main/java/org/apache/cloudstack/storage/datastore/provider/SimulatorObjectStoreProviderImpl.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.storage.datastore.provider;
+
+import com.cloud.utils.component.ComponentContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectStoreProvider;
+import org.apache.cloudstack.storage.datastore.driver.SimulatorObjectStoreDriverImpl;
+import org.apache.cloudstack.storage.datastore.lifecycle.SimulatorObjectStoreLifeCycleImpl;
+import org.apache.cloudstack.storage.object.ObjectStoreDriver;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreHelper;
+import org.apache.cloudstack.storage.object.datastore.ObjectStoreProviderManager;
+import org.apache.cloudstack.storage.object.store.lifecycle.ObjectStoreLifeCycle;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@Component
+public class SimulatorObjectStoreProviderImpl implements ObjectStoreProvider {
+
+    @Inject
+    ObjectStoreProviderManager storeMgr;
+    @Inject
+    ObjectStoreHelper helper;
+
+    private final String providerName = "Simulator";
+    protected ObjectStoreLifeCycle lifeCycle;
+    protected ObjectStoreDriver driver;
+
+    @Override
+    public DataStoreLifeCycle getDataStoreLifeCycle() {
+        return lifeCycle;
+    }
+
+    @Override
+    public String getName() {
+        return this.providerName;
+    }
+
+    @Override
+    public boolean configure(Map<String, Object> params) {
+        lifeCycle = ComponentContext.inject(SimulatorObjectStoreLifeCycleImpl.class);
+        driver = ComponentContext.inject(SimulatorObjectStoreDriverImpl.class);
+        storeMgr.registerDriver(this.getName(), driver);
+        return true;
+    }
+
+    @Override
+    public DataStoreDriver getDataStoreDriver() {
+        return this.driver;
+    }
+
+    @Override
+    public HypervisorHostListener getHostListener() {
+        return null;
+    }
+
+    @Override
+    public Set<DataStoreProviderType> getTypes() {
+        Set<DataStoreProviderType> types = new HashSet<DataStoreProviderType>();
+        types.add(DataStoreProviderType.OBJECT);
+        return types;
+    }
+}
diff --git a/plugins/storage/object/simulator/src/main/resources/META-INF/cloudstack/storage-object-simulator/module.properties b/plugins/storage/object/simulator/src/main/resources/META-INF/cloudstack/storage-object-simulator/module.properties
new file mode 100644
index 0000000..552f08c
--- /dev/null
+++ b/plugins/storage/object/simulator/src/main/resources/META-INF/cloudstack/storage-object-simulator/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=storage-object-simulator
+parent=storage
diff --git a/plugins/storage/object/simulator/src/main/resources/META-INF/cloudstack/storage-object-simulator/spring-storage-object-simulator-context.xml b/plugins/storage/object/simulator/src/main/resources/META-INF/cloudstack/storage-object-simulator/spring-storage-object-simulator-context.xml
new file mode 100644
index 0000000..863b881
--- /dev/null
+++ b/plugins/storage/object/simulator/src/main/resources/META-INF/cloudstack/storage-object-simulator/spring-storage-object-simulator-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+    <bean id="simulatorObjectStoreProviderImpl"
+        class="org.apache.cloudstack.storage.datastore.provider.SimulatorObjectStoreProviderImpl" />
+</beans>
diff --git a/plugins/storage/object/simulator/src/test/java/org/apache/cloudstack/storage/datastore/provider/SimulatorObjectStoreProviderImplTest.java b/plugins/storage/object/simulator/src/test/java/org/apache/cloudstack/storage/datastore/provider/SimulatorObjectStoreProviderImplTest.java
new file mode 100644
index 0000000..57c7eee
--- /dev/null
+++ b/plugins/storage/object/simulator/src/test/java/org/apache/cloudstack/storage/datastore/provider/SimulatorObjectStoreProviderImplTest.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.storage.datastore.provider;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+public class SimulatorObjectStoreProviderImplTest {
+
+    private SimulatorObjectStoreProviderImpl simulatorObjectStoreProviderImpl;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        simulatorObjectStoreProviderImpl = new SimulatorObjectStoreProviderImpl();
+    }
+
+    @Test
+    public void testGetName() {
+        String name = simulatorObjectStoreProviderImpl.getName();
+        assertEquals("Simulator", name);
+    }
+
+    @Test
+    public void testGetTypes() {
+        Set<DataStoreProvider.DataStoreProviderType> types = simulatorObjectStoreProviderImpl.getTypes();
+        assertEquals(1, types.size());
+        assertEquals("OBJECT", types.toArray()[0].toString());
+    }
+}
diff --git a/plugins/storage/volume/adaptive/README.md b/plugins/storage/volume/adaptive/README.md
new file mode 100644
index 0000000..041f1f1
--- /dev/null
+++ b/plugins/storage/volume/adaptive/README.md
@@ -0,0 +1,58 @@
+# CloudStack Volume Provider Adaptive Plugin Base
+
+The Adaptive Plugin Base is an abstract volume storage provider that
+provides a generic implementation for managing volumes that are exposed
+to hosts through FiberChannel and similar methods but managed independently
+through a storage API or interface.  The ProviderAdapter, and associated
+classes, provide a decoupled interface from the rest of
+Cloudstack that covers the exact actions needed
+to interface with a storage provider.  Each storage provider can extend
+and implement the ProviderAdapter without needing to understand the internal
+logic of volume management, database structure, etc.
+
+## Implement the Provider Interface
+To implement a provider, create another module -- or a standalone project --
+and implement the following interfaces from the **org.apache.cloudstack.storage.datastore.adapter** package:
+
+1. **ProviderAdapter** - this is the primary interface used to communicate with the storage provider when volume management actions are required.
+2. **ProviderAdapterFactory** - the implementation of this class creates the correct ProviderAdapter when needed.
+
+Follow Javadoc for each class on further instructions for implementing each function.
+
+## Implement the Primary Datastore Provider Plugin
+Once the provider interface is implemented, you will need to extend the **org.apache.cloudstack.storage.datastore.provider.AdaptiveProviderDatastoreProviderImpl** class.  When extending it, you simply need to implement a default
+constructor that creates an instance of the ProviderAdapterFactory implementation created in #2 above.  Once created, you need to call the parent constructor and pass the factory object.
+
+## Provide the Configuration for the Provider Plugin
+Lastly, you need to include a module file and Spring configuration for your Primary Datastore Provider Plugin class so Cloudstack will load it during startup.
+
+### Module Properties
+This provides the hint to Cloudstack to load this as a module during startup.
+```
+#resources/META-INF/cloudstack/storage-volume-<providername>/module.properties
+name=storage-volume-<providername>
+parent=storage
+```
+### Spring Bean Context Configuration
+This provides instructions of which provider implementation class to load when the Spring bean initilization is running.
+```
+<!-- resources/META-INF/cloudstack/storage-volume-<providername>/spring-storage-volume-<providername>-context.xml -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+
+    <bean id="<providername>DataStoreProvider"
+        class="org.apache.cloudstack.storage.datastore.provider.<providername>PrimaryDatastoreProviderImpl">
+	  </bean>
+</beans>
+```
+## Build and Deploy the Jar
+Once you build the new jar, start Cloudstack Management Server or, if a standalone jar, add it to the classpath before start.  You should now have a new storage provider of the designated name once Cloudstack finishes loading
+all configured modules.
diff --git a/plugins/storage/volume/adaptive/pom.xml b/plugins/storage/volume/adaptive/pom.xml
new file mode 100644
index 0000000..1c2e7fe
--- /dev/null
+++ b/plugins/storage/volume/adaptive/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-storage-volume-adaptive</artifactId>
+    <name>Apache CloudStack Plugin - Storage Volume Adaptive Base Provider</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-volume</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-snapshot</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-volume-default</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.java
new file mode 100644
index 0000000..0cd44cd
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapter.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.storage.datastore.adapter;
+
+import java.util.Map;
+
+/**
+ * A simple DataStore adaptive interface.  This interface allows the ManagedVolumeDataStoreDriverImpl
+ * to interact with the external provider without the provider needing to interface with any CloudStack
+ * objects, factories or database tables, simplifying the implementation and maintenance of the provider
+ * interface.
+ */
+public interface ProviderAdapter {
+    // some common keys across providers.  Provider code determines what to do with it
+    public static final String API_USERNAME_KEY = "api_username";
+    public static final String API_PASSWORD_KEY = "api_password";
+    public static final String API_TOKEN_KEY = "api_token";
+    public static final String API_PRIVATE_KEY = "api_privatekey";
+    public static final String API_URL_KEY = "api_url";
+    public static final String API_SKIP_TLS_VALIDATION_KEY = "api_skiptlsvalidation";
+    // one of: basicauth (default), apitoken, privatekey
+    public static final String API_AUTHENTICATION_TYPE_KEY = "api_authn_type";
+
+    /**
+     * Refresh the connector with the provided details
+     * @param details
+     */
+    public void refresh(Map<String,String> details);
+
+    /**
+     * Return if currently connected/configured properly, otherwise throws a RuntimeException
+     * with information about what is misconfigured
+     * @return
+     */
+    public void validate();
+
+    /**
+     * Forcefully remove/disconnect
+     */
+    public void disconnect();
+
+    /**
+     * Create a new volume on the storage provider
+     * @param context
+     * @param volume
+     * @param diskOffering
+     * @param sizeInBytes
+     * @return
+     */
+    public ProviderVolume create(ProviderAdapterContext context, ProviderAdapterDataObject volume, ProviderAdapterDiskOffering diskOffering, long sizeInBytes);
+
+    /**
+     * Attach the volume to the target object for the provided context.  Returns the scope-specific connection value (for example, the LUN)
+     * @param context
+     * @param request
+     * @return
+     */
+    public String attach(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Detach the host from the storage context
+     * @param context
+     * @param request
+     */
+    public void detach(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Delete the provided volume/object
+     * @param context
+     * @param request
+     */
+    public void delete(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Copy a source object to a destination volume.  The source object can be a Volume, Snapshot, or Template
+     */
+    public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolume, ProviderAdapterDataObject targetVolume);
+
+    /**
+     * Make a device-specific snapshot of the provided volume
+     */
+    public ProviderSnapshot snapshot(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolume, ProviderAdapterDataObject targetSnapshot);
+
+    /**
+     * Revert the snapshot to its base volume.  Replaces the base volume with the snapshot point on the storage array
+     * @param context
+     * @param request
+     * @return
+     */
+    public ProviderVolume revert(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Resize a volume
+     * @param context
+     * @param request
+     * @param totalNewSizeInBytes
+     */
+    public void resize(ProviderAdapterContext context, ProviderAdapterDataObject request, long totalNewSizeInBytes);
+
+    /**
+     * Return the managed volume info from storage system.
+     * @param context
+     * @param request
+     * @return ProviderVolume object or null if the object was not found but no errors were encountered.
+     */
+    public ProviderVolume getVolume(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Return the managed snapshot info from storage system
+     * @param context
+     * @param request
+     * @return ProviderSnapshot object or null if the object was not found but no errors were encountered.
+     */
+    public ProviderSnapshot getSnapshot(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Given an array-specific address, find the matching volume information from the array
+     * @param addressType
+     * @param address
+     * @return
+     */
+    public ProviderVolume getVolumeByAddress(ProviderAdapterContext context, ProviderVolume.AddressType addressType, String address);
+
+    /**
+     * Returns stats about the managed storage where the volumes and snapshots are created/managed
+     * @return
+     */
+    public ProviderVolumeStorageStats getManagedStorageStats();
+
+    /**
+     * Returns stats about a specific volume
+     * @return
+     */
+    public ProviderVolumeStats getVolumeStats(ProviderAdapterContext context, ProviderAdapterDataObject request);
+
+    /**
+     * Returns true if the given hostname is accessible to the storage provider.
+     * @param context
+     * @param request
+     * @return
+     */
+    public boolean canAccessHost(ProviderAdapterContext context, String hostname);
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterConstants.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterConstants.java
new file mode 100644
index 0000000..e5e9f77
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterConstants.java
@@ -0,0 +1,22 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter;
+
+public class ProviderAdapterConstants {
+    public static final String EXTERNAL_UUID = "external_uuid";
+    public static final String EXTERNAL_NAME = "external_name";
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterContext.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterContext.java
new file mode 100644
index 0000000..c726fd6
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterContext.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.storage.datastore.adapter;
+
+public class ProviderAdapterContext {
+    private String domainUuid;
+    private String domainName;
+    private Long domainId;
+    private String zoneUuid;
+    private String zoneName;
+    private Long zoneId;
+    private String accountUuid;
+    private String accountName;
+    private Long accountId;
+    public String getDomainUuid() {
+        return domainUuid;
+    }
+    public void setDomainUuid(String domainUuid) {
+        this.domainUuid = domainUuid;
+    }
+    public String getDomainName() {
+        return domainName;
+    }
+    public void setDomainName(String domainName) {
+        this.domainName = domainName;
+    }
+    public Long getDomainId() {
+        return domainId;
+    }
+    public void setDomainId(Long domainId) {
+        this.domainId = domainId;
+    }
+    public String getZoneUuid() {
+        return zoneUuid;
+    }
+    public void setZoneUuid(String zoneUuid) {
+        this.zoneUuid = zoneUuid;
+    }
+    public String getZoneName() {
+        return zoneName;
+    }
+    public void setZoneName(String zoneName) {
+        this.zoneName = zoneName;
+    }
+    public Long getZoneId() {
+        return zoneId;
+    }
+    public void setZoneId(Long zoneId) {
+        this.zoneId = zoneId;
+    }
+    public String getAccountUuid() {
+        return accountUuid;
+    }
+    public void setAccountUuid(String accountUuid) {
+        this.accountUuid = accountUuid;
+    }
+    public String getAccountName() {
+        return accountName;
+    }
+    public void setAccountName(String accountName) {
+        this.accountName = accountName;
+    }
+    public Long getAccountId() {
+        return accountId;
+    }
+    public void setAccountId(Long accountId) {
+        this.accountId = accountId;
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDataObject.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDataObject.java
new file mode 100644
index 0000000..16e0170
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDataObject.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.storage.datastore.adapter;
+
+/**
+ * Represents a translation object for transmitting meta-data about a volume,
+ * snapshot or template between cloudstack and the storage provider
+ */
+public class ProviderAdapterDataObject {
+    public enum Type {
+        VOLUME(),
+        SNAPSHOT(),
+        TEMPLATE(),
+        ARCHIVE()
+    }
+    /**
+     * The cloudstack UUID of the object
+     */
+    private String uuid;
+    /**
+     * The cloudstack name of the object (generated or user provided)
+     */
+    private String name;
+    /**
+     * The type of the object
+     */
+    private Type type;
+    /**
+     * The internal local ID of the object (not globally unique)
+     */
+    private Long id;
+    /**
+     * The external name assigned on the storage array. it may be dynamiically
+     * generated or derived from cloudstack data
+     */
+    private String externalName;
+
+    /**
+     * The external UUID of the object on the storage array. This may be different
+     * or the same as the cloudstack UUID depending on implementation.
+     */
+    private String externalUuid;
+
+    /**
+     * The internal (non-global) ID of the datastore this object is defined in
+     */
+    private Long dataStoreId;
+
+    /**
+     * The global ID of the datastore this object is defined in
+     */
+    private String dataStoreUuid;
+
+    /**
+     * The name of the data store this object is defined in
+     */
+    private String dataStoreName;
+
+    /**
+     * Represents the device connection id, typically a LUN, used to find the volume in conjunction with Address and AddressType.
+     */
+    private String externalConnectionId;
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    public String getExternalName() {
+        return externalName;
+    }
+
+    public void setExternalName(String externalName) {
+        this.externalName = externalName;
+    }
+
+    public String getExternalUuid() {
+        return externalUuid;
+    }
+
+    public void setExternalUuid(String externalUuid) {
+        this.externalUuid = externalUuid;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getDataStoreId() {
+        return dataStoreId;
+    }
+
+    public void setDataStoreId(Long dataStoreId) {
+        this.dataStoreId = dataStoreId;
+    }
+
+    public String getDataStoreUuid() {
+        return dataStoreUuid;
+    }
+
+    public void setDataStoreUuid(String dataStoreUuid) {
+        this.dataStoreUuid = dataStoreUuid;
+    }
+
+    public String getDataStoreName() {
+        return dataStoreName;
+    }
+
+    public void setDataStoreName(String dataStoreName) {
+        this.dataStoreName = dataStoreName;
+    }
+
+    public String getExternalConnectionId() {
+        return externalConnectionId;
+    }
+
+    public void setExternalConnectionId(String externalConnectionId) {
+        this.externalConnectionId = externalConnectionId;
+    }
+
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java
new file mode 100644
index 0000000..1db5efb
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java
@@ -0,0 +1,194 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.storage.datastore.adapter;
+
+import java.util.Date;
+import org.apache.commons.lang.NotImplementedException;
+import com.cloud.offering.DiskOffering;
+
+/**
+ * Wrapper Disk Offering that masks the cloudstack-dependent classes from the storage provider code
+ */
+public class ProviderAdapterDiskOffering {
+    private ProvisioningType type;
+    private DiskCacheMode diskCacheMode;
+    private DiskOffering hiddenDiskOffering;
+    private State state;
+    public ProviderAdapterDiskOffering(DiskOffering hiddenDiskOffering) {
+        this.hiddenDiskOffering = hiddenDiskOffering;
+        if (hiddenDiskOffering.getProvisioningType() != null) {
+            this.type = ProvisioningType.getProvisioningType(hiddenDiskOffering.getProvisioningType().toString());
+        }
+        if (hiddenDiskOffering.getCacheMode() != null) {
+            this.diskCacheMode = DiskCacheMode.getDiskCasehMode(hiddenDiskOffering.getCacheMode().toString());
+        }
+        if (hiddenDiskOffering.getState() != null) {
+            this.state = State.valueOf(hiddenDiskOffering.getState().toString());
+        }
+    }
+    public Long getBytesReadRate() {
+        return hiddenDiskOffering.getBytesReadRate();
+    }
+    public Long getBytesReadRateMax() {
+        return hiddenDiskOffering.getBytesReadRateMax();
+    }
+    public Long getBytesReadRateMaxLength() {
+        return hiddenDiskOffering.getBytesReadRateMaxLength();
+    }
+    public Long getBytesWriteRate() {
+        return hiddenDiskOffering.getBytesWriteRate();
+    }
+    public Long getBytesWriteRateMax() {
+        return hiddenDiskOffering.getBytesWriteRateMax();
+    }
+    public Long getBytesWriteRateMaxLength() {
+        return hiddenDiskOffering.getBytesWriteRateMaxLength();
+    }
+    public DiskCacheMode getCacheMode() {
+        return diskCacheMode;
+    }
+    public Date getCreated() {
+        return hiddenDiskOffering.getCreated();
+    }
+    public long getDiskSize() {
+        return hiddenDiskOffering.getDiskSize();
+    }
+    public boolean getDiskSizeStrictness() {
+        return hiddenDiskOffering.getDiskSizeStrictness();
+    }
+    public String getDisplayText() {
+        return hiddenDiskOffering.getDisplayText();
+    }
+    public boolean getEncrypt() {
+        return hiddenDiskOffering.getEncrypt();
+    }
+    public Integer getHypervisorSnapshotReserve() {
+        return hiddenDiskOffering.getHypervisorSnapshotReserve();
+    }
+    public long getId() {
+        return hiddenDiskOffering.getId();
+    }
+    public Long getIopsReadRate() {
+        return hiddenDiskOffering.getIopsReadRate();
+    }
+    public Long getIopsReadRateMax() {
+        return hiddenDiskOffering.getIopsReadRateMax();
+    }
+    public Long getIopsReadRateMaxLength() {
+        return hiddenDiskOffering.getIopsReadRateMaxLength();
+    }
+    public Long getIopsWriteRate() {
+        return hiddenDiskOffering.getIopsWriteRate();
+    }
+    public Long getIopsWriteRateMax() {
+        return hiddenDiskOffering.getIopsWriteRateMax();
+    }
+    public Long getIopsWriteRateMaxLength() {
+        return hiddenDiskOffering.getIopsWriteRateMaxLength();
+    }
+    public Long getMaxIops() {
+        return hiddenDiskOffering.getMaxIops();
+    }
+    public Long getMinIops() {
+        return hiddenDiskOffering.getMinIops();
+    }
+    public String getName() {
+        return hiddenDiskOffering.getName();
+    }
+    public State getState() {
+        return state;
+    }
+    public String getTags() {
+        return hiddenDiskOffering.getTags();
+    }
+    public String[] getTagsArray() {
+        return hiddenDiskOffering.getTagsArray();
+    }
+    public String getUniqueName() {
+        return hiddenDiskOffering.getUniqueName();
+    }
+    public String getUuid() {
+        return hiddenDiskOffering.getUuid();
+    }
+    public ProvisioningType getType() {
+        return type;
+    }
+    public void setType(ProvisioningType type) {
+        this.type = type;
+    }
+
+    public static enum ProvisioningType {
+        THIN("thin"),
+        SPARSE("sparse"),
+        FAT("fat");
+
+        private final String provisionType;
+
+        private ProvisioningType(String provisionType){
+            this.provisionType = provisionType;
+        }
+
+        public String toString(){
+            return this.provisionType;
+        }
+
+        public static ProvisioningType getProvisioningType(String provisioningType){
+
+            if(provisioningType.equals(THIN.provisionType)){
+                return ProvisioningType.THIN;
+            } else if(provisioningType.equals(SPARSE.provisionType)){
+                return ProvisioningType.SPARSE;
+            } else if (provisioningType.equals(FAT.provisionType)){
+                return ProvisioningType.FAT;
+            } else {
+                throw new NotImplementedException("Invalid provisioning type specified: " + provisioningType);
+            }
+        }
+    }
+
+
+    enum State {
+        Inactive, Active,
+    }
+
+    enum DiskCacheMode {
+        NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough");
+
+        private final String _diskCacheMode;
+
+        DiskCacheMode(String cacheMode) {
+            _diskCacheMode = cacheMode;
+        }
+
+        @Override
+        public String toString() {
+            return _diskCacheMode;
+        }
+
+        public static DiskCacheMode getDiskCasehMode(String cacheMode) {
+            if (cacheMode.equals(NONE._diskCacheMode)) {
+                return NONE;
+            } else if (cacheMode.equals(WRITEBACK._diskCacheMode)) {
+                return WRITEBACK;
+            } else if (cacheMode.equals(WRITETHROUGH._diskCacheMode)) {
+                return WRITETHROUGH;
+            } else {
+                throw new NotImplementedException("Invalid cache mode specified: " + cacheMode);
+            }
+        }
+    };
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterFactory.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterFactory.java
new file mode 100644
index 0000000..13a843d
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterFactory.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.storage.datastore.adapter;
+
+import java.util.Map;
+
+public interface ProviderAdapterFactory {
+    public String getProviderName();
+    public ProviderAdapter create(String url, Map<String, String> details);
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderSnapshot.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderSnapshot.java
new file mode 100644
index 0000000..50262ae
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderSnapshot.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.storage.datastore.adapter;
+
+public interface ProviderSnapshot extends ProviderVolume {
+    /**
+     * Returns true if the provider supports directly attaching the snapshot.
+     * If false is returned, it indicates that cloudstack needs to perform
+     * a temporary volume copy prior to copying the snapshot to a new
+     * volume on another provider
+     * @return
+     */
+    public Boolean canAttachDirectly();
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolume.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolume.java
new file mode 100644
index 0000000..2557790
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolume.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.storage.datastore.adapter;
+
+public interface ProviderVolume {
+
+    public Boolean isDestroyed();
+    public String getId();
+    public void setId(String id);
+    public String getName();
+    public void setName(String name);
+    public Integer getPriority();
+    public void setPriority(Integer priority);
+    public String getState();
+    public AddressType getAddressType();
+    public void setAddressType(AddressType addressType);
+    public String getAddress();
+    public Long getAllocatedSizeInBytes();
+    public Long getUsedBytes();
+    public String getExternalUuid();
+    public String getExternalName();
+    public String getExternalConnectionId();
+    public enum AddressType {
+        FIBERWWN
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeNamer.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeNamer.java
new file mode 100644
index 0000000..5a72871
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeNamer.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.storage.datastore.adapter;
+
+public class ProviderVolumeNamer {
+
+    private static final String SNAPSHOT_PREFIX = "snap";
+    private static final String VOLUME_PREFIX = "vol";
+    private static final String TEMPLATE_PREFIX = "tpl";
+    /** Simple method to allow sharing storage setup, primarily in lab/testing environment */
+    private static final String ENV_PREFIX = System.getProperty("adaptive.storage.provider.envIdentifier");
+
+    public static String generateObjectName(ProviderAdapterContext context, ProviderAdapterDataObject obj) {
+        ProviderAdapterDataObject.Type objType = obj.getType();
+        String prefix = null;
+        if (objType == ProviderAdapterDataObject.Type.SNAPSHOT) {
+            prefix = SNAPSHOT_PREFIX;
+        } else if (objType == ProviderAdapterDataObject.Type.VOLUME) {
+            prefix = VOLUME_PREFIX;
+        } else if (objType == ProviderAdapterDataObject.Type.TEMPLATE) {
+            prefix = TEMPLATE_PREFIX;
+        } else {
+            throw new RuntimeException("Unknown ManagedDataObject type provided: " + obj.getType());
+        }
+
+        if (ENV_PREFIX != null) {
+            prefix = ENV_PREFIX + "-" + prefix;
+        }
+
+        return prefix + "-" + obj.getDataStoreId() + "-" + context.getDomainId() + "-" + context.getAccountId() + "-" + obj.getId();
+    }
+
+
+   public static String generateObjectComment(ProviderAdapterContext context, ProviderAdapterDataObject obj) {
+        return "CSInfo [Account=" + context.getAccountName()
+            + "; Domain=" + context.getDomainName()
+            + "; DomainUUID=" + context.getDomainUuid()
+            + "; Account=" + context.getAccountName()
+            + "; AccountUUID=" + context.getAccountUuid()
+            + "; ObjectEndUserName=" + obj.getName()
+            + "; ObjectUUID=" + obj.getUuid() + "]";
+    }
+
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeStats.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeStats.java
new file mode 100644
index 0000000..33638e1
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeStats.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.storage.datastore.adapter;
+
+public class ProviderVolumeStats {
+    private Long allocatedInBytes;
+    private Long virtualUsedInBytes;
+    private Long actualUsedInBytes;
+    private Long iops;
+    private Long throughput;
+    public Long getAllocatedInBytes() {
+        return allocatedInBytes;
+    }
+    public void setAllocatedInBytes(Long allocatedInBytes) {
+        this.allocatedInBytes = allocatedInBytes;
+    }
+    public Long getVirtualUsedInBytes() {
+        return virtualUsedInBytes;
+    }
+    public void setVirtualUsedInBytes(Long virtualUsedInBytes) {
+        this.virtualUsedInBytes = virtualUsedInBytes;
+    }
+    public Long getActualUsedInBytes() {
+        return actualUsedInBytes;
+    }
+    public void setActualUsedInBytes(Long actualUsedInBytes) {
+        this.actualUsedInBytes = actualUsedInBytes;
+    }
+    public Long getIops() {
+        return iops;
+    }
+    public void setIops(Long iops) {
+        this.iops = iops;
+    }
+    public Long getThroughput() {
+        return throughput;
+    }
+    public void setThroughput(Long throughput) {
+        this.throughput = throughput;
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeStorageStats.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeStorageStats.java
new file mode 100644
index 0000000..0624ef2
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderVolumeStorageStats.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.storage.datastore.adapter;
+
+public class ProviderVolumeStorageStats {
+    /**
+     * Total capacity in bytes currently physically used on the storage system within the scope of given API configuration
+     */
+    private long capacityInBytes;
+    /**
+     * Virtual amount of bytes allocated for use.  Typically what the users of the volume think they have before
+     * any compression, deduplication, or thin-provisioning semantics are accounted for.
+     */
+    private Long virtualUsedInBytes;
+    /**
+     * Actual physical bytes used on the storage system within the scope of the given API configuration
+     */
+    private Long actualUsedInBytes;
+    /**
+     * Current IOPS
+     */
+    private Long iops;
+    /**
+     * Current raw throughput
+     */
+    private Long throughput;
+    public Long getVirtualUsedInBytes() {
+        return virtualUsedInBytes;
+    }
+    public void setVirtualUsedInBytes(Long virtualUsedInBytes) {
+        this.virtualUsedInBytes = virtualUsedInBytes;
+    }
+    public Long getActualUsedInBytes() {
+        return actualUsedInBytes;
+    }
+    public void setActualUsedInBytes(Long actualUsedInBytes) {
+        this.actualUsedInBytes = actualUsedInBytes;
+    }
+    public Long getIops() {
+        return iops;
+    }
+    public void setIops(Long iops) {
+        this.iops = iops;
+    }
+    public Long getThroughput() {
+        return throughput;
+    }
+    public void setThroughput(Long throughput) {
+        this.throughput = throughput;
+    }
+    public Long getCapacityInBytes() {
+        return capacityInBytes;
+    }
+    public void setCapacityInBytes(Long capacityInBytes) {
+        this.capacityInBytes = capacityInBytes;
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java
new file mode 100644
index 0000000..d908d48
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/driver/AdaptiveDataStoreDriverImpl.java
@@ -0,0 +1,901 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.driver;
+
+import java.util.Map;
+import javax.inject.Inject;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+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.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CopyCmdAnswer;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterConstants;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterContext;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDataObject;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDiskOffering;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderSnapshot;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolume;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStats;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStorageStats;
+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.StoragePoolDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.provider.AdaptivePrimaryDatastoreAdapterFactoryMap;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.cloudstack.storage.snapshot.SnapshotObject;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.host.Host;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.projects.dao.ProjectDao;
+import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage.ImageFormat;
+
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VirtualMachine;
+
+public class AdaptiveDataStoreDriverImpl extends CloudStackPrimaryDataStoreDriverImpl {
+
+    static final Logger s_logger = Logger.getLogger(AdaptiveDataStoreDriverImpl.class);
+
+    private String providerName = null;
+
+    @Inject
+    AccountManager _accountMgr;
+    @Inject
+    DiskOfferingDao _diskOfferingDao;
+    @Inject
+    VolumeDao _volumeDao;
+    @Inject
+    PrimaryDataStoreDao _storagePoolDao;
+    @Inject
+    ProjectDao _projectDao;
+    @Inject
+    SnapshotDataStoreDao _snapshotDataStoreDao;
+    @Inject
+    SnapshotDetailsDao _snapshotDetailsDao;
+    @Inject
+    VolumeDetailsDao _volumeDetailsDao;
+    @Inject
+    VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject
+    AccountDao _accountDao;
+    @Inject
+    StoragePoolDetailsDao _storagePoolDetailsDao;
+    @Inject
+    SnapshotDao _snapshotDao;
+    @Inject
+    VMTemplateDao _vmTemplateDao;
+    @Inject
+    DataCenterDao _datacenterDao;
+    @Inject
+    DomainDao _domainDao;
+    @Inject
+    VolumeService _volumeService;
+
+    private AdaptivePrimaryDatastoreAdapterFactoryMap _adapterFactoryMap = null;
+
+    public AdaptiveDataStoreDriverImpl(AdaptivePrimaryDatastoreAdapterFactoryMap factoryMap) {
+        this._adapterFactoryMap = factoryMap;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data) {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store) {
+        return null;
+    }
+
+    public ProviderAdapter getAPI(StoragePool pool, Map<String, String> details) {
+        return _adapterFactoryMap.getAPI(pool.getUuid(), pool.getStorageProviderName(), details);
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject dataObject,
+            AsyncCompletionCallback<CreateCmdResult> callback) {
+        CreateCmdResult result = null;
+        try {
+            s_logger.info("Volume creation starting for data store [" + dataStore.getName() +
+                    "] and data object [" + dataObject.getUuid() + "] of type [" + dataObject.getType() + "]");
+
+            // quota size of the cloudbyte volume will be increased with the given
+            // HypervisorSnapshotReserve
+            Long volumeSizeBytes = dataObject.getSize();
+            // cloudstack talks bytes, primera talks MiB
+            StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+            Map<String, String> details = _storagePoolDao.getDetails(storagePool.getId());
+
+            ProviderAdapter api = getAPI(storagePool, details);
+            ProviderAdapterContext context = newManagedVolumeContext(dataObject);
+            ProviderAdapterDataObject dataIn = newManagedDataObject(dataObject, storagePool);
+            ProviderAdapterDiskOffering inDiskOffering = null;
+            // only get the offering if its a volume type.  If its a template type we skip this.
+            if (DataObjectType.VOLUME.equals(dataObject.getType())) {
+                // get the disk offering as provider may need to see details of this to
+                // provision the correct type of volume
+                VolumeVO volumeVO = _volumeDao.findById(dataObject.getId());
+                DiskOfferingVO diskOffering = _diskOfferingDao.findById(volumeVO.getDiskOfferingId());
+                if (diskOffering.isUseLocalStorage()) {
+                    throw new CloudRuntimeException(
+                            "Disk offering requires local storage but this storage provider does not suppport local storage.  Please contact the cloud adminstrator to have the disk offering configuration updated to avoid this conflict.");
+                }
+                inDiskOffering = new ProviderAdapterDiskOffering(diskOffering);
+            }
+
+            // if its a template and it already exist, just return the info -- may mean a previous attempt to
+            // copy this template failed after volume creation and its state has not advanced yet.
+            ProviderVolume volume = null;
+            if (DataObjectType.TEMPLATE.equals(dataObject.getType())) {
+                volume = api.getVolume(context, dataIn);
+                if (volume != null) {
+                    s_logger.info("Template volume already exists [" + dataObject.getUuid() + "]");
+                }
+            }
+
+            // create the volume if it didn't already exist
+            if (volume == null) {
+                // klunky - if this fails AND this detail property is set, it means upstream may have already created it
+                // in VolumeService and DataMotionStrategy tries to do it again before copying...
+                try {
+                    volume = api.create(context, dataIn, inDiskOffering, volumeSizeBytes);
+                } catch (Exception e) {
+                    VolumeDetailVO csId = _volumeDetailsDao.findDetail(dataObject.getId(), "cloneOfTemplate");
+                    if (csId != null && csId.getId() > 0) {
+                        volume = api.getVolume(context, dataIn);
+                    } else {
+                        throw e;
+                    }
+                }
+                s_logger.info("New volume created on remote storage for [" + dataObject.getUuid() + "]");
+            }
+
+            // set these from the discovered or created volume before proceeding
+            dataIn.setExternalName(volume.getExternalName());
+            dataIn.setExternalUuid(volume.getExternalUuid());
+
+            // add the volume to the host set
+            String connectionId = api.attach(context, dataIn);
+
+            // update the cloudstack metadata about the volume
+            persistVolumeOrTemplateData(storagePool, details, dataObject, volume, connectionId);
+
+            result = new CreateCmdResult(dataObject.getUuid(), new Answer(null));
+            result.setSuccess(true);
+            s_logger.info("Volume creation complete for [" + dataObject.getUuid() + "]");
+        } catch (Throwable e) {
+            s_logger.error("Volume creation  failed for dataObject [" + dataObject.getUuid() + "]: " + e.toString(), e);
+            result = new CreateCmdResult(null, new Answer(null));
+            result.setResult(e.toString());
+            result.setSuccess(false);
+            throw new CloudRuntimeException(e.getMessage());
+        } finally {
+            if (callback != null)
+                callback.complete(result);
+        }
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject,
+            AsyncCompletionCallback<CommandResult> callback) {
+        s_logger.debug("Delete volume started");
+        CommandResult result = new CommandResult();
+        try {
+            StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+            Map<String, String> details = _storagePoolDao.getDetails(storagePool.getId());
+            ProviderAdapter api = getAPI(storagePool, details);
+            ProviderAdapterContext context = newManagedVolumeContext(dataObject);
+            ProviderAdapterDataObject inData = newManagedDataObject(dataObject, storagePool);
+            // skip adapter delete if neither external identifier is set.  Probably means the volume
+            // create failed before this chould be set
+            if (!(inData.getExternalName() == null && inData.getExternalUuid() == null)) {
+                api.delete(context, inData);
+            }
+            result.setResult("Successfully deleted volume");
+            result.setSuccess(true);
+        } catch (Throwable e) {
+            s_logger.error("Result to volume delete failed with exception", e);
+            result.setResult(e.toString());
+        } finally {
+            if (callback != null)
+                callback.complete(result);
+        }
+    }
+
+    @Override
+    public void copyAsync(DataObject srcdata, DataObject destdata,
+            AsyncCompletionCallback<CopyCommandResult> callback) {
+        CopyCommandResult result = null;
+        try {
+            s_logger.info("Copying volume " + srcdata.getUuid() + " to " + destdata.getUuid() + "]");
+
+            if (!canCopy(srcdata, destdata)) {
+                throw new CloudRuntimeException(
+                        "The data store provider is unable to perform copy operations because the source or destination object is not the correct type of volume");
+            }
+
+            try {
+                StoragePoolVO storagePool = _storagePoolDao.findById(srcdata.getDataStore().getId());
+                Map<String, String> details = _storagePoolDao.getDetails(storagePool.getId());
+                ProviderAdapter api = getAPI(storagePool, details);
+
+                s_logger.info("Copy volume " + srcdata.getUuid() + " to " + destdata.getUuid());
+
+                ProviderVolume outVolume;
+                ProviderAdapterContext context = newManagedVolumeContext(destdata);
+                ProviderAdapterDataObject sourceIn = newManagedDataObject(srcdata, storagePool);
+                ProviderAdapterDataObject destIn = newManagedDataObject(destdata, storagePool);
+                outVolume = api.copy(context, sourceIn, destIn);
+
+                // populate this data - it may be needed later
+                destIn.setExternalName(outVolume.getExternalName());
+                destIn.setExternalConnectionId(outVolume.getExternalConnectionId());
+                destIn.setExternalUuid(outVolume.getExternalUuid());
+
+                // if we copied from one volume to another, the target volume's disk offering or user input may be of a larger size
+                // we won't, however, shrink a volume if its smaller.
+                if (outVolume.getAllocatedSizeInBytes() < destdata.getSize()) {
+                    s_logger.info("Resizing volume " + destdata.getUuid() + " to requested target volume size of " + destdata.getSize());
+                    api.resize(context, destIn, destdata.getSize());
+                }
+
+                String connectionId = api.attach(context, destIn);
+
+                String finalPath;
+                // format: type=fiberwwn; address=<address>; connid=<connid>
+                if (connectionId != null) {
+                    finalPath = String.format("type=%s; address=%s; connid=%s", outVolume.getAddressType().toString(), outVolume.getAddress().toLowerCase(), connectionId);
+                } else {
+                    finalPath = String.format("type=%s; address=%s;", outVolume.getAddressType().toString(), outVolume.getAddress().toLowerCase());
+                }
+
+                persistVolumeData(storagePool, details, destdata, outVolume, connectionId);
+                s_logger.info("Copy completed from [" + srcdata.getUuid() + "] to [" + destdata.getUuid() + "]");
+
+                VolumeObjectTO voto = new VolumeObjectTO();
+                voto.setPath(finalPath);
+
+                result = new CopyCommandResult(finalPath, new CopyCmdAnswer(voto));
+                result.setSuccess(true);
+            } catch (Throwable e) {
+                s_logger.error("Result to volume copy failed with exception", e);
+                result = new CopyCommandResult(null, null);
+                result.setSuccess(false);
+                result.setResult(e.toString());
+            }
+        } finally {
+            if (callback != null)
+                callback.complete(result);
+        }
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost,
+            AsyncCompletionCallback<CopyCommandResult> callback) {
+        copyAsync(srcData, destData, callback);
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData) {
+        s_logger.debug("canCopy: Checking srcData [" + srcData.getUuid() + ":" + srcData.getType() + ":"
+                + srcData.getDataStore().getId() + " AND destData ["
+                + destData.getUuid() + ":" + destData.getType() + ":" + destData.getDataStore().getId() + "]");
+        try {
+            if (!isSameProvider(srcData)) {
+                s_logger.debug("canCopy: No we can't -- the source provider is NOT the correct type for this driver!");
+                return false;
+            }
+
+            if (!isSameProvider(destData)) {
+                s_logger.debug("canCopy: No we can't -- the destination provider is NOT the correct type for this driver!");
+                return false;
+            }
+            s_logger.debug(
+                    "canCopy: Source and destination are the same so we can copy via storage endpoint, checking that the source actually exists");
+            StoragePoolVO poolVO = _storagePoolDao.findById(srcData.getDataStore().getId());
+            Map<String, String> details = _storagePoolDao.getDetails(srcData.getDataStore().getId());
+            ProviderAdapter api = getAPI(poolVO, details);
+
+            /**
+             * The storage provider generates its own names for snapshots which we store and
+             * retrieve when needed
+             */
+            ProviderAdapterContext context = newManagedVolumeContext(srcData);
+            ProviderAdapterDataObject srcDataObject = newManagedDataObject(srcData, poolVO);
+            if (srcData instanceof SnapshotObject) {
+                ProviderSnapshot snapshot = api.getSnapshot(context, srcDataObject);
+                if (snapshot == null) {
+                    return false;
+                } else {
+                    return true;
+                }
+            } else {
+                ProviderVolume vol = api.getVolume(context, srcDataObject);
+                if (vol == null) {
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        } catch (Throwable e) {
+            s_logger.warn("Problem checking if we canCopy", e);
+            return false;
+        }
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
+        s_logger.debug("Resize volume started");
+        CreateCmdResult result = null;
+        try {
+
+            // Boolean status = false;
+            VolumeObject vol = (VolumeObject) data;
+            StoragePool pool = (StoragePool) data.getDataStore();
+
+            ResizeVolumePayload resizeParameter = (ResizeVolumePayload) vol.getpayload();
+
+            StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
+
+            if (!(poolVO.isManaged())) {
+                super.resize(data, callback);
+                return;
+            }
+
+            try {
+                Map<String, String> details = _storagePoolDao.getDetails(pool.getId());
+                ProviderAdapter api = getAPI(pool, details);
+
+                // doesn't support shrink (maybe can truncate but separate API calls to
+                // investigate)
+                if (vol.getSize() > resizeParameter.newSize) {
+                    throw new CloudRuntimeException("Storage provider does not support shrinking an existing volume");
+                }
+
+                ProviderAdapterContext context = newManagedVolumeContext(data);
+                ProviderAdapterDataObject dataIn = newManagedDataObject(data, poolVO);
+                if (s_logger.isDebugEnabled()) s_logger.debug("Calling provider API to resize volume " + data.getUuid() + " to " + resizeParameter.newSize);
+                api.resize(context, dataIn, resizeParameter.newSize);
+
+                if (vol.isAttachedVM()) {
+                    if (VirtualMachine.State.Running.equals(vol.getAttachedVM().getState())) {
+                        if (s_logger.isDebugEnabled()) s_logger.debug("Notify currently attached VM of volume resize for " + data.getUuid() + " to " + resizeParameter.newSize);
+                        _volumeService.resizeVolumeOnHypervisor(vol.getId(), resizeParameter.newSize, vol.getAttachedVM().getHostId(), vol.getAttachedVM().getInstanceName());
+                    }
+                }
+
+                result = new CreateCmdResult(data.getUuid(), new Answer(null));
+                result.setSuccess(true);
+            } catch (Throwable e) {
+                s_logger.error("Resize volume failed, please contact cloud support.", e);
+                result = new CreateCmdResult(null, new Answer(null));
+                result.setResult(e.toString());
+                result.setSuccess(false);
+            }
+        } finally {
+            if (callback != null)
+                callback.complete(result);
+        }
+
+    }
+
+    @Override
+    public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo,
+            QualityOfServiceState qualityOfServiceState) {
+        s_logger.info("handleQualityOfServiceVolumeMigration: " + volumeInfo.getUuid() + " " +
+                volumeInfo.getPath() + ": " + qualityOfServiceState.toString());
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) {
+        VolumeInfo volume = (VolumeInfo) dataObject;
+        long volumeSize = volume.getSize();
+        Integer hypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve();
+
+        if (hypervisorSnapshotReserve != null) {
+            if (hypervisorSnapshotReserve < 25) {
+                hypervisorSnapshotReserve = 25;
+            }
+
+            volumeSize += volumeSize * (hypervisorSnapshotReserve / 100f);
+        }
+
+        return volumeSize;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject) {
+        return null;
+    }
+
+    @Override
+    public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
+        CreateCmdResult result = null;
+        try {
+            s_logger.debug("taking volume snapshot");
+            SnapshotObjectTO snapshotTO = (SnapshotObjectTO) snapshot.getTO();
+
+            VolumeInfo baseVolume = snapshot.getBaseVolume();
+            DataStore ds = baseVolume.getDataStore();
+            StoragePoolVO storagePool = _storagePoolDao.findById(ds.getId());
+
+            Map<String, String> details = _storagePoolDao.getDetails(ds.getId());
+            ProviderAdapter api = getAPI(storagePool, details);
+
+            ProviderAdapterContext context = newManagedVolumeContext(snapshot);
+            ProviderAdapterDataObject inVolumeDO = newManagedDataObject(baseVolume, storagePool);
+            ProviderAdapterDataObject inSnapshotDO = newManagedDataObject(snapshot, storagePool);
+            ProviderSnapshot outSnapshot = api.snapshot(context, inVolumeDO, inSnapshotDO);
+
+            // add the snapshot to the host group (needed for copying to non-provider storage
+            // to create templates, etc)
+            String connectionId = null;
+            String finalAddress = outSnapshot.getAddress();
+            if (outSnapshot.canAttachDirectly()) {
+                connectionId = api.attach(context, inSnapshotDO);
+                if (connectionId != null) {
+                    finalAddress = finalAddress + "::" + connectionId;
+                }
+            }
+
+            snapshotTO.setPath(finalAddress);
+            snapshotTO.setName(outSnapshot.getName());
+            snapshotTO.setHypervisorType(HypervisorType.KVM);
+
+            // unclear why this is needed vs snapshotTO.setPath, but without it the path on
+            // the target snapshot object isn't set
+            // so a volume created from it also is not set and can't be attached to a VM
+            SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(snapshot.getId(),
+                    DiskTO.PATH, finalAddress, true);
+            _snapshotDetailsDao.persist(snapshotDetail);
+
+            // save the name (reuse on revert)
+            snapshotDetail = new SnapshotDetailsVO(snapshot.getId(),
+                    ProviderAdapterConstants.EXTERNAL_NAME, outSnapshot.getExternalName(), true);
+            _snapshotDetailsDao.persist(snapshotDetail);
+
+            // save the uuid (reuse on revert)
+            snapshotDetail = new SnapshotDetailsVO(snapshot.getId(),
+                    ProviderAdapterConstants.EXTERNAL_UUID, outSnapshot.getExternalUuid(), true);
+            _snapshotDetailsDao.persist(snapshotDetail);
+
+            result = new CreateCmdResult(finalAddress, new CreateObjectAnswer(snapshotTO));
+            result.setResult("Snapshot completed with new WWN " + finalAddress);
+            result.setSuccess(true);
+        } catch (Throwable e) {
+            s_logger.debug("Failed to take snapshot: " + e.getMessage());
+            result = new CreateCmdResult(null, null);
+            result.setResult(e.toString());
+        } finally {
+            if (callback != null)
+                callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore,
+            AsyncCompletionCallback<CommandResult> callback) {
+
+        CommandResult result = new CommandResult();
+        ProviderAdapter api = null;
+        try {
+            DataStore ds = snapshotOnPrimaryStore.getDataStore();
+            StoragePoolVO storagePool = _storagePoolDao.findById(ds.getId());
+            Map<String, String> details = _storagePoolDao.getDetails(ds.getId());
+            api = getAPI(storagePool, details);
+
+            String externalName = null;
+            String externalUuid = null;
+            List<SnapshotDetailsVO> list = _snapshotDetailsDao.findDetails(snapshot.getId(),
+                    ProviderAdapterConstants.EXTERNAL_NAME);
+            if (list != null && list.size() > 0) {
+                externalName = list.get(0).getValue();
+            }
+
+            list = _snapshotDetailsDao.findDetails(snapshot.getId(), ProviderAdapterConstants.EXTERNAL_UUID);
+            if (list != null && list.size() > 0) {
+                externalUuid = list.get(0).getValue();
+            }
+
+            ProviderAdapterContext context = newManagedVolumeContext(snapshot);
+            ProviderAdapterDataObject inSnapshotDO = newManagedDataObject(snapshot, storagePool);
+            inSnapshotDO.setExternalName(externalName);
+            inSnapshotDO.setExternalUuid(externalUuid);
+
+            // perform promote (async, wait for job to finish)
+            api.revert(context, inSnapshotDO);
+
+            // set command as success
+            result.setSuccess(true);
+        } catch (Throwable e) {
+            s_logger.warn("revertSnapshot failed", e);
+            result.setResult(e.toString());
+            result.setSuccess(false);
+        } finally {
+            if (callback != null)
+                callback.complete(result);
+        }
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool) {
+        long usedSpaceBytes = 0;
+        // Volumes
+        List<VolumeVO> volumes = _volumeDao.findByPoolIdAndState(storagePool.getId(), Volume.State.Ready);
+        if (volumes != null) {
+            for (VolumeVO volume : volumes) {
+                usedSpaceBytes += volume.getSize();
+
+                long vmSnapshotChainSize = volume.getVmSnapshotChainSize() == null ? 0
+                        : volume.getVmSnapshotChainSize();
+                usedSpaceBytes += vmSnapshotChainSize;
+            }
+        }
+
+        // Snapshots
+        List<SnapshotDataStoreVO> snapshots = _snapshotDataStoreDao.listByStoreIdAndState(storagePool.getId(),
+                ObjectInDataStoreStateMachine.State.Ready);
+        if (snapshots != null) {
+            for (SnapshotDataStoreVO snapshot : snapshots) {
+                usedSpaceBytes += snapshot.getSize();
+            }
+        }
+
+        // Templates
+        List<VMTemplateStoragePoolVO> templates = _vmTemplatePoolDao.listByPoolIdAndState(storagePool.getId(),
+                ObjectInDataStoreStateMachine.State.Ready);
+        if (templates != null) {
+            for (VMTemplateStoragePoolVO template : templates) {
+                usedSpaceBytes += template.getTemplateSize();
+            }
+        }
+
+        s_logger.debug("Used/Allocated storage space (in bytes): " + String.valueOf(usedSpaceBytes));
+
+        return usedSpaceBytes;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool) {
+        return super.getUsedIops(storagePool);
+    }
+
+    @Override
+    public Map<String, String> getCapabilities() {
+        Map<String, String> mapCapabilities = new HashMap<String, String>();
+
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString()); // set to false because it causes weird behavior when copying templates to root volumes
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        // indicates the datastore can create temporary volumes for use when copying
+        // data from a snapshot
+        mapCapabilities.put("CAN_CREATE_TEMP_VOLUME_FROM_SNAPSHOT", Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public boolean canProvideStorageStats() {
+        return true;
+    }
+
+    @Override
+    public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
+        Map<String, String> details = _storagePoolDao.getDetails(storagePool.getId());
+        String capacityBytesStr = details.get("capacityBytes");
+        Long capacityBytes = null;
+        if (capacityBytesStr == null) {
+            ProviderAdapter api = getAPI(storagePool, details);
+            ProviderVolumeStorageStats stats = api.getManagedStorageStats();
+            if (stats == null) {
+                return null;
+            }
+            capacityBytes = stats.getCapacityInBytes();
+        } else {
+            capacityBytes = Long.parseLong(capacityBytesStr);
+        }
+        Long usedBytes = this.getUsedBytes(storagePool);
+        return new Pair<Long, Long>(capacityBytes, usedBytes);
+    }
+
+    @Override
+    public boolean canProvideVolumeStats() {
+        return true;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    @Override
+    public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumePath) {
+        Map<String, String> details = _storagePoolDao.getDetails(storagePool.getId());
+        ProviderAdapter api = getAPI(storagePool, details);
+        ProviderVolume.AddressType addressType = null;
+        if (volumePath.indexOf(";") > 1) {
+            String[] fields = volumePath.split(";");
+            if (fields.length > 0) {
+                for (String field: fields) {
+                    if (field.trim().startsWith("address=")) {
+                        String[] toks = field.split("=");
+                        if (toks.length > 1) {
+                            volumePath = toks[1];
+                        }
+                    } else if (field.trim().startsWith("type=")) {
+                        String[] toks = field.split("=");
+                        if (toks.length > 1) {
+                            addressType = ProviderVolume.AddressType.valueOf(toks[1]);
+                        }
+                    }
+                }
+            }
+        } else {
+            addressType = ProviderVolume.AddressType.FIBERWWN;
+        }
+        // limited context since this is not at an account level
+        ProviderAdapterContext context = new ProviderAdapterContext();
+        context.setZoneId(storagePool.getDataCenterId());
+        ProviderVolume volume = api.getVolumeByAddress(context, addressType, volumePath);
+
+        if (volume == null) {
+            return null;
+        }
+
+        ProviderAdapterDataObject object = new ProviderAdapterDataObject();
+        object.setExternalUuid(volume.getExternalUuid());
+        object.setExternalName(volume.getExternalName());
+        object.setType(ProviderAdapterDataObject.Type.VOLUME);
+        ProviderVolumeStats stats = api.getVolumeStats(context, object);
+
+        Long provisionedSizeInBytes = stats.getActualUsedInBytes();
+        Long allocatedSizeInBytes = stats.getAllocatedInBytes();
+        if (provisionedSizeInBytes == null || allocatedSizeInBytes == null) {
+            return null;
+        }
+        return new Pair<Long, Long>(provisionedSizeInBytes, allocatedSizeInBytes);
+    }
+
+    @Override
+    public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
+        Map<String, String> details = _storagePoolDao.getDetails(pool.getId());
+        ProviderAdapter api = getAPI(pool, details);
+
+        ProviderAdapterContext context = new ProviderAdapterContext();
+        context.setZoneId(host.getDataCenterId());
+        return api.canAccessHost(context, host.getName());
+    }
+
+    void persistVolumeOrTemplateData(StoragePoolVO storagePool, Map<String, String> storagePoolDetails,
+            DataObject dataObject, ProviderVolume volume, String connectionId) {
+        if (dataObject.getType() == DataObjectType.VOLUME) {
+            persistVolumeData(storagePool, storagePoolDetails, dataObject, volume, connectionId);
+        } else if (dataObject.getType() == DataObjectType.TEMPLATE) {
+            persistTemplateData(storagePool, storagePoolDetails, dataObject, volume, connectionId);
+        }
+    }
+
+    void persistVolumeData(StoragePoolVO storagePool, Map<String, String> details, DataObject dataObject,
+            ProviderVolume managedVolume, String connectionId) {
+        VolumeVO volumeVO = _volumeDao.findById(dataObject.getId());
+
+        // if its null check if the storage provider returned one that is already set
+        if (connectionId == null) {
+            connectionId = managedVolume.getExternalConnectionId();
+        }
+
+        String finalPath;
+        // format: type=fiberwwn; address=<address>; connid=<connid>
+        if (connectionId != null) {
+            finalPath = String.format("type=%s; address=%s; connid=%s", managedVolume.getAddressType().toString(), managedVolume.getAddress().toLowerCase(), connectionId);
+        } else {
+            finalPath = String.format("type=%s; address=%s;", managedVolume.getAddressType().toString(), managedVolume.getAddress().toLowerCase());
+        }
+
+        volumeVO.setPath(finalPath);
+        volumeVO.setFormat(ImageFormat.RAW);
+        volumeVO.setPoolId(storagePool.getId());
+        volumeVO.setExternalUuid(managedVolume.getExternalUuid());
+        volumeVO.setDisplay(true);
+        volumeVO.setDisplayVolume(true);
+        _volumeDao.update(volumeVO.getId(), volumeVO);
+
+        volumeVO = _volumeDao.findById(volumeVO.getId());
+
+        VolumeDetailVO volumeDetailVO = new VolumeDetailVO(volumeVO.getId(),
+                DiskTO.PATH, finalPath, true);
+        _volumeDetailsDao.persist(volumeDetailVO);
+
+        volumeDetailVO = new VolumeDetailVO(volumeVO.getId(),
+                ProviderAdapterConstants.EXTERNAL_NAME, managedVolume.getExternalName(), true);
+        _volumeDetailsDao.persist(volumeDetailVO);
+
+        volumeDetailVO = new VolumeDetailVO(volumeVO.getId(),
+                ProviderAdapterConstants.EXTERNAL_UUID, managedVolume.getExternalUuid(), true);
+        _volumeDetailsDao.persist(volumeDetailVO);
+    }
+
+    void persistTemplateData(StoragePoolVO storagePool, Map<String, String> details, DataObject dataObject,
+            ProviderVolume volume, String connectionId) {
+        TemplateInfo templateInfo = (TemplateInfo) dataObject;
+        VMTemplateStoragePoolVO templatePoolRef = _vmTemplatePoolDao.findByPoolTemplate(storagePool.getId(),
+                templateInfo.getId(), null);
+        // template pool ref doesn't have a details object so we'll save:
+        // 1. external name ==> installPath
+        // 2. address ==> local download path
+        if (connectionId == null) {
+            templatePoolRef.setInstallPath(String.format("type=%s; address=%s", volume.getAddressType().toString(),
+                volume.getAddress().toLowerCase()));
+        } else {
+            templatePoolRef.setInstallPath(String.format("type=%s; address=%s; connid=%s", volume.getAddressType().toString(),
+                volume.getAddress().toLowerCase(), connectionId));
+        }
+        templatePoolRef.setLocalDownloadPath(volume.getExternalName());
+        templatePoolRef.setTemplateSize(volume.getAllocatedSizeInBytes());
+        _vmTemplatePoolDao.update(templatePoolRef.getId(), templatePoolRef);
+    }
+
+    ProviderAdapterContext newManagedVolumeContext(DataObject obj) {
+        ProviderAdapterContext ctx = new ProviderAdapterContext();
+        if (obj instanceof VolumeInfo) {
+            VolumeVO vol = _volumeDao.findById(obj.getId());
+            ctx.setAccountId(vol.getAccountId());
+            ctx.setDomainId(vol.getDomainId());
+        } else if (obj instanceof SnapshotInfo) {
+            SnapshotVO snap = _snapshotDao.findById(obj.getId());
+            ctx.setAccountId(snap.getAccountId());
+            ctx.setDomainId(snap.getDomainId());
+        } else if (obj instanceof TemplateInfo) {
+            VMTemplateVO template = _vmTemplateDao.findById(obj.getId());
+            ctx.setAccountId(template.getAccountId());
+            // templates don't have a domain ID so always set to 0
+            ctx.setDomainId(0L);
+        }
+
+        if (ctx.getAccountId() != null) {
+            AccountVO acct = _accountDao.findById(ctx.getAccountId());
+            if (acct != null) {
+                ctx.setAccountUuid(acct.getUuid());
+                ctx.setAccountName(acct.getName());
+            }
+        }
+
+        if (ctx.getDomainId() != null) {
+            DomainVO domain  = _domainDao.findById(ctx.getDomainId());
+            if (domain != null) {
+                ctx.setDomainUuid(domain.getUuid());
+                ctx.setDomainName(domain.getName());
+            }
+        }
+
+        return ctx;
+    }
+
+    boolean isSameProvider(DataObject obj) {
+        StoragePoolVO storagePool = this._storagePoolDao.findById(obj.getDataStore().getId());
+        if (storagePool != null && storagePool.getStorageProviderName().equals(this.getProviderName())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    ProviderAdapterDataObject newManagedDataObject(DataObject data, StoragePool storagePool) {
+        ProviderAdapterDataObject dataIn = new ProviderAdapterDataObject();
+        if (data instanceof VolumeInfo) {
+            List<VolumeDetailVO> list = _volumeDetailsDao.findDetails(data.getId(),
+                    ProviderAdapterConstants.EXTERNAL_NAME);
+            String externalName = null;
+            if (list != null && list.size() > 0) {
+                externalName = list.get(0).getValue();
+            }
+
+            list = _volumeDetailsDao.findDetails(data.getId(), ProviderAdapterConstants.EXTERNAL_UUID);
+            String externalUuid = null;
+            if (list != null && list.size() > 0) {
+                externalUuid = list.get(0).getValue();
+            }
+
+            dataIn.setName(((VolumeInfo) data).getName());
+            dataIn.setExternalName(externalName);
+            dataIn.setExternalUuid(externalUuid);
+        } else if (data instanceof SnapshotInfo) {
+            List<SnapshotDetailsVO> list = _snapshotDetailsDao.findDetails(data.getId(),
+                    ProviderAdapterConstants.EXTERNAL_NAME);
+            String externalName = null;
+            if (list != null && list.size() > 0) {
+                externalName = list.get(0).getValue();
+            }
+
+            list = _snapshotDetailsDao.findDetails(data.getId(), ProviderAdapterConstants.EXTERNAL_UUID);
+            String externalUuid = null;
+            if (list != null && list.size() > 0) {
+                externalUuid = list.get(0).getValue();
+            }
+
+            dataIn = new ProviderAdapterDataObject();
+            dataIn.setName(((SnapshotInfo) data).getName());
+            dataIn.setExternalName(externalName);
+            dataIn.setExternalUuid(externalUuid);
+        } else if (data instanceof TemplateInfo) {
+            TemplateInfo ti = (TemplateInfo)data;
+            dataIn.setName(ti.getName());
+            VMTemplateStoragePoolVO templatePoolRef = _vmTemplatePoolDao.findByPoolTemplate(storagePool.getId(), ti.getId(), null);
+            dataIn.setExternalName(templatePoolRef.getLocalDownloadPath());
+        }
+        dataIn.setId(data.getId());
+        dataIn.setDataStoreId(data.getDataStore().getId());
+        dataIn.setDataStoreUuid(data.getDataStore().getUuid());
+        dataIn.setDataStoreName(data.getDataStore().getName());
+        dataIn.setUuid(data.getUuid());
+        dataIn.setType(ProviderAdapterDataObject.Type.valueOf(data.getType().toString()));
+        return dataIn;
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java
new file mode 100644
index 0000000..56d9a25
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java
@@ -0,0 +1,407 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.lifecycle;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
+import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStorageStats;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.provider.AdaptivePrimaryDatastoreAdapterFactoryMap;
+import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.log4j.Logger;
+
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.resource.ResourceManager;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.StoragePoolAutomation;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.host.Host;
+
+/**
+ * Manages the lifecycle of a Managed Data Store in CloudStack
+ */
+public class AdaptiveDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
+    @Inject
+    private PrimaryDataStoreDao _storagePoolDao;
+    private static final Logger s_logger = Logger.getLogger(AdaptiveDataStoreLifeCycleImpl.class);
+
+    @Inject
+    PrimaryDataStoreHelper _dataStoreHelper;
+    @Inject
+    protected ResourceManager _resourceMgr;
+    @Inject
+    private StoragePoolAutomation _storagePoolAutomation;
+    @Inject
+    private PrimaryDataStoreDao _primaryDataStoreDao;
+    @Inject
+    private StorageManager _storageMgr;
+    @Inject
+    private ClusterDao _clusterDao;
+    AdaptivePrimaryDatastoreAdapterFactoryMap _adapterFactoryMap;
+
+    public AdaptiveDataStoreLifeCycleImpl(AdaptivePrimaryDatastoreAdapterFactoryMap factoryMap) {
+        _adapterFactoryMap = factoryMap;
+    }
+
+    /**
+     * Initialize the storage pool
+     * https://hostname:port?cpg=<cpgname>&snapcpg=<snapcpg>&hostset=<hostsetname>&disabletlsvalidation=true&
+     */
+    @Override
+    public DataStore initialize(Map<String, Object> dsInfos) {
+        // https://hostanme:443/cpgname/hostsetname.  hostset should map to the cluster or zone (all nodes in the cluster or zone MUST be in the hostset and be configured outside cloudstack for now)
+        String url = (String) dsInfos.get("url");
+        Long zoneId = (Long) dsInfos.get("zoneId");
+        Long podId = (Long)dsInfos.get("podId");
+        Long clusterId = (Long)dsInfos.get("clusterId");
+        String dsName = (String) dsInfos.get("name");
+        String providerName = (String) dsInfos.get("providerName");
+        Long capacityBytes = (Long) dsInfos.get("capacityBytes");
+        Long capacityIops = (Long)dsInfos.get("capacityIops");
+        String tags = (String)dsInfos.get("tags");
+        @SuppressWarnings("unchecked")
+        Map<String, String> details = (Map<String, String>) dsInfos.get("details");
+
+        // validate inputs are valid/provided as required
+        if (zoneId == null) throw new CloudRuntimeException("Zone Id must be specified.");
+
+        URL uri = null;
+        try {
+            uri = new URL(url);
+        } catch (Exception ignored) {
+            throw new CloudRuntimeException(url + " is not a valid uri");
+        }
+
+        String username = null;
+        String password = null;
+        String token = null;
+        String userInfo = uri.getUserInfo();
+        if (userInfo == null || userInfo.split(":").length < 2) {
+            // check if it was passed in the details object
+            username = details.get(ProviderAdapter.API_USERNAME_KEY);
+            if (username != null) {
+                password = details.get(ProviderAdapter.API_PASSWORD_KEY);
+                userInfo = username + ":" + password;
+            } else {
+                token = details.get(ProviderAdapter.API_TOKEN_KEY);
+            }
+        } else {
+            try {
+                userInfo = java.net.URLDecoder.decode(userInfo, StandardCharsets.UTF_8.toString());
+            } catch (UnsupportedEncodingException e) {
+                throw new CloudRuntimeException("Unexpected error parsing the provided user info; check that it does not include any invalid characters");
+            }
+
+            username = userInfo.split(":")[0];
+            password = userInfo.split(":")[1];
+        }
+
+        s_logger.info("Registering block storage provider with user=" + username);
+
+
+        if (clusterId != null) {
+            Hypervisor.HypervisorType hypervisorType = getHypervisorTypeForCluster(clusterId);
+
+            if (!hypervisorType.equals(HypervisorType.KVM)) {
+                throw new CloudRuntimeException("Unsupported hypervisor type for provided cluster: " + hypervisorType.toString());
+            }
+
+            // Primary datastore is cluster-wide, check and set the podId and clusterId parameters
+            if (podId == null) {
+                throw new CloudRuntimeException("Pod Id must also be specified when the Cluster Id is specified for Cluster-wide primary storage.");
+            }
+
+            s_logger.info("Registering with clusterid=" + clusterId + " which is confirmed to be a KVM host");
+
+        } else if (podId != null) {
+            throw new CloudRuntimeException("Cluster Id must also be specified when the Pod Id is specified for Cluster-wide primary storage.");
+        }
+
+        // validate we don't have any duplication going on
+        List<StoragePoolVO> storagePoolVO = _primaryDataStoreDao.findPoolsByProvider(providerName);
+        if (CollectionUtils.isNotEmpty(storagePoolVO)) {
+            for (StoragePoolVO poolVO : storagePoolVO) {
+                Map <String, String> poolDetails = _primaryDataStoreDao.getDetails(poolVO.getId());
+                String otherPoolUrl = poolDetails.get(ProviderAdapter.API_URL_KEY);
+                if (dsName.equals(poolVO.getName())) {
+                    throw new InvalidParameterValueException("A pool with the name [" + dsName + "] already exists, choose another name");
+                }
+
+                if (uri.toString().equals(otherPoolUrl)) {
+                    throw new IllegalArgumentException("Provider URL [" + otherPoolUrl + "] is already in use by another storage pool named [" + poolVO.getName() + "], please validate you have correct API and CPG");
+                }
+            }
+        }
+
+        s_logger.info("Validated no other pool exists with this name: " + dsName);
+
+        try {
+            PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
+            parameters.setHost(uri.getHost());
+            parameters.setPort(uri.getPort());
+            parameters.setPath(uri.getPath() + "?" + uri.getQuery());
+            parameters.setType(StoragePoolType.FiberChannel);
+            parameters.setZoneId(zoneId);
+            parameters.setPodId(podId);
+            parameters.setClusterId(clusterId);
+            parameters.setName(dsName);
+            parameters.setProviderName(providerName);
+            parameters.setManaged(true);
+            parameters.setCapacityBytes(capacityBytes);
+            parameters.setUsedBytes(0);
+            parameters.setCapacityIops(capacityIops);
+            parameters.setHypervisorType(HypervisorType.KVM);
+            parameters.setTags(tags);
+            parameters.setUserInfo(userInfo);
+            parameters.setUuid(UUID.randomUUID().toString());
+
+            details.put(ProviderAdapter.API_URL_KEY, uri.toString());
+            if (username != null) {
+                details.put(ProviderAdapter.API_USERNAME_KEY, username);
+            }
+
+            if (password != null) {
+                details.put(ProviderAdapter.API_PASSWORD_KEY, DBEncryptionUtil.encrypt(password));
+            }
+
+            if (token != null) {
+                details.put(ProviderAdapter.API_TOKEN_KEY, DBEncryptionUtil.encrypt(details.get(ProviderAdapter.API_TOKEN_KEY)));
+            }
+            // this appears to control placing the storage pool above network file system based storage pools in priority
+            details.put(Storage.Capability.HARDWARE_ACCELERATION.toString(), "true");
+            // this new capablity indicates the storage pool allows volumes to migrate to/from other pools (i.e. to/from NFS pools)
+            details.put(Storage.Capability.ALLOW_MIGRATE_OTHER_POOLS.toString(), "true");
+            parameters.setDetails(details);
+
+            // make sure the storage array is connectable and the pod and hostgroup objects exist
+            ProviderAdapter api = _adapterFactoryMap.getAPI(parameters.getUuid(), providerName, details);
+
+            // validate the provided details are correct/valid for the provider
+            api.validate();
+
+            // if we have user-provided capacity bytes, validate they do not exceed the manaaged storage capacity bytes
+            ProviderVolumeStorageStats stats = api.getManagedStorageStats();
+            if (capacityBytes != null && capacityBytes != 0) {
+                if (stats.getCapacityInBytes() > 0) {
+                    if (stats.getCapacityInBytes() < capacityBytes) {
+                        throw new InvalidParameterValueException("Capacity bytes provided exceeds the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes());
+                    }
+                }
+                parameters.setCapacityBytes(capacityBytes);
+            }
+            // if we have no user-provided capacity bytes, use the ones provided by storage
+            else {
+                if (stats.getCapacityInBytes() <= 0) {
+                    throw new InvalidParameterValueException("Capacity bytes note available from the storage provider, user provided capacity bytes must be specified");
+                }
+                parameters.setCapacityBytes(stats.getCapacityInBytes());
+            }
+
+            s_logger.info("Persisting [" + dsName + "] storage pool metadata to database");
+            return _dataStoreHelper.createPrimaryDataStore(parameters);
+        } catch (Throwable e) {
+            s_logger.error("Problem persisting storage pool", e);
+            throw new CloudRuntimeException(e);
+        }
+    }
+
+   /**
+     * Get the type of Hypervisor from the cluster id
+     * @param clusterId
+     * @return
+     */
+    private Hypervisor.HypervisorType getHypervisorTypeForCluster(long clusterId) {
+        ClusterVO cluster = _clusterDao.findById(clusterId);
+        if (cluster == null) {
+            throw new CloudRuntimeException("Unable to locate the specified cluster: " + clusterId);
+        }
+
+        return cluster.getHypervisorType();
+    }
+
+    /**
+     * Attach the pool to a cluster (all hosts in a single cluster)
+     */
+    @Override
+    public boolean attachCluster(DataStore store, ClusterScope scope) {
+        s_logger.info("Attaching storage pool [" + store.getName() + "] to cluster [" + scope.getScopeId() + "]");
+        _dataStoreHelper.attachCluster(store);
+
+        StoragePoolVO dataStoreVO = _storagePoolDao.findById(store.getId());
+
+        PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo) store;
+        // Check if there is host up in this cluster
+        List<HostVO> allHosts = _resourceMgr.listAllUpHosts(Host.Type.Routing, primarystore.getClusterId(), primarystore.getPodId(), primarystore.getDataCenterId());
+        if (allHosts.isEmpty()) {
+            _primaryDataStoreDao.expunge(primarystore.getId());
+            throw new CloudRuntimeException("No host up to associate a storage pool with in cluster " + primarystore.getClusterId());
+        }
+
+        if (dataStoreVO.isManaged()) {
+            //boolean success = false;
+            for (HostVO h : allHosts) {
+                s_logger.debug("adding host " + h.getName() + " to storage pool " + store.getName());
+            }
+        }
+
+        s_logger.debug("In createPool Adding the pool to each of the hosts");
+        List<HostVO> poolHosts = new ArrayList<HostVO>();
+        for (HostVO h : allHosts) {
+            try {
+                _storageMgr.connectHostToSharedPool(h.getId(), primarystore.getId());
+                poolHosts.add(h);
+            } catch (Exception e) {
+                s_logger.warn("Unable to establish a connection between " + h + " and " + primarystore, e);
+            }
+        }
+
+        if (poolHosts.isEmpty()) {
+            s_logger.warn("No host can access storage pool " + primarystore + " on cluster " + primarystore.getClusterId());
+            _primaryDataStoreDao.expunge(primarystore.getId());
+            throw new CloudRuntimeException("Failed to access storage pool");
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
+        s_logger.info("Attaching storage pool [" + store.getName() + "] to host [" + scope.getScopeId() + "]");
+        _dataStoreHelper.attachHost(store, scope, existingInfo);
+        return true;
+    }
+
+    @Override
+    public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
+        s_logger.info("Attaching storage pool [" + dataStore.getName() + "] to zone [" + scope.getScopeId() + "]");
+        List<HostVO> hosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(hypervisorType, scope.getScopeId());
+        List<HostVO> poolHosts = new ArrayList<HostVO>();
+        for (HostVO host : hosts) {
+            try {
+                _storageMgr.connectHostToSharedPool(host.getId(), dataStore.getId());
+                poolHosts.add(host);
+            } catch (Exception e) {
+                s_logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
+            }
+        }
+        if (poolHosts.isEmpty()) {
+            s_logger.warn("No host can access storage pool " + dataStore + " in this zone.");
+            _primaryDataStoreDao.expunge(dataStore.getId());
+            throw new CloudRuntimeException("Failed to create storage pool as it is not accessible to hosts.");
+        }
+        _dataStoreHelper.attachZone(dataStore, hypervisorType);
+        return true;
+    }
+
+    /**
+     * Put the storage pool in maintenance mode
+     */
+    @Override
+    public boolean maintain(DataStore store) {
+        s_logger.info("Placing storage pool [" + store.getName() + "] in maintainence mode");
+        if (_storagePoolAutomation.maintain(store)) {
+            return _dataStoreHelper.maintain(store);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Cancel maintenance mode
+     */
+    @Override
+    public boolean cancelMaintain(DataStore store) {
+        s_logger.info("Canceling storage pool maintainence for [" + store.getName() + "]");
+        if (_dataStoreHelper.cancelMaintain(store)) {
+            return _storagePoolAutomation.cancelMaintain(store);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Delete the data store
+     */
+    @Override
+    public boolean deleteDataStore(DataStore store) {
+        s_logger.info("Delete datastore called for [" + store.getName() + "]");
+        return _dataStoreHelper.deletePrimaryDataStore(store);
+    }
+
+    /**
+     * Migrate objects in this store to another store
+     */
+    @Override
+    public boolean migrateToObjectStore(DataStore store) {
+        s_logger.info("Migrate datastore called for [" + store.getName() + "].  This is not currently implemented for this provider at this time");
+        return false;
+    }
+
+    /**
+     * Update the storage pool configuration
+     */
+    @Override
+    public void updateStoragePool(StoragePool storagePool, Map<String, String> details) {
+        _adapterFactoryMap.updateAPI(storagePool.getUuid(), storagePool.getStorageProviderName(), details);
+    }
+
+    /**
+     * Enable the storage pool (allows volumes from this pool)
+     */
+    @Override
+    public void enableStoragePool(DataStore store) {
+        s_logger.info("Enabling storage pool [" + store.getName() + "]");
+        _dataStoreHelper.enable(store);
+    }
+
+    /**
+     * Disable storage pool (stops new volume provisioning from pool)
+     */
+    @Override
+    public void disableStoragePool(DataStore store) {
+        s_logger.info("Disabling storage pool [" + store.getName() + "]");
+        _dataStoreHelper.disable(store);
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryDatastoreAdapterFactoryMap.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryDatastoreAdapterFactoryMap.java
new file mode 100644
index 0000000..ee5caa7
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryDatastoreAdapterFactoryMap.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.storage.datastore.provider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterFactory;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class AdaptivePrimaryDatastoreAdapterFactoryMap {
+    private final Logger logger = Logger.getLogger(ProviderAdapter.class);
+    private Map<String,ProviderAdapterFactory> factoryMap = new HashMap<String,ProviderAdapterFactory>();
+    private Map<String,ProviderAdapter> apiMap = new HashMap<String,ProviderAdapter>();
+
+    public AdaptivePrimaryDatastoreAdapterFactoryMap() {
+
+    }
+
+    /**
+     * Given a storage pool return current client. Reconfigure if changes are
+     * discovered
+     */
+    public final ProviderAdapter getAPI(String uuid, String providerName, Map<String, String> details) {
+        ProviderAdapter api = apiMap.get(uuid);
+        if (api == null) {
+            synchronized (this) {
+                api = apiMap.get(uuid);
+                if (api == null) {
+                    api = createNewAdapter(uuid, providerName, details);
+                    apiMap.put(uuid, api);
+                    logger.debug("Cached the new ProviderAdapter for storage pool " + uuid);
+                }
+            }
+        }
+        return api;
+    }
+
+    /**
+     * Update the API with the given UUID.  allows for URL changes and authentication updates
+     * @param uuid
+     * @param providerName
+     * @param details
+     */
+    public final void updateAPI(String uuid, String providerName, Map<String, String> details) {
+        // attempt to create (which validates) the new info before updating the cache
+        ProviderAdapter adapter = createNewAdapter(uuid, providerName, details);
+
+        // if its null its likely because no action has occured yet to trigger the API object to be loaded
+        if (adapter == null) {
+            throw new CloudRuntimeException("Adapter configruation failed for an unknown reason");
+        }
+
+        ProviderAdapter oldAdapter = apiMap.get(uuid);
+        apiMap.put(uuid, adapter);
+        try {
+            if (oldAdapter != null) oldAdapter.disconnect();
+        } catch (Throwable e) {
+            logger.debug("Failure closing the old ProviderAdapter during an update of the cached data after validation of the new adapter configuration, likely the configuration is no longer valid", e);
+        }
+    }
+
+    public void register(ProviderAdapterFactory factory) {
+        factoryMap.put(factory.getProviderName(), factory);
+    }
+
+    protected ProviderAdapter createNewAdapter(String uuid, String providerName, Map<String, String> details) {
+        String authnType = details.get(ProviderAdapter.API_AUTHENTICATION_TYPE_KEY);
+        if (authnType == null) authnType = "basicauth";
+        String lookupKey = null;
+        if (authnType.equals("basicauth")) {
+            lookupKey = details.get(ProviderAdapter.API_USERNAME_KEY);
+            if (lookupKey == null) {
+                throw new RuntimeException("Storage provider configuration property [" + ProviderAdapter.API_USERNAME_KEY + "] is required when using authentication type [" + authnType + "]");
+            }
+        } else if (authnType.equals("apitoken")) {
+            lookupKey = details.get(ProviderAdapter.API_TOKEN_KEY);
+            if (lookupKey == null) {
+                throw new RuntimeException("Storage provider configuration property [" + ProviderAdapter.API_TOKEN_KEY + "] is required when using authentication type [" + authnType + "]");
+            }
+        } else {
+            throw new RuntimeException("Storage provider configuration property [" + ProviderAdapter.API_AUTHENTICATION_TYPE_KEY + "] not set to valid value");
+        }
+
+        String url = details.get(ProviderAdapter.API_URL_KEY);
+        if (url == null) {
+            throw new RuntimeException("URL required when configuring a Managed Block API storage provider");
+        }
+
+        logger.debug("Looking for Provider [" + providerName + "] at [" + url + "]");
+        ProviderAdapterFactory factory = factoryMap.get(providerName);
+        if (factory == null) {
+            throw new RuntimeException("Unable to find a storage provider API factory for provider: " + providerName);
+        }
+
+        // decrypt password or token before sending to provider
+        if (authnType.equals("basicauth")) {
+            try {
+                details.put(ProviderAdapter.API_PASSWORD_KEY, DBEncryptionUtil.decrypt(details.get(ProviderAdapter.API_PASSWORD_KEY)));
+            } catch (Exception e) {
+                logger.warn("Failed to decrypt managed block API property: [" + ProviderAdapter.API_PASSWORD_KEY + "], trying to use as-is");
+            }
+        } else if (authnType.equals("apitoken")) {
+            try {
+                details.put(ProviderAdapter.API_TOKEN_KEY, DBEncryptionUtil.decrypt(details.get(ProviderAdapter.API_TOKEN_KEY)));
+            } catch (Exception e) {
+                logger.warn("Failed to decrypt managed block API property: [" + ProviderAdapter.API_TOKEN_KEY + "], trying to use as-is");
+            }
+        }
+
+        ProviderAdapter api = factory.create(url, details);
+        api.validate();
+        logger.debug("Creating new ProviderAdapter object for endpoint: " + providerName + "@" + url);
+        return api;
+    }
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryDatastoreProviderImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryDatastoreProviderImpl.java
new file mode 100644
index 0000000..2008447
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryDatastoreProviderImpl.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.storage.datastore.provider;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import com.cloud.utils.component.ComponentContext;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterFactory;
+import org.apache.cloudstack.storage.datastore.driver.AdaptiveDataStoreDriverImpl;
+import org.apache.cloudstack.storage.datastore.lifecycle.AdaptiveDataStoreLifeCycleImpl;
+
+@Component
+public abstract class AdaptivePrimaryDatastoreProviderImpl implements PrimaryDataStoreProvider {
+    static final Logger s_logger = Logger.getLogger(AdaptivePrimaryDatastoreProviderImpl.class);
+
+    AdaptiveDataStoreDriverImpl driver;
+
+    HypervisorHostListener listener;
+
+    AdaptivePrimaryDatastoreAdapterFactoryMap factoryMap = new AdaptivePrimaryDatastoreAdapterFactoryMap();
+
+    DataStoreLifeCycle lifecycle;
+
+    AdaptivePrimaryDatastoreProviderImpl(ProviderAdapterFactory f) {
+        s_logger.info("Creating " + f.getProviderName());
+        factoryMap.register(f);
+    }
+
+    @Override
+    public DataStoreLifeCycle getDataStoreLifeCycle() {
+        return this.lifecycle;
+    }
+
+    @Override
+    public boolean configure(Map<String, Object> params) {
+        s_logger.info("Configuring " + getName());
+        driver = new AdaptiveDataStoreDriverImpl(factoryMap);
+        driver.setProviderName(getName());
+        lifecycle = ComponentContext.inject(new AdaptiveDataStoreLifeCycleImpl(factoryMap));
+        driver = ComponentContext.inject(driver);
+        listener = ComponentContext.inject(new AdaptivePrimaryHostListener(factoryMap));
+        return true;
+    }
+
+    @Override
+    public PrimaryDataStoreDriver getDataStoreDriver() {
+        return this.driver;
+    }
+
+    @Override
+    public HypervisorHostListener getHostListener() {
+        return this.listener;
+    }
+
+    @Override
+    public Set<DataStoreProviderType> getTypes() {
+        Set<DataStoreProviderType> types = new HashSet<DataStoreProviderType>();
+        types.add(DataStoreProviderType.PRIMARY);
+        return types;
+    }
+
+}
diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryHostListener.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryHostListener.java
new file mode 100644
index 0000000..68dd4a1
--- /dev/null
+++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/provider/AdaptivePrimaryHostListener.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.storage.datastore.provider;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+import org.apache.log4j.Logger;
+
+import com.cloud.exception.StorageConflictException;
+import com.cloud.storage.StoragePoolHostVO;
+import com.cloud.storage.dao.StoragePoolHostDao;
+
+public class AdaptivePrimaryHostListener implements HypervisorHostListener {
+    static final Logger s_logger = Logger.getLogger(AdaptivePrimaryHostListener.class);
+
+    @Inject
+    StoragePoolHostDao storagePoolHostDao;
+
+    public AdaptivePrimaryHostListener(AdaptivePrimaryDatastoreAdapterFactoryMap factoryMap) {
+
+    }
+
+    @Override
+    public boolean hostAboutToBeRemoved(long hostId) {
+        s_logger.debug("hostAboutToBeRemoved called");
+        return true;
+    }
+
+    @Override
+    public boolean hostAdded(long hostId) {
+        s_logger.debug("hostAdded called");
+        return true;
+    }
+
+    @Override
+    public boolean hostConnect(long hostId, long poolId) throws StorageConflictException {
+        s_logger.debug("hostConnect called for hostid [" + hostId + "], poolId [" + poolId + "]");
+        StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(poolId, hostId);
+        if (storagePoolHost == null) {
+            storagePoolHost = new StoragePoolHostVO(poolId, hostId, "");
+            storagePoolHostDao.persist(storagePoolHost);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean hostDisconnected(long hostId, long poolId) {
+        s_logger.debug("hostDisconnected called for hostid [" + hostId + "], poolId [" + poolId + "]");
+        StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(poolId, hostId);
+
+        if (storagePoolHost != null) {
+            storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean hostEnabled(long hostId) {
+        s_logger.debug("hostEnabled called");
+        return true;
+    }
+
+    @Override
+    public boolean hostRemoved(long hostId, long clusterId) {
+        s_logger.debug("hostRemoved called");
+        return true;
+    }
+}
diff --git a/plugins/storage/volume/cloudbyte/pom.xml b/plugins/storage/volume/cloudbyte/pom.xml
index f1a451d..8ab39e8 100644
--- a/plugins/storage/volume/cloudbyte/pom.xml
+++ b/plugins/storage/volume/cloudbyte/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -39,8 +39,8 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -57,9 +57,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java
index d6a67b4..0798f9f 100644
--- a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java
+++ b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java
@@ -107,6 +107,7 @@
         Long capacityBytes = (Long) dsInfos.get("capacityBytes");
         Long capacityIops = (Long) dsInfos.get("capacityIops");
         String tags = (String) dsInfos.get("tags");
+        Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
         boolean managed = (Boolean) dsInfos.get("managed");
         Map<String, String> details = (Map<String, String>) dsInfos.get("details");
         String domainName = details.get("domainname");
@@ -196,6 +197,7 @@
         parameters.setCapacityIops(capacityIops);
         parameters.setHypervisorType(HypervisorType.Any);
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
         parameters.setDetails(details);
         parameters.setClusterId(clusterId);
 
diff --git a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java
index 2a27265..2f2ad25 100644
--- a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java
+++ b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java
@@ -244,7 +244,7 @@
 
         if (listAccountResponse.getAccounts().getCount() != 0) {
             int i;
-            // check weather a account in elasticenter with given Domain name is
+            // check whether an account in elasticenter with given Domain name is
             // already present in the list of accounts
             for (i = 0; i < listAccountResponse.getAccounts().getCount(); i++) {
                 if (domainName.equals(listAccountResponse.getAccounts().getAccount(i).getName())) {
diff --git a/plugins/storage/volume/cloudbyte/src/main/resources/META-INF/cloudstack/storage-volume-cloudbyte/module.properties b/plugins/storage/volume/cloudbyte/src/main/resources/META-INF/cloudstack/storage-volume-cloudbyte/module.properties
index 92cd58f..c494608 100755
--- a/plugins/storage/volume/cloudbyte/src/main/resources/META-INF/cloudstack/storage-volume-cloudbyte/module.properties
+++ b/plugins/storage/volume/cloudbyte/src/main/resources/META-INF/cloudstack/storage-volume-cloudbyte/module.properties
@@ -18,4 +18,4 @@
 #
 
 name=storage-volume-cloudbyte
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/volume/datera/pom.xml b/plugins/storage/volume/datera/pom.xml
index 1c27700..2b5a0a0 100644
--- a/plugins/storage/volume/datera/pom.xml
+++ b/plugins/storage/volume/datera/pom.xml
@@ -16,7 +16,7 @@
   <parent>
     <groupId>org.apache.cloudstack</groupId>
     <artifactId>cloudstack-plugins</artifactId>
-    <version>4.18.3.0-SNAPSHOT</version>
+    <version>4.19.1.0-SNAPSHOT</version>
     <relativePath>../../../pom.xml</relativePath>
   </parent>
   <dependencies>
@@ -49,9 +49,6 @@
     <plugins>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <skipTests>true</skipTests>
-        </configuration>
         <executions>
           <execution>
             <phase>integration-test</phase>
diff --git a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/driver/DateraPrimaryDataStoreDriver.java b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/driver/DateraPrimaryDataStoreDriver.java
index 79b6216..b331249 100644
--- a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/driver/DateraPrimaryDataStoreDriver.java
+++ b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/driver/DateraPrimaryDataStoreDriver.java
@@ -66,8 +66,10 @@
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.Storage;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.SnapshotDao;
@@ -1879,4 +1881,18 @@
     @Override
     public void provideVmTags(long vmId, long volumeId, String tagValue) {
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return false;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
+
+    @Override
+    public boolean volumesRequireGrantAccessWhenUsed() {
+        return true;
+    }
 }
diff --git a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java
index 0906e64..6fd4200 100644
--- a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java
+++ b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java
@@ -100,6 +100,7 @@
         Long capacityBytes = (Long) dsInfos.get("capacityBytes");
         Long capacityIops = (Long) dsInfos.get("capacityIops");
         String tags = (String) dsInfos.get("tags");
+        boolean isTagARule = (Boolean)dsInfos.get("isTagARule");
         @SuppressWarnings("unchecked")
         Map<String, String> details = (Map<String, String>) dsInfos.get("details");
         String domainName = details.get("domainname");
@@ -181,6 +182,7 @@
         parameters.setCapacityIops(capacityIops);
         parameters.setHypervisorType(HypervisorType.Any);
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
         parameters.setDetails(details);
 
         String managementVip = DateraUtil.getManagementVip(url);
diff --git a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/util/DateraUtil.java b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/util/DateraUtil.java
index baadd13..a1084bf 100644
--- a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/util/DateraUtil.java
+++ b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/util/DateraUtil.java
@@ -943,7 +943,7 @@
     }
 
     /**
-     * Checks wether a host initiator is present in an initiator group
+     * Checks whether a host initiator is present in an initiator group
      *
      * @param initiator      Host initiator to check
      * @param initiatorGroup the initiator group
diff --git a/plugins/storage/volume/default/pom.xml b/plugins/storage/volume/default/pom.xml
index 6ccdaa1..cef1dbb 100644
--- a/plugins/storage/volume/default/pom.xml
+++ b/plugins/storage/volume/default/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -38,9 +38,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
index 4453906..a0aaab1 100644
--- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
+++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
@@ -255,7 +255,7 @@
                 }
             }
         } catch (Exception ex) {
-            s_logger.debug("Unable to destoy volume" + data.getId(), ex);
+            s_logger.debug("Unable to destroy volume" + data.getId(), ex);
             result.setResult(ex.toString());
         }
         callback.complete(result);
@@ -552,4 +552,13 @@
         }
         return false;
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return StoragePoolType.NetworkFilesystem == type;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
 }
diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
index 213e562..685565d 100644
--- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
+++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
@@ -27,6 +27,7 @@
 import com.cloud.alert.AlertManager;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.StorageConflictException;
+import com.cloud.exception.StorageUnavailableException;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
@@ -44,7 +45,6 @@
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.user.dao.UserDao;
 import com.cloud.utils.NumbersUtil;
-import com.cloud.utils.UriUtils;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VirtualMachineManager;
@@ -67,10 +67,6 @@
 import org.apache.log4j.Logger;
 
 import javax.inject.Inject;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -138,64 +134,27 @@
 
         PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
 
-        UriUtils.UriInfo uriInfo = UriUtils.getUriInfo(url);
-
-        String scheme = uriInfo.getScheme();
-        String storageHost = uriInfo.getStorageHost();
-        String storagePath = uriInfo.getStoragePath();
-        try {
-            if (scheme == null) {
-                throw new InvalidParameterValueException("scheme is null " + url + ", add nfs:// (or cifs://) as a prefix");
-            } else if (scheme.equalsIgnoreCase("nfs")) {
-                if (storageHost == null || storagePath == null || storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) {
-                    throw new InvalidParameterValueException("host or path is null, should be nfs://hostname/path");
-                }
-            } else if (scheme.equalsIgnoreCase("cifs")) {
-                // Don't validate against a URI encoded URI.
-                URI cifsUri = new URI(url);
-                String warnMsg = UriUtils.getCifsUriParametersProblems(cifsUri);
-                if (warnMsg != null) {
-                    throw new InvalidParameterValueException(warnMsg);
-                }
-            } else if (scheme.equalsIgnoreCase("sharedMountPoint")) {
-                if (storagePath == null) {
-                    throw new InvalidParameterValueException("host or path is null, should be sharedmountpoint://localhost/path");
-                }
-            } else if (scheme.equalsIgnoreCase("rbd")) {
-                if (storagePath == null) {
-                    throw new InvalidParameterValueException("host or path is null, should be rbd://hostname/pool");
-                }
-            } else if (scheme.equalsIgnoreCase("gluster")) {
-                if (storageHost == null || storagePath == null || storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) {
-                    throw new InvalidParameterValueException("host or path is null, should be gluster://hostname/volume");
-                }
-            }
-        } catch (URISyntaxException e) {
-            throw new InvalidParameterValueException(url + " is not a valid uri");
-        }
-
         String tags = (String)dsInfos.get("tags");
         Map<String, String> details = (Map<String, String>)dsInfos.get("details");
 
         parameters.setTags(tags);
+        parameters.setIsTagARule((Boolean)dsInfos.get("isTagARule"));
         parameters.setDetails(details);
 
-        String hostPath = null;
-        try {
-            hostPath = URLDecoder.decode(storagePath, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            s_logger.error("[ignored] we are on a platform not supporting \"UTF-8\"!?!", e);
-        }
-        if (hostPath == null) { // if decoding fails, use getPath() anyway
-            hostPath = storagePath;
-        }
+        String scheme = dsInfos.get("scheme").toString();
+        String storageHost = dsInfos.get("host").toString();
+        String hostPath = dsInfos.get("hostPath").toString();
+        String uri = String.format("%s://%s%s", scheme, storageHost, hostPath);
+
         Object localStorage = dsInfos.get("localStorage");
-        if (localStorage != null) {
-            hostPath = hostPath.replaceFirst("/", "");
+           if (localStorage != null) {
+            hostPath = hostPath.contains("//") ? hostPath.replaceFirst("/", "") : hostPath;
             hostPath = hostPath.replace("+", " ");
         }
-        String userInfo = uriInfo.getUserInfo();
-        int port = uriInfo.getPort();
+
+        String userInfo = dsInfos.get("userInfo") != null ? dsInfos.get("userInfo").toString() : null;
+        int port = dsInfos.get("port") != null ? Integer.parseInt(dsInfos.get("port").toString()) : -1;
+
         if (s_logger.isDebugEnabled()) {
             s_logger.debug("createPool Params @ scheme - " + scheme + " storageHost - " + storageHost + " hostPath - " + hostPath + " port - " + port);
         }
@@ -312,8 +271,8 @@
                 parameters.setPort(0);
                 parameters.setPath(hostPath);
             } else {
-                s_logger.warn("Unable to figure out the scheme for URI: " + uriInfo);
-                throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + uriInfo);
+                s_logger.warn("Unable to figure out the scheme for URI: " + scheme);
+                throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + scheme);
             }
         }
 
@@ -321,7 +280,7 @@
             List<StoragePoolVO> pools = primaryDataStoreDao.listPoolByHostPath(storageHost, hostPath);
             if (!pools.isEmpty() && !scheme.equalsIgnoreCase("sharedmountpoint")) {
                 Long oldPodId = pools.get(0).getPodId();
-                throw new CloudRuntimeException("Storage pool " + uriInfo + " already in use by another pod (id=" + oldPodId + ")");
+                throw new CloudRuntimeException("Storage pool " + hostPath + " already in use by another pod (id=" + oldPodId + ")");
             }
         }
 
@@ -550,7 +509,16 @@
 
     @Override
     public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
-        dataStoreHelper.attachHost(store, scope, existingInfo);
+        DataStore dataStore = dataStoreHelper.attachHost(store, scope, existingInfo);
+        if(existingInfo.getCapacityBytes() == 0){
+            try {
+                storageMgr.connectHostToSharedPool(scope.getScopeId(), dataStore.getId());
+            } catch (StorageUnavailableException ex) {
+                s_logger.error("Storage unavailable ",ex);
+            } catch (StorageConflictException ex) {
+                s_logger.error("Storage already exists ",ex);
+            }
+        }
         return true;
     }
 
diff --git a/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties b/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties
index 6136988..e596415 100644
--- a/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties
+++ b/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-volume-default
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/volume/default/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImplTest.java b/plugins/storage/volume/default/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImplTest.java
index ffdc514..dbd13d8 100644
--- a/plugins/storage/volume/default/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImplTest.java
+++ b/plugins/storage/volume/default/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImplTest.java
@@ -23,6 +23,7 @@
 import com.cloud.agent.api.ModifyStoragePoolAnswer;
 import com.cloud.agent.api.ModifyStoragePoolCommand;
 import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.exception.StorageConflictException;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
@@ -32,7 +33,6 @@
 import com.cloud.storage.Storage;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StorageManagerImpl;
-import com.cloud.storage.StoragePoolHostVO;
 import com.cloud.storage.dao.StoragePoolHostDao;
 import junit.framework.TestCase;
 import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
@@ -45,8 +45,9 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.apache.cloudstack.storage.datastore.provider.DefaultHostListener;
 import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
+import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,8 +55,8 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,8 +65,6 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -77,7 +76,6 @@
     @InjectMocks
     PrimaryDataStoreLifeCycle _cloudStackPrimaryDataStoreLifeCycle = new CloudStackPrimaryDataStoreLifeCycleImpl();
 
-    @Spy
     @InjectMocks
     StorageManager storageMgr = new StorageManagerImpl();
 
@@ -93,9 +91,8 @@
     @Mock
     DataStoreProviderManager _dataStoreProviderMgr;
 
-    @Spy
-    @InjectMocks
-    HypervisorHostListener hostListener = new DefaultHostListener();
+    @Mock
+    HypervisorHostListener hostListener;
 
     @Mock
     StoragePoolHostDao storagePoolHostDao;
@@ -121,10 +118,16 @@
     @Mock
     PrimaryDataStoreHelper primaryDataStoreHelper;
 
-    @Before
-    public void initMocks() {
+    AutoCloseable closeable;
 
-        MockitoAnnotations.initMocks(this);
+    @Before
+    public void initMocks() throws StorageConflictException {
+        closeable = MockitoAnnotations.openMocks(this);
+
+        ReflectionTestUtils.setField(storageMgr, "_storagePoolDao", primaryStoreDao);
+        ReflectionTestUtils.setField(storageMgr, "_dataStoreProviderMgr", _dataStoreProviderMgr);
+        ReflectionTestUtils.setField(storageMgr, "_dataStoreMgr", _dataStoreMgr);
+        ReflectionTestUtils.setField(_cloudStackPrimaryDataStoreLifeCycle, "storageMgr", storageMgr);
 
         List<HostVO> hostList = new ArrayList<HostVO>();
         HostVO host1 = new HostVO(1L, "aa01", Host.Type.Routing, "192.168.1.1", "255.255.255.0", null, null, null, null, null, null, null, null, null, null,
@@ -141,30 +144,31 @@
         when(store.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
         when(store.isShared()).thenReturn(true);
         when(store.getName()).thenReturn("newPool");
+        when(store.getStorageProviderName()).thenReturn("default");
+
 
         when(_dataStoreProviderMgr.getDataStoreProvider(anyString())).thenReturn(dataStoreProvider);
         when(dataStoreProvider.getName()).thenReturn("default");
-        ((StorageManagerImpl)storageMgr).registerHostListener("default", hostListener);
+
+        when(hostListener.hostConnect(Mockito.anyLong(), Mockito.anyLong())).thenReturn(true);
+        storageMgr.registerHostListener("default", hostListener);
+
 
         when(_resourceMgr.listAllUpHosts(eq(Host.Type.Routing), anyLong(), anyLong(), anyLong())).thenReturn(hostList);
         when(agentMgr.easySend(anyLong(), Mockito.any(ModifyStoragePoolCommand.class))).thenReturn(answer);
         when(answer.getResult()).thenReturn(true);
-        when(answer.getPoolInfo()).thenReturn(info);
 
-        when(info.getLocalPath()).thenReturn("/mnt/1");
-        when(info.getCapacityBytes()).thenReturn(0L);
-        when(info.getAvailableBytes()).thenReturn(0L);
-
-        when(storagePoolHostDao.findByPoolHost(anyLong(), anyLong())).thenReturn(null);
         when(primaryStoreDao.findById(anyLong())).thenReturn(storagePool);
-        when(primaryStoreDao.update(anyLong(), Mockito.any(StoragePoolVO.class))).thenReturn(true);
         when(primaryDataStoreHelper.attachCluster(Mockito.any(DataStore.class))).thenReturn(null);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void testAttachCluster() throws Exception {
-        _cloudStackPrimaryDataStoreLifeCycle.attachCluster(store, new ClusterScope(1L, 1L, 1L));
-        verify(storagePoolHostDao,times(2)).persist(Mockito.any(StoragePoolHostVO.class));
-
+        Assert.assertTrue(_cloudStackPrimaryDataStoreLifeCycle.attachCluster(store, new ClusterScope(1L, 1L, 1L)));
     }
 }
diff --git a/plugins/storage/volume/flasharray/pom.xml b/plugins/storage/volume/flasharray/pom.xml
new file mode 100644
index 0000000..c971bf0
--- /dev/null
+++ b/plugins/storage/volume/flasharray/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-storage-volume-flasharray</artifactId>
+    <name>Apache CloudStack Plugin - Storage Volume - Pure Flash Array</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-volume-adaptive</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java
new file mode 100644
index 0000000..3082a19
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java
@@ -0,0 +1,1086 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.flasharray;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.Header;
+import org.apache.http.NameValuePair;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterContext;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDataObject;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDiskOffering;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderSnapshot;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolume;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeNamer;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStats;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStorageStats;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolume.AddressType;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPatch;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.TrustAllStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Array API
+ */
+public class FlashArrayAdapter implements ProviderAdapter {
+    private Logger logger = Logger.getLogger(FlashArrayAdapter.class);
+
+    public static final String HOSTGROUP = "hostgroup";
+    public static final String STORAGE_POD = "pod";
+    public static final String KEY_TTL = "keyttl";
+    public static final String CONNECT_TIMEOUT_MS = "connectTimeoutMs";
+    public static final String POST_COPY_WAIT_MS = "postCopyWaitMs";
+    public static final String API_LOGIN_VERSION = "apiLoginVersion";
+    public static final String API_VERSION = "apiVersion";
+
+    private static final long KEY_TTL_DEFAULT = (1000 * 60 * 14);
+    private static final long CONNECT_TIMEOUT_MS_DEFAULT = 600000;
+    private static final long POST_COPY_WAIT_MS_DEFAULT = 5000;
+    private static final String API_LOGIN_VERSION_DEFAULT = "1.19";
+    private static final String API_VERSION_DEFAULT = "2.23";
+
+    static final ObjectMapper mapper = new ObjectMapper();
+    public String pod = null;
+    public String hostgroup = null;
+    private String username;
+    private String password;
+    private String accessToken;
+    private String url;
+    private long keyExpiration = -1;
+    private long keyTtl = KEY_TTL_DEFAULT;
+    private long connTimeout = CONNECT_TIMEOUT_MS_DEFAULT;
+    private long postCopyWait = POST_COPY_WAIT_MS_DEFAULT;
+    private CloseableHttpClient _client = null;
+    private boolean skipTlsValidation;
+    private String apiLoginVersion = API_LOGIN_VERSION_DEFAULT;
+    private String apiVersion = API_VERSION_DEFAULT;
+
+    private Map<String, String> connectionDetails = null;
+
+    protected FlashArrayAdapter(String url, Map<String, String> details) {
+        this.url = url;
+        this.connectionDetails = details;
+        login();
+    }
+
+    @Override
+    public ProviderVolume create(ProviderAdapterContext context, ProviderAdapterDataObject dataObject, ProviderAdapterDiskOffering offering, long size) {
+        FlashArrayVolume request = new FlashArrayVolume();
+        request.setExternalName(
+                pod + "::" + ProviderVolumeNamer.generateObjectName(context, dataObject));
+        request.setPodName(pod);
+        request.setAllocatedSizeBytes(roundUp512Boundary(size));
+        FlashArrayList<FlashArrayVolume> list = POST("/volumes?names=" + request.getExternalName() + "&overwrite=false",
+                request, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+
+        return (ProviderVolume) getFlashArrayItem(list);
+    }
+
+    /**
+     * Volumes must be added to a host set to be visable to the hosts.
+     * the Hostset should contain all the hosts that are membrers of the zone or
+     * cluster (depending on Cloudstack Storage Pool configuration)
+     */
+    @Override
+    public String attach(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
+        String volumeName = normalizeName(pod, dataObject.getExternalName());
+        try {
+            FlashArrayList<FlashArrayConnection> list = POST("/connections?host_group_names=" + hostgroup + "&volume_names=" + volumeName, null, new TypeReference<FlashArrayList<FlashArrayConnection>> () { });
+
+            if (list == null || list.getItems() == null || list.getItems().size() == 0) {
+                throw new RuntimeException("Volume attach did not return lun information");
+            }
+
+            FlashArrayConnection connection = (FlashArrayConnection)this.getFlashArrayItem(list);
+            if (connection.getLun() == null) {
+                throw new RuntimeException("Volume attach missing lun field");
+            }
+
+            return ""+connection.getLun();
+
+        } catch (Throwable e) {
+            // the volume is already attached.  happens in some scenarios where orchestration creates the volume before copying to it
+            if (e.toString().contains("Connection already exists")) {
+                FlashArrayList<FlashArrayConnection> list = GET("/connections?volume_names=" + volumeName,
+                    new TypeReference<FlashArrayList<FlashArrayConnection>>() {
+                    });
+                if (list != null && list.getItems() != null) {
+                    return ""+list.getItems().get(0).getLun();
+                } else {
+                    throw new RuntimeException("Volume lun is not found in existing connection");
+                }
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public void detach(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
+        String volumeName = normalizeName(pod, dataObject.getExternalName());
+        DELETE("/connections?host_group_names=" + hostgroup + "&volume_names=" + volumeName);
+    }
+
+    @Override
+    public void delete(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
+        // public void deleteVolume(String volumeNamespace, String volumeName) {
+        // first make sure we are disconnected
+        removeVlunsAll(context, pod, dataObject.getExternalName());
+        String fullName = normalizeName(pod, dataObject.getExternalName());
+
+        FlashArrayVolume volume = new FlashArrayVolume();
+        volume.setDestroyed(true);
+        try {
+            PATCH("/volumes?names=" + fullName, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+            });
+        } catch (CloudRuntimeException e) {
+            if (e.toString().contains("Volume does not exist")) {
+                return;
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public ProviderVolume getVolume(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
+        String externalName = dataObject.getExternalName();
+        // if its not set, look for the generated name for some edge cases
+        if (externalName == null) {
+            externalName = pod + "::" + ProviderVolumeNamer.generateObjectName(context, dataObject);
+        }
+        FlashArrayVolume volume = null;
+        try {
+            volume = getVolume(externalName);
+            // if we didn't get an address back its likely an empty object
+            if (volume != null && volume.getAddress() == null) {
+                return null;
+            } else if (volume == null) {
+                return null;
+            }
+
+            populateConnectionId(volume);
+
+            return volume;
+        } catch (Exception e) {
+            // assume any exception is a not found. Flash returns 400's for most errors
+            return null;
+        }
+    }
+
+    @Override
+    public ProviderVolume getVolumeByAddress(ProviderAdapterContext context, AddressType addressType, String address) {
+        // public FlashArrayVolume getVolumeByWwn(String wwn) {
+        if (address == null ||addressType == null) {
+            throw new RuntimeException("Invalid search criteria provided for getVolumeByAddress");
+        }
+
+        // only support WWN type addresses at this time.
+        if (!ProviderVolume.AddressType.FIBERWWN.equals(addressType)) {
+            throw new RuntimeException(
+                    "Invalid volume address type [" + addressType + "] requested for volume search");
+        }
+
+        // convert WWN to serial to search on. strip out WWN type # + Flash OUI value
+        String serial = address.substring(FlashArrayVolume.PURE_OUI.length() + 1).toUpperCase();
+        String query = "serial='" + serial + "'";
+
+        FlashArrayVolume volume = null;
+        try {
+            FlashArrayList<FlashArrayVolume> list = GET("/volumes?filter=" + query,
+                new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+
+            // if we didn't get an address back its likely an empty object
+            if (list == null || list.getItems() == null || list.getItems().size() == 0) {
+                return null;
+            }
+
+            volume = (FlashArrayVolume)this.getFlashArrayItem(list);
+            if (volume != null && volume.getAddress() == null) {
+                return null;
+            }
+
+            populateConnectionId(volume);
+
+            return volume;
+        } catch (Exception e) {
+            // assume any exception is a not found. Flash returns 400's for most errors
+            return null;
+        }
+    }
+
+    private void populateConnectionId(FlashArrayVolume volume) {
+        // we need to see if there is a connection (lun) associated with this volume.
+        // note we assume 1 lun for the hostgroup associated with this object
+        FlashArrayList<FlashArrayConnection> list = null;
+        try {
+            list = GET("/connections?volume_names=" + volume.getExternalName(),
+                    new TypeReference<FlashArrayList<FlashArrayConnection>>() {
+                    });
+        } catch (CloudRuntimeException e) {
+            // this means there is no attachment associated with this volume on the array
+            if (e.toString().contains("Bad Request")) {
+                return;
+            }
+        }
+
+        if (list != null && list.getItems() != null) {
+            for (FlashArrayConnection conn: list.getItems()) {
+                if (conn.getHostGroup() != null && conn.getHostGroup().getName().equals(this.hostgroup)) {
+                    volume.setExternalConnectionId(""+conn.getLun());
+                    break;
+                }
+            }
+
+        }
+    }
+
+    @Override
+    public void resize(ProviderAdapterContext context, ProviderAdapterDataObject dataObject, long newSizeInBytes) {
+        // public void resizeVolume(String volumeNamespace, String volumeName, long
+        // newSizeInBytes) {
+        FlashArrayVolume volume = new FlashArrayVolume();
+        volume.setAllocatedSizeBytes(roundUp512Boundary(newSizeInBytes));
+        PATCH("/volumes?names=" + dataObject.getExternalName(), volume, null);
+    }
+
+    /**
+     * Take a snapshot and return a Volume representing that snapshot
+     *
+     * @param volumeName
+     * @param snapshotName
+     * @return
+     */
+    @Override
+    public ProviderSnapshot snapshot(ProviderAdapterContext context, ProviderAdapterDataObject sourceDataObject, ProviderAdapterDataObject targetDataObject) {
+        // public FlashArrayVolume snapshotVolume(String volumeNamespace, String
+        // volumeName, String snapshotName) {
+        FlashArrayList<FlashArrayVolume> list = POST(
+                "/volume-snapshots?source_names=" + sourceDataObject.getExternalName(), null,
+                new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+
+        return (FlashArrayVolume) getFlashArrayItem(list);
+    }
+
+    /**
+     * Replaces the base volume with the given snapshot. Note this can only be done
+     * when the snapshot and volume
+     * are
+     *
+     * @param name
+     * @return
+     */
+    @Override
+    public ProviderVolume revert(ProviderAdapterContext context, ProviderAdapterDataObject snapshotDataObject) {
+        // public void promoteSnapshot(String namespace, String snapshotName) {
+        if (snapshotDataObject == null || snapshotDataObject.getExternalName() == null) {
+            throw new RuntimeException("Snapshot revert not possible as an external snapshot name was not provided");
+        }
+
+        FlashArrayVolume snapshot = this.getSnapshot(snapshotDataObject.getExternalName());
+        if (snapshot.getSource() == null) {
+            throw new CloudRuntimeException("Snapshot source was not available from the storage array");
+        }
+
+        String origVolumeName = snapshot.getSource().getName();
+
+        // now "create" a new volume with the snapshot volume as its source (basically a
+        // Flash array copy)
+        // and overwrite to true (volume already exists, we are recreating it)
+        FlashArrayVolume input = new FlashArrayVolume();
+        input.setExternalName(origVolumeName);
+        input.setAllocatedSizeBytes(roundUp512Boundary(snapshot.getAllocatedSizeInBytes()));
+        input.setSource(new FlashArrayVolumeSource(snapshot.getExternalName()));
+        POST("/volumes?names=" + origVolumeName + "&overwrite=true", input, null);
+
+        return this.getVolume(origVolumeName);
+    }
+
+    @Override
+    public ProviderSnapshot getSnapshot(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
+        FlashArrayList<FlashArrayVolume> list = GET(
+                "/volume-snapshots?names=" + dataObject.getExternalName(),
+                new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+        return (FlashArrayVolume) getFlashArrayItem(list);
+    }
+
+    @Override
+    public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceDataObject, ProviderAdapterDataObject destDataObject) {
+        // private ManagedVolume copy(ManagedVolume sourceVolume, String destNamespace,
+        // String destName) {
+        if (sourceDataObject == null || sourceDataObject.getExternalName() == null
+                ||sourceDataObject.getType() == null) {
+            throw new RuntimeException("Provided volume has no external source information");
+        }
+
+        if (destDataObject == null) {
+            throw new RuntimeException("Provided volume target information was not provided");
+        }
+
+        if (destDataObject.getExternalName() == null) {
+            // this means its a new volume? so our external name will be the Cloudstack UUID
+            destDataObject
+                    .setExternalName(ProviderVolumeNamer.generateObjectName(context, destDataObject));
+        }
+
+        FlashArrayVolume currentVol;
+        if (sourceDataObject.getType().equals(ProviderAdapterDataObject.Type.SNAPSHOT)) {
+            currentVol = getSnapshot(sourceDataObject.getExternalName());
+        } else {
+            currentVol = (FlashArrayVolume) this
+                    .getFlashArrayItem(GET("/volumes?names=" + sourceDataObject.getExternalName(),
+                            new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                            }));
+        }
+
+        if (currentVol == null) {
+            throw new RuntimeException("Unable to find current volume to copy from");
+        }
+
+        // now "create" a new volume with the snapshot volume as its source (basically a
+        // Flash array copy)
+        // and overwrite to true (volume already exists, we are recreating it)
+        FlashArrayVolume payload = new FlashArrayVolume();
+        payload.setExternalName(normalizeName(pod, destDataObject.getExternalName()));
+        payload.setPodName(pod);
+        payload.setAllocatedSizeBytes(roundUp512Boundary(currentVol.getAllocatedSizeInBytes()));
+        payload.setSource(new FlashArrayVolumeSource(sourceDataObject.getExternalName()));
+        FlashArrayList<FlashArrayVolume> list = POST(
+                "/volumes?names=" + payload.getExternalName() + "&overwrite=true", payload,
+                new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+        FlashArrayVolume outVolume = (FlashArrayVolume) getFlashArrayItem(list);
+        pause(postCopyWait);
+        return outVolume;
+    }
+
+    private void pause(long period) {
+        try {
+            Thread.sleep(period);
+        } catch (InterruptedException e) {
+
+        }
+    }
+
+    public boolean supportsSnapshotConnection() {
+        return false;
+    }
+
+    @Override
+    public void refresh(Map<String, String> details) {
+        this.connectionDetails = details;
+        this.refreshSession(true);
+    }
+
+    @Override
+    public void validate() {
+        login();
+        // check if hostgroup and pod from details really exist - we will
+        // require a distinct configuration object/connection object for each type
+        if (this.getHostgroup(hostgroup) == null) {
+            throw new RuntimeException("Hostgroup [" + hostgroup + "] not found in FlashArray at [" + url
+                    + "], please validate configuration");
+        }
+
+        if (this.getVolumeNamespace(pod) == null) {
+            throw new RuntimeException(
+                    "Pod [" + pod + "] not found in FlashArray at [" + url + "], please validate configuration");
+        }
+    }
+
+    @Override
+    public void disconnect() {
+        return;
+    }
+
+    @Override
+    public ProviderVolumeStorageStats getManagedStorageStats() {
+        FlashArrayPod pod = getVolumeNamespace(this.pod);
+        // just in case
+        if (pod == null || pod.getFootprint() == 0) {
+            return null;
+        }
+        Long capacityBytes = pod.getQuotaLimit();
+        Long usedBytes = pod.getQuotaLimit() - (pod.getQuotaLimit() - pod.getFootprint());
+        ProviderVolumeStorageStats stats = new ProviderVolumeStorageStats();
+        stats.setCapacityInBytes(capacityBytes);
+        stats.setActualUsedInBytes(usedBytes);
+        return stats;
+    }
+
+    @Override
+    public ProviderVolumeStats getVolumeStats(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
+        ProviderVolume vol = getVolume(dataObject.getExternalName());
+        Long usedBytes = vol.getUsedBytes();
+        Long allocatedSizeInBytes = vol.getAllocatedSizeInBytes();
+        if (usedBytes == null || allocatedSizeInBytes == null) {
+            return null;
+        }
+        ProviderVolumeStats stats = new ProviderVolumeStats();
+        stats.setAllocatedInBytes(allocatedSizeInBytes);
+        stats.setActualUsedInBytes(usedBytes);
+        return stats;
+    }
+
+    @Override
+    public boolean canAccessHost(ProviderAdapterContext context, String hostname) {
+        if (hostname == null) {
+            throw new RuntimeException("Unable to validate host access because a hostname was not provided");
+        }
+
+        List<String> members = getHostgroupMembers(hostgroup);
+
+        // check for fqdn and shortname combinations.  this assumes there is at least a shortname match in both the storage array and cloudstack
+        // hostname configuration
+        String shortname;
+        if (hostname.indexOf('.') > 0) {
+            shortname = hostname.substring(0, (hostname.indexOf('.')));
+        } else {
+            shortname = hostname;
+        }
+
+        for (String member : members) {
+            // exact match (short or long names)
+            if (member.equals(hostname)) {
+                return true;
+            }
+
+            // primera has short name and cloudstack had long name
+            if (member.equals(shortname)) {
+                return true;
+            }
+
+            // member has long name but cloudstack had shortname
+            if (member.indexOf('.') > 0) {
+                if (member.substring(0, (member.indexOf('.'))).equals(shortname)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private String getAccessToken() {
+        refreshSession(false);
+        return accessToken;
+    }
+
+    private synchronized void refreshSession(boolean force) {
+        try {
+            if (force || keyExpiration < System.currentTimeMillis()) {
+                // close client to force connection reset on appliance -- not doing this can
+                // result in NotAuthorized error...guessing
+                _client.close();
+                ;
+                _client = null;
+                login();
+                keyExpiration = System.currentTimeMillis() + keyTtl;
+            }
+        } catch (Exception e) {
+            // retry frequently but not every request to avoid DDOS on storage API
+            logger.warn("Failed to refresh FlashArray API key for " + username + "@" + url + ", will retry in 5 seconds",
+                    e);
+            keyExpiration = System.currentTimeMillis() + (5 * 1000);
+        }
+    }
+
+    private void validateLoginInfo(String urlStr) {
+        URL urlFull;
+        try {
+            urlFull = new URL(urlStr);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("Invalid URL format: " + urlStr, e);
+        }
+        ;
+
+        int port = urlFull.getPort();
+        if (port <= 0) {
+            port = 443;
+        }
+        this.url = urlFull.getProtocol() + "://" + urlFull.getHost() + ":" + port + urlFull.getPath();
+
+        Map<String, String> queryParms = new HashMap<String, String>();
+        if (urlFull.getQuery() != null) {
+            String[] queryToks = urlFull.getQuery().split("&");
+            for (String tok : queryToks) {
+                if (tok.endsWith("=")) {
+                    continue;
+                }
+                int i = tok.indexOf("=");
+                if (i > 0) {
+                    queryParms.put(tok.substring(0, i), tok.substring(i + 1));
+                }
+            }
+        }
+
+        pod = connectionDetails.get(FlashArrayAdapter.STORAGE_POD);
+        if (pod == null) {
+            pod = queryParms.get(FlashArrayAdapter.STORAGE_POD);
+            if (pod == null) {
+                throw new RuntimeException(
+                        FlashArrayAdapter.STORAGE_POD + " paramater/option required to configure this storage pool");
+            }
+        }
+
+        hostgroup = connectionDetails.get(FlashArrayAdapter.HOSTGROUP);
+        if (hostgroup == null) {
+            hostgroup = queryParms.get(FlashArrayAdapter.HOSTGROUP);
+            if (hostgroup == null) {
+                throw new RuntimeException(
+                        FlashArrayAdapter.STORAGE_POD + " paramater/option required to configure this storage pool");
+            }
+        }
+
+        apiLoginVersion = connectionDetails.get(FlashArrayAdapter.API_LOGIN_VERSION);
+        if (apiLoginVersion == null) {
+            apiLoginVersion = queryParms.get(FlashArrayAdapter.API_LOGIN_VERSION);
+            if (apiLoginVersion == null) {
+                apiLoginVersion = API_LOGIN_VERSION_DEFAULT;
+            }
+        }
+
+        apiVersion = connectionDetails.get(FlashArrayAdapter.API_VERSION);
+        if (apiVersion == null) {
+            apiVersion = queryParms.get(FlashArrayAdapter.API_VERSION);
+            if (apiVersion == null) {
+                apiVersion = API_VERSION_DEFAULT;
+            }
+        }
+
+        String connTimeoutStr = connectionDetails.get(FlashArrayAdapter.CONNECT_TIMEOUT_MS);
+        if (connTimeoutStr == null) {
+            connTimeoutStr = queryParms.get(FlashArrayAdapter.CONNECT_TIMEOUT_MS);
+        }
+        if (connTimeoutStr == null) {
+            connTimeout = CONNECT_TIMEOUT_MS_DEFAULT;
+        } else {
+            try {
+                connTimeout = Integer.parseInt(connTimeoutStr);
+            } catch (NumberFormatException e) {
+                logger.warn("Connection timeout not formatted correctly, using default", e);
+                connTimeout = CONNECT_TIMEOUT_MS_DEFAULT;
+            }
+        }
+
+        String keyTtlString = connectionDetails.get(FlashArrayAdapter.KEY_TTL);
+        if (keyTtlString == null) {
+            keyTtlString = queryParms.get(FlashArrayAdapter.KEY_TTL);
+        }
+        if (keyTtlString == null) {
+            keyTtl = KEY_TTL_DEFAULT;
+        } else {
+            try {
+                keyTtl = Integer.parseInt(keyTtlString);
+            } catch (NumberFormatException e) {
+                logger.warn("Key TTL not formatted correctly, using default", e);
+                keyTtl = KEY_TTL_DEFAULT;
+            }
+        }
+
+        String copyWaitStr = connectionDetails.get(FlashArrayAdapter.POST_COPY_WAIT_MS);
+        if (copyWaitStr == null) {
+            copyWaitStr = queryParms.get(FlashArrayAdapter.POST_COPY_WAIT_MS);
+        }
+        if (copyWaitStr == null) {
+            postCopyWait = POST_COPY_WAIT_MS_DEFAULT;
+        } else {
+            try {
+                postCopyWait = Integer.parseInt(copyWaitStr);
+            } catch (NumberFormatException e) {
+                logger.warn("Key TTL not formatted correctly, using default", e);
+                postCopyWait = KEY_TTL_DEFAULT;
+            }
+        }
+
+        String skipTlsValidationStr = connectionDetails.get(ProviderAdapter.API_SKIP_TLS_VALIDATION_KEY);
+        if (skipTlsValidationStr == null) {
+            skipTlsValidationStr = queryParms.get(ProviderAdapter.API_SKIP_TLS_VALIDATION_KEY);
+        }
+
+        if (skipTlsValidationStr != null) {
+            skipTlsValidation = Boolean.parseBoolean(skipTlsValidationStr);
+        } else {
+            skipTlsValidation = true;
+        }
+    }
+
+    /**
+     * Login to the array and get an access token
+     */
+    private void login() {
+        username = connectionDetails.get(ProviderAdapter.API_USERNAME_KEY);
+        password = connectionDetails.get(ProviderAdapter.API_PASSWORD_KEY);
+        String urlStr = connectionDetails.get(ProviderAdapter.API_URL_KEY);
+        validateLoginInfo(urlStr);
+        CloseableHttpResponse response = null;
+        try {
+            HttpPost request = new HttpPost(url + "/" + apiLoginVersion + "/auth/apitoken");
+            // request.addHeader("Content-Type", "application/json");
+            // request.addHeader("Accept", "application/json");
+            ArrayList<NameValuePair> postParms = new ArrayList<NameValuePair>();
+            postParms.add(new BasicNameValuePair("username", username));
+            postParms.add(new BasicNameValuePair("password", password));
+            request.setEntity(new UrlEncodedFormEntity(postParms, "UTF-8"));
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+
+            int statusCode = response.getStatusLine().getStatusCode();
+            FlashArrayApiToken apitoken = null;
+            if (statusCode == 200 | statusCode == 201) {
+                apitoken = mapper.readValue(response.getEntity().getContent(), FlashArrayApiToken.class);
+                if (apitoken == null) {
+                    throw new CloudRuntimeException(
+                            "Authentication responded successfully but no api token was returned");
+                }
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new CloudRuntimeException(
+                        "Authentication or Authorization to FlashArray [" + url + "] with user [" + username
+                                + "] failed, unable to retrieve session token");
+            } else {
+                throw new CloudRuntimeException(
+                        "Unexpected HTTP response code from FlashArray [" + url + "] - [" + statusCode
+                                + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+
+            // now we need to get the access token
+            request = new HttpPost(url + "/" + apiVersion + "/login");
+            request.addHeader("api-token", apitoken.getApiToken());
+            response = (CloseableHttpResponse) client.execute(request);
+
+            statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 | statusCode == 201) {
+                Header[] headers = response.getHeaders("x-auth-token");
+                if (headers == null || headers.length == 0) {
+                    throw new CloudRuntimeException(
+                            "Getting access token responded successfully but access token was not available");
+                }
+                accessToken = headers[0].getValue();
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new CloudRuntimeException(
+                        "Authentication or Authorization to FlashArray [" + url + "] with user [" + username
+                                + "] failed, unable to retrieve session token");
+            } else {
+                throw new CloudRuntimeException(
+                        "Unexpected HTTP response code from FlashArray [" + url + "] - [" + statusCode
+                                + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+
+        } catch (UnsupportedEncodingException e) {
+            throw new CloudRuntimeException("Error creating input for login, check username/password encoding");
+        } catch (UnsupportedOperationException e) {
+            throw new CloudRuntimeException("Error processing login response from FlashArray [" + url + "]", e);
+        } catch (IOException e) {
+            throw new CloudRuntimeException("Error sending login request to FlashArray [" + url + "]", e);
+        } finally {
+            try {
+                if (response != null) {
+                    response.close();
+                }
+            } catch (IOException e) {
+                logger.debug("Error closing response from login attempt to FlashArray", e);
+            }
+        }
+    }
+
+    private void removeVlunsAll(ProviderAdapterContext context, String volumeNamespace, String volumeName) {
+        volumeName = normalizeName(volumeNamespace, volumeName);
+        FlashArrayList<FlashArrayConnection> list = null;
+
+        try {
+            list = GET("/connections?volume_names=" + volumeName,
+                    new TypeReference<FlashArrayList<FlashArrayConnection>>() {
+                    });
+        } catch (CloudRuntimeException e) {
+            // this means the volume being deleted no longer exists so no connections can be
+            // searched
+            if (e.toString().contains("Bad Request")) {
+                return;
+            }
+        }
+
+        if (list != null && list.getItems() != null) {
+            for (FlashArrayConnection conn : list.getItems()) {
+                DELETE("/connections?host_group_names=" + conn.getHostGroup().getName() + "&volume_names=" + volumeName);
+            }
+        }
+    }
+
+    private FlashArrayVolume getVolume(String volumeName) {
+        FlashArrayList<FlashArrayVolume> list = GET("/volumes?names=" + volumeName,
+                new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+        return (FlashArrayVolume) getFlashArrayItem(list);
+    }
+
+    private FlashArrayPod getVolumeNamespace(String name) {
+        FlashArrayList<FlashArrayPod> list = GET("/pods?names=" + name, new TypeReference<FlashArrayList<FlashArrayPod>>() {
+        });
+        return (FlashArrayPod) getFlashArrayItem(list);
+    }
+
+    private FlashArrayHostgroup getHostgroup(String name) {
+        FlashArrayList<FlashArrayHostgroup> list = GET("/host-groups?name=" + name,
+                new TypeReference<FlashArrayList<FlashArrayHostgroup>>() {
+                });
+        return (FlashArrayHostgroup) getFlashArrayItem(list);
+    }
+
+    private List<String> getHostgroupMembers(String groupname) {
+        FlashArrayGroupMemberReferenceList list = GET("/hosts/host-groups?group_names=" + groupname,
+                new TypeReference<FlashArrayGroupMemberReferenceList>() {
+                });
+        if (list == null || list.getItems().size() == 0) {
+            return null;
+        }
+        List<String> hostnames = new ArrayList<String>();
+        for (FlashArrayGroupMemberReference ref : list.getItems()) {
+            hostnames.add(ref.getMember().getName());
+        }
+        return hostnames;
+    }
+
+    private FlashArrayVolume getSnapshot(String snapshotName) {
+        FlashArrayList<FlashArrayVolume> list = GET("/volume-snapshots?names=" + snapshotName,
+                new TypeReference<FlashArrayList<FlashArrayVolume>>() {
+                });
+        return (FlashArrayVolume) getFlashArrayItem(list);
+    }
+
+    private Object getFlashArrayItem(FlashArrayList<?> list) {
+        if (list.getItems() != null && list.getItems().size() > 0) {
+            return list.getItems().get(0);
+        } else {
+            return null;
+        }
+    }
+
+    private String normalizeName(String volumeNamespace, String volumeName) {
+        if (!volumeName.contains("::")) {
+            if (volumeNamespace != null) {
+                volumeName = volumeNamespace + "::" + volumeName;
+            }
+        }
+        return volumeName;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T POST(String path, Object input, final TypeReference<T> type) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpPost request = new HttpPost(url + "/" + apiVersion + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-auth-token", getAccessToken());
+            if (input != null) {
+                try {
+                    String data = mapper.writeValueAsString(input);
+                    request.setEntity(new StringEntity(data));
+                } catch (UnsupportedEncodingException | JsonProcessingException e) {
+                    throw new CloudRuntimeException(
+                            "Error processing request payload to [" + url + "] for path [" + path + "]", e);
+                }
+            }
+
+            CloseableHttpClient client = getClient();
+            try {
+                response = (CloseableHttpResponse) client
+                        .execute(request);
+            } catch (IOException e) {
+                throw new CloudRuntimeException("Error sending request to FlashArray [" + url + path + "]", e);
+            }
+
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 || statusCode == 201) {
+                try {
+                    if (type != null) {
+                        Header header = response.getFirstHeader("Location");
+                        if (type.getType().getTypeName().equals(String.class.getName())) {
+                            if (header != null) {
+                                return (T) header.getValue();
+                            } else {
+                                return null;
+                            }
+                        } else {
+                            return mapper.readValue(response.getEntity().getContent(), type);
+                        }
+                    }
+                    return null;
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new CloudRuntimeException("Error processing response from FlashArray [" + url + path + "]", e);
+                }
+            } else if (statusCode == 400) {
+                try {
+                    Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                            new TypeReference<Map<String, Object>>() {
+                            });
+                    throw new CloudRuntimeException("Invalid request error 400: " + payload);
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new CloudRuntimeException(
+                            "Error processing bad request response from FlashArray [" + url + path + "]", e);
+                }
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new CloudRuntimeException(
+                        "Authentication or Authorization to FlashArray [" + url + "] with user [" + username
+                                + "] failed, unable to retrieve session token");
+            } else {
+                try {
+                    Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                            new TypeReference<Map<String, Object>>() {
+                            });
+                    throw new CloudRuntimeException("Invalid request error " + statusCode + ": " + payload);
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new CloudRuntimeException(
+                            "Unexpected HTTP response code from FlashArray on POST [" + url + path + "] - ["
+                                    + statusCode + "] - " + response.getStatusLine().getReasonPhrase());
+                }
+            }
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to FlashArray API", e);
+                }
+            }
+        }
+    }
+
+    private <T> T PATCH(String path, Object input, final TypeReference<T> type) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpPatch request = new HttpPatch(url + "/" + apiVersion + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-auth-token", getAccessToken());
+            String data = mapper.writeValueAsString(input);
+            request.setEntity(new StringEntity(data));
+
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 || statusCode == 201) {
+                if (type != null)
+                    return mapper.readValue(response.getEntity().getContent(), type);
+                return null;
+            } else if (statusCode == 400) {
+                Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                        new TypeReference<Map<String, Object>>() {
+                        });
+                throw new CloudRuntimeException("Invalid request error 400: " + payload);
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new CloudRuntimeException(
+                        "Authentication or Authorization to FlashArray [" + url + "] with user [" + username
+                                + "] failed, unable to retrieve session token");
+            } else {
+                Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                        new TypeReference<Map<String, Object>>() {
+                        });
+                throw new CloudRuntimeException(
+                        "Invalid request error from FlashArray on PUT [" + url + path + "]" + statusCode + ": "
+                                + response.getStatusLine().getReasonPhrase() + " - " + payload);
+            }
+        } catch (UnsupportedEncodingException | JsonProcessingException e) {
+            throw new CloudRuntimeException(
+                    "Error processing request payload to [" + url + "] for path [" + path + "]", e);
+        } catch (UnsupportedOperationException e) {
+            throw new CloudRuntimeException("Error processing bad request response from FlashArray [" + url + "]",
+                    e);
+        } catch (IOException e) {
+            throw new CloudRuntimeException("Error sending request to FlashArray [" + url + "]", e);
+
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to FlashArray API", e);
+                }
+            }
+        }
+
+    }
+
+    private <T> T GET(String path, final TypeReference<T> type) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpGet request = new HttpGet(url + "/" + apiVersion + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-auth-token", getAccessToken());
+
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200) {
+                try {
+                    return mapper.readValue(response.getEntity().getContent(), type);
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new CloudRuntimeException("Error processing response from FlashArray [" + url + "]", e);
+                }
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new CloudRuntimeException(
+                        "Authentication or Authorization to FlashArray [" + url + "] with user [" + username
+                                + "] failed, unable to retrieve session token");
+            } else {
+                throw new CloudRuntimeException(
+                        "Unexpected HTTP response code from FlashArray on GET [" + request.getURI() + "] - ["
+                                + statusCode + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+        } catch (IOException e) {
+            throw new CloudRuntimeException("Error sending request to FlashArray [" + url + "]", e);
+        } catch (UnsupportedOperationException e) {
+            throw new CloudRuntimeException("Error processing response from FlashArray [" + url + "]", e);
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to FlashArray API", e);
+                }
+            }
+        }
+    }
+
+    private void DELETE(String path) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpDelete request = new HttpDelete(url + "/" + apiVersion + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-auth-token", getAccessToken());
+
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 || statusCode == 404 || statusCode == 400) {
+                // this means the volume was deleted successfully, or doesn't exist (effective
+                // delete), or
+                // the volume name is malformed or too long - meaning it never got created to
+                // begin with (effective delete)
+                return;
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new CloudRuntimeException(
+                        "Authentication or Authorization to FlashArray [" + url + "] with user [" + username
+                                + "] failed, unable to retrieve session token");
+            } else if (statusCode == 409) {
+                throw new CloudRuntimeException(
+                        "The volume cannot be deleted at this time due to existing dependencies.  Validate that all snapshots associated with this volume have been deleted and try again.");
+            } else {
+                throw new CloudRuntimeException(
+                        "Unexpected HTTP response code from FlashArray on DELETE [" + url + path + "] - ["
+                                + statusCode + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+        } catch (IOException e) {
+            throw new CloudRuntimeException("Error sending request to FlashArray [" + url + "]", e);
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to FlashArray API", e);
+                }
+            }
+        }
+    }
+
+    private CloseableHttpClient getClient() {
+        if (_client == null) {
+            RequestConfig config = RequestConfig.custom()
+                    .setConnectTimeout((int) connTimeout)
+                    .setConnectionRequestTimeout((int) connTimeout)
+                    .setSocketTimeout((int) connTimeout).build();
+
+            HostnameVerifier verifier = null;
+            SSLContext sslContext = null;
+
+            if (this.skipTlsValidation) {
+                try {
+                    verifier = NoopHostnameVerifier.INSTANCE;
+                    sslContext = new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build();
+                } catch (KeyManagementException e) {
+                    throw new CloudRuntimeException(e);
+                } catch (NoSuchAlgorithmException e) {
+                    throw new CloudRuntimeException(e);
+                } catch (KeyStoreException e) {
+                    throw new CloudRuntimeException(e);
+                }
+            }
+
+            _client = HttpClients.custom()
+                    .setDefaultRequestConfig(config)
+                    .setSSLHostnameVerifier(verifier)
+                    .setSSLContext(sslContext)
+                    .build();
+        }
+        return _client;
+    }
+
+    /**
+     * pure array requires volume sizes in multiples of 512...we'll just round up to
+     * next 512 boundary
+     *
+     * @param sizeInBytes
+     * @return
+     */
+    private Long roundUp512Boundary(Long sizeInBytes) {
+        Long remainder = sizeInBytes % 512;
+        if (remainder > 0) {
+            sizeInBytes = sizeInBytes + (512 - remainder);
+        }
+        return sizeInBytes;
+    }
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapterFactory.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapterFactory.java
new file mode 100644
index 0000000..d1c3cee
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapterFactory.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.datastore.adapter.flasharray;
+
+import java.util.Map;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterFactory;
+
+public class FlashArrayAdapterFactory implements ProviderAdapterFactory {
+
+    @Override
+    public String getProviderName() {
+        return "Flash Array";
+    }
+
+    @Override
+    public ProviderAdapter create(String url, Map<String, String> details) {
+        return new FlashArrayAdapter(url, details);
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayApiToken.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayApiToken.java
new file mode 100644
index 0000000..0f1e133
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayApiToken.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 org.apache.cloudstack.storage.datastore.adapter.flasharray;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayApiToken {
+    @JsonProperty("api_token")
+    private String apiToken;
+    public void setApiToken(String apiToken) {
+        this.apiToken = apiToken;
+    }
+    public String getApiToken() {
+        return apiToken;
+    }
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnection.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnection.java
new file mode 100644
index 0000000..76cec9f
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnection.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.storage.datastore.adapter.flasharray;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayConnection {
+    @JsonProperty("host_group")
+    private FlashArrayConnectionHostgroup hostGroup;
+    @JsonProperty("host")
+    private FlashArrayConnectionHost host;
+    @JsonProperty("volume")
+    private FlashArrayVolume volume;
+    @JsonProperty("lun")
+    private Integer lun;
+
+    public FlashArrayConnectionHostgroup getHostGroup() {
+        return hostGroup;
+    }
+
+    public void setHostGroup(FlashArrayConnectionHostgroup hostGroup) {
+        this.hostGroup = hostGroup;
+    }
+
+    public FlashArrayConnectionHost getHost() {
+        return host;
+    }
+
+    public void setHost(FlashArrayConnectionHost host) {
+        this.host = host;
+    }
+
+    public FlashArrayVolume getVolume() {
+        return volume;
+    }
+
+    public void setVolume(FlashArrayVolume volume) {
+        this.volume = volume;
+    }
+
+    public Integer getLun() {
+        return lun;
+    }
+
+    public void setLun(Integer lun) {
+        this.lun = lun;
+    }
+
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnectionHost.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnectionHost.java
new file mode 100644
index 0000000..27dcf08
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnectionHost.java
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.storage.datastore.adapter.flasharray;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayConnectionHost {
+    @JsonProperty("name")
+    private String name;
+    public FlashArrayConnectionHost() {}
+    public FlashArrayConnectionHost(String name) {
+        this.name = name;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnectionHostgroup.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnectionHostgroup.java
new file mode 100644
index 0000000..27a0f60
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayConnectionHostgroup.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.storage.datastore.adapter.flasharray;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayConnectionHostgroup {
+    @JsonProperty("name")
+    private String name;
+
+    public FlashArrayConnectionHostgroup() {}
+    public FlashArrayConnectionHostgroup(String name) {
+        this.name = name;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayGroupMemberReference.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayGroupMemberReference.java
new file mode 100644
index 0000000..f0f6d9e
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayGroupMemberReference.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.storage.datastore.adapter.flasharray;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayGroupMemberReference {
+    @JsonProperty("group")
+    private FlashArrayGroupNameWrapper group;
+    @JsonProperty("member")
+    private FlashArrayGroupMemberNameWrapper member;
+
+    public static class FlashArrayGroupNameWrapper {
+        @JsonProperty("name")
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    public static class FlashArrayGroupMemberNameWrapper {
+        @JsonProperty("name")
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    public FlashArrayGroupNameWrapper getGroup() {
+        return group;
+    }
+
+    public void setGroup(FlashArrayGroupNameWrapper group) {
+        this.group = group;
+    }
+
+    public FlashArrayGroupMemberNameWrapper getMember() {
+        return member;
+    }
+
+    public void setMember(FlashArrayGroupMemberNameWrapper member) {
+        this.member = member;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayGroupMemberReferenceList.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayGroupMemberReferenceList.java
new file mode 100644
index 0000000..b17c8a5
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayGroupMemberReferenceList.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.storage.datastore.adapter.flasharray;
+import java.util.ArrayList;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayGroupMemberReferenceList {
+    @JsonProperty("items")
+    private ArrayList<FlashArrayGroupMemberReference> items;
+
+    public ArrayList<FlashArrayGroupMemberReference> getItems() {
+        return items;
+    }
+
+    public void setItems(ArrayList<FlashArrayGroupMemberReference> items) {
+        this.items = items;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayHostgroup.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayHostgroup.java
new file mode 100644
index 0000000..1a2e391
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayHostgroup.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.storage.datastore.adapter.flasharray;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayHostgroup {
+    @JsonProperty("name")
+    private String name;
+    @JsonProperty("connection_count")
+    private Long connectionCount;
+    @JsonProperty("host_count")
+    private Long hostCount;
+    @JsonProperty("is_local")
+    private Boolean local;
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public Long getConnectionCount() {
+        return connectionCount;
+    }
+    public void setConnectionCount(Long connectionCount) {
+        this.connectionCount = connectionCount;
+    }
+    public Long getHostCount() {
+        return hostCount;
+    }
+    public void setHostCount(Long hostCount) {
+        this.hostCount = hostCount;
+    }
+    public Boolean getLocal() {
+        return local;
+    }
+    public void setLocal(Boolean local) {
+        this.local = local;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayList.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayList.java
new file mode 100644
index 0000000..992c3fc
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayList.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.storage.datastore.adapter.flasharray;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayList<T> {
+    @JsonProperty("more_items_remaining")
+    private Boolean moreItemsRemaining;
+    @JsonProperty("total_item_count")
+    private Integer totalItemCount;
+    @JsonProperty("continuation_token")
+    private String continuationToken;
+    @JsonProperty("items")
+    private List<T> items;
+    public Boolean getMoreItemsRemaining() {
+        return moreItemsRemaining;
+    }
+    public void setMoreItemsRemaining(Boolean moreItemsRemaining) {
+        this.moreItemsRemaining = moreItemsRemaining;
+    }
+    public Integer getTotalItemCount() {
+        return totalItemCount;
+    }
+    public void setTotalItemCount(Integer totalItemCount) {
+        this.totalItemCount = totalItemCount;
+    }
+    public String getContinuationToken() {
+        return continuationToken;
+    }
+    public void setContinuationToken(String continuationToken) {
+        this.continuationToken = continuationToken;
+    }
+    public List<T> getItems() {
+        return items;
+    }
+    public void setItems(List<T> items) {
+        this.items = items;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayPod.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayPod.java
new file mode 100644
index 0000000..ddbfc29
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayPod.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.storage.datastore.adapter.flasharray;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayPod {
+    @JsonProperty("name")
+    private String name;
+    @JsonProperty("id")
+    private String id;
+    @JsonProperty("destroyed")
+    private Boolean destroyed;
+    @JsonProperty("footprint")
+    private Long footprint;
+    @JsonProperty("quota_limit")
+    private Long quotaLimit;
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public String getId() {
+        return id;
+    }
+    public void setId(String id) {
+        this.id = id;
+    }
+    public Boolean getDestroyed() {
+        return destroyed;
+    }
+    public void setDestroyed(Boolean destroyed) {
+        this.destroyed = destroyed;
+    }
+    public Long getFootprint() {
+        return footprint;
+    }
+    public void setFootprint(Long footprint) {
+        this.footprint = footprint;
+    }
+    public Long getQuotaLimit() {
+        return quotaLimit;
+    }
+    public void setQuotaLimit(Long quotaLimit) {
+        this.quotaLimit = quotaLimit;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayTag.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayTag.java
new file mode 100644
index 0000000..685d4e1
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayTag.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.storage.datastore.adapter.flasharray;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayTag {
+    @JsonProperty("copyable")
+    private Boolean copyable;
+    @JsonProperty("key")
+    private String key;
+    @JsonProperty("namespace")
+    private String namespace;
+    @JsonProperty("value")
+    private String value;
+
+    public FlashArrayTag() {
+
+    }
+
+    public FlashArrayTag(String namespace, String key, String value) {
+        this.key = key;
+        this.namespace = namespace;
+        this.value = value;
+    }
+
+    public Boolean getCopyable() {
+        return copyable;
+    }
+
+    public void setCopyable(Boolean copyable) {
+        this.copyable = copyable;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayTagList.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayTagList.java
new file mode 100644
index 0000000..7a23343
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayTagList.java
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.storage.datastore.adapter.flasharray;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayTagList {
+    @JsonProperty("tags")
+    public List<FlashArrayTag> tags;
+
+    public List<FlashArrayTag> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<FlashArrayTag> tags) {
+        this.tags = tags;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolume.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolume.java
new file mode 100644
index 0000000..f939d70
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolume.java
@@ -0,0 +1,253 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.flasharray;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderSnapshot;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayVolume implements ProviderSnapshot {
+    public static final String PURE_OUI = "24a9370";
+
+    @JsonProperty("destroyed")
+    private Boolean destroyed;
+    /** The virtual size requested for this volume */
+    @JsonProperty("provisioned")
+    private Long allocatedSizeBytes;
+    @JsonIgnore
+    private String id;
+    @JsonIgnore // we don't use the Cloudstack user name at all
+    private String name;
+    @JsonIgnore
+    private String shortExternalName;
+    @JsonProperty("pod")
+    private FlashArrayVolumePod pod;
+    @JsonProperty("priority")
+    private Integer priority;
+    @JsonProperty("promotion_status")
+    private String promotionStatus;
+    @JsonProperty("subtype")
+    private String subtype;
+    @JsonProperty("space")
+    private FlashArrayVolumeSpace space;
+    @JsonProperty("source")
+    private FlashArrayVolumeSource source;
+    @JsonProperty("serial")
+    private String serial;
+    @JsonProperty("name")
+    private String externalName;
+    @JsonProperty("id")
+    private String externalUuid;
+    @JsonIgnore
+    private AddressType addressType;
+    @JsonIgnore
+    private String connectionId;
+
+    public FlashArrayVolume() {
+        this.addressType = AddressType.FIBERWWN;
+    }
+
+    @Override
+    public Boolean isDestroyed() {
+        return destroyed;
+    }
+    @Override
+    @JsonIgnore
+    public String getId() {
+        return id;
+    }
+    @Override
+    @JsonIgnore
+    public String getName() {
+        return name;
+    }
+    @JsonIgnore
+    public String getPodName() {
+        if (pod != null) {
+            return pod.getName();
+        } else {
+            return null;
+        }
+    }
+    @Override
+    @JsonIgnore
+    public Integer getPriority() {
+        return priority;
+    }
+    @Override
+    @JsonIgnore
+    public String getState() {
+        return null;
+    }
+    @Override
+    @JsonIgnore
+    public AddressType getAddressType() {
+        return addressType;
+    }
+    @Override
+    @JsonIgnore
+    public String getAddress() {
+        if (serial == null) return null;
+        return ("6" + PURE_OUI + serial).toLowerCase();
+    }
+    @Override
+    public String getExternalConnectionId() {
+        return connectionId;
+    }
+
+    @JsonIgnore
+    public void setExternalConnectionId(String externalConnectionId) {
+        this.connectionId = externalConnectionId;
+    }
+
+    @Override
+    public void setId(String id) {
+        this.id = id;
+    }
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+    public void setPodName(String podname) {
+        FlashArrayVolumePod pod = new FlashArrayVolumePod();
+        pod.setName(podname);
+        this.pod = pod;
+    }
+    @Override
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+    @Override
+    public void setAddressType(AddressType addressType) {
+        this.addressType = addressType;
+    }
+    @Override
+    @JsonIgnore
+    public Long getAllocatedSizeInBytes() {
+        return this.allocatedSizeBytes;
+    }
+    public void setAllocatedSizeBytes(Long size) {
+        this.allocatedSizeBytes = size;
+    }
+    @Override
+    @JsonIgnore
+    public Long getUsedBytes() {
+        if (space != null) {
+            return space.getVirtual();
+        } else {
+            return null;
+        }
+    }
+
+    public void setDestroyed(Boolean destroyed) {
+        this.destroyed = destroyed;
+    }
+    public FlashArrayVolumeSource getSource() {
+        return source;
+    }
+    public void setSource(FlashArrayVolumeSource source) {
+        this.source = source;
+    }
+    @Override
+    public String getExternalUuid() {
+        return externalUuid;
+    }
+    @Override
+    public String getExternalName() {
+        return externalName;
+    }
+
+    public void setExternalUuid(String uuid) {
+        this.externalUuid = uuid;
+    }
+
+    public void setExternalName(String name) {
+        this.externalName = name;
+    }
+    @Override
+    public Boolean canAttachDirectly() {
+        return false;
+    }
+    public String getConnectionId() {
+        return connectionId;
+    }
+    public void setConnectionId(String connectionId) {
+        this.connectionId = connectionId;
+    }
+
+    public Boolean getDestroyed() {
+        return destroyed;
+    }
+
+    public Long getAllocatedSizeBytes() {
+        return allocatedSizeBytes;
+    }
+
+    public String getShortExternalName() {
+        return shortExternalName;
+    }
+
+    public void setShortExternalName(String shortExternalName) {
+        this.shortExternalName = shortExternalName;
+    }
+
+    public FlashArrayVolumePod getPod() {
+        return pod;
+    }
+
+    public void setPod(FlashArrayVolumePod pod) {
+        this.pod = pod;
+    }
+
+    public String getPromotionStatus() {
+        return promotionStatus;
+    }
+
+    public void setPromotionStatus(String promotionStatus) {
+        this.promotionStatus = promotionStatus;
+    }
+
+    public String getSubtype() {
+        return subtype;
+    }
+
+    public void setSubtype(String subtype) {
+        this.subtype = subtype;
+    }
+
+    public FlashArrayVolumeSpace getSpace() {
+        return space;
+    }
+
+    public void setSpace(FlashArrayVolumeSpace space) {
+        this.space = space;
+    }
+
+    public String getSerial() {
+        return serial;
+    }
+
+    public void setSerial(String serial) {
+        this.serial = serial;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumePod.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumePod.java
new file mode 100644
index 0000000..1e46441
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumePod.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.storage.datastore.adapter.flasharray;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayVolumePod {
+    @JsonProperty("id")
+    private String id;
+    @JsonProperty("name")
+    private String name;
+
+    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;
+    }
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumeSource.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumeSource.java
new file mode 100644
index 0000000..9bc8dec
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumeSource.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.storage.datastore.adapter.flasharray;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayVolumeSource {
+    @JsonProperty("id")
+    private String id;
+    @JsonProperty("name")
+    private String name;
+    public FlashArrayVolumeSource() { }
+    public FlashArrayVolumeSource(String sourceName) {
+        this.name = sourceName;
+    }
+    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;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumeSpace.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumeSpace.java
new file mode 100644
index 0000000..95e148c
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolumeSpace.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.storage.datastore.adapter.flasharray;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class FlashArrayVolumeSpace {
+    @JsonProperty("data_reduction")
+    private Float dataReduction;
+    @JsonProperty("snapshots")
+    private Integer snapshots;
+    @JsonProperty("snapshots_effective")
+    private Integer snapshotsEffective;
+    @JsonProperty("thin_provisioning")
+    private Float thinProvisioning;
+    @JsonProperty("total_effective")
+    private Long totalEffective;
+    @JsonProperty("total_physical")
+    private Long totalPhysical;
+    @JsonProperty("total_provisioned")
+    private Long totalProvisioned;
+    @JsonProperty("total_reduction")
+    private Float totalReduction;
+    @JsonProperty("unique")
+    private Long unique;
+    @JsonProperty("unique_effective")
+    private Long uniqueEffective;
+    @JsonProperty("user_provisioned")
+    private Long usedProvisioned;
+    @JsonProperty("virtual")
+    private Long virtual;
+    public Float getData_reduction() {
+        return dataReduction;
+    }
+    public void setData_reduction(Float dataReduction) {
+        this.dataReduction = dataReduction;
+    }
+    public Integer getSnapshots() {
+        return snapshots;
+    }
+    public void setSnapshots(Integer snapshots) {
+        this.snapshots = snapshots;
+    }
+    public Integer getSnapshotsEffective() {
+        return snapshotsEffective;
+    }
+    public void setSnapshotsEffective(Integer snapshotsEffective) {
+        this.snapshotsEffective = snapshotsEffective;
+    }
+    public Float getThinProvisioning() {
+        return thinProvisioning;
+    }
+    public void setThinProvisioning(Float thinProvisioning) {
+        this.thinProvisioning = thinProvisioning;
+    }
+    public Long getTotalEffective() {
+        return totalEffective;
+    }
+    public void setTotalEffective(Long totalEffective) {
+        this.totalEffective = totalEffective;
+    }
+    public Long getTotalPhysical() {
+        return totalPhysical;
+    }
+    public void setTotal_physical(Long totalPhysical) {
+        this.totalPhysical = totalPhysical;
+    }
+    public Long getTotalProvisioned() {
+        return totalProvisioned;
+    }
+    public void setTotalProvisioned(Long totalProvisioned) {
+        this.totalProvisioned = totalProvisioned;
+    }
+    public Float getTotalReduction() {
+        return totalReduction;
+    }
+    public void setTotalReduction(Float totalReduction) {
+        this.totalReduction = totalReduction;
+    }
+    public Long getUnique() {
+        return unique;
+    }
+    public void setUnique(Long unique) {
+        this.unique = unique;
+    }
+    public Long getUniqueEffective() {
+        return uniqueEffective;
+    }
+    public void setUniqueEffective(Long uniqueEffective) {
+        this.uniqueEffective = uniqueEffective;
+    }
+    public Long getUsedProvisioned() {
+        return usedProvisioned;
+    }
+    public void setUsed_provisioned(Long usedProvisioned) {
+        this.usedProvisioned = usedProvisioned;
+    }
+    public Long getVirtual() {
+        return virtual;
+    }
+    public void setVirtual(Long virtual) {
+        this.virtual = virtual;
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/provider/FlashArrayPrimaryDatastoreProviderImpl.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/provider/FlashArrayPrimaryDatastoreProviderImpl.java
new file mode 100644
index 0000000..0750ef2
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/provider/FlashArrayPrimaryDatastoreProviderImpl.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.storage.datastore.provider;
+
+import org.apache.cloudstack.storage.datastore.adapter.flasharray.FlashArrayAdapterFactory;
+
+public class FlashArrayPrimaryDatastoreProviderImpl extends AdaptivePrimaryDatastoreProviderImpl {
+
+    public FlashArrayPrimaryDatastoreProviderImpl() {
+        super(new FlashArrayAdapterFactory());
+    }
+
+    @Override
+    public String getName() {
+        return "Flash Array";
+    }
+
+}
diff --git a/plugins/storage/volume/flasharray/src/main/resources/META-INF/cloudstack/storage-volume-flasharray/module.properties b/plugins/storage/volume/flasharray/src/main/resources/META-INF/cloudstack/storage-volume-flasharray/module.properties
new file mode 100644
index 0000000..ac3c1e2
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/resources/META-INF/cloudstack/storage-volume-flasharray/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=storage-volume-flasharray
+parent=storage
diff --git a/plugins/storage/volume/flasharray/src/main/resources/META-INF/cloudstack/storage-volume-flasharray/spring-storage-volume-flasharray-context.xml b/plugins/storage/volume/flasharray/src/main/resources/META-INF/cloudstack/storage-volume-flasharray/spring-storage-volume-flasharray-context.xml
new file mode 100644
index 0000000..030e9de
--- /dev/null
+++ b/plugins/storage/volume/flasharray/src/main/resources/META-INF/cloudstack/storage-volume-flasharray/spring-storage-volume-flasharray-context.xml
@@ -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
+    to you under the Apache License, Version 2.0 (thefile
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+
+    <bean id="flashArrayDataStoreProvider"
+      class="org.apache.cloudstack.storage.datastore.provider.FlashArrayPrimaryDatastoreProviderImpl">
+    </bean>
+</beans>
diff --git a/plugins/storage/volume/linstor/pom.xml b/plugins/storage/volume/linstor/pom.xml
index f7d78f1..8e1fbfb 100644
--- a/plugins/storage/volume/linstor/pom.xml
+++ b/plugins/storage/volume/linstor/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -43,14 +43,17 @@
             <artifactId>cloud-plugin-hypervisor-kvm</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-engine-storage-snapshot</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/api/storage/LinstorBackupSnapshotCommand.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/api/storage/LinstorBackupSnapshotCommand.java
new file mode 100644
index 0000000..8d887db
--- /dev/null
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/api/storage/LinstorBackupSnapshotCommand.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 com.cloud.api.storage;
+
+import com.cloud.agent.api.to.DataTO;
+import org.apache.cloudstack.storage.command.CopyCommand;
+
+public class LinstorBackupSnapshotCommand extends CopyCommand
+{
+    public LinstorBackupSnapshotCommand(DataTO srcData, DataTO destData, int timeout, boolean executeInSequence)
+    {
+        super(srcData, destData, timeout, executeInSequence);
+    }
+}
diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/api/storage/LinstorRevertBackupSnapshotCommand.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/api/storage/LinstorRevertBackupSnapshotCommand.java
new file mode 100644
index 0000000..1f1880d
--- /dev/null
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/api/storage/LinstorRevertBackupSnapshotCommand.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 com.cloud.api.storage;
+
+import com.cloud.agent.api.to.DataTO;
+import org.apache.cloudstack.storage.command.CopyCommand;
+
+public class LinstorRevertBackupSnapshotCommand extends CopyCommand
+{
+    public LinstorRevertBackupSnapshotCommand(DataTO srcData, DataTO destData, int timeout, boolean executeInSequence)
+    {
+        super(srcData, destData, timeout, executeInSequence);
+    }
+}
diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java
new file mode 100644
index 0000000..a210d53
--- /dev/null
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java
@@ -0,0 +1,167 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.hypervisor.kvm.resource.wrapper;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.NfsTO;
+import com.cloud.api.storage.LinstorBackupSnapshotCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.storage.Storage;
+import com.cloud.utils.script.Script;
+import org.apache.cloudstack.storage.command.CopyCmdAnswer;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.commons.io.FileUtils;
+import org.apache.log4j.Logger;
+import org.joda.time.Duration;
+import org.libvirt.LibvirtException;
+
+@ResourceWrapper(handles = LinstorBackupSnapshotCommand.class)
+public final class LinstorBackupSnapshotCommandWrapper
+    extends CommandWrapper<LinstorBackupSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource>
+{
+    private static final Logger s_logger = Logger.getLogger(LinstorBackupSnapshotCommandWrapper.class);
+
+    private String zfsSnapdev(boolean hide, String zfsUrl) {
+        Script script = new Script("/usr/bin/zfs", Duration.millis(5000));
+        script.add("set");
+        script.add("snapdev=" + (hide ? "hidden" : "visible"));
+        script.add(zfsUrl.substring(6));  // cutting zfs://
+        return script.execute();
+    }
+
+    private String qemuShrink(String path, long sizeByte, long timeout) {
+        Script qemuImg = new Script("qemu-img", Duration.millis(timeout));
+        qemuImg.add("resize");
+        qemuImg.add("--shrink");
+        qemuImg.add(path);
+        qemuImg.add("" + sizeByte);
+        return qemuImg.execute();
+    }
+
+    static void cleanupSecondaryPool(final KVMStoragePool secondaryPool) {
+        if (secondaryPool != null) {
+            try {
+                secondaryPool.delete();
+            } catch (final Exception e) {
+                s_logger.debug("Failed to delete secondary storage", e);
+            }
+        }
+    }
+
+    private String convertImageToQCow2(
+        final String srcPath,
+        final SnapshotObjectTO dst,
+        final KVMStoragePool secondaryPool,
+        int waitMilliSeconds
+        )
+        throws LibvirtException, QemuImgException, IOException
+    {
+        final String dstDir = secondaryPool.getLocalPath() + File.separator + dst.getPath();
+        FileUtils.forceMkdir(new File(dstDir));
+
+        final String dstPath = dstDir + File.separator + dst.getName();
+        final QemuImgFile srcFile = new QemuImgFile(srcPath, QemuImg.PhysicalDiskFormat.RAW);
+        final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.QCOW2);
+
+        // NOTE: the qemu img will also contain the drbd metadata at the end
+        final QemuImg qemu = new QemuImg(waitMilliSeconds);
+        qemu.convert(srcFile, dstFile);
+        s_logger.info("Backup snapshot " + srcFile + " to " + dstPath);
+        return dstPath;
+    }
+
+    private SnapshotObjectTO setCorrectSnapshotSize(final SnapshotObjectTO dst, final String dstPath) {
+        final File snapFile = new File(dstPath);
+        final long size = snapFile.exists() ? snapFile.length() : 0;
+
+        final SnapshotObjectTO snapshot = new SnapshotObjectTO();
+        snapshot.setPath(dst.getPath() + File.separator + dst.getName());
+        snapshot.setPhysicalSize(size);
+        return snapshot;
+    }
+
+    @Override
+    public CopyCmdAnswer execute(LinstorBackupSnapshotCommand cmd, LibvirtComputingResource serverResource)
+    {
+        s_logger.debug("LinstorBackupSnapshotCommandWrapper: " + cmd.getSrcTO().getPath() + " -> " + cmd.getDestTO().getPath());
+        final SnapshotObjectTO src = (SnapshotObjectTO) cmd.getSrcTO();
+        final SnapshotObjectTO dst = (SnapshotObjectTO) cmd.getDestTO();
+        KVMStoragePool secondaryPool = null;
+        final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
+        KVMStoragePool linstorPool = storagePoolMgr.getStoragePool(Storage.StoragePoolType.Linstor, src.getDataStore().getUuid());
+        boolean zfsHidden = false;
+        String srcPath = src.getPath();
+
+        if (linstorPool == null) {
+            return new CopyCmdAnswer("Unable to get linstor storage pool from destination volume.");
+        }
+
+        final DataStoreTO dstDataStore = dst.getDataStore();
+        if (!(dstDataStore instanceof NfsTO)) {
+            return new CopyCmdAnswer("Backup Linstor snapshot: Only NFS secondary supported at present!");
+        }
+
+        try
+        {
+            // provide the linstor snapshot block device
+            // on lvm thin this should already be there in /dev/mapper/vg-snapshotname
+            // on zfs we need to unhide the snapshot block device
+            s_logger.info("Src: " + srcPath + " | " + src.getName());
+            if (srcPath.startsWith("zfs://")) {
+                zfsHidden = true;
+                if (zfsSnapdev(false, srcPath) != null) {
+                    return new CopyCmdAnswer("Unable to unhide zfs snapshot device.");
+                }
+                srcPath = "/dev/" + srcPath.substring(6);
+            }
+
+            secondaryPool = storagePoolMgr.getStoragePoolByURI(dstDataStore.getUrl());
+
+            String dstPath = convertImageToQCow2(srcPath, dst, secondaryPool, cmd.getWaitInMillSeconds());
+
+            // resize to real volume size, cutting of drbd metadata
+            String result = qemuShrink(dstPath, src.getVolume().getSize(), cmd.getWaitInMillSeconds());
+            if (result != null) {
+                return new CopyCmdAnswer("qemu-img shrink failed: " + result);
+            }
+            s_logger.info("Backup shrunk " + dstPath + " to actual size " + src.getVolume().getSize());
+
+            SnapshotObjectTO snapshot = setCorrectSnapshotSize(dst, dstPath);
+            return new CopyCmdAnswer(snapshot);
+        } catch (final Exception e) {
+            final String error = String.format("Failed to backup snapshot with id [%s] with a pool %s, due to %s",
+                cmd.getSrcTO().getId(), cmd.getSrcTO().getDataStore().getUuid(), e.getMessage());
+            s_logger.error(error);
+            return new CopyCmdAnswer(cmd, e);
+        } finally {
+            cleanupSecondaryPool(secondaryPool);
+            if (zfsHidden) {
+                zfsSnapdev(true, src.getPath());
+            }
+        }
+    }
+}
diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java
new file mode 100644
index 0000000..511b5a4
--- /dev/null
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.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.hypervisor.kvm.resource.wrapper;
+
+import java.io.File;
+
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.api.storage.LinstorRevertBackupSnapshotCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+import com.cloud.storage.Storage;
+import org.apache.cloudstack.storage.command.CopyCmdAnswer;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@ResourceWrapper(handles = LinstorRevertBackupSnapshotCommand.class)
+public final class LinstorRevertBackupSnapshotCommandWrapper
+    extends CommandWrapper<LinstorRevertBackupSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource>
+{
+    private static final Logger s_logger = Logger.getLogger(LinstorRevertBackupSnapshotCommandWrapper.class);
+
+    private void convertQCow2ToRAW(final String srcPath, final String dstPath, int waitMilliSeconds)
+        throws LibvirtException, QemuImgException
+    {
+        final QemuImgFile srcQemuFile = new QemuImgFile(
+            srcPath, QemuImg.PhysicalDiskFormat.QCOW2);
+        final QemuImg qemu = new QemuImg(waitMilliSeconds);
+        final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.RAW);
+        qemu.convert(srcQemuFile, dstFile);
+    }
+
+    @Override
+    public CopyCmdAnswer execute(LinstorRevertBackupSnapshotCommand cmd, LibvirtComputingResource serverResource)
+    {
+        s_logger.debug("LinstorRevertBackupSnapshotCommandWrapper: " + cmd.getSrcTO().getPath() + " -> " + cmd.getDestTO().getPath());
+        final SnapshotObjectTO src = (SnapshotObjectTO) cmd.getSrcTO();
+        final VolumeObjectTO dst = (VolumeObjectTO) cmd.getDestTO();
+        KVMStoragePool secondaryPool = null;
+        final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
+        KVMStoragePool linstorPool = storagePoolMgr.getStoragePool(Storage.StoragePoolType.Linstor, dst.getDataStore().getUuid());
+
+        if (linstorPool == null) {
+            return new CopyCmdAnswer("Unable to get linstor storage pool from destination volume.");
+        }
+
+        try
+        {
+            final DataStoreTO srcDataStore = src.getDataStore();
+            File srcFile = new File(src.getPath());
+            secondaryPool = storagePoolMgr.getStoragePoolByURI(
+                srcDataStore.getUrl() + File.separator + srcFile.getParent());
+
+            convertQCow2ToRAW(
+                secondaryPool.getLocalPath() + File.separator + srcFile.getName(),
+                linstorPool.getPhysicalDisk(dst.getPath()).getPath(),
+                cmd.getWaitInMillSeconds());
+
+            final VolumeObjectTO dstVolume = new VolumeObjectTO();
+            dstVolume.setPath(dst.getPath());
+            return new CopyCmdAnswer(dstVolume);
+        } catch (final Exception e) {
+            final String error = String.format("Failed to revert snapshot with id [%s] with a pool %s, due to %s",
+                cmd.getSrcTO().getId(), cmd.getSrcTO().getDataStore().getUuid(), e.getMessage());
+            s_logger.error(error);
+            return new CopyCmdAnswer(cmd, e);
+        } finally {
+            LinstorBackupSnapshotCommandWrapper.cleanupSecondaryPool(secondaryPool);
+        }
+    }
+}
diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
index 9ad8332..dd50c8d 100644
--- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
@@ -16,18 +16,16 @@
 // under the License.
 package com.cloud.hypervisor.kvm.storage;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.StringJoiner;
 
 import javax.annotation.Nonnull;
 
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 import org.apache.cloudstack.utils.qemu.QemuImg;
 import org.apache.cloudstack.utils.qemu.QemuImgException;
@@ -35,8 +33,6 @@
 import org.apache.log4j.Logger;
 import org.libvirt.LibvirtException;
 
-import com.cloud.storage.Storage;
-import com.cloud.utils.exception.CloudRuntimeException;
 import com.linbit.linstor.api.ApiClient;
 import com.linbit.linstor.api.ApiException;
 import com.linbit.linstor.api.Configuration;
@@ -48,7 +44,6 @@
 import com.linbit.linstor.api.model.Resource;
 import com.linbit.linstor.api.model.ResourceDefinition;
 import com.linbit.linstor.api.model.ResourceDefinitionModify;
-import com.linbit.linstor.api.model.ResourceGroup;
 import com.linbit.linstor.api.model.ResourceGroupSpawn;
 import com.linbit.linstor.api.model.ResourceMakeAvailable;
 import com.linbit.linstor.api.model.ResourceWithVolumes;
@@ -67,30 +62,8 @@
         return new DevelopersApi(client);
     }
 
-    private String getLinstorRscName(String name) {
-        return "cs-" + name;
-    }
-
-    private String getHostname() {
-        // either there is already some function for that in the agent or a better way.
-        ProcessBuilder pb = new ProcessBuilder("hostname");
-        try
-        {
-            String result;
-            Process p = pb.start();
-            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-
-            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
-            reader.lines().iterator().forEachRemaining(sj::add);
-            result = sj.toString();
-
-            p.waitFor();
-            p.destroy();
-            return result.trim();
-        } catch (IOException | InterruptedException exc) {
-            Thread.currentThread().interrupt();
-            throw new CloudRuntimeException("Unable to run 'hostname' command.");
-        }
+    private static String getLinstorRscName(String name) {
+        return LinstorUtil.RSC_PREFIX + name;
     }
 
     private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
@@ -123,7 +96,7 @@
     }
 
     public LinstorStorageAdaptor() {
-        localNodeName = getHostname();
+        localNodeName = LinstorStoragePool.getHostname();
     }
 
     @Override
@@ -216,6 +189,7 @@
     public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
                                               Storage.ProvisioningType provisioningType, long size, byte[] passphrase)
     {
+        s_logger.debug(String.format("Linstor.createPhysicalDisk: %s;%s", name, format));
         final String rscName = getLinstorRscName(name);
         LinstorStoragePool lpool = (LinstorStoragePool) pool;
         final DevelopersApi api = getLinstorAPI(pool);
@@ -256,6 +230,7 @@
                 throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
             }
         } catch (ApiException apiEx) {
+            s_logger.error(String.format("Linstor.createPhysicalDisk: ApiException: %s", apiEx.getBestMessage()));
             throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
         }
     }
@@ -440,7 +415,7 @@
     @Override
     public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType)
     {
-        s_logger.debug("Linstor: copyPhysicalDisk");
+        s_logger.debug(String.format("Linstor.copyPhysicalDisk: %s -> %s", disk.getPath(), name));
         final QemuImg.PhysicalDiskFormat sourceFormat = disk.getFormat();
         final String sourcePath = disk.getPath();
 
@@ -449,6 +424,7 @@
         final KVMPhysicalDisk dstDisk = destPools.createPhysicalDisk(
             name, QemuImg.PhysicalDiskFormat.RAW, provisioningType, disk.getVirtualSize(), null);
 
+        s_logger.debug(String.format("Linstor.copyPhysicalDisk: dstPath: %s", dstDisk.getPath()));
         final QemuImgFile destFile = new QemuImgFile(dstDisk.getPath());
         destFile.setFormat(dstDisk.getFormat());
         destFile.setSize(disk.getVirtualSize());
@@ -513,25 +489,7 @@
         DevelopersApi linstorApi = getLinstorAPI(pool);
         final String rscGroupName = pool.getResourceGroup();
         try {
-            List<ResourceGroup> rscGrps = linstorApi.resourceGroupList(
-                Collections.singletonList(rscGroupName),
-                null,
-                null,
-                null);
-
-            if (rscGrps.isEmpty()) {
-                final String errMsg = String.format("Linstor: Resource group '%s' not found", rscGroupName);
-                s_logger.error(errMsg);
-                throw new CloudRuntimeException(errMsg);
-            }
-
-            List<StoragePool> storagePools = linstorApi.viewStoragePools(
-                Collections.emptyList(),
-                rscGrps.get(0).getSelectFilter().getStoragePoolList(),
-                null,
-                null,
-                null
-            );
+            List<StoragePool> storagePools = LinstorUtil.getRscGroupStoragePools(linstorApi, rscGroupName);
 
             final long free = storagePools.stream()
                 .filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS)
@@ -549,25 +507,7 @@
         DevelopersApi linstorApi = getLinstorAPI(pool);
         final String rscGroupName = pool.getResourceGroup();
         try {
-            List<ResourceGroup> rscGrps = linstorApi.resourceGroupList(
-                Collections.singletonList(rscGroupName),
-                null,
-                null,
-                null);
-
-            if (rscGrps.isEmpty()) {
-                final String errMsg = String.format("Linstor: Resource group '%s' not found", rscGroupName);
-                s_logger.error(errMsg);
-                throw new CloudRuntimeException(errMsg);
-            }
-
-            List<StoragePool> storagePools = linstorApi.viewStoragePools(
-                Collections.emptyList(),
-                rscGrps.get(0).getSelectFilter().getStoragePoolList(),
-                null,
-                null,
-                null
-            );
+            List<StoragePool> storagePools = LinstorUtil.getRscGroupStoragePools(linstorApi, rscGroupName);
 
             final long used = storagePools.stream()
                 .filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS)
diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
index 5bc60fd..4077d5d 100644
--- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
@@ -19,17 +19,33 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.cloudstack.utils.qemu.QemuImg;
-
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.OutputInterpreter;
+import com.cloud.utils.script.Script;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.log4j.Logger;
+import org.joda.time.Duration;
 
 public class LinstorStoragePool implements KVMStoragePool {
+    private static final Logger s_logger = Logger.getLogger(LinstorStoragePool.class);
     private final String _uuid;
     private final String _sourceHost;
     private final int _sourcePort;
     private final Storage.StoragePoolType _storagePoolType;
     private final StorageAdaptor _storageAdaptor;
     private final String _resourceGroup;
+    private final String localNodeName;
 
     public LinstorStoragePool(String uuid, String host, int port, String resourceGroup,
                               Storage.StoragePoolType storagePoolType, StorageAdaptor storageAdaptor) {
@@ -39,6 +55,7 @@
         _storagePoolType = storagePoolType;
         _storageAdaptor = storageAdaptor;
         _resourceGroup = resourceGroup;
+        localNodeName = getHostname();
     }
 
     @Override
@@ -194,4 +211,135 @@
     public String getResourceGroup() {
         return _resourceGroup;
     }
+
+    @Override
+    public boolean isPoolSupportHA() {
+        return true;
+    }
+
+    @Override
+    public String getHearthBeatPath() {
+        String kvmScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR);
+        return Script.findScript(kvmScriptsDir, "kvmspheartbeat.sh");
+    }
+
+    @Override
+    public String createHeartBeatCommand(HAStoragePool pool, String hostPrivateIp,
+            boolean hostValidation) {
+        s_logger.trace(String.format("Linstor.createHeartBeatCommand: %s, %s, %b", pool.getPoolIp(), hostPrivateIp, hostValidation));
+        boolean isStorageNodeUp = checkingHeartBeat(pool, null);
+        if (!isStorageNodeUp && !hostValidation) {
+            //restart the host
+            s_logger.debug(String.format("The host [%s] will be restarted because the health check failed for the storage pool [%s]", hostPrivateIp, pool.getPool().getType()));
+            Script cmd = new Script(pool.getPool().getHearthBeatPath(), Duration.millis(HeartBeatUpdateTimeout), s_logger);
+            cmd.add("-c");
+            cmd.execute();
+            return "Down";
+        }
+        return isStorageNodeUp ? null : "Down";
+    }
+
+    @Override
+    public String getStorageNodeId() {
+        // only called by storpool
+        return null;
+    }
+
+    static String getHostname() {
+        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
+        Script sc = new Script("hostname", Duration.millis(10000L), s_logger);
+        String res = sc.execute(parser);
+        if (res != null) {
+            throw new CloudRuntimeException(String.format("Unable to run 'hostname' command: %s", res));
+        }
+        String response = parser.getLines();
+        return response.trim();
+    }
+
+    @Override
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
+        String hostName;
+        if (host == null) {
+            hostName = localNodeName;
+        } else {
+            hostName = host.getParent();
+            if (hostName == null) {
+                s_logger.error("No hostname set in host.getParent()");
+                return false;
+            }
+        }
+
+        return checkHostUpToDateAndConnected(hostName);
+    }
+
+    private String executeDrbdSetupStatus(OutputInterpreter.AllLinesParser parser) {
+        Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeout), s_logger);
+        sc.add("status");
+        sc.add("--json");
+        return sc.execute(parser);
+    }
+
+    private boolean checkDrbdSetupStatusOutput(String output, String otherNodeName) {
+        JsonParser jsonParser = new JsonParser();
+        JsonArray jResources = (JsonArray) jsonParser.parse(output);
+        for (JsonElement jElem : jResources) {
+            JsonObject jRes = (JsonObject) jElem;
+            JsonArray jConnections = jRes.getAsJsonArray("connections");
+            for (JsonElement jConElem : jConnections) {
+                JsonObject jConn = (JsonObject) jConElem;
+                if (jConn.getAsJsonPrimitive("name").getAsString().equals(otherNodeName)
+                        && jConn.getAsJsonPrimitive("connection-state").getAsString().equalsIgnoreCase("Connected")) {
+                    return true;
+                }
+            }
+        }
+        s_logger.warn(String.format("checkDrbdSetupStatusOutput: no resource connected to %s.", otherNodeName));
+        return false;
+    }
+
+    private String executeDrbdEventsNow(OutputInterpreter.AllLinesParser parser) {
+        Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeout), s_logger);
+        sc.add("events2");
+        sc.add("--now");
+        return sc.execute(parser);
+    }
+
+    private boolean checkDrbdEventsNowOutput(String output) {
+        boolean healthy = output.lines().noneMatch(line -> line.matches(".*role:Primary .* promotion_score:0.*"));
+        if (!healthy) {
+            s_logger.warn("checkDrbdEventsNowOutput: primary resource with promotion score==0; HA false");
+        }
+        return healthy;
+    }
+
+    private boolean checkHostUpToDateAndConnected(String hostName) {
+        s_logger.trace(String.format("checkHostUpToDateAndConnected: %s/%s", localNodeName, hostName));
+        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
+
+        if (localNodeName.equalsIgnoreCase(hostName)) {
+            String res = executeDrbdEventsNow(parser);
+            if (res != null) {
+                return false;
+            }
+            return checkDrbdEventsNowOutput(parser.getLines());
+        } else {
+            // check drbd connections
+            String res = executeDrbdSetupStatus(parser);
+            if (res != null) {
+                return false;
+            }
+            try {
+                return checkDrbdSetupStatusOutput(parser.getLines(), hostName);
+            } catch (JsonIOException | JsonSyntaxException e) {
+                s_logger.error("Error parsing drbdsetup status --json", e);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
+        s_logger.trace(String.format("Linstor.vmActivityCheck: %s, %s", pool.getPoolIp(), host.getPrivateNetwork().getIp()));
+        return checkingHeartBeat(pool, host);
+    }
 }
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
index d2d13ea..328b3d2 100644
--- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
@@ -28,6 +28,7 @@
 import com.linbit.linstor.api.model.ResourceDefinitionCreate;
 import com.linbit.linstor.api.model.ResourceDefinitionModify;
 import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceMakeAvailable;
 import com.linbit.linstor.api.model.ResourceWithVolumes;
 import com.linbit.linstor.api.model.Snapshot;
 import com.linbit.linstor.api.model.SnapshotRestore;
@@ -43,20 +44,31 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.storage.ResizeVolumeAnswer;
 import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.to.DataObjectType;
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.DiskTO;
 import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.api.storage.LinstorBackupSnapshotCommand;
+import com.cloud.api.storage.LinstorRevertBackupSnapshotCommand;
+import com.cloud.configuration.Config;
 import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
+import com.cloud.resource.ResourceState;
+import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.ResizeVolumePayload;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.Storage;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeDetailVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.SnapshotDao;
@@ -65,8 +77,10 @@
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VirtualMachineManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
@@ -78,10 +92,14 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.RemoteHostEndPoint;
 import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.CreateObjectAnswer;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
 import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.cloudstack.storage.volume.VolumeObject;
@@ -96,6 +114,10 @@
     @Inject private SnapshotDao _snapshotDao;
     @Inject private SnapshotDetailsDao _snapshotDetailsDao;
     @Inject private StorageManager _storageMgr;
+    @Inject
+    ConfigurationDao _configDao;
+    @Inject
+    private HostDao _hostDao;
 
     public LinstorPrimaryDataStoreDriverImpl()
     {
@@ -107,10 +129,12 @@
         Map<String, String> mapCapabilities = new HashMap<>();
 
         // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        // This enables template caching on our primary storage
         mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
 
         // fetch if lvm-thin or ZFS
-        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        boolean system_snapshot = !LinstorConfigurationManager.BackupSnapshots.value();
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.toString(system_snapshot));
 
         // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
         mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
@@ -191,6 +215,7 @@
                 }
                 throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
             }
+            s_logger.info(String.format("Linstor: Deleted resource %s", rscDefName));
         } catch (ApiException apiEx)
         {
             s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
@@ -214,6 +239,7 @@
                 }
                 throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
             }
+            s_logger.info("Linstor: Deleted snapshot " + snapshotName + " for resource " + rscDefName);
         } catch (ApiException apiEx)
         {
             s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
@@ -400,27 +426,46 @@
         }
     }
 
-    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
-    {
-        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
-        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+    private String getRscGrp(StoragePoolVO storagePoolVO) {
+        return storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
             storagePoolVO.getUserInfo() : "DfltRscGrp";
+    }
 
+    private String createResourceBase(
+        String rscName, long sizeInBytes, String volName, String vmName, DevelopersApi api, String rscGrp) {
         ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
-        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
         rscGrpSpawn.setResourceDefinitionName(rscName);
-        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+        rscGrpSpawn.addVolumeSizesItem(sizeInBytes / 1024);
 
         try
         {
             s_logger.info("Linstor: Spawn resource " + rscName);
-            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            ApiCallRcList answers = api.resourceGroupSpawn(rscGrp, rscGrpSpawn);
             checkLinstorAnswersThrow(answers);
 
-            applyAuxProps(linstorApi, rscName, vol.getName(), vol.getAttachedVmName());
+            applyAuxProps(api, rscName, volName, vmName);
+
+            return getDeviceName(api, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO) {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = getRscGrp(storagePoolVO);
+
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        String deviceName = createResourceBase(
+            rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(), linstorApi, rscGrp);
+
+        try
+        {
             applyQoSSettings(storagePoolVO, linstorApi, rscName, vol.getMaxIops());
 
-            return getDeviceName(linstorApi, rscName);
+            return deviceName;
         } catch (ApiException apiEx)
         {
             s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
@@ -484,9 +529,18 @@
         }
     }
 
+    private ResourceDefinitionCreate createResourceDefinitionCreate(String rscName, String rscGrpName)
+            throws ApiException {
+        ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+        ResourceDefinition rd = new ResourceDefinition();
+        rd.setName(rscName);
+        rd.setResourceGroupName(rscGrpName);
+        rdCreate.setResourceDefinition(rd);
+        return rdCreate;
+    }
+
     private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
-        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
-            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final String rscGrp = getRscGrp(storagePoolVO);
         final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
 
         SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
@@ -497,11 +551,7 @@
         try
         {
             s_logger.debug("Create new resource definition: " + rscName);
-            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
-            ResourceDefinition rd = new ResourceDefinition();
-            rd.setName(rscName);
-            rd.setResourceGroupName(rscGrp);
-            rdCreate.setResourceDefinition(rd);
+            ResourceDefinitionCreate rdCreate = createResourceDefinitionCreate(rscName, rscGrp);
             ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
             checkLinstorAnswersThrow(answers);
 
@@ -652,6 +702,63 @@
         }
     }
 
+    private String revertSnapshotFromImageStore(
+        final SnapshotInfo snapshot,
+        final VolumeInfo volumeInfo,
+        final DevelopersApi linstorApi,
+        final String rscName)
+    throws ApiException {
+        String resultMsg = null;
+        String value = _configDao.getValue(Config.BackupSnapshotWait.toString());
+        int _backupsnapshotwait = NumbersUtil.parseInt(
+            value, Integer.parseInt(Config.BackupSnapshotWait.getDefaultValue()));
+
+        LinstorRevertBackupSnapshotCommand cmd = new LinstorRevertBackupSnapshotCommand(
+            snapshot.getTO(),
+            volumeInfo.getTO(),
+            _backupsnapshotwait,
+            VirtualMachineManager.ExecuteInSequence.value());
+
+        Optional<RemoteHostEndPoint> optEP = getDiskfullEP(linstorApi, rscName);
+        if (optEP.isEmpty()) {
+            optEP = getLinstorEP(linstorApi, rscName);
+        }
+
+        if (optEP.isPresent()) {
+            Answer answer = optEP.get().sendMessage(cmd);
+            if (!answer.getResult()) {
+                resultMsg = answer.getDetails();
+            }
+        } else {
+            resultMsg = "Unable to get matching Linstor endpoint.";
+        }
+        return resultMsg;
+    }
+
+    private String doRevertSnapshot(final SnapshotInfo snapshot, final VolumeInfo volumeInfo) {
+        final StoragePool pool = (StoragePool) volumeInfo.getDataStore();
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+        String resultMsg;
+        try {
+            if (snapshot.getDataStore().getRole() == DataStoreRole.Primary) {
+                final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+
+                ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+                resultMsg = checkLinstorAnswers(answers);
+            } else if (snapshot.getDataStore().getRole() == DataStoreRole.Image) {
+                resultMsg = revertSnapshotFromImageStore(snapshot, volumeInfo, linstorApi, rscName);
+            } else {
+                resultMsg = "Linstor: Snapshot revert datastore not supported";
+            }
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        return resultMsg;
+    }
+
     @Override
     public void revertSnapshot(
         SnapshotInfo snapshot,
@@ -668,19 +775,7 @@
             return;
         }
 
-        String resultMsg;
-        try {
-            final StoragePool pool = (StoragePool) snapshot.getDataStore();
-            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
-            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
-            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
-
-            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
-            resultMsg = checkLinstorAnswers(answers);
-        } catch (ApiException apiEx) {
-            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
-            resultMsg = apiEx.getBestMessage();
-        }
+        String resultMsg = doRevertSnapshot(snapshot, volumeInfo);
 
         if (callback != null)
         {
@@ -690,24 +785,269 @@
         }
     }
 
+    private static boolean canCopySnapshotCond(DataObject srcData, DataObject dstData) {
+        return srcData.getType() == DataObjectType.SNAPSHOT && dstData.getType() == DataObjectType.SNAPSHOT
+            && (dstData.getDataStore().getRole() == DataStoreRole.Image
+            || dstData.getDataStore().getRole() == DataStoreRole.ImageCache);
+    }
+
+    private static boolean canCopyTemplateCond(DataObject srcData, DataObject dstData) {
+        return srcData.getType() == DataObjectType.TEMPLATE && dstData.getType() == DataObjectType.TEMPLATE
+            && dstData.getDataStore().getRole() == DataStoreRole.Primary
+            && (srcData.getDataStore().getRole() == DataStoreRole.Image
+            || srcData.getDataStore().getRole() == DataStoreRole.ImageCache);
+    }
+
     @Override
-    public boolean canCopy(DataObject srcData, DataObject destData)
+    public boolean canCopy(DataObject srcData, DataObject dstData)
     {
+        s_logger.debug("LinstorPrimaryDataStoreDriverImpl.canCopy: " + srcData.getType() + " -> " + dstData.getType());
+
+        if (canCopySnapshotCond(srcData, dstData)) {
+            SnapshotInfo sinfo = (SnapshotInfo) srcData;
+            VolumeInfo volume = sinfo.getBaseVolume();
+            StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
+            return storagePool.getStorageProviderName().equals(LinstorUtil.PROVIDER_NAME);
+        } else if (canCopyTemplateCond(srcData, dstData)) {
+            TemplateInfo tInfo = (TemplateInfo) dstData;
+            StoragePoolVO storagePoolVO = _storagePoolDao.findById(dstData.getDataStore().getId());
+            return storagePoolVO != null
+                && storagePoolVO.getPoolType() == Storage.StoragePoolType.Linstor
+                && tInfo.getSize() != null;
+        }
         return false;
     }
 
     @Override
-    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCallback<CopyCommandResult> callback)
     {
-        // as long as canCopy is false, this isn't called
-        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+        s_logger.debug("LinstorPrimaryDataStoreDriverImpl.copyAsync: "
+            + srcData.getType() + " -> " + dstData.getType());
+
+        final CopyCommandResult res;
+        if (canCopySnapshotCond(srcData, dstData)) {
+            String errMsg = null;
+            Answer answer = copySnapshot(srcData, dstData);
+            if (answer != null && !answer.getResult()) {
+                errMsg = answer.getDetails();
+            } else {
+                // delete primary storage snapshot
+                SnapshotInfo sinfo = (SnapshotInfo) srcData;
+                VolumeInfo volume = sinfo.getBaseVolume();
+                deleteSnapshot(
+                    srcData.getDataStore(),
+                    LinstorUtil.RSC_PREFIX + volume.getUuid(),
+                    LinstorUtil.RSC_PREFIX + sinfo.getUuid());
+            }
+            res = new CopyCommandResult(null, answer);
+            res.setResult(errMsg);
+        } else if (canCopyTemplateCond(srcData, dstData)) {
+            Answer answer = copyTemplate(srcData, dstData);
+            res = new CopyCommandResult(null, answer);
+        } else {
+            Answer answer = new Answer(null, false, "noimpl");
+            res = new CopyCommandResult(null, answer);
+            res.setResult("Not implemented yet");
+        }
+        callback.complete(res);
+    }
+
+    /**
+     * Tries to get a Linstor cloudstack end point, that is at least diskless.
+     *
+     * @param api Linstor java api object
+     * @param rscName resource name to make available on node
+     * @return Optional RemoteHostEndPoint if one could get found.
+     * @throws ApiException
+     */
+    private Optional<RemoteHostEndPoint> getLinstorEP(DevelopersApi api, String rscName) throws ApiException {
+        List<String> linstorNodeNames = LinstorUtil.getLinstorNodeNames(api);
+        Collections.shuffle(linstorNodeNames);  // do not always pick the first linstor node
+
+        Host host = null;
+        for (String nodeName : linstorNodeNames) {
+            host = _hostDao.findByName(nodeName);
+            if (host != null && host.getResourceState() == ResourceState.Enabled) {
+                s_logger.info(String.format("Linstor: Make resource %s available on node %s ...", rscName, nodeName));
+                ApiCallRcList answers = api.resourceMakeAvailableOnNode(rscName, nodeName, new ResourceMakeAvailable());
+                if (!answers.hasError()) {
+                    break; // found working host
+                } else {
+                    s_logger.error(
+                        String.format("Linstor: Unable to make resource %s on node %s available: %s",
+                            rscName,
+                            nodeName,
+                            LinstorUtil.getBestErrorMessage(answers)));
+                }
+            }
+        }
+
+        if (host == null)
+        {
+            s_logger.error("Linstor: Couldn't create a resource on any cloudstack host.");
+            return Optional.empty();
+        }
+        else
+        {
+            return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
+        }
+    }
+
+    private Optional<RemoteHostEndPoint> getDiskfullEP(DevelopersApi api, String rscName) throws ApiException {
+        List<com.linbit.linstor.api.model.StoragePool> linSPs = LinstorUtil.getDiskfulStoragePools(api, rscName);
+        if (linSPs != null) {
+            for (com.linbit.linstor.api.model.StoragePool sp : linSPs) {
+                Host host = _hostDao.findByName(sp.getNodeName());
+                if (host != null && host.getResourceState() == ResourceState.Enabled) {
+                    return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
+                }
+            }
+        }
+        s_logger.error("Linstor: No diskfull host found.");
+        return Optional.empty();
+    }
+
+    private String restoreResourceFromSnapshot(
+            DevelopersApi api,
+            StoragePoolVO storagePoolVO,
+            String rscName,
+            String snapshotName,
+            String restoredName) throws ApiException {
+        final String rscGrp = getRscGrp(storagePoolVO);
+        ResourceDefinitionCreate rdc = createResourceDefinitionCreate(restoredName, rscGrp);
+        api.resourceDefinitionCreate(rdc);
+
+        SnapshotRestore sr = new SnapshotRestore();
+        sr.toResource(restoredName);
+        api.resourceSnapshotsRestoreVolumeDefinition(rscName, snapshotName, sr);
+
+        api.resourceSnapshotRestore(rscName, snapshotName, sr);
+
+        return getDeviceName(api, restoredName);
+    }
+
+    private Answer copyTemplate(DataObject srcData, DataObject dstData) {
+        TemplateInfo tInfo = (TemplateInfo) dstData;
+        final StoragePoolVO pool = _storagePoolDao.findById(dstData.getDataStore().getId());
+        final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        final String rscName = LinstorUtil.RSC_PREFIX + dstData.getUuid();
+        createResourceBase(
+            LinstorUtil.RSC_PREFIX + dstData.getUuid(),
+            tInfo.getSize(),
+            tInfo.getName(),
+            "",
+            api,
+            getRscGrp(pool));
+
+        int nMaxExecutionMinutes = NumbersUtil.parseInt(
+            _configDao.getValue(Config.SecStorageCmdExecutionTimeMax.key()), 30);
+        CopyCommand cmd = new CopyCommand(
+            srcData.getTO(),
+            dstData.getTO(),
+            nMaxExecutionMinutes * 60 * 1000,
+            VirtualMachineManager.ExecuteInSequence.value());
+        Answer answer;
+
+        try {
+            Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, rscName);
+            if (optEP.isPresent()) {
+                answer = optEP.get().sendMessage(cmd);
+            }
+            else {
+                answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint.");
+                deleteResourceDefinition(pool, rscName);
+            }
+        } catch (ApiException exc) {
+            s_logger.error("copy template failed: ", exc);
+            deleteResourceDefinition(pool, rscName);
+            throw new CloudRuntimeException(exc.getBestMessage());
+        }
+        return answer;
+    }
+
+    /**
+     * Create a temporary resource from the snapshot to backup, so we can copy the data on a diskless agent
+     * @param api Linstor Developer api object
+     * @param pool StoragePool this resource resides on
+     * @param rscName rscName of the snapshotted resource
+     * @param snapshotInfo snapshot info of the snapshot
+     * @param origCmd original LinstorBackupSnapshotCommand that needs to have a patched path
+     * @return answer from agent operation
+     * @throws ApiException if any Linstor api operation fails
+     */
+    private Answer copyFromTemporaryResource(
+            DevelopersApi api, StoragePoolVO pool, String rscName, SnapshotInfo snapshotInfo, CopyCommand origCmd)
+            throws ApiException {
+        Answer answer;
+        String restoreName = rscName + "-rst";
+        String snapshotName = LinstorUtil.RSC_PREFIX + snapshotInfo.getUuid();
+        String devName = restoreResourceFromSnapshot(api, pool, rscName, snapshotName, restoreName);
+
+        Optional<RemoteHostEndPoint> optEPAny = getLinstorEP(api, restoreName);
+        if (optEPAny.isPresent()) {
+            // patch the src device path to the temporary linstor resource
+            SnapshotObjectTO soTO = (SnapshotObjectTO)snapshotInfo.getTO();
+            soTO.setPath(devName);
+            origCmd.setSrcTO(soTO);
+            answer = optEPAny.get().sendMessage(origCmd);
+        } else{
+            answer = new Answer(origCmd, false, "Unable to get matching Linstor endpoint.");
+        }
+        // delete the temporary resource, noop if already gone
+        api.resourceDefinitionDelete(restoreName);
+        return answer;
+    }
+
+    protected Answer copySnapshot(DataObject srcData, DataObject destData) {
+        String value = _configDao.getValue(Config.BackupSnapshotWait.toString());
+        int _backupsnapshotwait = NumbersUtil.parseInt(
+            value, Integer.parseInt(Config.BackupSnapshotWait.getDefaultValue()));
+
+        SnapshotInfo snapshotInfo = (SnapshotInfo)srcData;
+        Boolean snapshotFullBackup = snapshotInfo.getFullBackup();
+        final StoragePoolVO pool = _storagePoolDao.findById(srcData.getDataStore().getId());
+        final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        boolean fullSnapshot = true;
+        if (snapshotFullBackup != null) {
+            fullSnapshot = snapshotFullBackup;
+        }
+        Map<String, String> options = new HashMap<>();
+        options.put("fullSnapshot", fullSnapshot + "");
+        options.put(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.key(),
+            String.valueOf(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value()));
+        options.put("volumeSize", snapshotInfo.getBaseVolume().getSize() + "");
+
+        try {
+            CopyCommand cmd = new LinstorBackupSnapshotCommand(
+                srcData.getTO(),
+                destData.getTO(),
+                _backupsnapshotwait,
+                VirtualMachineManager.ExecuteInSequence.value());
+            cmd.setOptions(options);
+
+            String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getUuid();
+            Optional<RemoteHostEndPoint> optEP = getDiskfullEP(api, rscName);
+            Answer answer;
+            if (optEP.isPresent()) {
+                answer = optEP.get().sendMessage(cmd);
+            } else {
+                s_logger.debug("No diskfull endpoint found to copy image, creating diskless endpoint");
+                answer = copyFromTemporaryResource(api, pool, rscName, snapshotInfo, cmd);
+            }
+            return answer;
+        } catch (Exception e) {
+            s_logger.debug("copy snapshot failed: ", e);
+            throw new CloudRuntimeException(e.toString());
+        }
+
     }
 
     @Override
     public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
     {
         // as long as canCopy is false, this isn't called
-        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+        s_logger.debug("Linstor: copyAsync with host");
+        copyAsync(srcData, destData, callback);
     }
 
     private CreateCmdResult notifyResize(
@@ -792,6 +1132,23 @@
         s_logger.debug("Linstor: handleQualityOfServiceForVolumeMigration");
     }
 
+    private Answer createAnswerAndPerstistDetails(DevelopersApi api, SnapshotInfo snapshotInfo, String rscName)
+        throws ApiException {
+        SnapshotObjectTO snapshotTO = (SnapshotObjectTO)snapshotInfo.getTO();
+        com.linbit.linstor.api.model.StoragePool linStoragePool = LinstorUtil.getDiskfulStoragePool(api, rscName);
+        if (linStoragePool == null) {
+            throw new CloudRuntimeException("Linstor: Unable to find storage pool for resource " + rscName);
+        }
+
+        final String path = LinstorUtil.getSnapshotPath(linStoragePool, rscName, LinstorUtil.RSC_PREFIX + snapshotInfo.getUuid());
+        snapshotTO.setPath(path);
+        SnapshotDetailsVO details = new SnapshotDetailsVO(
+            snapshotInfo.getId(), snapshotInfo.getUuid(), path, false);
+        _snapshotDetailsDao.persist(details);
+
+        return new CreateObjectAnswer(snapshotTO);
+    }
+
     @Override
     public void takeSnapshot(SnapshotInfo snapshotInfo, AsyncCompletionCallback<CreateCmdResult> callback)
     {
@@ -821,12 +1178,11 @@
                 result.setResult(errMsg);
             } else
             {
-                s_logger.info(String.format("Successfully took snapshot from %s", rscName));
+                s_logger.info(String.format("Successfully took snapshot %s from %s", snapshot.getName(), rscName));
 
-                SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO)snapshotInfo.getTO();
-                snapshotObjectTo.setPath(rscName + "-" + snapshotInfo.getName());
+                Answer answer = createAnswerAndPerstistDetails(api, snapshotInfo, rscName);
 
-                result = new CreateCmdResult(null, new CreateObjectAnswer(snapshotObjectTo));
+                result = new CreateCmdResult(null, answer);
                 result.setResult(null);
             }
         } catch (ApiException apiExc)
@@ -881,4 +1237,13 @@
     @Override
     public void provideVmTags(long vmId, long volumeId, String tagValue) {
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return true;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
 }
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
index a7d0c7f..efc6943 100644
--- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
@@ -91,6 +91,7 @@
         String providerName = (String) dsInfos.get("providerName");
         Long capacityIops = (Long) dsInfos.get("capacityIops");
         String tags = (String) dsInfos.get("tags");
+        Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
         @SuppressWarnings("unchecked")
         Map<String, String> details = (Map<String, String>) dsInfos.get("details");
 
@@ -168,6 +169,7 @@
         parameters.setCapacityIops(capacityIops);
         parameters.setHypervisorType(HypervisorType.KVM);
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
         parameters.setDetails(details);
         parameters.setUserInfo(resourceGroup);
 
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorHostListener.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorHostListener.java
new file mode 100644
index 0000000..534431e
--- /dev/null
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorHostListener.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.storage.datastore.provider;
+
+import com.cloud.exception.StorageConflictException;
+import com.cloud.host.HostVO;
+
+public class LinstorHostListener extends DefaultHostListener {
+    @Override
+    public boolean hostConnect(long hostId, long poolId) throws StorageConflictException {
+        HostVO host = hostDao.findById(hostId);
+        if (host.getParent() == null) {
+            host.setParent(host.getName());
+            hostDao.update(host.getId(), host);
+        }
+        return super.hostConnect(hostId, poolId);
+    }
+}
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorPrimaryDatastoreProviderImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorPrimaryDatastoreProviderImpl.java
index de0a169..962c84f 100644
--- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorPrimaryDatastoreProviderImpl.java
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/provider/LinstorPrimaryDatastoreProviderImpl.java
@@ -27,16 +27,16 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
 import org.apache.cloudstack.storage.datastore.driver.LinstorPrimaryDataStoreDriverImpl;
 import org.apache.cloudstack.storage.datastore.lifecycle.LinstorPrimaryDataStoreLifeCycleImpl;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 
 public class LinstorPrimaryDatastoreProviderImpl implements PrimaryDataStoreProvider {
-    private final static String PROVIDER_NAME = "Linstor";
     protected PrimaryDataStoreDriver driver;
     protected HypervisorHostListener listener;
     protected DataStoreLifeCycle lifecycle;
 
     @Override
     public String getName() {
-        return PROVIDER_NAME;
+        return LinstorUtil.PROVIDER_NAME;
     }
 
     @Override
@@ -48,7 +48,7 @@
     public boolean configure(Map<String, Object> params) {
         lifecycle = ComponentContext.inject(LinstorPrimaryDataStoreLifeCycleImpl.class);
         driver = ComponentContext.inject(LinstorPrimaryDataStoreDriverImpl.class);
-        listener = ComponentContext.inject(DefaultHostListener.class);
+        listener = ComponentContext.inject(LinstorHostListener.class);
         return true;
     }
 
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
new file mode 100644
index 0000000..90ebf30
--- /dev/null
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.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.storage.datastore.util;
+
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+
+public class LinstorConfigurationManager implements Configurable
+{
+    public static final ConfigKey<Boolean> BackupSnapshots = new ConfigKey<>(Boolean.class, "lin.backup.snapshots", "Advanced", "true",
+        "Backup Linstor primary storage snapshots to secondary storage (deleting ps snapshot)", true, ConfigKey.Scope.Global, null);
+
+    public static final ConfigKey<?>[] CONFIG_KEYS = new ConfigKey<?>[] { BackupSnapshots };
+
+    @Override
+    public String getConfigComponentName()
+    {
+        return LinstorConfigurationManager.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys()
+    {
+        return CONFIG_KEYS;
+    }
+}
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
index cc85c98..33cbea0 100644
--- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
@@ -22,12 +22,18 @@
 import com.linbit.linstor.api.DevelopersApi;
 import com.linbit.linstor.api.model.ApiCallRc;
 import com.linbit.linstor.api.model.ApiCallRcList;
+import com.linbit.linstor.api.model.Node;
 import com.linbit.linstor.api.model.ProviderKind;
 import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
 import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.Volume;
+
+import javax.annotation.Nonnull;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.log4j.Logger;
@@ -35,6 +41,7 @@
 public class LinstorUtil {
     private static final Logger s_logger = Logger.getLogger(LinstorUtil.class);
 
+    public final static String PROVIDER_NAME = "Linstor";
     public static final String RSC_PREFIX = "cs-";
     public static final String RSC_GROUP = "resourceGroup";
 
@@ -51,35 +58,121 @@
 
     public static String getBestErrorMessage(ApiCallRcList answers) {
         return answers != null && !answers.isEmpty() ?
-                answers.stream()
-                        .filter(ApiCallRc::isError)
-                        .findFirst()
-                        .map(ApiCallRc::getMessage)
-                        .orElse((answers.get(0)).getMessage()) : null;
+            answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage)
+                .orElse((answers.get(0)).getMessage()) : null;
     }
 
-    public static long getCapacityBytes(String linstorUrl, String rscGroupName) {
-        DevelopersApi linstorApi = getLinstorAPI(linstorUrl);
-        try {
-            List<ResourceGroup> rscGrps = linstorApi.resourceGroupList(
+    public static List<String> getLinstorNodeNames(@Nonnull DevelopersApi api) throws ApiException
+    {
+        List<Node> nodes = api.nodeList(
+            Collections.emptyList(),
+            Collections.emptyList(),
+            null,
+            null
+        );
+
+        return nodes.stream().map(Node::getName).collect(Collectors.toList());
+    }
+
+    public static List<com.linbit.linstor.api.model.StoragePool>
+    getDiskfulStoragePools(@Nonnull DevelopersApi api, @Nonnull String rscName) throws ApiException
+    {
+        List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                null,
+                null);
+
+        String nodeName = null;
+        String storagePoolName = null;
+        for (ResourceWithVolumes rwv : resources) {
+            if (rwv.getVolumes().isEmpty()) {
+                continue;
+            }
+            Volume vol = rwv.getVolumes().get(0);
+            if (vol.getProviderKind() != ProviderKind.DISKLESS) {
+                nodeName = rwv.getNodeName();
+                storagePoolName = vol.getStoragePoolName();
+                break;
+            }
+        }
+
+        if (nodeName == null) {
+            return null;
+        }
+
+        List<com.linbit.linstor.api.model.StoragePool> sps = api.viewStoragePools(
+                Collections.singletonList(nodeName),
+                Collections.singletonList(storagePoolName),
+                Collections.emptyList(),
+                null,
+                null
+        );
+        return sps != null ? sps : Collections.emptyList();
+    }
+
+    public static com.linbit.linstor.api.model.StoragePool
+    getDiskfulStoragePool(@Nonnull DevelopersApi api, @Nonnull String rscName) throws ApiException
+    {
+        List<com.linbit.linstor.api.model.StoragePool> sps = getDiskfulStoragePools(api, rscName);
+        if (sps != null) {
+            return !sps.isEmpty() ? sps.get(0) : null;
+        }
+        return null;
+    }
+
+    public static String getSnapshotPath(com.linbit.linstor.api.model.StoragePool sp, String rscName, String snapshotName) {
+        final String suffix = "00000";
+        final String backingPool = sp.getProps().get("StorDriver/StorPoolName");
+        final String path;
+        switch (sp.getProviderKind()) {
+            case LVM_THIN:
+                path = String.format("/dev/mapper/%s-%s_%s_%s",
+                    backingPool.split("/")[0], rscName.replace("-", "--"), suffix, snapshotName.replace("-", "--"));
+                break;
+            case ZFS:
+            case ZFS_THIN:
+                path = String.format("zfs://%s/%s_%s@%s", backingPool.split("/")[0], rscName, suffix, snapshotName);
+                break;
+            default:
+                throw new CloudRuntimeException(
+                    String.format("Linstor: storage pool type %s doesn't support snapshots.", sp.getProviderKind()));
+        }
+        return path;
+    }
+
+    public static List<StoragePool> getRscGroupStoragePools(DevelopersApi api, String rscGroupName)
+            throws ApiException {
+        List<ResourceGroup> rscGrps = api.resourceGroupList(
                 Collections.singletonList(rscGroupName),
                 null,
                 null,
                 null);
 
-            if (rscGrps.isEmpty()) {
-                final String errMsg = String.format("Linstor: Resource group '%s' not found", rscGroupName);
-                s_logger.error(errMsg);
-                throw new CloudRuntimeException(errMsg);
-            }
+        if (rscGrps.isEmpty()) {
+            final String errMsg = String.format("Linstor: Resource group '%s' not found", rscGroupName);
+            s_logger.error(errMsg);
+            throw new CloudRuntimeException(errMsg);
+        }
 
-            List<StoragePool> storagePools = linstorApi.viewStoragePools(
+        return api.viewStoragePools(
                 Collections.emptyList(),
                 rscGrps.get(0).getSelectFilter().getStoragePoolList(),
                 null,
                 null,
                 null
-            );
+        );
+    }
+
+    public static long getCapacityBytes(String linstorUrl, String rscGroupName) {
+        DevelopersApi linstorApi = getLinstorAPI(linstorUrl);
+        try {
+            List<StoragePool> storagePools = getRscGroupStoragePools(linstorApi, rscGroupName);
 
             return storagePools.stream()
                 .filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS)
diff --git a/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml b/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
index e4fcb45..48913cd 100644
--- a/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
+++ b/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
@@ -29,4 +29,6 @@
 
     <bean id="linstorPrimaryDataStoreProviderImpl"
         class="org.apache.cloudstack.storage.datastore.provider.LinstorPrimaryDatastoreProviderImpl" />
+    <bean id="linstorConfigManager"
+          class="org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager" />
 </beans>
diff --git a/plugins/storage/volume/nexenta/pom.xml b/plugins/storage/volume/nexenta/pom.xml
index 6dedcc8..6d8d40d 100644
--- a/plugins/storage/volume/nexenta/pom.xml
+++ b/plugins/storage/volume/nexenta/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -38,9 +38,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java
index 8405188..6c81141 100644
--- a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java
+++ b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java
@@ -49,7 +49,9 @@
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.host.Host;
 import com.cloud.storage.Storage;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StoragePool;
+import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.user.dao.AccountDao;
@@ -257,4 +259,18 @@
     @Override
     public void provideVmTags(long vmId, long volumeId, String tagValue) {
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return false;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
+
+    @Override
+    public boolean volumesRequireGrantAccessWhenUsed() {
+        return true;
+    }
 }
diff --git a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java
index 3273566..507189e 100644
--- a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java
+++ b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java
@@ -69,6 +69,7 @@
         Long capacityBytes = (Long)dsInfos.get("capacityBytes");
         Long capacityIops = (Long)dsInfos.get("capacityIops");
         String tags = (String)dsInfos.get("tags");
+        Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
         Map<String, String> details = (Map<String, String>) dsInfos.get("details");
         NexentaUtil.NexentaPluginParameters params = NexentaUtil.parseNexentaPluginUrl(url);
         DataCenterVO zone = zoneDao.findById(zoneId);
@@ -98,6 +99,7 @@
         parameters.setCapacityIops(capacityIops);
         parameters.setHypervisorType(Hypervisor.HypervisorType.Any);
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
 
         details.put(NexentaUtil.NMS_URL, params.getNmsUrl().toString());
 
diff --git a/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties b/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties
index c203600..fe95f28 100644
--- a/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties
+++ b/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-volume-nexenta
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml b/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml
index 52032e1..c1dae74 100644
--- a/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml
+++ b/plugins/storage/volume/nexenta/src/main/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml
@@ -29,4 +29,4 @@
   <bean id="nexentaStorDataStoreProvider"
         class="org.apache.cloudstack.storage.datastore.provider.NexentaPrimaryDataStoreProvider" />
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/plugins/storage/volume/nexenta/src/test/java/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java b/plugins/storage/volume/nexenta/src/test/java/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java
index 6dc59eb..749c04b 100644
--- a/plugins/storage/volume/nexenta/src/test/java/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java
+++ b/plugins/storage/volume/nexenta/src/test/java/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java
@@ -119,7 +119,6 @@
         when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroups")).thenReturn(null);
         assertFalse(appliance.isIscsiTargetGroupExists(targetGroup));
 
-        when(client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targetgroups")).thenReturn(new ListOfIscsiTargetsNmsResponse());
         assertFalse(appliance.isIscsiTargetGroupExists(targetGroup));
 
         LinkedList<String> result = new LinkedList<String>();
diff --git a/plugins/storage/volume/primera/pom.xml b/plugins/storage/volume/primera/pom.xml
new file mode 100644
index 0000000..fc373b5
--- /dev/null
+++ b/plugins/storage/volume/primera/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-storage-volume-primera</artifactId>
+    <name>Apache CloudStack Plugin - Storage Volume - HPE Primera</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-plugin-storage-volume-adaptive</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java
new file mode 100644
index 0000000..69f9856
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapter.java
@@ -0,0 +1,930 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.primera;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterContext;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDataObject;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDiskOffering;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderSnapshot;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolume;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolume.AddressType;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeNamer;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStats;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderVolumeStorageStats;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterDiskOffering.ProvisioningType;
+import org.apache.http.Header;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.TrustAllStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.log4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class PrimeraAdapter implements ProviderAdapter {
+
+    static final Logger logger = Logger.getLogger(PrimeraAdapter.class);
+
+    public static final String HOSTSET = "hostset";
+    public static final String CPG = "cpg";
+    public static final String SNAP_CPG = "snapCpg";
+    public static final String KEY_TTL = "keyttl";
+    public static final String CONNECT_TIMEOUT_MS = "connectTimeoutMs";
+    public static final String POST_COPY_WAIT_MS = "postCopyWaitMs";
+    public static final String TASK_WAIT_TIMEOUT_MS = "taskWaitTimeoutMs";
+
+    private static final long KEY_TTL_DEFAULT = (1000 * 60 * 14);
+    private static final long CONNECT_TIMEOUT_MS_DEFAULT = 600000;
+    private static final long TASK_WAIT_TIMEOUT_MS_DEFAULT = 10 * 60 * 1000;
+    public static final long BYTES_IN_MiB = 1048576;
+
+    static final ObjectMapper mapper = new ObjectMapper();
+    public String cpg = null;
+    public String snapCpg = null;
+    public String hostset = null;
+    private String username;
+    private String password;
+    private String key;
+    private String url;
+    private long keyExpiration = -1;
+    private long keyTtl = KEY_TTL_DEFAULT;
+    private long connTimeout = CONNECT_TIMEOUT_MS_DEFAULT;
+    private long taskWaitTimeoutMs = TASK_WAIT_TIMEOUT_MS_DEFAULT;
+    private CloseableHttpClient _client = null;
+    private boolean skipTlsValidation;
+
+    private Map<String, String> connectionDetails = null;
+
+    public PrimeraAdapter(String url, Map<String, String> details) {
+        this.url = url;
+        this.connectionDetails = details;
+        login();
+    }
+
+    @Override
+    public void refresh(Map<String, String> details) {
+        this.connectionDetails = details;
+        this.refreshSession(true);
+    }
+
+    /**
+     * Validate that the hostgroup and pod from the details data exists.  Each
+     * configuration object/connection needs a distinct set of these 2 things.
+     */
+    @Override
+    public void validate() {
+        login();
+        if (this.getHostset(hostset) == null) {
+            throw new RuntimeException("Hostgroup [" + hostset + "] not found in FlashArray at [" + url
+                    + "], please validate configuration");
+        }
+
+        if (this.getCpg(cpg) == null) {
+            throw new RuntimeException(
+                    "Pod [" + cpg + "] not found in FlashArray at [" + url + "], please validate configuration");
+        }
+    }
+
+    @Override
+    public void disconnect() {
+        return;
+    }
+
+    @Override
+    public ProviderVolume create(ProviderAdapterContext context, ProviderAdapterDataObject dataIn,
+            ProviderAdapterDiskOffering diskOffering, long sizeInBytes) {
+        PrimeraVolumeRequest request = new PrimeraVolumeRequest();
+        String externalName = ProviderVolumeNamer.generateObjectName(context, dataIn);
+        request.setName(externalName);
+        request.setCpg(cpg);
+        request.setSnapCPG(snapCpg);
+        if (sizeInBytes < BYTES_IN_MiB) {
+            request.setSizeMiB(1);
+        } else {
+            request.setSizeMiB(sizeInBytes/BYTES_IN_MiB);
+        }
+
+        // determine volume type based on offering
+        // THIN: tpvv=true, reduce=false
+        // SPARSE: tpvv=true, reduce=true
+        // THICK: tpvv=false, tpZeroFill=true (not supported)
+        if (diskOffering != null) {
+            if (diskOffering.getType() == ProvisioningType.THIN) {
+                request.setTpvv(true);
+                request.setReduce(false);
+            } else if (diskOffering.getType() == ProvisioningType.SPARSE) {
+                request.setTpvv(false);
+                request.setReduce(true);
+            } else if (diskOffering.getType() == ProvisioningType.FAT) {
+                throw new RuntimeException("This storage provider does not support FAT provisioned volumes");
+            }
+
+            // sets the amount of space allowed for snapshots as a % of the volumes size
+            if (diskOffering.getHypervisorSnapshotReserve() != null) {
+                request.setSsSpcAllocLimitPct(diskOffering.getHypervisorSnapshotReserve());
+            }
+        } else {
+            // default to deduplicated volume
+            request.setReduce(true);
+            request.setTpvv(false);
+        }
+
+        request.setComment(ProviderVolumeNamer.generateObjectComment(context, dataIn));
+        POST("/volumes", request, null);
+        dataIn.setExternalName(externalName);
+        ProviderVolume volume = getVolume(context, dataIn);
+        return volume;
+    }
+
+    @Override
+    public String attach(ProviderAdapterContext context, ProviderAdapterDataObject dataIn) {
+        assert dataIn.getExternalName() != null : "External name not provided internally on volume attach";
+        PrimeraHostset.PrimeraHostsetVLUNRequest request = new PrimeraHostset.PrimeraHostsetVLUNRequest();
+        request.setHostname("set:" + hostset);
+        request.setVolumeName(dataIn.getExternalName());
+        request.setAutoLun(true);
+        // auto-lun returned here: Location: /api/v1/vluns/test_vv02,252,mysystem,2:2:4
+        String location = POST("/vluns", request, new TypeReference<String>() {});
+        if (location == null) {
+            throw new RuntimeException("Attach volume failed with empty location response to vlun add command on storage provider");
+        }
+        String[] toks = location.split(",");
+        if (toks.length <2) {
+            throw new RuntimeException("Attach volume failed with invalid location response to vlun add command on storage provider.  Provided location: " + location);
+        }
+        return toks[1];
+    }
+
+    @Override
+    public void detach(ProviderAdapterContext context, ProviderAdapterDataObject request) {
+        // we expect to only be attaching one hostset to the vluns, so on detach we'll
+        // remove ALL vluns we find.
+        assert request.getExternalName() != null : "External name not provided internally on volume detach";
+        removeAllVluns(request.getExternalName());
+    }
+
+    public void removeVlun(String name, Integer lunid, String hostString) {
+        // hostString can be a hostname OR "set:<hostsetname>".  It is stored this way
+        // in the appliance and returned as the vlun's name/string.
+        DELETE("/vluns/" + name + "," + lunid + "," + hostString);
+    }
+
+    /**
+     * Removes all vluns - this should only be done when you are sure the volume is no longer in use
+     * @param name
+     */
+    public void removeAllVluns(String name) {
+        PrimeraVlunList list = getVolumeHostsets(name);
+        if (list != null && list.getMembers() != null) {
+            for (PrimeraVlun vlun: list.getMembers()) {
+                removeVlun(vlun.getVolumeName(), vlun.getLun(), vlun.getHostname());
+            }
+        }
+    }
+
+    public PrimeraVlunList getVolumeHostsets(String name) {
+        String query = "%22volumeName%20EQ%20" + name + "%22";
+        return GET("/vluns?query=" + query, new TypeReference<PrimeraVlunList>() {});
+    }
+
+    @Override
+    public void delete(ProviderAdapterContext context, ProviderAdapterDataObject request) {
+        assert request.getExternalName() != null : "External name not provided internally on volume delete";
+
+        // first remove vluns (take volumes from vluns) from hostset
+        removeAllVluns(request.getExternalName());
+        DELETE("/volumes/" + request.getExternalName());
+    }
+
+    @Override
+    public ProviderVolume copy(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolumeInfo,
+            ProviderAdapterDataObject targetVolumeInfo) {
+        PrimeraVolumeCopyRequest request = new PrimeraVolumeCopyRequest();
+        PrimeraVolumeCopyRequestParameters parms = new PrimeraVolumeCopyRequestParameters();
+
+        assert sourceVolumeInfo.getExternalName() != null: "External provider name not provided on copy request to Primera volume provider";
+
+        // if we have no external name, treat it as a new volume
+        if (targetVolumeInfo.getExternalName() == null) {
+            targetVolumeInfo.setExternalName(ProviderVolumeNamer.generateObjectName(context, targetVolumeInfo));
+        }
+
+        ProviderVolume sourceVolume = this.getVolume(context, sourceVolumeInfo);
+        if (sourceVolume == null) {
+            throw new RuntimeException("Source volume " + sourceVolumeInfo.getExternalUuid() + " with provider name " + sourceVolumeInfo.getExternalName() + " not found on storage provider");
+        }
+
+        ProviderVolume targetVolume = this.getVolume(context, targetVolumeInfo);
+        if (targetVolume == null) {
+            this.create(context, targetVolumeInfo, null, sourceVolume.getAllocatedSizeInBytes());
+        }
+
+        parms.setDestVolume(targetVolumeInfo.getExternalName());
+        parms.setOnline(false);
+        request.setParameters(parms);
+
+        PrimeraTaskReference taskref = POST("/volumes/" + sourceVolumeInfo.getExternalName(), request, new TypeReference<PrimeraTaskReference>() {});
+        if (taskref == null) {
+            throw new RuntimeException("Unable to retrieve task used to copy to newly created volume");
+        }
+
+        waitForTaskToComplete(taskref.getTaskid(), "copy volume " + sourceVolumeInfo.getExternalName() + " to " +
+            targetVolumeInfo.getExternalName(), taskWaitTimeoutMs);
+
+        return this.getVolume(context, targetVolumeInfo);
+    }
+
+    private void waitForTaskToComplete(String taskid, String taskDescription, Long timeoutMs) {
+        // first wait for task to complete
+        long taskWaitTimeout = System.currentTimeMillis() + timeoutMs;
+        boolean timedOut = true;
+        PrimeraTaskStatus status = null;
+        long starttime = System.currentTimeMillis();
+        while (System.currentTimeMillis() <= taskWaitTimeout) {
+            status = this.getTaskStatus(taskid);
+            if (status != null && status.isFinished()) {
+                timedOut = false;
+                if (!status.isSuccess()) {
+                    throw new RuntimeException("Task " + taskDescription + " was cancelled.  TaskID: " + status.getId() + "; Final Status: " + status.getStatusName());
+                }
+                break;
+            } else {
+                if (status != null) {
+                    logger.info("Task " + taskDescription + " is still running.  TaskID: " + status.getId() + "; Current Status: " + status.getStatusName());
+                }
+                // ugly...to keep from hot-polling API
+                try {
+                    Thread.sleep(5000);
+                } catch (InterruptedException e) {
+
+                }
+            }
+        }
+
+        if (timedOut) {
+            if (status != null) {
+                throw new RuntimeException("Task " + taskDescription + " timed out.  TaskID: " + status.getId() + ", Last Known Status: " + status.getStatusName());
+            } else {
+                throw new RuntimeException("Task " + taskDescription + " timed out and a current status could not be retrieved from storage endpoint");
+            }
+        }
+
+        logger.info(taskDescription + " completed in " + ((System.currentTimeMillis() - starttime)/1000) + " seconds");
+    }
+
+    private PrimeraTaskStatus getTaskStatus(String taskid) {
+        return GET("/tasks/" + taskid + "?view=excludeDetail", new TypeReference<PrimeraTaskStatus>() {
+        });
+    }
+
+    @Override
+    public ProviderSnapshot snapshot(ProviderAdapterContext context, ProviderAdapterDataObject sourceVolume,
+            ProviderAdapterDataObject targetSnapshot) {
+        assert sourceVolume.getExternalName() != null : "External name not set";
+        PrimeraVolumeSnapshotRequest request = new PrimeraVolumeSnapshotRequest();
+        PrimeraVolumeSnapshotRequestParameters parms = new PrimeraVolumeSnapshotRequestParameters();
+        parms.setName(ProviderVolumeNamer.generateObjectName(context, targetSnapshot));
+        request.setParameters(parms);
+        POST("/volumes/" + sourceVolume.getExternalName(), request, null);
+        targetSnapshot.setExternalName(parms.getName());
+        return getSnapshot(context, targetSnapshot);
+    }
+
+    @Override
+    public ProviderVolume revert(ProviderAdapterContext context, ProviderAdapterDataObject dataIn) {
+        assert dataIn.getExternalName() != null: "External name not internally set for provided snapshot when requested storage provider to revert";
+        // first get original volume
+        PrimeraVolume snapVol = (PrimeraVolume)getVolume(context, dataIn);
+        assert snapVol != null: "Storage volume associated with snapshot externally named [" + dataIn.getExternalName() + "] not found";
+        assert snapVol.getParentId() != null: "Unable to determine parent volume/snapshot for snapshot named [" + dataIn.getExternalName() + "]";
+
+        PrimeraVolumeRevertSnapshotRequest request = new PrimeraVolumeRevertSnapshotRequest();
+        request.setOnline(true);
+        request.setPriority(2);
+        PrimeraTaskReference taskref = PUT("/volumes/" + dataIn.getExternalName(), request, new TypeReference<PrimeraTaskReference>() {});
+        if (taskref == null) {
+            throw new RuntimeException("Unable to retrieve task used to revert snapshot to base volume");
+        }
+
+        waitForTaskToComplete(taskref.getTaskid(), "revert snapshot " + dataIn.getExternalName(), taskWaitTimeoutMs);
+
+        return getVolumeById(context, snapVol.getParentId());
+    }
+
+    /**
+     * Resize the volume to the new size.  For HPE Primera, the API takes the additional space to add to the volume
+     * so this method will first retrieve the current volume's size and subtract that from the new size provided
+     * before calling the API.
+     *
+     * This method uses option GROW_VOLUME=3 for the API at this URL:
+     * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=v25706371.html
+     *
+     */
+    @Override
+    public void resize(ProviderAdapterContext context, ProviderAdapterDataObject request, long totalNewSizeInBytes) {
+        assert request.getExternalName() != null: "External name not internally set for provided volume when requesting resize of volume";
+
+        PrimeraVolume existingVolume = (PrimeraVolume) getVolume(context, request);
+        assert existingVolume != null: "Storage volume resize request not possible as existing volume not found for external provider name: " + request.getExternalName();
+        long existingSizeInBytes = existingVolume.getSizeMiB() * PrimeraAdapter.BYTES_IN_MiB;
+        assert existingSizeInBytes < totalNewSizeInBytes: "Existing volume size is larger than requested new size for volume resize request.  The Primera storage system does not support truncating/shrinking volumes.";
+        long addOnSizeInBytes = totalNewSizeInBytes - existingSizeInBytes;
+
+        PrimeraVolume volume = new PrimeraVolume();
+        volume.setSizeMiB((int) (addOnSizeInBytes / PrimeraAdapter.BYTES_IN_MiB));
+        volume.setAction(3);
+        PUT("/volumes/" + request.getExternalName(), volume, null);
+    }
+
+    @Override
+    public ProviderVolume getVolume(ProviderAdapterContext context, ProviderAdapterDataObject request) {
+        String externalName;
+
+        // if the external name isn't provided, look for the derived contextual name.  some failure scenarios
+        // may result in the volume for this context being created but a subsequent failure causing the external
+        // name to not be persisted for later use.  This is true of template-type objects being cached on primary
+        // storage
+        if (request.getExternalName() == null) {
+            externalName = ProviderVolumeNamer.generateObjectName(context, request);
+        } else {
+            externalName = request.getExternalName();
+        }
+
+        return GET("/volumes/" + externalName, new TypeReference<PrimeraVolume>() {
+        });
+    }
+
+    private ProviderVolume getVolumeById(ProviderAdapterContext context, Integer id) {
+        String query = "%22id%20EQ%20" + id + "%22";
+        return GET("/volumes?query=" + query, new TypeReference<PrimeraVolume>() {});
+    }
+
+    @Override
+    public ProviderSnapshot getSnapshot(ProviderAdapterContext context, ProviderAdapterDataObject request) {
+        assert request.getExternalName() != null: "External name not provided internally when finding snapshot on storage provider";
+        return GET("/volumes/" + request.getExternalName(), new TypeReference<PrimeraVolume>() {
+        });
+    }
+
+    @Override
+    public ProviderVolume getVolumeByAddress(ProviderAdapterContext context, AddressType addressType, String address) {
+        assert address != null: "External volume address not provided";
+        assert AddressType.FIBERWWN.equals(addressType): "This volume provider currently does not support address type " + addressType.name();
+        String query = "%22wwn%20EQ%20" + address + "%22";
+        return GET("/volumes?query=" + query, new TypeReference<PrimeraVolume>() {});
+    }
+
+    @Override
+    public ProviderVolumeStorageStats getManagedStorageStats() {
+        PrimeraCpg cpgobj = getCpg(cpg);
+        // just in case
+        if (cpgobj == null || cpgobj.getTotalSpaceMiB() == 0) {
+            return null;
+        }
+        Long capacityBytes = 0L;
+        if (cpgobj.getsDGrowth() != null) {
+            capacityBytes = cpgobj.getsDGrowth().getLimitMiB() * PrimeraAdapter.BYTES_IN_MiB;
+        }
+        Long usedBytes = 0L;
+        if (cpgobj.getUsrUsage() != null) {
+            usedBytes = (cpgobj.getUsrUsage().getRawUsedMiB()) * PrimeraAdapter.BYTES_IN_MiB;
+        }
+        ProviderVolumeStorageStats stats = new ProviderVolumeStorageStats();
+        stats.setActualUsedInBytes(usedBytes);
+        stats.setCapacityInBytes(capacityBytes);
+        return stats;
+    }
+
+    @Override
+    public ProviderVolumeStats getVolumeStats(ProviderAdapterContext context, ProviderAdapterDataObject request) {
+        PrimeraVolume vol = (PrimeraVolume)getVolume(context, request);
+        if (vol == null || vol.getSizeMiB() == null || vol.getSizeMiB() == 0) {
+            return null;
+        }
+
+        Long virtualSizeInBytes = vol.getHostWriteMiB()  * PrimeraAdapter.BYTES_IN_MiB;
+        Long allocatedSizeInBytes = vol.getSizeMiB() * PrimeraAdapter.BYTES_IN_MiB;
+        Long actualUsedInBytes = vol.getTotalUsedMiB() * PrimeraAdapter.BYTES_IN_MiB;
+        ProviderVolumeStats stats = new ProviderVolumeStats();
+        stats.setActualUsedInBytes(actualUsedInBytes);
+        stats.setAllocatedInBytes(allocatedSizeInBytes);
+        stats.setVirtualUsedInBytes(virtualSizeInBytes);
+        return stats;
+    }
+
+    @Override
+    public boolean canAccessHost(ProviderAdapterContext context, String hostname) {
+        PrimeraHostset hostset = getHostset(this.hostset);
+
+        List<String> members = hostset.getSetmembers();
+
+        // check for fqdn and shortname combinations.  this assumes there is at least a shortname match in both the storage array and cloudstack
+        // hostname configuration
+        String shortname;
+        if (hostname.indexOf('.') > 0) {
+            shortname = hostname.substring(0, (hostname.indexOf('.')));
+        } else {
+            shortname = hostname;
+        }
+        for (String member: members) {
+            // exact match (short or long names)
+            if (member.equals(hostname)) {
+                return true;
+            }
+
+            // primera has short name and cloudstack had long name
+            if (member.equals(shortname)) {
+                return true;
+            }
+
+            // member has long name but cloudstack had shortname
+            int index = member.indexOf(".");
+            if (index > 0) {
+                if (member.substring(0, (member.indexOf('.'))).equals(shortname)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private PrimeraCpg getCpg(String name) {
+        return GET("/cpgs/" + name, new TypeReference<PrimeraCpg>() {
+        });
+    }
+
+    private PrimeraHostset getHostset(String name) {
+        return GET("/hostsets/" + name, new TypeReference<PrimeraHostset>() {
+        });
+    }
+
+    private String getSessionKey() {
+        refreshSession(false);
+        return key;
+    }
+
+    private synchronized void refreshSession(boolean force) {
+        try {
+            if (force || keyExpiration < System.currentTimeMillis()) {
+                // close client to force connection reset on appliance -- not doing this can result in NotAuthorized error...guessing
+                _client.close();;
+                _client = null;
+                login();
+                keyExpiration = System.currentTimeMillis() + keyTtl;
+            }
+        } catch (Exception e) {
+            // retry frequently but not every request to avoid DDOS on storage API
+            logger.warn("Failed to refresh Primera API key for " + username + "@" + url + ", will retry in 5 seconds", e);
+            keyExpiration = System.currentTimeMillis() + (5*1000);
+        }
+    }
+
+    private void validateLoginInfo(String urlStr) {
+        URL urlFull;
+        try {
+            urlFull = new URL(urlStr);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("Invalid URL format: " + urlStr, e);
+        }
+        ;
+
+        int port = urlFull.getPort();
+        if (port <= 0) {
+            port = 443;
+        }
+        this.url = urlFull.getProtocol() + "://" + urlFull.getHost() + ":" + port + urlFull.getPath();
+
+        Map<String, String> queryParms = new HashMap<String, String>();
+        if (urlFull.getQuery() != null) {
+            String[] queryToks = urlFull.getQuery().split("&");
+            for (String tok : queryToks) {
+                if (tok.endsWith("=")) {
+                    continue;
+                }
+                int i = tok.indexOf("=");
+                if (i > 0) {
+                    queryParms.put(tok.substring(0, i), tok.substring(i + 1));
+                }
+            }
+        }
+
+        cpg = connectionDetails.get(PrimeraAdapter.CPG);
+        if (cpg == null) {
+            cpg = queryParms.get(PrimeraAdapter.CPG);
+            if (cpg == null) {
+                throw new RuntimeException(
+                        PrimeraAdapter.CPG + " paramater/option required to configure this storage pool");
+            }
+        }
+
+        snapCpg = connectionDetails.get(PrimeraAdapter.SNAP_CPG);
+        if (snapCpg == null) {
+            snapCpg = queryParms.get(PrimeraAdapter.SNAP_CPG);
+            if (snapCpg == null) {
+                // default to using same CPG as the volume
+                snapCpg = cpg;
+            }
+        }
+
+        hostset = connectionDetails.get(PrimeraAdapter.HOSTSET);
+        if (hostset == null) {
+            hostset = queryParms.get(PrimeraAdapter.HOSTSET);
+            if (hostset == null) {
+                throw new RuntimeException(
+                        PrimeraAdapter.HOSTSET + " paramater/option required to configure this storage pool");
+            }
+        }
+
+        String connTimeoutStr = connectionDetails.get(PrimeraAdapter.CONNECT_TIMEOUT_MS);
+        if (connTimeoutStr == null) {
+            connTimeoutStr = queryParms.get(PrimeraAdapter.CONNECT_TIMEOUT_MS);
+        }
+        if (connTimeoutStr == null) {
+            connTimeout = CONNECT_TIMEOUT_MS_DEFAULT;
+        } else {
+            try {
+                connTimeout = Integer.parseInt(connTimeoutStr);
+            } catch (NumberFormatException e) {
+                logger.warn("Connection timeout not formatted correctly, using default", e);
+                connTimeout = CONNECT_TIMEOUT_MS_DEFAULT;
+            }
+        }
+
+        String keyTtlString = connectionDetails.get(PrimeraAdapter.KEY_TTL);
+        if (keyTtlString == null) {
+            keyTtlString = queryParms.get(PrimeraAdapter.KEY_TTL);
+        }
+        if (keyTtlString == null) {
+            keyTtl = KEY_TTL_DEFAULT;
+        } else {
+            try {
+                keyTtl = Integer.parseInt(keyTtlString);
+            } catch (NumberFormatException e) {
+                logger.warn("Key TTL not formatted correctly, using default", e);
+                keyTtl = KEY_TTL_DEFAULT;
+            }
+        }
+
+        String taskWaitTimeoutMsStr = connectionDetails.get(PrimeraAdapter.TASK_WAIT_TIMEOUT_MS);
+        if (taskWaitTimeoutMsStr == null) {
+            taskWaitTimeoutMsStr = queryParms.get(PrimeraAdapter.TASK_WAIT_TIMEOUT_MS);
+            if (taskWaitTimeoutMsStr == null) {
+                taskWaitTimeoutMs = PrimeraAdapter.TASK_WAIT_TIMEOUT_MS_DEFAULT;
+            } else {
+                try {
+                    taskWaitTimeoutMs = Long.parseLong(taskWaitTimeoutMsStr);
+                } catch (NumberFormatException e) {
+                    logger.warn(PrimeraAdapter.TASK_WAIT_TIMEOUT_MS + " property not set to a proper number, using default value");
+                }
+            }
+        }
+
+        String skipTlsValidationStr = connectionDetails.get(ProviderAdapter.API_SKIP_TLS_VALIDATION_KEY);
+        if (skipTlsValidationStr == null) {
+            skipTlsValidationStr = queryParms.get(ProviderAdapter.API_SKIP_TLS_VALIDATION_KEY);
+        }
+
+        if (skipTlsValidationStr != null) {
+            skipTlsValidation = Boolean.parseBoolean(skipTlsValidationStr);
+        } else {
+            skipTlsValidation = true;
+        }
+    }
+
+    /**
+     * Login to the array and get an access token
+     */
+    private void login() {
+        username = connectionDetails.get(ProviderAdapter.API_USERNAME_KEY);
+        password = connectionDetails.get(ProviderAdapter.API_PASSWORD_KEY);
+        String urlStr = connectionDetails.get(ProviderAdapter.API_URL_KEY);
+        validateLoginInfo(urlStr);
+        CloseableHttpResponse response = null;
+        try {
+            HttpPost request = new HttpPost(url + "/credentials");
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.setEntity(new StringEntity("{\"user\":\"" + username + "\", \"password\":\"" + password + "\"}"));
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 | statusCode == 201) {
+                PrimeraKey keyobj = mapper.readValue(response.getEntity().getContent(), PrimeraKey.class);
+                key = keyobj.getKey();
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new RuntimeException("Authentication or Authorization to Primera [" + url + "] with user [" + username
+                        + "] failed, unable to retrieve session token");
+            } else {
+                throw new RuntimeException("Unexpected HTTP response code from Primera [" + url + "] - [" + statusCode
+                        + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Error creating input for login, check username/password encoding");
+        } catch (UnsupportedOperationException e) {
+            throw new RuntimeException("Error processing login response from Primera [" + url + "]", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error sending login request to Primera [" + url + "]", e);
+        } finally {
+            try {
+                if (response != null) {
+                    response.close();
+                }
+            } catch (IOException e) {
+                logger.debug("Error closing response from login attempt to Primera", e);
+            }
+        }
+    }
+
+    private CloseableHttpClient getClient() {
+        if (_client == null) {
+            RequestConfig config = RequestConfig.custom()
+                    .setConnectTimeout((int) connTimeout)
+                    .setConnectionRequestTimeout((int) connTimeout)
+                    .setSocketTimeout((int) connTimeout).build();
+
+            HostnameVerifier verifier = null;
+            SSLContext sslContext = null;
+
+            if (this.skipTlsValidation) {
+                try {
+                    verifier = NoopHostnameVerifier.INSTANCE;
+                    sslContext = new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build();
+                } catch (KeyManagementException e) {
+                    throw new RuntimeException(e);
+                } catch (NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                } catch (KeyStoreException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            _client = HttpClients.custom()
+                    .setDefaultRequestConfig(config)
+                    .setSSLHostnameVerifier(verifier)
+                    .setSSLContext(sslContext)
+                    .build();
+        }
+        return _client;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T POST(String path, Object input, final TypeReference<T> type) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpPost request = new HttpPost(url + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-HP3PAR-WSAPI-SessionKey", getSessionKey());
+            try {
+                String data = mapper.writeValueAsString(input);
+                request.setEntity(new StringEntity(data));
+                logger.debug("POST data: " + request.getEntity());
+            } catch (UnsupportedEncodingException | JsonProcessingException e) {
+                throw new RuntimeException(
+                        "Error processing request payload to [" + url + "] for path [" + path + "]", e);
+            }
+
+            CloseableHttpClient client = getClient();
+            try {
+                response = (CloseableHttpResponse) client
+                        .execute(request);
+            } catch (IOException e) {
+                throw new RuntimeException("Error sending request to Primera [" + url + path + "]", e);
+            }
+
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 || statusCode == 201) {
+                try {
+                    if (type != null) {
+                        Header header = response.getFirstHeader("Location");
+                        if (type.getType().getTypeName().equals(String.class.getName())) {
+                            if (header != null) {
+                                return (T) header.getValue();
+                            } else {
+                                return null;
+                            }
+                        } else if (type.getType().getTypeName().equals(PrimeraTaskReference.class.getName())) {
+                            T obj = mapper.readValue(response.getEntity().getContent(), type);
+                            PrimeraTaskReference taskref = (PrimeraTaskReference) obj;
+                            taskref.setLocation(header.getValue());
+                            return obj;
+                        } else {
+                            return mapper.readValue(response.getEntity().getContent(), type);
+                        }
+                    }
+                    return null;
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new RuntimeException("Error processing response from Primera [" + url + path + "]", e);
+                }
+            } else if (statusCode == 400) {
+                try {
+                    Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                            new TypeReference<Map<String, Object>>() {
+                            });
+                    throw new RuntimeException("Invalid request error 400: " + payload);
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new RuntimeException(
+                            "Error processing bad request response from Primera [" + url + path + "]", e);
+                }
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new RuntimeException("Authentication or Authorization to Primera [" + url + "] with user [" + username
+                        + "] failed, unable to retrieve session token");
+            } else {
+                try {
+                    Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                            new TypeReference<Map<String, Object>>() {
+                            });
+                    throw new RuntimeException("Invalid request error " + statusCode + ": " + payload);
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new RuntimeException("Unexpected HTTP response code from Primera on POST [" + url + path + "] - ["
+                            + statusCode + "] - " + response.getStatusLine().getReasonPhrase());
+                }
+            }
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to Primera API", e);
+                }
+            }
+        }
+    }
+
+    private <T> T PUT(String path, Object input, final TypeReference<T> type) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpPut request = new HttpPut(url + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-HP3PAR-WSAPI-SessionKey", getSessionKey());
+            String data = mapper.writeValueAsString(input);
+            request.setEntity(new StringEntity(data));
+
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 || statusCode == 201) {
+                if (type != null)
+                    return mapper.readValue(response.getEntity().getContent(), type);
+                return null;
+            } else if (statusCode == 400) {
+                Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                        new TypeReference<Map<String, Object>>() {
+                        });
+                throw new RuntimeException("Invalid request error 400: " + payload);
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new RuntimeException("Authentication or Authorization to Primera [" + url + "] with user [" + username
+                        + "] failed, unable to retrieve session token");
+            } else {
+                Map<String, Object> payload = mapper.readValue(response.getEntity().getContent(),
+                        new TypeReference<Map<String, Object>>() {});
+                throw new RuntimeException("Invalid request error from Primera on PUT [" + url + path + "]" + statusCode + ": "
+                        + response.getStatusLine().getReasonPhrase() + " - " + payload);
+            }
+        } catch (UnsupportedEncodingException | JsonProcessingException e) {
+            throw new RuntimeException(
+                    "Error processing request payload to [" + url + "] for path [" + path + "]", e);
+        } catch (UnsupportedOperationException e) {
+            throw new RuntimeException("Error processing bad request response from Primera [" + url + "]",
+                    e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error sending request to Primera [" + url + "]", e);
+
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to Primera API", e);
+                }
+            }
+        }
+    }
+
+    private <T> T GET(String path, final TypeReference<T> type) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpGet request = new HttpGet(url + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-HP3PAR-WSAPI-SessionKey", getSessionKey());
+
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200) {
+                try {
+                    return mapper.readValue(response.getEntity().getContent(), type);
+                } catch (UnsupportedOperationException | IOException e) {
+                    throw new RuntimeException("Error processing response from Primera [" + url + "]", e);
+                }
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new RuntimeException("Authentication or Authorization to Primera [" + url + "] with user [" + username
+                        + "] failed, unable to retrieve session token");
+            } else if (statusCode == 404) {
+                return null;
+            } else {
+                throw new RuntimeException("Unexpected HTTP response code from Primera on GET [" + url + path + "] - ["
+                        + statusCode + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Error sending request to Primera [" + url + "]", e);
+        } catch (UnsupportedOperationException e) {
+            throw new RuntimeException("Error processing response from Primera [" + url + "]", e);
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to Primera API", e);
+                }
+            }
+        }
+    }
+
+    private void DELETE(String path) {
+        CloseableHttpResponse response = null;
+        try {
+            this.refreshSession(false);
+            HttpDelete request = new HttpDelete(url + path);
+            request.addHeader("Content-Type", "application/json");
+            request.addHeader("Accept", "application/json");
+            request.addHeader("X-HP3PAR-WSAPI-SessionKey", getSessionKey());
+
+            CloseableHttpClient client = getClient();
+            response = (CloseableHttpResponse) client.execute(request);
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == 200 || statusCode == 404 || statusCode == 400) {
+                // this means the volume was deleted successfully, or doesn't exist (effective delete), or
+                // the volume name is malformed or too long - meaning it never got created to begin with (effective delete)
+                return;
+            } else if (statusCode == 401 || statusCode == 403) {
+                throw new RuntimeException("Authentication or Authorization to Primera [" + url + "] with user [" + username
+                        + "] failed, unable to retrieve session token");
+            } else if (statusCode == 409) {
+                throw new RuntimeException("The volume cannot be deleted at this time due to existing dependencies.  Validate that all snapshots associated with this volume have been deleted and try again." );
+            } else {
+                throw new RuntimeException("Unexpected HTTP response code from Primera on DELETE [" + url + path + "] - ["
+                        + statusCode + "] - " + response.getStatusLine().getReasonPhrase());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Error sending request to Primera [" + url + "]", e);
+        } finally {
+            if (response != null) {
+                try {
+                    response.close();
+                } catch (IOException e) {
+                    logger.debug("Unexpected failure closing response to Primera API", e);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapterFactory.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapterFactory.java
new file mode 100644
index 0000000..81ae442
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraAdapterFactory.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.datastore.adapter.primera;
+
+import java.util.Map;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
+import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapterFactory;
+
+public class PrimeraAdapterFactory implements ProviderAdapterFactory {
+
+    @Override
+    public String getProviderName() {
+        return "Primera";
+    }
+
+    @Override
+    public ProviderAdapter create(String url, Map<String, String> details) {
+        return new PrimeraAdapter(url, details);
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpg.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpg.java
new file mode 100644
index 0000000..6ac9697
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpg.java
@@ -0,0 +1,203 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.primera;
+
+import java.util.ArrayList;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpg {
+    private long ddsRsvdMiB;
+    private String tdvvVersion;
+    private PrimeraCpgSAGrowth sAGrowth;
+    private PrimeraCpgSAUsage sAUsage;
+    private PrimeraCpgSDGrowth sDGrowth;
+    private PrimeraCpgSDUsage sDUsage;
+    private PrimeraCpgUsrUsage usrUsage;
+    private ArrayList<Object> additionalStates;
+    private boolean dedupCapable;
+    private ArrayList<Object> degradedStates;
+    private ArrayList<Object> failedStates;
+    private int freeSpaceMiB;
+    private String name;
+    private int numFPVVs;
+    private int numTDVVs;
+    private int numTPVVs;
+    private PrimeraCpgPrivateSpaceMiB privateSpaceMiB;
+    private int rawFreeSpaceMiB;
+    private int rawSharedSpaceMiB;
+    private int rawTotalSpaceMiB;
+    private int sharedSpaceMiB;
+    private int state;
+    private int totalSpaceMiB;
+    private String uuid;
+    private int id;
+    public long getDdsRsvdMiB() {
+        return ddsRsvdMiB;
+    }
+    public void setDdsRsvdMiB(long ddsRsvdMiB) {
+        this.ddsRsvdMiB = ddsRsvdMiB;
+    }
+    public String getTdvvVersion() {
+        return tdvvVersion;
+    }
+    public void setTdvvVersion(String tdvvVersion) {
+        this.tdvvVersion = tdvvVersion;
+    }
+    public PrimeraCpgSAGrowth getsAGrowth() {
+        return sAGrowth;
+    }
+    public void setsAGrowth(PrimeraCpgSAGrowth sAGrowth) {
+        this.sAGrowth = sAGrowth;
+    }
+    public PrimeraCpgSAUsage getsAUsage() {
+        return sAUsage;
+    }
+    public void setsAUsage(PrimeraCpgSAUsage sAUsage) {
+        this.sAUsage = sAUsage;
+    }
+    public PrimeraCpgSDGrowth getsDGrowth() {
+        return sDGrowth;
+    }
+    public void setsDGrowth(PrimeraCpgSDGrowth sDGrowth) {
+        this.sDGrowth = sDGrowth;
+    }
+    public PrimeraCpgSDUsage getsDUsage() {
+        return sDUsage;
+    }
+    public void setsDUsage(PrimeraCpgSDUsage sDUsage) {
+        this.sDUsage = sDUsage;
+    }
+    public PrimeraCpgUsrUsage getUsrUsage() {
+        return usrUsage;
+    }
+    public void setUsrUsage(PrimeraCpgUsrUsage usrUsage) {
+        this.usrUsage = usrUsage;
+    }
+    public ArrayList<Object> getAdditionalStates() {
+        return additionalStates;
+    }
+    public void setAdditionalStates(ArrayList<Object> additionalStates) {
+        this.additionalStates = additionalStates;
+    }
+    public boolean isDedupCapable() {
+        return dedupCapable;
+    }
+    public void setDedupCapable(boolean dedupCapable) {
+        this.dedupCapable = dedupCapable;
+    }
+    public ArrayList<Object> getDegradedStates() {
+        return degradedStates;
+    }
+    public void setDegradedStates(ArrayList<Object> degradedStates) {
+        this.degradedStates = degradedStates;
+    }
+    public ArrayList<Object> getFailedStates() {
+        return failedStates;
+    }
+    public void setFailedStates(ArrayList<Object> failedStates) {
+        this.failedStates = failedStates;
+    }
+    public int getFreeSpaceMiB() {
+        return freeSpaceMiB;
+    }
+    public void setFreeSpaceMiB(int freeSpaceMiB) {
+        this.freeSpaceMiB = freeSpaceMiB;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public int getNumFPVVs() {
+        return numFPVVs;
+    }
+    public void setNumFPVVs(int numFPVVs) {
+        this.numFPVVs = numFPVVs;
+    }
+    public int getNumTDVVs() {
+        return numTDVVs;
+    }
+    public void setNumTDVVs(int numTDVVs) {
+        this.numTDVVs = numTDVVs;
+    }
+    public int getNumTPVVs() {
+        return numTPVVs;
+    }
+    public void setNumTPVVs(int numTPVVs) {
+        this.numTPVVs = numTPVVs;
+    }
+    public PrimeraCpgPrivateSpaceMiB getPrivateSpaceMiB() {
+        return privateSpaceMiB;
+    }
+    public void setPrivateSpaceMiB(PrimeraCpgPrivateSpaceMiB privateSpaceMiB) {
+        this.privateSpaceMiB = privateSpaceMiB;
+    }
+    public int getRawFreeSpaceMiB() {
+        return rawFreeSpaceMiB;
+    }
+    public void setRawFreeSpaceMiB(int rawFreeSpaceMiB) {
+        this.rawFreeSpaceMiB = rawFreeSpaceMiB;
+    }
+    public int getRawSharedSpaceMiB() {
+        return rawSharedSpaceMiB;
+    }
+    public void setRawSharedSpaceMiB(int rawSharedSpaceMiB) {
+        this.rawSharedSpaceMiB = rawSharedSpaceMiB;
+    }
+    public int getRawTotalSpaceMiB() {
+        return rawTotalSpaceMiB;
+    }
+    public void setRawTotalSpaceMiB(int rawTotalSpaceMiB) {
+        this.rawTotalSpaceMiB = rawTotalSpaceMiB;
+    }
+    public int getSharedSpaceMiB() {
+        return sharedSpaceMiB;
+    }
+    public void setSharedSpaceMiB(int sharedSpaceMiB) {
+        this.sharedSpaceMiB = sharedSpaceMiB;
+    }
+    public int getState() {
+        return state;
+    }
+    public void setState(int state) {
+        this.state = state;
+    }
+    public int getTotalSpaceMiB() {
+        return totalSpaceMiB;
+    }
+    public void setTotalSpaceMiB(int totalSpaceMiB) {
+        this.totalSpaceMiB = totalSpaceMiB;
+    }
+    public String getUuid() {
+        return uuid;
+    }
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+    public int getId() {
+        return id;
+    }
+    public void setId(int id) {
+        this.id = id;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgDiskPattern.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgDiskPattern.java
new file mode 100644
index 0000000..3bb8d4c
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgDiskPattern.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgDiskPattern {
+    private int diskType;
+
+    public int getDiskType() {
+        return diskType;
+    }
+
+    public void setDiskType(int diskType) {
+        this.diskType = diskType;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgLDLayout.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgLDLayout.java
new file mode 100644
index 0000000..770480f
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgLDLayout.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.storage.datastore.adapter.primera;
+
+import java.util.ArrayList;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgLDLayout {
+    private int rAIDType;
+    private ArrayList<PrimeraCpgDiskPattern> diskPatterns;
+    private int hA;
+    public int getrAIDType() {
+        return rAIDType;
+    }
+    public void setrAIDType(int rAIDType) {
+        this.rAIDType = rAIDType;
+    }
+    public ArrayList<PrimeraCpgDiskPattern> getDiskPatterns() {
+        return diskPatterns;
+    }
+    public void setDiskPatterns(ArrayList<PrimeraCpgDiskPattern> diskPatterns) {
+        this.diskPatterns = diskPatterns;
+    }
+    public int gethA() {
+        return hA;
+    }
+    public void sethA(int hA) {
+        this.hA = hA;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgPrivateSpaceMiB.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgPrivateSpaceMiB.java
new file mode 100644
index 0000000..b38aa10
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgPrivateSpaceMiB.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgPrivateSpaceMiB {
+    private int base;
+    private int rawBase;
+    private int rawSnapshot;
+    private int snapshot;
+    public int getBase() {
+        return base;
+    }
+    public void setBase(int base) {
+        this.base = base;
+    }
+    public int getRawBase() {
+        return rawBase;
+    }
+    public void setRawBase(int rawBase) {
+        this.rawBase = rawBase;
+    }
+    public int getRawSnapshot() {
+        return rawSnapshot;
+    }
+    public void setRawSnapshot(int rawSnapshot) {
+        this.rawSnapshot = rawSnapshot;
+    }
+    public int getSnapshot() {
+        return snapshot;
+    }
+    public void setSnapshot(int snapshot) {
+        this.snapshot = snapshot;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSAGrowth.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSAGrowth.java
new file mode 100644
index 0000000..83f67f9
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSAGrowth.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgSAGrowth {
+    private PrimeraCpgLDLayout lDLayout;
+    private int incrementMiB;
+    public PrimeraCpgLDLayout getlDLayout() {
+        return lDLayout;
+    }
+    public void setlDLayout(PrimeraCpgLDLayout lDLayout) {
+        this.lDLayout = lDLayout;
+    }
+    public int getIncrementMiB() {
+        return incrementMiB;
+    }
+    public void setIncrementMiB(int incrementMiB) {
+        this.incrementMiB = incrementMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSAUsage.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSAUsage.java
new file mode 100644
index 0000000..11b1df6
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSAUsage.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgSAUsage {
+    private int rawTotalMiB;
+    private int rawUsedMiB;
+    private int totalMiB;
+    private int usedMiB;
+    public int getRawTotalMiB() {
+        return rawTotalMiB;
+    }
+    public void setRawTotalMiB(int rawTotalMiB) {
+        this.rawTotalMiB = rawTotalMiB;
+    }
+    public int getRawUsedMiB() {
+        return rawUsedMiB;
+    }
+    public void setRawUsedMiB(int rawUsedMiB) {
+        this.rawUsedMiB = rawUsedMiB;
+    }
+    public int getTotalMiB() {
+        return totalMiB;
+    }
+    public void setTotalMiB(int totalMiB) {
+        this.totalMiB = totalMiB;
+    }
+    public int getUsedMiB() {
+        return usedMiB;
+    }
+    public void setUsedMiB(int usedMiB) {
+        this.usedMiB = usedMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSDGrowth.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSDGrowth.java
new file mode 100644
index 0000000..fc54e63
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSDGrowth.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgSDGrowth {
+    private PrimeraCpgLDLayout lDLayout;
+    private int incrementMiB;
+    private int limitMiB;
+    private int warningMiB;
+    public PrimeraCpgLDLayout getlDLayout() {
+        return lDLayout;
+    }
+    public void setlDLayout(PrimeraCpgLDLayout lDLayout) {
+        this.lDLayout = lDLayout;
+    }
+    public int getIncrementMiB() {
+        return incrementMiB;
+    }
+    public void setIncrementMiB(int incrementMiB) {
+        this.incrementMiB = incrementMiB;
+    }
+    public int getLimitMiB() {
+        return limitMiB;
+    }
+    public void setLimitMiB(int limitMiB) {
+        this.limitMiB = limitMiB;
+    }
+    public int getWarningMiB() {
+        return warningMiB;
+    }
+    public void setWarningMiB(int warningMiB) {
+        this.warningMiB = warningMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSDUsage.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSDUsage.java
new file mode 100644
index 0000000..5de74fe
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgSDUsage.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgSDUsage {
+    private int rawTotalMiB;
+    private int rawUsedMiB;
+    private int totalMiB;
+    private int usedMiB;
+    public int getRawTotalMiB() {
+        return rawTotalMiB;
+    }
+    public void setRawTotalMiB(int rawTotalMiB) {
+        this.rawTotalMiB = rawTotalMiB;
+    }
+    public int getRawUsedMiB() {
+        return rawUsedMiB;
+    }
+    public void setRawUsedMiB(int rawUsedMiB) {
+        this.rawUsedMiB = rawUsedMiB;
+    }
+    public int getTotalMiB() {
+        return totalMiB;
+    }
+    public void setTotalMiB(int totalMiB) {
+        this.totalMiB = totalMiB;
+    }
+    public int getUsedMiB() {
+        return usedMiB;
+    }
+    public void setUsedMiB(int usedMiB) {
+        this.usedMiB = usedMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgUsrUsage.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgUsrUsage.java
new file mode 100644
index 0000000..2cce6c9
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraCpgUsrUsage.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraCpgUsrUsage {
+    private int rawTotalMiB;
+    private int rawUsedMiB;
+    private int totalMiB;
+    private int usedMiB;
+    public int getRawTotalMiB() {
+        return rawTotalMiB;
+    }
+    public void setRawTotalMiB(int rawTotalMiB) {
+        this.rawTotalMiB = rawTotalMiB;
+    }
+    public int getRawUsedMiB() {
+        return rawUsedMiB;
+    }
+    public void setRawUsedMiB(int rawUsedMiB) {
+        this.rawUsedMiB = rawUsedMiB;
+    }
+    public int getTotalMiB() {
+        return totalMiB;
+    }
+    public void setTotalMiB(int totalMiB) {
+        this.totalMiB = totalMiB;
+    }
+    public int getUsedMiB() {
+        return usedMiB;
+    }
+    public void setUsedMiB(int usedMiB) {
+        this.usedMiB = usedMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraHostset.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraHostset.java
new file mode 100644
index 0000000..e062f07
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraHostset.java
@@ -0,0 +1,141 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.primera;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraHostset {
+
+    private String comment;
+    private Integer id;
+    private String name;
+    private List<String> setmembers = new ArrayList<String>();
+    private String uuid;
+    private Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<String> getSetmembers() {
+        return setmembers;
+    }
+
+    public void setSetmembers(List<String> setmembers) {
+        this.setmembers = setmembers;
+    }
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public Map<String, Object> getAdditionalProperties() {
+        return additionalProperties;
+    }
+
+    public void setAdditionalProperties(Map<String, Object> additionalProperties) {
+        this.additionalProperties = additionalProperties;
+    }
+
+    // adds members to a hostset
+    public static class PrimeraHostsetVLUNRequest {
+        private String volumeName;
+        private Boolean autoLun = true;
+        private Integer lun = 0;
+        private Integer maxAutoLun = 0;
+        /**
+         * This can be a single hostname OR the set of hosts in the format
+         * "set:<hostset>".
+         * For the purposes of this driver, its expected that the predominate usecase is
+         * to use
+         * a hostset that is aligned with a CloudStack Cluster.
+         */
+        private String hostname;
+
+        public String getVolumeName() {
+            return volumeName;
+        }
+
+        public void setVolumeName(String volumeName) {
+            this.volumeName = volumeName;
+        }
+
+        public Boolean getAutoLun() {
+            return autoLun;
+        }
+
+        public void setAutoLun(Boolean autoLun) {
+            this.autoLun = autoLun;
+        }
+
+        public Integer getLun() {
+            return lun;
+        }
+
+        public void setLun(Integer lun) {
+            this.lun = lun;
+        }
+
+        public Integer getMaxAutoLun() {
+            return maxAutoLun;
+        }
+
+        public void setMaxAutoLun(Integer maxAutoLun) {
+            this.maxAutoLun = maxAutoLun;
+        }
+
+        public String getHostname() {
+            return hostname;
+        }
+
+        public void setHostname(String hostname) {
+            this.hostname = hostname;
+        }
+
+    }
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraKey.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraKey.java
new file mode 100644
index 0000000..0fc050e
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraKey.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraKey {
+    private String key;
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraTaskReference.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraTaskReference.java
new file mode 100644
index 0000000..0a31203
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraTaskReference.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 org.apache.cloudstack.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraTaskReference {
+    private String taskid;
+    /**
+     * not really returned, but if there is a Location header in a
+     * response we'll add it automatically if this is the type
+     **/
+    private String location;
+    public String getTaskid() {
+        return taskid;
+    }
+    public void setTaskid(String taskid) {
+        this.taskid = taskid;
+    }
+    public String getLocation() {
+        return location;
+    }
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraTaskStatus.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraTaskStatus.java
new file mode 100644
index 0000000..293efb1
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraTaskStatus.java
@@ -0,0 +1,174 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraTaskStatus {
+    private Integer id;
+    private Integer type;
+    private String name;
+    private Integer status;
+    private Integer completedPhases;
+    private Integer totalPhases;
+    private Integer completedSteps;
+    private Integer totalSteps;
+    private String startTime;
+    private String finishTime;
+    private Integer priority;
+    private String user;
+    private String detailedStatus;
+    public static final Integer STATUS_DONE = 1;
+    public static final Integer STATUS_ACTIVE = 2;
+    public static final Integer STATUS_CANCELLED = 3;
+    public static final Integer STATUS_FAILED = 4;
+
+    public boolean isFinished() {
+        if (status != STATUS_ACTIVE) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isSuccess() {
+        if (status == STATUS_DONE) {
+            return true;
+        }
+        return false;
+    }
+
+    public String getStatusName() {
+        if (status == PrimeraTaskStatus.STATUS_DONE) {
+            return "DONE";
+        } else if (status == PrimeraTaskStatus.STATUS_ACTIVE) {
+            return "ACTIVE";
+        } else if (status == PrimeraTaskStatus.STATUS_CANCELLED) {
+            return "CANCELLED";
+        } else if (status == PrimeraTaskStatus.STATUS_FAILED) {
+            return "FAILED";
+        } else {
+            return "UNKNOWN";
+        }
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getCompletedPhases() {
+        return completedPhases;
+    }
+
+    public void setCompletedPhases(Integer completedPhases) {
+        this.completedPhases = completedPhases;
+    }
+
+    public Integer getTotalPhases() {
+        return totalPhases;
+    }
+
+    public void setTotalPhases(Integer totalPhases) {
+        this.totalPhases = totalPhases;
+    }
+
+    public Integer getCompletedSteps() {
+        return completedSteps;
+    }
+
+    public void setCompletedSteps(Integer completedSteps) {
+        this.completedSteps = completedSteps;
+    }
+
+    public Integer getTotalSteps() {
+        return totalSteps;
+    }
+
+    public void setTotalSteps(Integer totalSteps) {
+        this.totalSteps = totalSteps;
+    }
+
+    public String getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    public String getFinishTime() {
+        return finishTime;
+    }
+
+    public void setFinishTime(String finishTime) {
+        this.finishTime = finishTime;
+    }
+
+    public Integer getPriority() {
+        return priority;
+    }
+
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getDetailedStatus() {
+        return detailedStatus;
+    }
+
+    public void setDetailedStatus(String detailedStatus) {
+        this.detailedStatus = detailedStatus;
+    }
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVlun.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVlun.java
new file mode 100644
index 0000000..d35b16c
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVlun.java
@@ -0,0 +1,180 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVlun {
+    private int lun;
+    private String volumeName;
+    private String hostname;
+    private String remoteName;
+    private int type;
+    private String serial;
+    private PrimeraPortPosition portPos;
+    private String volumeWWN;
+    private int multipathing;
+    private int failedPathPol;
+    private int failedPathInterval;
+    private String hostDeviceName;
+    @JsonProperty("Subsystem_NQN")
+    private String subsystemNQN;
+    private boolean active;
+
+    public static class PrimeraPortPosition {
+        private int node;
+        private int slot;
+        private int cardPort;
+        public int getNode() {
+            return node;
+        }
+        public void setNode(int node) {
+            this.node = node;
+        }
+        public int getSlot() {
+            return slot;
+        }
+        public void setSlot(int slot) {
+            this.slot = slot;
+        }
+        public int getCardPort() {
+            return cardPort;
+        }
+        public void setCardPort(int cardPort) {
+            this.cardPort = cardPort;
+        }
+
+    }
+
+    public int getLun() {
+        return lun;
+    }
+
+    public void setLun(int lun) {
+        this.lun = lun;
+    }
+
+    public String getVolumeName() {
+        return volumeName;
+    }
+
+    public void setVolumeName(String volumeName) {
+        this.volumeName = volumeName;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public String getRemoteName() {
+        return remoteName;
+    }
+
+    public void setRemoteName(String remoteName) {
+        this.remoteName = remoteName;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public String getSerial() {
+        return serial;
+    }
+
+    public void setSerial(String serial) {
+        this.serial = serial;
+    }
+
+    public PrimeraPortPosition getPortPos() {
+        return portPos;
+    }
+
+    public void setPortPos(PrimeraPortPosition portPos) {
+        this.portPos = portPos;
+    }
+
+    public String getVolumeWWN() {
+        return volumeWWN;
+    }
+
+    public void setVolumeWWN(String volumeWWN) {
+        this.volumeWWN = volumeWWN;
+    }
+
+    public int getMultipathing() {
+        return multipathing;
+    }
+
+    public void setMultipathing(int multipathing) {
+        this.multipathing = multipathing;
+    }
+
+    public int getFailedPathPol() {
+        return failedPathPol;
+    }
+
+    public void setFailedPathPol(int failedPathPol) {
+        this.failedPathPol = failedPathPol;
+    }
+
+    public int getFailedPathInterval() {
+        return failedPathInterval;
+    }
+
+    public void setFailedPathInterval(int failedPathInterval) {
+        this.failedPathInterval = failedPathInterval;
+    }
+
+    public String getHostDeviceName() {
+        return hostDeviceName;
+    }
+
+    public void setHostDeviceName(String hostDeviceName) {
+        this.hostDeviceName = hostDeviceName;
+    }
+
+    public String getSubsystemNQN() {
+        return subsystemNQN;
+    }
+
+    public void setSubsystemNQN(String subsystemNQN) {
+        this.subsystemNQN = subsystemNQN;
+    }
+
+    public boolean isActive() {
+        return active;
+    }
+
+    public void setActive(boolean active) {
+        this.active = active;
+    }
+
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVlunList.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVlunList.java
new file mode 100644
index 0000000..d50fdfe
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVlunList.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.storage.datastore.adapter.primera;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVlunList {
+    private int total;
+    private String serial;
+    private List<PrimeraVlun> members;
+    public int getTotal() {
+        return total;
+    }
+    public void setTotal(int total) {
+        this.total = total;
+    }
+    public String getSerial() {
+        return serial;
+    }
+    public void setSerial(String serial) {
+        this.serial = serial;
+    }
+    public List<PrimeraVlun> getMembers() {
+        return members;
+    }
+    public void setMembers(List<PrimeraVlun> members) {
+        this.members = members;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolume.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolume.java
new file mode 100644
index 0000000..9ae58e4
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolume.java
@@ -0,0 +1,420 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.datastore.adapter.primera;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import org.apache.cloudstack.storage.datastore.adapter.ProviderSnapshot;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolume implements ProviderSnapshot {
+    @JsonIgnore
+    private AddressType addressType = AddressType.FIBERWWN;
+    @JsonIgnore
+    private String connectionId;
+    @JsonIgnore
+    private Integer priority = 0;
+
+    private String physParentId = null;
+    private Integer parentId = null;
+    private String copyOf = null;
+    private Integer roChildId = null;
+    private Integer rwChildId = null;
+    private String snapCPG = null;
+    private Long total = null;
+    /**
+     * Actions are enumerated and listed at
+     * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=v25706371.html
+     */
+    private Integer action = null;
+    private String comment = null;
+    private Integer id = null;
+    private String name = null;
+    private Integer deduplicationState = null;
+    private Integer compressionState = null;
+    private Integer provisioningType = null;
+    private Integer copyType = null;
+    private Integer baseId = null;
+    private Boolean readOnly = null;
+    private Integer state = null;
+    private ArrayList<Object> failedStates = null;
+    private ArrayList<Object> degradedStates = null;
+    private ArrayList<Object> additionalStates = null;
+    private PrimeraVolumeAdminSpace adminSpace = null;
+    private PrimeraVolumeSnapshotSpace snapshotSpace = null;
+    private PrimeraVolumeUserSpace userSpace = null;
+    private Integer totalReservedMiB = null;
+    private Integer totalUsedMiB = null;
+    private Integer sizeMiB = null;
+    private Integer hostWriteMiB = null;
+    private String wwn = null;
+    private Integer creationTimeSec = null;
+    private Date creationTime8601 = null;
+    private Integer ssSpcAllocWarningPct;
+    private Integer ssSpcAllocLimitPct = null;
+    private Integer usrSpcAllocWarningPct = null;
+    private Integer usrSpcAllocLimitPct = null;
+    private PrimeraVolumePolicies policies = null;
+    private String userCPG = null;
+    private String uuid = null;
+    private Integer sharedParentId = null;
+    private Integer udid = null;
+    private PrimeraVolumeCapacityEfficiency capacityEfficiency = null;
+    private Integer rcopyStatus = null;
+    private ArrayList<PrimeraVolumeLink> links = null;
+    public String getPhysParentId() {
+        return physParentId;
+    }
+    public void setPhysParentId(String physParentId) {
+        this.physParentId = physParentId;
+    }
+    public Integer getParentId() {
+        return parentId;
+    }
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
+    public String getCopyOf() {
+        return copyOf;
+    }
+    public void setCopyOf(String copyOf) {
+        this.copyOf = copyOf;
+    }
+    public Integer getRoChildId() {
+        return roChildId;
+    }
+    public void setRoChildId(Integer roChildId) {
+        this.roChildId = roChildId;
+    }
+    public Integer getRwChildId() {
+        return rwChildId;
+    }
+    public void setRwChildId(Integer rwChildId) {
+        this.rwChildId = rwChildId;
+    }
+    public String getSnapCPG() {
+        return snapCPG;
+    }
+    public void setSnapCPG(String snapCPG) {
+        this.snapCPG = snapCPG;
+    }
+    public Long getTotal() {
+        return total;
+    }
+    public void setTotal(Long total) {
+        this.total = total;
+    }
+    public Integer getAction() {
+        return action;
+    }
+    public void setAction(Integer action) {
+        this.action = action;
+    }
+    public String getComment() {
+        return comment;
+    }
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public Integer getDeduplicationState() {
+        return deduplicationState;
+    }
+    public void setDeduplicationState(Integer deduplicationState) {
+        this.deduplicationState = deduplicationState;
+    }
+    public Integer getCompressionState() {
+        return compressionState;
+    }
+    public void setCompressionState(Integer compressionState) {
+        this.compressionState = compressionState;
+    }
+    public Integer getProvisioningType() {
+        return provisioningType;
+    }
+    public void setProvisioningType(Integer provisioningType) {
+        this.provisioningType = provisioningType;
+    }
+    public Integer getCopyType() {
+        return copyType;
+    }
+    public void setCopyType(Integer copyType) {
+        this.copyType = copyType;
+    }
+    public Integer getBaseId() {
+        return baseId;
+    }
+    public void setBaseId(Integer baseId) {
+        this.baseId = baseId;
+    }
+    public Boolean getReadOnly() {
+        return readOnly;
+    }
+    public void setReadOnly(Boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+    public String getState() {
+        if (state != null) {
+            return state.toString();
+        }
+        return null;
+    }
+    public void setState(Integer state) {
+        this.state = state;
+    }
+    public ArrayList<Object> getFailedStates() {
+        return failedStates;
+    }
+    public void setFailedStates(ArrayList<Object> failedStates) {
+        this.failedStates = failedStates;
+    }
+    public ArrayList<Object> getDegradedStates() {
+        return degradedStates;
+    }
+    public void setDegradedStates(ArrayList<Object> degradedStates) {
+        this.degradedStates = degradedStates;
+    }
+    public ArrayList<Object> getAdditionalStates() {
+        return additionalStates;
+    }
+    public void setAdditionalStates(ArrayList<Object> additionalStates) {
+        this.additionalStates = additionalStates;
+    }
+    public PrimeraVolumeAdminSpace getAdminSpace() {
+        return adminSpace;
+    }
+    public void setAdminSpace(PrimeraVolumeAdminSpace adminSpace) {
+        this.adminSpace = adminSpace;
+    }
+    public PrimeraVolumeSnapshotSpace getSnapshotSpace() {
+        return snapshotSpace;
+    }
+    public void setSnapshotSpace(PrimeraVolumeSnapshotSpace snapshotSpace) {
+        this.snapshotSpace = snapshotSpace;
+    }
+    public PrimeraVolumeUserSpace getUserSpace() {
+        return userSpace;
+    }
+    public void setUserSpace(PrimeraVolumeUserSpace userSpace) {
+        this.userSpace = userSpace;
+    }
+    public Integer getTotalReservedMiB() {
+        return totalReservedMiB;
+    }
+    public void setTotalReservedMiB(Integer totalReservedMiB) {
+        this.totalReservedMiB = totalReservedMiB;
+    }
+    public Integer getTotalUsedMiB() {
+        return totalUsedMiB;
+    }
+    public void setTotalUsedMiB(Integer totalUsedMiB) {
+        this.totalUsedMiB = totalUsedMiB;
+    }
+    public Integer getSizeMiB() {
+        return sizeMiB;
+    }
+    public void setSizeMiB(Integer sizeMiB) {
+        this.sizeMiB = sizeMiB;
+    }
+    public Integer getHostWriteMiB() {
+        return hostWriteMiB;
+    }
+    public void setHostWriteMiB(Integer hostWriteMiB) {
+        this.hostWriteMiB = hostWriteMiB;
+    }
+    public String getWwn() {
+        return wwn;
+    }
+    public void setWwn(String wwn) {
+        this.wwn = wwn;
+    }
+    public Integer getCreationTimeSec() {
+        return creationTimeSec;
+    }
+    public void setCreationTimeSec(Integer creationTimeSec) {
+        this.creationTimeSec = creationTimeSec;
+    }
+    public Date getCreationTime8601() {
+        return creationTime8601;
+    }
+    public void setCreationTime8601(Date creationTime8601) {
+        this.creationTime8601 = creationTime8601;
+    }
+    public Integer getSsSpcAllocWarningPct() {
+        return ssSpcAllocWarningPct;
+    }
+    public void setSsSpcAllocWarningPct(Integer ssSpcAllocWarningPct) {
+        this.ssSpcAllocWarningPct = ssSpcAllocWarningPct;
+    }
+    public Integer getSsSpcAllocLimitPct() {
+        return ssSpcAllocLimitPct;
+    }
+    public void setSsSpcAllocLimitPct(Integer ssSpcAllocLimitPct) {
+        this.ssSpcAllocLimitPct = ssSpcAllocLimitPct;
+    }
+    public Integer getUsrSpcAllocWarningPct() {
+        return usrSpcAllocWarningPct;
+    }
+    public void setUsrSpcAllocWarningPct(Integer usrSpcAllocWarningPct) {
+        this.usrSpcAllocWarningPct = usrSpcAllocWarningPct;
+    }
+    public Integer getUsrSpcAllocLimitPct() {
+        return usrSpcAllocLimitPct;
+    }
+    public void setUsrSpcAllocLimitPct(Integer usrSpcAllocLimitPct) {
+        this.usrSpcAllocLimitPct = usrSpcAllocLimitPct;
+    }
+    public PrimeraVolumePolicies getPolicies() {
+        return policies;
+    }
+    public void setPolicies(PrimeraVolumePolicies policies) {
+        this.policies = policies;
+    }
+    public String getUserCPG() {
+        return userCPG;
+    }
+    public void setUserCPG(String userCPG) {
+        this.userCPG = userCPG;
+    }
+    public String getUuid() {
+        return uuid;
+    }
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+    public Integer getSharedParentId() {
+        return sharedParentId;
+    }
+    public void setSharedParentId(Integer sharedParentId) {
+        this.sharedParentId = sharedParentId;
+    }
+    public Integer getUdid() {
+        return udid;
+    }
+    public void setUdid(Integer udid) {
+        this.udid = udid;
+    }
+    public PrimeraVolumeCapacityEfficiency getCapacityEfficiency() {
+        return capacityEfficiency;
+    }
+    public void setCapacityEfficiency(PrimeraVolumeCapacityEfficiency capacityEfficiency) {
+        this.capacityEfficiency = capacityEfficiency;
+    }
+    public Integer getRcopyStatus() {
+        return rcopyStatus;
+    }
+    public void setRcopyStatus(Integer rcopyStatus) {
+        this.rcopyStatus = rcopyStatus;
+    }
+    public ArrayList<PrimeraVolumeLink> getLinks() {
+        return links;
+    }
+    public void setLinks(ArrayList<PrimeraVolumeLink> links) {
+        this.links = links;
+    }
+    @Override
+    @JsonIgnore
+    public Boolean isDestroyed() {
+        return false;
+    }
+    @Override
+    public void setId(String id) {
+        this.id = Integer.parseInt(id);
+    }
+    public String getId() {
+        if (id != null) {
+            return Integer.toString(id);
+        }
+        return null;
+    }
+    @Override
+    public Integer getPriority() {
+        return priority;
+    }
+    @Override
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+    @Override
+    public AddressType getAddressType() {
+        return addressType;
+    }
+    @Override
+    public void setAddressType(AddressType addressType) {
+        this.addressType = addressType;
+    }
+    @Override
+    public String getAddress() {
+        return this.wwn;
+    }
+    @Override
+    @JsonIgnore
+    public Long getAllocatedSizeInBytes() {
+        if (this.getSizeMiB() != null) {
+            return this.getSizeMiB() * PrimeraAdapter.BYTES_IN_MiB;
+        }
+        return 0L;
+    }
+    @Override
+    @JsonIgnore
+    public Long getUsedBytes() {
+        if (this.getTotalReservedMiB() != null) {
+            return this.getTotalReservedMiB() * PrimeraAdapter.BYTES_IN_MiB;
+        }
+        return 0L;
+    }
+    @Override
+    @JsonIgnore
+    public String getExternalUuid() {
+        return uuid;
+    }
+    public void setExternalUuid(String uuid) {
+        this.uuid = uuid;
+    }
+    @Override
+    @JsonIgnore
+    public String getExternalName() {
+        return name;
+    }
+    public void setExternalName(String name) {
+        this.name = name;
+    }
+    @Override
+    @JsonIgnore
+    public String getExternalConnectionId() {
+        return connectionId;
+    }
+    public void setExternalConnection(String connectionId) {
+        this.connectionId = connectionId;
+    }
+    @Override
+    @JsonIgnore
+    public Boolean canAttachDirectly() {
+        return true;
+    }
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeAdminSpace.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeAdminSpace.java
new file mode 100644
index 0000000..63ddf09
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeAdminSpace.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeAdminSpace {
+    private int reservedMiB;
+    private int rawReservedMiB;
+    private int usedMiB;
+    private int freeMiB;
+    public int getReservedMiB() {
+        return reservedMiB;
+    }
+    public void setReservedMiB(int reservedMiB) {
+        this.reservedMiB = reservedMiB;
+    }
+    public int getRawReservedMiB() {
+        return rawReservedMiB;
+    }
+    public void setRawReservedMiB(int rawReservedMiB) {
+        this.rawReservedMiB = rawReservedMiB;
+    }
+    public int getUsedMiB() {
+        return usedMiB;
+    }
+    public void setUsedMiB(int usedMiB) {
+        this.usedMiB = usedMiB;
+    }
+    public int getFreeMiB() {
+        return freeMiB;
+    }
+    public void setFreeMiB(int freeMiB) {
+        this.freeMiB = freeMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCapacityEfficiency.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCapacityEfficiency.java
new file mode 100644
index 0000000..b058902
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCapacityEfficiency.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeCapacityEfficiency {
+    private double compaction;
+    private double deduplication;
+    public double getCompaction() {
+        return compaction;
+    }
+    public void setCompaction(double compaction) {
+        this.compaction = compaction;
+    }
+    public double getDeduplication() {
+        return deduplication;
+    }
+    public void setDeduplication(double deduplication) {
+        this.deduplication = deduplication;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCopyRequest.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCopyRequest.java
new file mode 100644
index 0000000..779064f
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCopyRequest.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.storage.datastore.adapter.primera;
+
+/**
+ * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=v24885490.html
+ */
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeCopyRequest {
+    private String action = "createPhysicalCopy";
+    private PrimeraVolumeCopyRequestParameters parameters;
+    public String getAction() {
+        return action;
+    }
+    public void setAction(String action) {
+        this.action = action;
+    }
+    public PrimeraVolumeCopyRequestParameters getParameters() {
+        return parameters;
+    }
+    public void setParameters(PrimeraVolumeCopyRequestParameters parameters) {
+        this.parameters = parameters;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCopyRequestParameters.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCopyRequestParameters.java
new file mode 100644
index 0000000..33ad0d4
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeCopyRequestParameters.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.storage.datastore.adapter.primera;
+
+/**
+ * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=v24885490.html
+ */
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeCopyRequestParameters {
+    private String destVolume = null;
+    private String destCPG = null;
+    private Boolean online = false;
+    private String wwn = null;
+    private Boolean tpvv = null;
+    private Boolean reduce = null;
+    private String snapCPG = null;
+    private Boolean skipZero = null;
+    private Boolean saveSnapshot = null;
+    /** 1=HIGH, 2=MED, 3=LOW */
+    private Integer priority = null;
+    public String getDestVolume() {
+        return destVolume;
+    }
+    public void setDestVolume(String destVolume) {
+        this.destVolume = destVolume;
+    }
+    public String getDestCPG() {
+        return destCPG;
+    }
+    public void setDestCPG(String destCPG) {
+        this.destCPG = destCPG;
+    }
+    public Boolean getOnline() {
+        return online;
+    }
+    public void setOnline(Boolean online) {
+        this.online = online;
+    }
+    public String getWwn() {
+        return wwn;
+    }
+    public void setWwn(String wwn) {
+        this.wwn = wwn;
+    }
+    public Boolean getTpvv() {
+        return tpvv;
+    }
+    public void setTpvv(Boolean tpvv) {
+        this.tpvv = tpvv;
+    }
+    public Boolean getReduce() {
+        return reduce;
+    }
+    public void setReduce(Boolean reduce) {
+        this.reduce = reduce;
+    }
+    public String getSnapCPG() {
+        return snapCPG;
+    }
+    public void setSnapCPG(String snapCPG) {
+        this.snapCPG = snapCPG;
+    }
+    public Boolean getSkipZero() {
+        return skipZero;
+    }
+    public void setSkipZero(Boolean skipZero) {
+        this.skipZero = skipZero;
+    }
+    public Boolean getSaveSnapshot() {
+        return saveSnapshot;
+    }
+    public void setSaveSnapshot(Boolean saveSnapshot) {
+        this.saveSnapshot = saveSnapshot;
+    }
+    public Integer getPriority() {
+        return priority;
+    }
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeLink.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeLink.java
new file mode 100644
index 0000000..27b19bc
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeLink.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeLink {
+    private String href;
+    private String rel;
+    public String getHref() {
+        return href;
+    }
+    public void setHref(String href) {
+        this.href = href;
+    }
+    public String getRel() {
+        return rel;
+    }
+    public void setRel(String rel) {
+        this.rel = rel;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeLinkList.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeLinkList.java
new file mode 100644
index 0000000..01bec70
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeLinkList.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.storage.datastore.adapter.primera;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeLinkList {
+    private List<PrimeraVolumeLink> list;
+
+    public List<PrimeraVolumeLink> getList() {
+        return list;
+    }
+
+    public void setList(List<PrimeraVolumeLink> list) {
+        this.list = list;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumePolicies.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumePolicies.java
new file mode 100644
index 0000000..2baa9a2
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumePolicies.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumePolicies {
+    private Boolean tpZeroFill;
+    private Boolean staleSS;
+    private Boolean oneHost;
+    private Boolean zeroDetect;
+    private Boolean system;
+    private Boolean caching;
+    private Boolean fsvc;
+    private Integer hostDIF;
+    public Boolean getTpZeroFill() {
+        return tpZeroFill;
+    }
+    public void setTpZeroFill(Boolean tpZeroFill) {
+        this.tpZeroFill = tpZeroFill;
+    }
+    public Boolean getStaleSS() {
+        return staleSS;
+    }
+    public void setStaleSS(Boolean staleSS) {
+        this.staleSS = staleSS;
+    }
+    public Boolean getOneHost() {
+        return oneHost;
+    }
+    public void setOneHost(Boolean oneHost) {
+        this.oneHost = oneHost;
+    }
+    public Boolean getZeroDetect() {
+        return zeroDetect;
+    }
+    public void setZeroDetect(Boolean zeroDetect) {
+        this.zeroDetect = zeroDetect;
+    }
+    public Boolean getSystem() {
+        return system;
+    }
+    public void setSystem(Boolean system) {
+        this.system = system;
+    }
+    public Boolean getCaching() {
+        return caching;
+    }
+    public void setCaching(Boolean caching) {
+        this.caching = caching;
+    }
+    public Boolean getFsvc() {
+        return fsvc;
+    }
+    public void setFsvc(Boolean fsvc) {
+        this.fsvc = fsvc;
+    }
+    public Integer getHostDIF() {
+        return hostDIF;
+    }
+    public void setHostDIF(Integer hostDIF) {
+        this.hostDIF = hostDIF;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumePromoteRequest.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumePromoteRequest.java
new file mode 100644
index 0000000..48898c2
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumePromoteRequest.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumePromoteRequest {
+    /**
+     * Defines action for the request as described at https://support.hpe.com/hpesc/public/docDisplay?docId=a00114827en_us&page=v25706371.html
+     */
+    private Integer action = 4;
+    private Boolean online = true;
+    private Integer priority = 2; // MEDIUM
+    private Boolean allowRemoteCopyParent = true;
+    public Integer getAction() {
+        return action;
+    }
+    public void setAction(Integer action) {
+        this.action = action;
+    }
+    public Boolean getOnline() {
+        return online;
+    }
+    public void setOnline(Boolean online) {
+        this.online = online;
+    }
+    public Integer getPriority() {
+        return priority;
+    }
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+    public Boolean getAllowRemoteCopyParent() {
+        return allowRemoteCopyParent;
+    }
+    public void setAllowRemoteCopyParent(Boolean allowRemoteCopyParent) {
+        this.allowRemoteCopyParent = allowRemoteCopyParent;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeRequest.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeRequest.java
new file mode 100644
index 0000000..b8f67fb
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeRequest.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeRequest {
+    private String name;
+    private String cpg;
+    private long sizeMiB;
+    private String comment;
+    private String snapCPG = null;
+    private Boolean reduce;
+    private Boolean tpvv;
+    private Integer ssSpcAllocLimitPct;
+    private Integer ssSpcAllocWarningPct;
+    private Integer usrSpcAllocWarningPct;
+    private Integer usrSpcAllocLimitPct;
+    private PrimeraVolumePolicies policies;
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public String getCpg() {
+        return cpg;
+    }
+    public void setCpg(String cpg) {
+        this.cpg = cpg;
+    }
+    public long getSizeMiB() {
+        return sizeMiB;
+    }
+    public void setSizeMiB(long sizeMiB) {
+        this.sizeMiB = sizeMiB;
+    }
+    public String getComment() {
+        return comment;
+    }
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+    public String getSnapCPG() {
+        return snapCPG;
+    }
+    public void setSnapCPG(String snapCPG) {
+        this.snapCPG = snapCPG;
+    }
+    public Boolean getReduce() {
+        return reduce;
+    }
+    public void setReduce(Boolean reduce) {
+        this.reduce = reduce;
+    }
+    public Boolean getTpvv() {
+        return tpvv;
+    }
+    public void setTpvv(Boolean tpvv) {
+        this.tpvv = tpvv;
+    }
+    public Integer getSsSpcAllocLimitPct() {
+        return ssSpcAllocLimitPct;
+    }
+    public void setSsSpcAllocLimitPct(Integer ssSpcAllocLimitPct) {
+        this.ssSpcAllocLimitPct = ssSpcAllocLimitPct;
+    }
+    public Integer getSsSpcAllocWarningPct() {
+        return ssSpcAllocWarningPct;
+    }
+    public void setSsSpcAllocWarningPct(Integer ssSpcAllocWarningPct) {
+        this.ssSpcAllocWarningPct = ssSpcAllocWarningPct;
+    }
+    public Integer getUsrSpcAllocWarningPct() {
+        return usrSpcAllocWarningPct;
+    }
+    public void setUsrSpcAllocWarningPct(Integer usrSpcAllocWarningPct) {
+        this.usrSpcAllocWarningPct = usrSpcAllocWarningPct;
+    }
+    public Integer getUsrSpcAllocLimitPct() {
+        return usrSpcAllocLimitPct;
+    }
+    public void setUsrSpcAllocLimitPct(Integer usrSpcAllocLimitPct) {
+        this.usrSpcAllocLimitPct = usrSpcAllocLimitPct;
+    }
+    public PrimeraVolumePolicies getPolicies() {
+        return policies;
+    }
+    public void setPolicies(PrimeraVolumePolicies policies) {
+        this.policies = policies;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeRevertSnapshotRequest.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeRevertSnapshotRequest.java
new file mode 100644
index 0000000..fcdd7a8
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeRevertSnapshotRequest.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.storage.datastore.adapter.primera;
+
+/**
+ * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=s_creating_snapshot_volumes.html
+ */
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeRevertSnapshotRequest {
+    private int action = 4; //PROMOTE_VIRTUAL_COPY
+    private Boolean online = true;
+    private Integer priority = 2;
+    public int getAction() {
+        return action;
+    }
+    public void setAction(int action) {
+        this.action = action;
+    }
+    public Boolean getOnline() {
+        return online;
+    }
+    public void setOnline(Boolean online) {
+        this.online = online;
+    }
+    public Integer getPriority() {
+        return priority;
+    }
+    public void setPriority(Integer priority) {
+        this.priority = priority;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotRequest.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotRequest.java
new file mode 100644
index 0000000..6fb0f19
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotRequest.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.storage.datastore.adapter.primera;
+
+/**
+ * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=s_creating_snapshot_volumes.html
+ */
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeSnapshotRequest {
+    private String action = "createSnapshot";
+    private PrimeraVolumeSnapshotRequestParameters parameters;
+    public String getAction() {
+        return action;
+    }
+    public void setAction(String action) {
+        this.action = action;
+    }
+    public PrimeraVolumeSnapshotRequestParameters getParameters() {
+        return parameters;
+    }
+    public void setParameters(PrimeraVolumeSnapshotRequestParameters parameters) {
+        this.parameters = parameters;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotRequestParameters.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotRequestParameters.java
new file mode 100644
index 0000000..de2fe24
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotRequestParameters.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.storage.datastore.adapter.primera;
+
+/**
+ * https://support.hpe.com/hpesc/public/docDisplay?docId=a00118636en_us&page=s_creating_snapshot_volumes.html
+ */
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeSnapshotRequestParameters {
+    private String name = null;
+    private String id = null;
+    private String comment = null;
+    private Boolean readOnly = false;
+    private Integer expirationHours = null;
+    private Integer retentionHours = null;
+    private String addToSet = null;
+    private Boolean syncSnapRcopy = false;
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public String getId() {
+        return id;
+    }
+    public void setId(String id) {
+        this.id = id;
+    }
+    public String getComment() {
+        return comment;
+    }
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+    public Boolean getReadOnly() {
+        return readOnly;
+    }
+    public void setReadOnly(Boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+    public Integer getExpirationHours() {
+        return expirationHours;
+    }
+    public void setExpirationHours(Integer expirationHours) {
+        this.expirationHours = expirationHours;
+    }
+    public Integer getRetentionHours() {
+        return retentionHours;
+    }
+    public void setRetentionHours(Integer retentionHours) {
+        this.retentionHours = retentionHours;
+    }
+    public String getAddToSet() {
+        return addToSet;
+    }
+    public void setAddToSet(String addToSet) {
+        this.addToSet = addToSet;
+    }
+    public Boolean getSyncSnapRcopy() {
+        return syncSnapRcopy;
+    }
+    public void setSyncSnapRcopy(Boolean syncSnapRcopy) {
+        this.syncSnapRcopy = syncSnapRcopy;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotSpace.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotSpace.java
new file mode 100644
index 0000000..2cb0d53
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeSnapshotSpace.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeSnapshotSpace {
+    private int reservedMiB;
+    private int rawReservedMiB;
+    private int usedMiB;
+    private int freeMiB;
+    public int getReservedMiB() {
+        return reservedMiB;
+    }
+    public void setReservedMiB(int reservedMiB) {
+        this.reservedMiB = reservedMiB;
+    }
+    public int getRawReservedMiB() {
+        return rawReservedMiB;
+    }
+    public void setRawReservedMiB(int rawReservedMiB) {
+        this.rawReservedMiB = rawReservedMiB;
+    }
+    public int getUsedMiB() {
+        return usedMiB;
+    }
+    public void setUsedMiB(int usedMiB) {
+        this.usedMiB = usedMiB;
+    }
+    public int getFreeMiB() {
+        return freeMiB;
+    }
+    public void setFreeMiB(int freeMiB) {
+        this.freeMiB = freeMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeUpdateRequest.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeUpdateRequest.java
new file mode 100644
index 0000000..07b3425
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeUpdateRequest.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeUpdateRequest {
+    private String comment;
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeUserSpace.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeUserSpace.java
new file mode 100644
index 0000000..e4cea17
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/adapter/primera/PrimeraVolumeUserSpace.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.storage.datastore.adapter.primera;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrimeraVolumeUserSpace {
+    private int reservedMiB;
+    private int rawReservedMiB;
+    private int usedMiB;
+    private int freeMiB;
+    public int getReservedMiB() {
+        return reservedMiB;
+    }
+    public void setReservedMiB(int reservedMiB) {
+        this.reservedMiB = reservedMiB;
+    }
+    public int getRawReservedMiB() {
+        return rawReservedMiB;
+    }
+    public void setRawReservedMiB(int rawReservedMiB) {
+        this.rawReservedMiB = rawReservedMiB;
+    }
+    public int getUsedMiB() {
+        return usedMiB;
+    }
+    public void setUsedMiB(int usedMiB) {
+        this.usedMiB = usedMiB;
+    }
+    public int getFreeMiB() {
+        return freeMiB;
+    }
+    public void setFreeMiB(int freeMiB) {
+        this.freeMiB = freeMiB;
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/provider/PrimeraPrimaryDatastoreProviderImpl.java b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/provider/PrimeraPrimaryDatastoreProviderImpl.java
new file mode 100644
index 0000000..e5ce327
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/java/org/apache/cloudstack/storage/datastore/provider/PrimeraPrimaryDatastoreProviderImpl.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.storage.datastore.provider;
+
+import org.apache.cloudstack.storage.datastore.adapter.primera.PrimeraAdapterFactory;
+
+public class PrimeraPrimaryDatastoreProviderImpl extends AdaptivePrimaryDatastoreProviderImpl {
+
+    public PrimeraPrimaryDatastoreProviderImpl() {
+        super(new PrimeraAdapterFactory());
+    }
+
+    @Override
+    public String getName() {
+        return "Primera";
+    }
+
+}
diff --git a/plugins/storage/volume/primera/src/main/resources/META-INF/cloudstack/storage-volume-primera/module.properties b/plugins/storage/volume/primera/src/main/resources/META-INF/cloudstack/storage-volume-primera/module.properties
new file mode 100644
index 0000000..8b4bb66
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/resources/META-INF/cloudstack/storage-volume-primera/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=storage-volume-primera
+parent=storage
diff --git a/plugins/storage/volume/primera/src/main/resources/META-INF/cloudstack/storage-volume-primera/spring-storage-volume-primera-context.xml b/plugins/storage/volume/primera/src/main/resources/META-INF/cloudstack/storage-volume-primera/spring-storage-volume-primera-context.xml
new file mode 100644
index 0000000..92f0bf2
--- /dev/null
+++ b/plugins/storage/volume/primera/src/main/resources/META-INF/cloudstack/storage-volume-primera/spring-storage-volume-primera-context.xml
@@ -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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd"
+                      >
+
+    <bean id="primeraDataStoreProvider"
+        class="org.apache.cloudstack.storage.datastore.provider.PrimeraPrimaryDatastoreProviderImpl">
+	  </bean>
+</beans>
diff --git a/plugins/storage/volume/sample/pom.xml b/plugins/storage/volume/sample/pom.xml
index 41d3559..e60d5b7 100644
--- a/plugins/storage/volume/sample/pom.xml
+++ b/plugins/storage/volume/sample/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -38,9 +38,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java b/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java
index 7327860..0b26ce0 100644
--- a/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java
+++ b/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java
@@ -44,7 +44,9 @@
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.host.Host;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StoragePool;
+import com.cloud.storage.Volume;
 import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
@@ -283,4 +285,13 @@
     @Override
     public void provideVmTags(long vmId, long volumeId, String tagValue) {
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return false;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
 }
diff --git a/plugins/storage/volume/scaleio/pom.xml b/plugins/storage/volume/scaleio/pom.xml
index 6602684..d5129cc 100644
--- a/plugins/storage/volume/scaleio/pom.xml
+++ b/plugins/storage/volume/scaleio/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -44,9 +44,6 @@
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
index 431fddb..1d2cace 100644
--- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
+++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java
@@ -22,13 +22,7 @@
 
 import javax.inject.Inject;
 
-import com.cloud.agent.api.storage.MigrateVolumeCommand;
-import com.cloud.agent.api.storage.ResizeVolumeCommand;
-import com.cloud.agent.api.to.StorageFilerTO;
-import com.cloud.host.HostVO;
-import com.cloud.vm.VMInstanceVO;
-import com.cloud.vm.VirtualMachine;
-import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
@@ -45,6 +39,8 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
+import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
 import org.apache.cloudstack.storage.RemoteHostEndPoint;
 import org.apache.cloudstack.storage.command.CommandResult;
 import org.apache.cloudstack.storage.command.CopyCommand;
@@ -69,19 +65,24 @@
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.storage.MigrateVolumeCommand;
+import com.cloud.agent.api.storage.ResizeVolumeCommand;
 import com.cloud.agent.api.to.DataObjectType;
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.DiskTO;
+import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.alert.AlertManager;
 import com.cloud.configuration.Config;
 import com.cloud.host.Host;
+import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
 import com.cloud.server.ManagementServerImpl;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.ResizeVolumePayload;
 import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.Storage;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StoragePoolHostVO;
@@ -97,7 +98,10 @@
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.dao.VMInstanceDao;
 import com.google.common.base.Preconditions;
 
 public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@@ -126,11 +130,15 @@
     @Inject
     private ConfigurationDao configDao;
     @Inject
+    private DiskOfferingDetailsDao diskOfferingDetailsDao;
+    @Inject
     private HostDao hostDao;
     @Inject
     private VMInstanceDao vmInstanceDao;
     @Inject
     private VolumeService volumeService;
+    @Inject
+    private VolumeOrchestrationService volumeMgr;
 
     public ScaleIOPrimaryDataStoreDriver() {
 
@@ -140,40 +148,47 @@
         return ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePoolId, storagePoolDetailsDao);
     }
 
+    private boolean setVolumeLimitsOnSDC(VolumeVO volume, Host host, DataStore dataStore, Long iopsLimit, Long bandwidthLimitInKbps) throws Exception {
+        final String sdcId = getConnectedSdc(dataStore.getId(), host.getId());
+        if (StringUtils.isBlank(sdcId)) {
+            alertHostSdcDisconnection(host);
+            throw new CloudRuntimeException("Unable to grant access to volume: " + volume.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
+        }
+
+        final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
+        return client.mapVolumeToSdcWithLimits(ScaleIOUtil.getVolumePath(volume.getPath()), sdcId, iopsLimit, bandwidthLimitInKbps);
+    }
+
+    private boolean setVolumeLimitsFromDetails(VolumeVO volume, Host host, DataStore dataStore) throws Exception {
+        Long bandwidthLimitInKbps = 0L; // Unlimited
+        // Check Bandwidth Limit parameter in volume details
+        final VolumeDetailVO bandwidthVolumeDetail = volumeDetailsDao.findDetail(volume.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS);
+        if (bandwidthVolumeDetail != null && bandwidthVolumeDetail.getValue() != null) {
+            bandwidthLimitInKbps = Long.parseLong(bandwidthVolumeDetail.getValue()) * 1024;
+        }
+
+        Long iopsLimit = 0L; // Unlimited
+        // Check IOPS Limit parameter in volume details, else try MaxIOPS
+        final VolumeDetailVO iopsVolumeDetail = volumeDetailsDao.findDetail(volume.getId(), Volume.IOPS_LIMIT);
+        if (iopsVolumeDetail != null && iopsVolumeDetail.getValue() != null) {
+            iopsLimit = Long.parseLong(iopsVolumeDetail.getValue());
+        } else if (volume.getMaxIops() != null) {
+            iopsLimit = volume.getMaxIops();
+        }
+        if (iopsLimit > 0 && iopsLimit < ScaleIOUtil.MINIMUM_ALLOWED_IOPS_LIMIT) {
+            iopsLimit = ScaleIOUtil.MINIMUM_ALLOWED_IOPS_LIMIT;
+        }
+
+        return setVolumeLimitsOnSDC(volume, host, dataStore, iopsLimit, bandwidthLimitInKbps);
+    }
+
     @Override
     public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
         try {
             if (DataObjectType.VOLUME.equals(dataObject.getType())) {
                 final VolumeVO volume = volumeDao.findById(dataObject.getId());
                 LOGGER.debug("Granting access for PowerFlex volume: " + volume.getPath());
-
-                Long bandwidthLimitInKbps = Long.valueOf(0); // Unlimited
-                // Check Bandwidht Limit parameter in volume details
-                final VolumeDetailVO bandwidthVolumeDetail = volumeDetailsDao.findDetail(volume.getId(), Volume.BANDWIDTH_LIMIT_IN_MBPS);
-                if (bandwidthVolumeDetail != null && bandwidthVolumeDetail.getValue() != null) {
-                    bandwidthLimitInKbps = Long.parseLong(bandwidthVolumeDetail.getValue()) * 1024;
-                }
-
-                Long iopsLimit = Long.valueOf(0); // Unlimited
-                // Check IOPS Limit parameter in volume details, else try MaxIOPS
-                final VolumeDetailVO iopsVolumeDetail = volumeDetailsDao.findDetail(volume.getId(), Volume.IOPS_LIMIT);
-                if (iopsVolumeDetail != null && iopsVolumeDetail.getValue() != null) {
-                    iopsLimit = Long.parseLong(iopsVolumeDetail.getValue());
-                } else if (volume.getMaxIops() != null) {
-                    iopsLimit = volume.getMaxIops();
-                }
-                if (iopsLimit > 0 && iopsLimit < ScaleIOUtil.MINIMUM_ALLOWED_IOPS_LIMIT) {
-                    iopsLimit = ScaleIOUtil.MINIMUM_ALLOWED_IOPS_LIMIT;
-                }
-
-                final String sdcId = getConnectedSdc(dataStore.getId(), host.getId());
-                if (StringUtils.isBlank(sdcId)) {
-                    alertHostSdcDisconnection(host);
-                    throw new CloudRuntimeException("Unable to grant access to volume: " + dataObject.getId() + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
-                }
-
-                final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
-                return client.mapVolumeToSdcWithLimits(ScaleIOUtil.getVolumePath(volume.getPath()), sdcId, iopsLimit, bandwidthLimitInKbps);
+                return setVolumeLimitsFromDetails(volume, host, dataStore);
             } else if (DataObjectType.TEMPLATE.equals(dataObject.getType())) {
                 final VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(dataStore.getId(), dataObject.getId(), null);
                 LOGGER.debug("Granting access for PowerFlex template volume: " + templatePoolRef.getInstallPath());
@@ -790,7 +805,15 @@
             LOGGER.error(errorMsg);
             answer = new Answer(cmd, false, errorMsg);
         } else {
-            answer = ep.sendMessage(cmd);
+            VolumeVO volume = volumeDao.findById(destData.getId());
+            Host host = destHost != null ? destHost : hostDao.findById(ep.getId());
+            try {
+                setVolumeLimitsOnSDC(volume, host, destData.getDataStore(), 0L, 0L);
+                answer = ep.sendMessage(cmd);
+            } catch (Exception e) {
+                LOGGER.error("Failed to copy template to volume due to: " + e.getMessage(), e);
+                answer = new Answer(cmd, false, e.getMessage());
+            }
         }
 
         return answer;
@@ -920,7 +943,7 @@
         List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcVolumeId);
         if (CollectionUtils.isNotEmpty(snapshots)) {
             for (SnapshotVO snapshot : snapshots) {
-                SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+                SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Primary, srcPoolId, snapshot.getId());
                 if (snapshotStore == null) {
                     continue;
                 }
@@ -1091,7 +1114,7 @@
                 List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcData.getId());
                 if (CollectionUtils.isNotEmpty(snapshots)) {
                     for (SnapshotVO snapshot : snapshots) {
-                        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+                        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Primary, srcPoolId, snapshot.getId());
                         if (snapshotStore == null) {
                             continue;
                         }
@@ -1180,7 +1203,7 @@
             ResizeVolumePayload payload = (ResizeVolumePayload)volumeInfo.getpayload();
             long newSizeInBytes = payload.newSize != null ? payload.newSize : volumeInfo.getSize();
             // Only increase size is allowed and size should be specified in granularity of 8 GB
-            if (newSizeInBytes <= volumeInfo.getSize()) {
+            if (newSizeInBytes < volumeInfo.getSize()) {
                 throw new CloudRuntimeException("Only increase size is allowed for volume: " + volumeInfo.getName());
             }
 
@@ -1209,6 +1232,20 @@
                 }
             }
 
+            Long newMaxIops = payload.newMaxIops != null ? payload.newMaxIops : volumeInfo.getMaxIops();
+            long newBandwidthLimit = 0L;
+            Long newDiskOfferingId = payload.newDiskOfferingId != null ? payload.newDiskOfferingId : volumeInfo.getDiskOfferingId();
+            if (newDiskOfferingId != null) {
+                DiskOfferingDetailVO bandwidthLimitDetail = diskOfferingDetailsDao.findDetail(newDiskOfferingId, Volume.BANDWIDTH_LIMIT_IN_MBPS);
+                if (bandwidthLimitDetail != null) {
+                    newBandwidthLimit = Long.parseLong(bandwidthLimitDetail.getValue()) * 1024;
+                }
+                DiskOfferingDetailVO iopsLimitDetail = diskOfferingDetailsDao.findDetail(newDiskOfferingId, Volume.IOPS_LIMIT);
+                if (iopsLimitDetail != null) {
+                    newMaxIops = Long.parseLong(iopsLimitDetail.getValue());
+                }
+            }
+
             if (volumeInfo.getFormat().equals(Storage.ImageFormat.QCOW2) || attachedRunning) {
                 LOGGER.debug("Volume needs to be resized at the hypervisor host");
 
@@ -1228,9 +1265,8 @@
                         volumeInfo.getPassphrase(), volumeInfo.getEncryptFormat());
 
                 try {
-                    if (!attachedRunning) {
-                        grantAccess(volumeInfo, ep, volumeInfo.getDataStore());
-                    }
+                    VolumeVO volume = volumeDao.findById(volumeInfo.getId());
+                    setVolumeLimitsOnSDC(volume, host, volumeInfo.getDataStore(), newMaxIops != null ? newMaxIops : 0L, newBandwidthLimit);
                     Answer answer = ep.sendMessage(resizeVolumeCommand);
 
                     if (!answer.getResult() && volumeInfo.getFormat().equals(Storage.ImageFormat.QCOW2)) {
@@ -1252,14 +1288,23 @@
             VolumeVO volume = volumeDao.findById(volumeInfo.getId());
             long oldVolumeSize = volume.getSize();
             volume.setSize(scaleIOVolume.getSizeInKb() * 1024);
+            if (payload.newMinIops != null) {
+                volume.setMinIops(payload.newMinIops);
+            }
+            if (payload.newMaxIops != null) {
+                volume.setMaxIops(payload.newMaxIops);
+            }
             volumeDao.update(volume.getId(), volume);
+            if (payload.newDiskOfferingId != null) {
+                volumeMgr.saveVolumeDetails(payload.newDiskOfferingId, volume.getId());
+            }
 
             long capacityBytes = storagePool.getCapacityBytes();
             long usedBytes = storagePool.getUsedBytes();
 
             long newVolumeSize = volume.getSize();
             usedBytes += newVolumeSize - oldVolumeSize;
-            storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
+            storagePool.setUsedBytes(Math.min(usedBytes, capacityBytes));
             storagePoolDao.update(storagePoolId, storagePool);
         } catch (Exception e) {
             String errMsg = "Unable to resize PowerFlex volume: " + volumeInfo.getId() + " due to " + e.getMessage();
@@ -1420,4 +1465,23 @@
         }
         return false;
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return false;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
+
+    @Override
+    public boolean volumesRequireGrantAccessWhenUsed() {
+        return true;
+    }
+
+    @Override
+    public boolean zoneWideVolumesAvailableWithoutClusterMotion() {
+        return true;
+    }
 }
diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java
index 65831e4..1715069 100644
--- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java
+++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java
@@ -139,6 +139,7 @@
         Long capacityBytes = (Long)dsInfos.get("capacityBytes");
         Long capacityIops = (Long)dsInfos.get("capacityIops");
         String tags = (String)dsInfos.get("tags");
+        Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
         Map<String, String> details = (Map<String, String>) dsInfos.get("details");
 
         if (zoneId == null) {
@@ -224,6 +225,7 @@
         parameters.setHypervisorType(Hypervisor.HypervisorType.KVM);
         parameters.setUuid(UUID.randomUUID().toString());
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
 
         StoragePoolStatistics poolStatistics = scaleIOPool.getStatistics();
         if (poolStatistics != null) {
diff --git a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java
index 577b918..817a67f 100644
--- a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java
+++ b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java
@@ -48,7 +48,7 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class ScaleIOGatewayClientImplTest {
-    private final int port = 443;
+    private final int port = 8443;
     private final int timeout = 30;
     private final int maxConnections = 50;
     private final String username = "admin";
@@ -58,7 +58,8 @@
 
     @Rule
     public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
-            .httpsPort(port)
+            .dynamicHttpsPort()
+            .dynamicPort()
             .needClientAuth(false)
             .basicAdminAuthenticator(username, password)
             .bindAddress("localhost"));
@@ -70,7 +71,7 @@
                         .withHeader("content-type", "application/json;charset=UTF-8")
                         .withBody(sessionKey)));
 
-        client = new ScaleIOGatewayClientImpl("https://localhost/api", username, password, false, timeout, maxConnections);
+        client = new ScaleIOGatewayClientImpl(String.format("https://localhost:%d/api", wireMockRule.httpsPort()), username, password, false, timeout, maxConnections);
 
         wireMockRule.stubFor(post("/api/types/Volume/instances")
                 .willReturn(aResponse()
diff --git a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java
index d147582..1852ec1 100644
--- a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java
+++ b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriverTest.java
@@ -49,28 +49,29 @@
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.BeforeClass;
 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.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Optional;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(RemoteHostEndPoint.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ScaleIOPrimaryDataStoreDriverTest {
 
     @Spy
@@ -93,6 +94,18 @@
     @Mock
     ConfigurationDao configDao;
 
+    static MockedStatic<RemoteHostEndPoint> remoteHostEndPointMock;
+
+    @BeforeClass
+    public static void init() {
+        remoteHostEndPointMock = mockStatic(RemoteHostEndPoint.class);
+    }
+
+    @AfterClass
+    public static void close() {
+        remoteHostEndPointMock.close();
+    }
+
     @Before
     public void initMocks() {
         MockitoAnnotations.initMocks(this);
@@ -180,9 +193,8 @@
         VolumeObjectTO destVolTO = Mockito.mock(VolumeObjectTO.class);
         when(destData.getTO()).thenReturn(destVolTO);
         Host host = prepareEndpointForVolumeOperation(srcData);
-        PowerMockito.mockStatic(RemoteHostEndPoint.class);
         RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
-        when(RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
+        remoteHostEndPointMock.when(() -> RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
 
         DataTO dataTO = Mockito.mock(DataTO.class);
         CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
@@ -225,9 +237,8 @@
         VolumeObjectTO destVolTO = Mockito.mock(VolumeObjectTO.class);
         when(destData.getTO()).thenReturn(destVolTO);
         Host host = prepareEndpointForVolumeOperation(srcData);
-        PowerMockito.mockStatic(RemoteHostEndPoint.class);
         RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
-        when(RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
+        remoteHostEndPointMock.when(() -> RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
 
         DataTO dataTO = Mockito.mock(DataTO.class);
         CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
@@ -493,9 +504,8 @@
         Host destHost = Mockito.mock(Host.class);
 
         doReturn(false).when(scaleIOPrimaryDataStoreDriver).anyVolumeRequiresEncryption(srcData, destData);
-        PowerMockito.mockStatic(RemoteHostEndPoint.class);
         RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
-        when(RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(ep);
+        remoteHostEndPointMock.when(() -> RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(ep);
         Answer answer = Mockito.mock(Answer.class);
         when(ep.sendMessage(any())).thenReturn(answer);
 
@@ -517,8 +527,7 @@
         Host destHost = Mockito.mock(Host.class);
 
         doReturn(false).when(scaleIOPrimaryDataStoreDriver).anyVolumeRequiresEncryption(srcData, destData);
-        PowerMockito.mockStatic(RemoteHostEndPoint.class);
-        when(RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(null);
+        remoteHostEndPointMock.when(() -> RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(null);
 
         Answer answer = scaleIOPrimaryDataStoreDriver.copyOfflineVolume(srcData, destData, destHost);
 
diff --git a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java
index 6cc7b87..4a6e73a 100644
--- a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java
+++ b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java
@@ -24,8 +24,7 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
@@ -40,13 +39,11 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
-import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
 import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
 import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientImpl;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.apache.cloudstack.storage.datastore.provider.ScaleIOHostListener;
 import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
 import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
 import org.junit.Before;
@@ -54,19 +51,13 @@
 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.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.agent.AgentManager;
-import com.cloud.agent.api.ModifyStoragePoolAnswer;
-import com.cloud.agent.api.ModifyStoragePoolCommand;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
-import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.resource.ResourceManager;
 import com.cloud.resource.ResourceState;
@@ -80,9 +71,9 @@
 import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.template.TemplateManager;
 import com.cloud.utils.exception.CloudRuntimeException;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@PrepareForTest(ScaleIOGatewayClient.class)
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ScaleIOPrimaryDataStoreLifeCycleTest {
 
     @Mock
@@ -96,8 +87,6 @@
     @Mock
     private StoragePoolAutomation storagePoolAutomation;
     @Mock
-    private HostDao hostDao;
-    @Mock
     private StoragePoolHostDao storagePoolHostDao;
     @Mock
     private DataStoreProviderManager dataStoreProviderMgr;
@@ -109,18 +98,12 @@
     private PrimaryDataStore store;
     @Mock
     private TemplateManager templateMgr;
-    @Mock
-    private AgentManager agentMgr;
-    @Mock
-    ModifyStoragePoolAnswer answer;
 
-    @Spy
     @InjectMocks
     private StorageManager storageMgr = new StorageManagerImpl();
 
-    @Spy
-    @InjectMocks
-    private HypervisorHostListener hostListener = new ScaleIOHostListener();
+    @Mock
+    private HypervisorHostListener hostListener;
 
     @InjectMocks
     private ScaleIOPrimaryDataStoreLifeCycle scaleIOPrimaryDataStoreLifeCycleTest;
@@ -128,6 +111,7 @@
     @Before
     public void setUp() {
         initMocks(this);
+        ReflectionTestUtils.setField(scaleIOPrimaryDataStoreLifeCycleTest, "storageMgr", storageMgr);
     }
 
     @Test
@@ -135,9 +119,11 @@
         final DataStore dataStore = mock(DataStore.class);
         when(dataStore.getId()).thenReturn(1L);
 
-        PowerMockito.mockStatic(ScaleIOGatewayClient.class);
+        MockedStatic<ScaleIOGatewayClientConnectionPool> scaleIOGatewayClientConnectionPoolMocked = mockStatic(ScaleIOGatewayClientConnectionPool.class);
         ScaleIOGatewayClientImpl client = mock(ScaleIOGatewayClientImpl.class);
-        when(ScaleIOGatewayClientConnectionPool.getInstance().getClient(1L, storagePoolDetailsDao)).thenReturn(client);
+        ScaleIOGatewayClientConnectionPool pool = mock(ScaleIOGatewayClientConnectionPool.class);
+        scaleIOGatewayClientConnectionPoolMocked.when(() -> ScaleIOGatewayClientConnectionPool.getInstance()).thenReturn(pool);
+        when(pool.getClient(1L, storagePoolDetailsDao)).thenReturn(client);
 
         when(client.haveConnectedSdcs()).thenReturn(true);
 
@@ -157,28 +143,19 @@
 
         when(dataStoreMgr.getDataStore(anyLong(), eq(DataStoreRole.Primary))).thenReturn(store);
         when(store.getId()).thenReturn(1L);
-        when(store.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
         when(store.isShared()).thenReturn(true);
         when(store.getName()).thenReturn("ScaleIOPool");
         when(store.getStorageProviderName()).thenReturn(ScaleIOUtil.PROVIDER_NAME);
 
         when(dataStoreProviderMgr.getDataStoreProvider(ScaleIOUtil.PROVIDER_NAME)).thenReturn(dataStoreProvider);
         when(dataStoreProvider.getName()).thenReturn(ScaleIOUtil.PROVIDER_NAME);
+        when(hostListener.hostConnect(Mockito.anyLong(), Mockito.anyLong())).thenReturn(true);
         storageMgr.registerHostListener(ScaleIOUtil.PROVIDER_NAME, hostListener);
 
-        when(agentMgr.easySend(anyLong(), Mockito.any(ModifyStoragePoolCommand.class))).thenReturn(answer);
-        when(answer.getResult()).thenReturn(true);
-
-        when(storagePoolHostDao.findByPoolHost(anyLong(), anyLong())).thenReturn(null);
-
-        when(hostDao.findById(1L)).thenReturn(host1);
-        when(hostDao.findById(2L)).thenReturn(host2);
-
         when(dataStoreHelper.attachZone(Mockito.any(DataStore.class))).thenReturn(null);
 
-        scaleIOPrimaryDataStoreLifeCycleTest.attachZone(dataStore, scope, Hypervisor.HypervisorType.KVM);
-        verify(storageMgr,times(2)).connectHostToSharedPool(Mockito.any(Long.class), Mockito.any(Long.class));
-        verify(storagePoolHostDao,times(2)).persist(Mockito.any(StoragePoolHostVO.class));
+        boolean result = scaleIOPrimaryDataStoreLifeCycleTest.attachZone(dataStore, scope, Hypervisor.HypervisorType.KVM);
+        assertThat(result).isTrue();
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -224,7 +201,6 @@
     public void testDeleteDataStoreWithStoragePoolNull() {
         final PrimaryDataStore store = mock(PrimaryDataStore.class);
         when(primaryDataStoreDao.findById(anyLong())).thenReturn(null);
-        when(dataStoreHelper.deletePrimaryDataStore(any(DataStore.class))).thenReturn(true);
         final boolean result = scaleIOPrimaryDataStoreLifeCycleTest.deleteDataStore(store);
         assertThat(result).isFalse();
     }
@@ -233,6 +209,7 @@
     public void testDeleteDataStore() {
         final PrimaryDataStore store = mock(PrimaryDataStore.class);
         final StoragePoolVO storagePoolVO = mock(StoragePoolVO.class);
+        when(store.getId()).thenReturn(1L);
         when(primaryDataStoreDao.findById(anyLong())).thenReturn(storagePoolVO);
         List<VMTemplateStoragePoolVO> unusedTemplates = new ArrayList<>();
         when(templateMgr.getUnusedTemplatesInPool(storagePoolVO)).thenReturn(unusedTemplates);
diff --git a/plugins/storage/volume/scaleio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/storage/volume/scaleio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/storage/volume/scaleio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/storage/volume/solidfire/pom.xml b/plugins/storage/volume/solidfire/pom.xml
index 7a652c6..8868459 100644
--- a/plugins/storage/volume/solidfire/pom.xml
+++ b/plugins/storage/volume/solidfire/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
index 4478dc9..a5d1a39 100644
--- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
+++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
@@ -1663,4 +1663,18 @@
     @Override
     public void provideVmTags(long vmId, long volumeId, String tagValue) {
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return false;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+    }
+
+    @Override
+    public boolean volumesRequireGrantAccessWhenUsed() {
+        return true;
+    }
 }
diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java
index 38c8c24..7a2767c 100644
--- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java
+++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java
@@ -91,6 +91,7 @@
         Long capacityBytes = (Long)dsInfos.get("capacityBytes");
         Long capacityIops = (Long)dsInfos.get("capacityIops");
         String tags = (String)dsInfos.get("tags");
+        Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
         @SuppressWarnings("unchecked")
         Map<String, String> details = (Map<String, String>)dsInfos.get("details");
 
@@ -142,6 +143,7 @@
         }
 
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
         parameters.setDetails(details);
 
         String managementVip = SolidFireUtil.getManagementVip(url);
diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java
index 9cc746d..557cc3f 100644
--- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java
+++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java
@@ -104,6 +104,7 @@
         Long capacityBytes = (Long)dsInfos.get("capacityBytes");
         Long capacityIops = (Long)dsInfos.get(CAPACITY_IOPS);
         String tags = (String)dsInfos.get("tags");
+        Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
         @SuppressWarnings("unchecked")
         Map<String, String> details = (Map<String, String>)dsInfos.get("details");
 
@@ -152,6 +153,7 @@
         parameters.setCapacityIops(capacityIops);
         parameters.setHypervisorType(hypervisorType);
         parameters.setTags(tags);
+        parameters.setIsTagARule(isTagARule);
         parameters.setDetails(details);
 
         String managementVip = SolidFireUtil.getManagementVip(url);
diff --git a/plugins/storage/volume/solidfire/src/main/resources/META-INF/cloudstack/storage-volume-solidfire/module.properties b/plugins/storage/volume/solidfire/src/main/resources/META-INF/cloudstack/storage-volume-solidfire/module.properties
index 335a9d2..72b2e03 100644
--- a/plugins/storage/volume/solidfire/src/main/resources/META-INF/cloudstack/storage-volume-solidfire/module.properties
+++ b/plugins/storage/volume/solidfire/src/main/resources/META-INF/cloudstack/storage-volume-solidfire/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=storage-volume-solidfire
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/plugins/storage/volume/storpool/README.md b/plugins/storage/volume/storpool/README.md
index 030abd7..7752234 100644
--- a/plugins/storage/volume/storpool/README.md
+++ b/plugins/storage/volume/storpool/README.md
@@ -117,6 +117,8 @@
 SP_AUTH_TOKEN - StorPool's token
 SP_TEMPLATE - name of StorPool's template
 
+> **NOTE:** You can use the alternative format option for the URL - storpool://{SP_AUTH_TOKEN}@{SP_API_HTTP}:{SP_API_HTTP_PORT}/{SP_TEMPLATE}
+
 Storage Tags: If left blank, the StorPool storage plugin will use the pool name to create a corresponding storage tag.
 This storage tag may be used later, when defining service or disk offerings.
 
diff --git a/plugins/storage/volume/storpool/pom.xml b/plugins/storage/volume/storpool/pom.xml
index 727270a..822e726 100644
--- a/plugins/storage/volume/storpool/pom.xml
+++ b/plugins/storage/volume/storpool/pom.xml
@@ -1,4 +1,4 @@
-<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
     license agreements. See the NOTICE file distributed with this work for additional
     information regarding copyright ownership. The ASF licenses this file to
     you under the Apache License, Version 2.0 (the "License"); you may not use
@@ -17,7 +17,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -53,11 +53,6 @@
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>4.7.0</version>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
             <artifactId>mockito-inline</artifactId>
             <version>4.7.0</version>
         </dependency>
@@ -96,9 +91,6 @@
             </plugin>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <skipTests>true</skipTests>
-                </configuration>
                 <executions>
                     <execution>
                         <phase>integration-test</phase>
diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java
index c27f3f8..437e786 100644
--- a/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java
+++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java
@@ -35,13 +35,15 @@
     private String poolType;
     private List<ModifyStoragePoolAnswer> datastoreClusterChildren = new ArrayList<>();
     private String clusterId;
+    private String clientNodeId;
 
-    public StorPoolModifyStoragePoolAnswer(StorPoolModifyStoragePoolCommand cmd, long capacityBytes, long availableBytes, Map<String, TemplateProp> tInfo, String clusterId) {
+    public StorPoolModifyStoragePoolAnswer(StorPoolModifyStoragePoolCommand cmd, long capacityBytes, long availableBytes, Map<String, TemplateProp> tInfo, String clusterId, String clientNodeId) {
         super(cmd);
         result = true;
         poolInfo = new StoragePoolInfo(null, cmd.getPool().getHost(), cmd.getPool().getPath(), cmd.getLocalPath(), cmd.getPool().getType(), capacityBytes, availableBytes);
         templateInfo = tInfo;
         this.clusterId = clusterId;
+        this.clientNodeId = clientNodeId;
     }
 
     public StorPoolModifyStoragePoolAnswer(String errMsg) {
@@ -91,4 +93,12 @@
     public String getClusterId() {
         return clusterId;
     }
+
+    public String getClientNodeId() {
+        return clientNodeId;
+    }
+
+    public void setClientNodeId(String clientNodeId) {
+        this.clientNodeId = clientNodeId;
+    }
 }
diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java
index b797b3c..8bd8a52 100644
--- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java
+++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java
@@ -33,6 +33,7 @@
 import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
 import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
 import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
+import com.cloud.hypervisor.kvm.storage.StorPoolStoragePool;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.storage.template.TemplateProp;
@@ -47,7 +48,7 @@
 
     @Override
     public Answer execute(final StorPoolModifyStoragePoolCommand command, final LibvirtComputingResource libvirtComputingResource) {
-        String clusterId = getSpClusterId();
+        String clusterId = StorPoolStoragePool.getStorPoolConfigParam("SP_CLUSTER_ID");
         if (clusterId == null) {
             log.debug(String.format("Could not get StorPool cluster id for a command [%s]", command.getClass()));
             return new Answer(command, false, "spNotFound");
@@ -66,40 +67,14 @@
                 return new Answer(command, false, String.format("Failed to create storage pool [%s]", command.getPool().getId()));
             }
 
-            final Map<String, TemplateProp> tInfo = new HashMap<String, TemplateProp>();
-            final StorPoolModifyStoragePoolAnswer answer = new StorPoolModifyStoragePoolAnswer(command, storagepool.getCapacity(), storagepool.getAvailable(), tInfo, clusterId);
-
-            return answer;
+            final Map<String, TemplateProp> tInfo = new HashMap<>();
+            return new StorPoolModifyStoragePoolAnswer(command, storagepool.getCapacity(), storagepool.getAvailable(), tInfo, clusterId, storagepool.getStorageNodeId());
         } catch (Exception e) {
             log.debug(String.format("Could not modify storage due to %s", e.getMessage()));
             return new Answer(command, e);
         }
     }
 
-    private String getSpClusterId() {
-        Script sc = new Script("storpool_confget", 0, log);
-        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
-
-        String SP_CLUSTER_ID = null;
-        final String err = sc.execute(parser);
-        if (err != null) {
-            StorPoolStorageAdaptor.SP_LOG("Could not execute storpool_confget. Error: %s", err);
-            return SP_CLUSTER_ID;
-        }
-
-        for (String line: parser.getLines().split("\n")) {
-            String[] toks = line.split("=");
-            if( toks.length != 2 ) {
-                continue;
-            }
-            if (toks[0].equals("SP_CLUSTER_ID")) {
-                SP_CLUSTER_ID = toks[1];
-                return SP_CLUSTER_ID;
-            }
-        }
-        return SP_CLUSTER_ID;
-    }
-
     public String attachOrDetachVolume(String command, String type, String volumeUuid) {
         final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(volumeUuid, true);
         if (name == null) {
@@ -126,7 +101,11 @@
                     Set<Entry<String, JsonElement>> obj2 = new JsonParser().parse(res).getAsJsonObject().entrySet();
                     for (Entry<String, JsonElement> entry : obj2) {
                         if (entry.getKey().equals("error")) {
-                            res = entry.getValue().getAsJsonObject().get("name").getAsString();
+                            JsonElement errName = entry.getValue().getAsJsonObject().get("name");
+                            if (errName != null) {
+                                res = errName.getAsString();
+                                break;
+                            }
                         }
                     }
                 } catch (Exception e) {
diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java
index 4793721..0209550 100644
--- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java
+++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java
@@ -18,13 +18,28 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.apache.log4j.Logger;
+import org.joda.time.Duration;
 
+import com.cloud.agent.api.to.HostTO;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
+import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.utils.script.OutputInterpreter;
+import com.cloud.utils.script.Script;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
 
 public class StorPoolStoragePool implements KVMStoragePool {
+    private static final Logger log = Logger.getLogger(StorPoolStoragePool.class);
     private String _uuid;
     private String _sourceHost;
     private int _sourcePort;
@@ -34,6 +49,7 @@
     private String _authSecret;
     private String _sourceDir;
     private String _localPath;
+    private String storageNodeId = getStorPoolConfigParam("SP_OURID");
 
     public StorPoolStoragePool(String uuid, String host, int port, StoragePoolType storagePoolType, StorageAdaptor storageAdaptor) {
         _uuid = uuid;
@@ -166,4 +182,123 @@
     public Map<String, String> getDetails() {
         return null;
     }
+
+    @Override
+    public boolean isPoolSupportHA() {
+        return true;
+    }
+
+    @Override
+    public String getHearthBeatPath() {
+        String kvmScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR);
+        return Script.findScript(kvmScriptsDir, "kvmspheartbeat.sh");
+    }
+
+    @Override
+    public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) {
+        boolean isStorageNodeUp = checkingHeartBeat(primaryStoragePool, null);
+        if (!isStorageNodeUp && !hostValidation) {
+            //restart the host
+            log.debug(String.format("The host [%s] will be restarted because the health check failed for the storage pool [%s]", hostPrivateIp, primaryStoragePool.getPool().getType()));
+            Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeout, log);
+            cmd.add("-c");
+            cmd.execute();
+            return "Down";
+        }
+        return isStorageNodeUp ? null : "Down";
+    }
+
+    @Override
+    public String getStorageNodeId() {
+        return storageNodeId;
+    }
+
+    public static final String getStorPoolConfigParam(String param) {
+        Script sc = new Script("storpool_confget", 0, Logger.getLogger(StorPoolStoragePool.class));
+        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
+
+        String configParam = null;
+        final String err = sc.execute(parser);
+        if (err != null) {
+            StorPoolStorageAdaptor.SP_LOG("Could not execute storpool_confget. Error: %s", err);
+            return configParam;
+        }
+
+        for (String line: parser.getLines().split("\n")) {
+            String[] toks = line.split("=");
+            if( toks.length != 2 ) {
+                continue;
+            }
+            if (toks[0].equals(param)) {
+                configParam = toks[1];
+                return configParam;
+            }
+        }
+        return configParam;
+    }
+
+    @Override
+    public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
+        boolean isNodeWorking = false;
+        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
+
+        String res = executeStorPoolServiceListCmd(parser);
+
+        if (res != null) {
+            return isNodeWorking;
+        }
+        String response = parser.getLines();
+
+        Integer hostStorageNodeId = null;
+        if (host == null) {
+            hostStorageNodeId = Integer.parseInt(storageNodeId);
+        } else {
+            hostStorageNodeId = host.getParent() != null ? Integer.parseInt(host.getParent()) : null;
+        }
+        if (hostStorageNodeId == null) {
+            return isNodeWorking;
+        }
+        try {
+            isNodeWorking = checkIfNodeIsRunning(response, hostStorageNodeId);
+        } catch (JsonIOException | JsonSyntaxException e) {
+            e.printStackTrace();
+        }
+        return isNodeWorking;
+    }
+
+    private boolean checkIfNodeIsRunning(String response, Integer hostStorageNodeId) {
+        boolean isNodeWorking = false;
+        JsonParser jsonParser = new JsonParser();
+        JsonObject stats = (JsonObject) jsonParser.parse(response);
+        JsonObject data = stats.getAsJsonObject("data");
+        if (data != null) {
+            JsonObject clients = data.getAsJsonObject("clients");
+            for (Entry<String, JsonElement> element : clients.entrySet()) {
+                String storageNodeStatus = element.getValue().getAsJsonObject().get("status").getAsString();
+                int nodeId = element.getValue().getAsJsonObject().get("nodeId").getAsInt();
+                if (hostStorageNodeId == nodeId) {
+                    if (storageNodeStatus.equals("running")) {
+                        return true;
+                    } else {
+                        return isNodeWorking;
+                    }
+                }
+            }
+        }
+        return isNodeWorking;
+    }
+
+    private String executeStorPoolServiceListCmd(OutputInterpreter.AllLinesParser parser) {
+        Script sc = new Script("storpool", 0, log);
+        sc.add("-j");
+        sc.add("service");
+        sc.add("list");
+        String res = sc.execute(parser);
+        return res;
+    }
+
+    @Override
+    public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUuidListString, String vmActivityCheckPath, long duration) {
+        return checkingHeartBeat(pool, host);
+    }
 }
diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
index 22ad73a..08a3252 100644
--- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
+++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
@@ -18,54 +18,17 @@
  */
 package org.apache.cloudstack.storage.datastore.driver;
 
-import com.cloud.agent.api.Answer;
-import com.cloud.agent.api.storage.ResizeVolumeAnswer;
-import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
-import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
-import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
-import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
-import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
-import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
-import com.cloud.agent.api.storage.StorPoolSetVolumeEncryptionAnswer;
-import com.cloud.agent.api.storage.StorPoolSetVolumeEncryptionCommand;
-import com.cloud.agent.api.to.DataObjectType;
-import com.cloud.agent.api.to.DataStoreTO;
-import com.cloud.agent.api.to.DataTO;
-import com.cloud.agent.api.to.StorageFilerTO;
-import com.cloud.dc.dao.ClusterDao;
-import com.cloud.host.Host;
-import com.cloud.host.HostVO;
-import com.cloud.host.dao.HostDao;
-import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
-import com.cloud.server.ResourceTag;
-import com.cloud.server.ResourceTag.ResourceObjectType;
-import com.cloud.storage.DataStoreRole;
-import com.cloud.storage.ResizeVolumePayload;
-import com.cloud.storage.Storage.StoragePoolType;
-import com.cloud.storage.StorageManager;
-import com.cloud.storage.StoragePool;
-import com.cloud.storage.VMTemplateDetailVO;
-import com.cloud.storage.VMTemplateStoragePoolVO;
-import com.cloud.storage.VolumeDetailVO;
-import com.cloud.storage.VolumeVO;
-import com.cloud.storage.dao.SnapshotDetailsDao;
-import com.cloud.storage.dao.SnapshotDetailsVO;
-import com.cloud.storage.dao.StoragePoolHostDao;
-import com.cloud.storage.dao.VMTemplateDetailsDao;
-import com.cloud.storage.dao.VolumeDao;
-import com.cloud.storage.dao.VolumeDetailsDao;
-import com.cloud.tags.dao.ResourceTagDao;
-import com.cloud.utils.Pair;
-import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.vm.VMInstanceVO;
-import com.cloud.vm.VirtualMachine.State;
-import com.cloud.vm.VirtualMachineManager;
-import com.cloud.vm.dao.VMInstanceDao;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
 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.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
@@ -101,10 +64,50 @@
 import org.apache.commons.collections4.MapUtils;
 import org.apache.log4j.Logger;
 
-import javax.inject.Inject;
-
-import java.util.List;
-import java.util.Map;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.storage.ResizeVolumeAnswer;
+import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
+import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
+import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
+import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
+import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
+import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
+import com.cloud.agent.api.storage.StorPoolSetVolumeEncryptionAnswer;
+import com.cloud.agent.api.storage.StorPoolSetVolumeEncryptionCommand;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
+import com.cloud.server.ResourceTag;
+import com.cloud.server.ResourceTag.ResourceObjectType;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateDetailVO;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.storage.dao.VMTemplateDetailsDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.tags.dao.ResourceTagDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine.State;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.dao.VMInstanceDao;
 
 public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
 
@@ -142,6 +145,18 @@
     private StoragePoolDetailsDao storagePoolDetailsDao;
     @Inject
     private StoragePoolHostDao storagePoolHostDao;
+    @Inject
+    DataStoreManager dataStoreManager;
+
+    private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
+        List<SnapshotDataStoreVO> snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
+        for (SnapshotDataStoreVO ref : snaps) {
+            if (zoneId == dataStoreManager.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
+                return ref;
+            }
+        }
+        return null;
+    }
 
     @Override
     public Map<String, String> getCapabilities() {
@@ -175,6 +190,15 @@
 
     @Override
     public void revokeAccess(DataObject data, Host host, DataStore dataStore) {
+        if (DataObjectType.VOLUME == data.getType()) {
+            final VolumeVO volume = volumeDao.findById(data.getId());
+            if (volume.getInstanceId() == null) {
+                StorPoolUtil.spLog("Removing tags from detached volume=%s", volume.toString());
+                SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
+                StorPoolUtil.volumeRemoveTags(StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true), conn);
+            }
+        }
+
     }
 
     private void updateStoragePool(final long poolId, final long deltaUsedBytes) {
@@ -468,7 +492,7 @@
                 } else if (resp.getError().getName().equals("objectDoesNotExist")) {
                     //check if snapshot is on secondary storage
                     StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snopshot on secondary storage", snapshotName);
-                    SnapshotDataStoreVO snap = snapshotDataStoreDao.findBySnapshot(sinfo.getId(), DataStoreRole.Image);
+                    SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId());
                     if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) {
                         resp = StorPoolUtil.volumeCreate(srcData.getUuid(), null, size, null, "no", "snapshot", sinfo.getBaseVolume().getMaxIops(), conn);
                         if (resp.getError() == null) {
@@ -1023,7 +1047,7 @@
         }
 
         if (vinfo.getMaxIops() != null) {
-            response = StorPoolUtil.volumeUpdateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
+            response = StorPoolUtil.volumeUpdateIopsAndTags(volumeName, null, vinfo.getMaxIops(), conn, null);
             if (response.getError() != null) {
                 StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s", response.getError().getDescr());
             }
@@ -1112,13 +1136,16 @@
     @Override
     public void provideVmInfo(long vmId, long volumeId) {
         VolumeVO volume = volumeDao.findById(volumeId);
+        if (volume.getInstanceId() == null) {
+            return;
+        }
         StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
         if (poolVO != null) {
             try {
                 SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
                 String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
                 VMInstanceVO userVM = vmInstanceDao.findById(vmId);
-                SpApiResponse resp = StorPoolUtil.volumeUpdateTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId));
+                SpApiResponse resp = StorPoolUtil.volumeUpdateIopsAndTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId));
                 if (resp.getError() != null) {
                     log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid()));
                 }
@@ -1150,4 +1177,20 @@
             }
         }
     }
+
+    @Override
+    public boolean isStorageSupportHA(StoragePoolType type) {
+        return true;
+    }
+
+    @Override
+    public void detachVolumeFromAllStorageNodes(Volume volume) {
+        StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
+        if (poolVO != null) {
+            SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
+            String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
+            SpApiResponse resp = StorPoolUtil.detachAllForced(volName, false, conn);
+            StorPoolUtil.spLog("The volume [%s] is detach from all clusters [%s]", volName, resp);
+        }
+    }
 }
diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java
index 9b5320d..bf7642b 100644
--- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java
+++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java
@@ -37,6 +37,8 @@
 import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
 import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
 import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
+import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
@@ -47,6 +49,7 @@
 import com.cloud.agent.manager.AgentAttache;
 import com.cloud.alert.AlertManager;
 import com.cloud.dc.ClusterDetailsDao;
+import com.cloud.dc.ClusterDetailsVO;
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.exception.StorageConflictException;
 import com.cloud.host.HostVO;
@@ -162,6 +165,11 @@
             poolHost.setLocalPath(mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
         }
 
+        if (host.getParent() == null && mspAnswer.getClientNodeId() != null) {
+            host.setParent(mspAnswer.getClientNodeId());
+            hostDao.update(host.getId(), host);
+        }
+
         StorPoolHelper.setSpClusterIdIfNeeded(hostId, mspAnswer.getClusterId(), clusterDao, hostDao, clusterDetailsDao);
 
         StorPoolUtil.spLog("Connection established between storage pool [%s] and host [%s]", poolVO.getName(), host.getName());
@@ -214,10 +222,23 @@
     }
 
     @Override
-    public boolean hostRemoved(long hostId, long clusterId) {
+    public synchronized boolean hostRemoved(long hostId, long clusterId) {
+        List<HostVO> hosts = hostDao.findByClusterId(clusterId);
+        if (CollectionUtils.isNotEmpty(hosts) && hosts.size() == 1) {
+            removeSPClusterIdWhenTheLastHostIsRemoved(clusterId);
+        }
         return true;
     }
 
+    private void removeSPClusterIdWhenTheLastHostIsRemoved(long clusterId) {
+        ClusterDetailsVO clusterDetailsVo = clusterDetailsDao.findDetail(clusterId,
+                StorPoolConfigurationManager.StorPoolClusterId.key());
+        if (clusterDetailsVo != null && (clusterDetailsVo.getValue() != null && !clusterDetailsVo.getValue().equals(StorPoolConfigurationManager.StorPoolClusterId.defaultValue())) ){
+            clusterDetailsVo.setValue(StorPoolConfigurationManager.StorPoolClusterId.defaultValue());
+            clusterDetailsDao.update(clusterDetailsVo.getId(), clusterDetailsVo);
+        }
+    }
+
     //workaround: we need this "hack" to add our command StorPoolModifyStoragePoolCommand in AgentAttache.s_commandsAllowedInMaintenanceMode
     //which checks the allowed commands when the host is in maintenance mode
     private void addModifyCommandToCommandsAllowedInMaintenanceMode() {
diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
index a7ff626..675dffb 100644
--- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
+++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
@@ -54,6 +54,8 @@
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -67,7 +69,10 @@
 public class StorPoolUtil {
     private static final Logger log = Logger.getLogger(StorPoolUtil.class);
 
-    private static final File spLogFile = new File("/var/log/cloudstack/management/storpool-plugin.log");
+    private static final File spLogFile = new File(
+            Files.exists(Paths.get("/var/log/cloudstack/management/")) ?
+                    "/var/log/cloudstack/management/storpool-plugin.log" :
+                    "/tmp/storpool-plugin.log");
     private static PrintWriter spLogPrinterWriter = spLogFileInitialize();
 
     private static PrintWriter spLogFileInitialize() {
@@ -167,6 +172,12 @@
         private String templateName;
 
         public SpConnectionDesc(String url) {
+            try {
+                extractUriParams(url);
+                return;
+            } catch (URISyntaxException e) {
+                log.debug("[ignore] the uri is not valid");
+            }
             String[] urlSplit = url.split(";");
             if (urlSplit.length == 1 && !urlSplit[0].contains("=")) {
                 this.templateName = url;
@@ -235,6 +246,16 @@
             }
         }
 
+        private void extractUriParams(String url) throws URISyntaxException {
+            URI uri = new URI(url);
+            if (!StringUtils.equalsIgnoreCase(uri.getScheme(), "storpool")) {
+                throw new CloudRuntimeException("The scheme is invalid. The URL should be with a format storpool://{SP_AUTH_TOKEN}@{SP_API_HTTP}:{SP_API_HTTP_PORT}/{SP_TEMPLATE}");
+            }
+            hostPort = uri.getHost() + ":" + uri.getPort();
+            authToken = uri.getUserInfo();
+            templateName = uri.getPath().replace("/", "");
+        }
+
         public SpConnectionDesc(String host, String authToken2, String templateName2) {
             this.hostPort = host;
             this.authToken = authToken2;
@@ -512,7 +533,14 @@
         return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
     }
 
-    public static SpApiResponse volumeUpdateTags(final String name, final String uuid, Long iops,
+    public static SpApiResponse volumeRemoveTags(String name, SpConnectionDesc conn) {
+        Map<String, Object> json = new HashMap<>();
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, "", null, "");
+        json.put("tags", tags);
+        return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
+    }
+
+    public static SpApiResponse volumeUpdateIopsAndTags(final String name, final String uuid, Long iops,
             SpConnectionDesc conn, String vcPolicy) {
         Map<String, Object> json = new HashMap<>();
         Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy);
diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java
index 4808ee2..55d691f 100644
--- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java
+++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java
@@ -16,10 +16,13 @@
 // under the License.
 package org.apache.cloudstack.storage.snapshot;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
 
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
@@ -36,6 +39,7 @@
 import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
 import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
 import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -48,6 +52,7 @@
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.SnapshotDetailsDao;
 import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.SnapshotZoneDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
@@ -73,6 +78,10 @@
     private SnapshotDataFactory snapshotDataFactory;
     @Inject
     private StoragePoolDetailsDao storagePoolDetailsDao;
+    @Inject
+    DataStoreManager dataStoreMgr;
+    @Inject
+    SnapshotZoneDao snapshotZoneDao;
 
     @Override
     public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) {
@@ -92,7 +101,7 @@
     }
 
     @Override
-    public boolean deleteSnapshot(Long snapshotId) {
+    public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
 
         final SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
         VolumeVO volume = _volumeDao.findByIdIncludingRemoved(snapshotVO.getVolumeId());
@@ -108,11 +117,7 @@
                     final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError());
                     StorPoolUtil.spLog(err);
                 } else {
-                    SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid());
-                    if (snapshotDetails != null) {
-                        _snapshotDetailsDao.removeDetails(snapshotId);
-                    }
-                    res = deleteSnapshotFromDb(snapshotId);
+                    res = deleteSnapshotFromDbIfNeeded(snapshotVO, zoneId);
                     StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfully=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name);
                 }
             } catch (Exception e) {
@@ -125,13 +130,22 @@
     }
 
     @Override
-    public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
+    public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
         log.debug(String.format("StorpoolSnapshotStrategy.canHandle: snapshot=%s, uuid=%s, op=%s", snapshot.getName(), snapshot.getUuid(), op));
 
         if (op != SnapshotOperation.DELETE) {
             return StrategyPriority.CANT_HANDLE;
         }
-
+        SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
+        if (snapshotOnPrimary == null) {
+            return StrategyPriority.CANT_HANDLE;
+        }
+        if (zoneId != null) { // If zoneId is present, then it should be same as the zoneId of primary store
+            StoragePoolVO storagePoolVO = _primaryDataStoreDao.findById(snapshotOnPrimary.getDataStoreId());
+            if (!zoneId.equals(storagePoolVO.getDataCenterId())) {
+                return StrategyPriority.CANT_HANDLE;
+            }
+        }
         String name = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), _snapshotStoreDao, _snapshotDetailsDao);
         if (name != null) {
             StorPoolUtil.spLog("StorpoolSnapshotStrategy.canHandle: globalId=%s", name);
@@ -147,6 +161,7 @@
 
     private boolean deleteSnapshotChain(SnapshotInfo snapshot) {
         log.debug("delete snapshot chain for snapshot: " + snapshot.getId());
+        final SnapshotInfo snapOnImage = snapshot;
         boolean result = false;
         boolean resultIsSet = false;
         try {
@@ -174,8 +189,7 @@
                     }
                 }
                 if (!deleted) {
-                    SnapshotInfo snap = snapshotDataFactory.getSnapshot(snapshot.getId(), DataStoreRole.Image);
-                    if (StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getPath(), true) == null) {
+                    if (StorPoolStorageAdaptor.getVolumeNameFromPath(snapOnImage.getPath(), true) == null) {
                         try {
                             boolean r = snapshotSvr.deleteSnapshot(snapshot);
                             if (r) {
@@ -204,8 +218,64 @@
         return result;
     }
 
-    private boolean deleteSnapshotFromDb(Long snapshotId) {
-        SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
+    protected boolean areLastSnapshotRef(long snapshotId) {
+        List<SnapshotDataStoreVO> snapshotStoreRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
+        if (CollectionUtils.isEmpty(snapshotStoreRefs) || snapshotStoreRefs.size() == 1) {
+            return true;
+        }
+        return snapshotStoreRefs.size() == 2 && DataStoreRole.Primary.equals(snapshotStoreRefs.get(1).getRole());
+    }
+
+    protected boolean deleteSnapshotOnImageAndPrimary(long snapshotId, DataStore store) {
+        SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, store);
+        SnapshotObject obj = (SnapshotObject)snapshotOnImage;
+        boolean areLastSnapshotRef = areLastSnapshotRef(snapshotId);
+        try {
+            if (areLastSnapshotRef) {
+                obj.processEvent(Snapshot.Event.DestroyRequested);
+            }
+        } catch (NoTransitionException e) {
+            log.debug("Failed to set the state to destroying: ", e);
+            return false;
+        }
+
+        try {
+            boolean result = deleteSnapshotChain(snapshotOnImage);
+            _snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotId, store.getId(), store.getRole(), false);
+            if (areLastSnapshotRef) {
+                obj.processEvent(Snapshot.Event.OperationSucceeded);
+            }
+            if (result) {
+                SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotOnImage.getSnapshotId(), DataStoreRole.Primary);
+                if (snapshotOnPrimary != null) {
+                    snapshotOnPrimary.setState(State.Destroyed);
+                    _snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary);
+                }
+            }
+        } catch (Exception e) {
+            log.debug("Failed to delete snapshot: ", e);
+            try {
+                if (areLastSnapshotRef) {
+                    obj.processEvent(Snapshot.Event.OperationFailed);
+                }
+            } catch (NoTransitionException e1) {
+                log.debug("Failed to change snapshot state: " + e.toString());
+            }
+            return false;
+        }
+        return true;
+    }
+
+    private boolean deleteSnapshotFromDbIfNeeded(SnapshotVO snapshotVO, Long zoneId) {
+        final long snapshotId = snapshotVO.getId();
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid());
+        if (snapshotDetails != null) {
+            _snapshotDetailsDao.removeDetails(snapshotId);
+        }
+
+        if (zoneId != null && List.of(Snapshot.State.Allocated, Snapshot.State.CreatedOnPrimary).contains(snapshotVO.getState())) {
+            throw new InvalidParameterValueException(String.format("Snapshot in %s can not be deleted for a zone", snapshotVO.getState()));
+        }
 
         if (snapshotVO.getState() == Snapshot.State.Allocated) {
             _snapshotDao.remove(snapshotId);
@@ -218,10 +288,21 @@
 
         if (Snapshot.State.Error.equals(snapshotVO.getState())) {
             List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
+            List<Long> deletedRefs = new ArrayList<>();
             for (SnapshotDataStoreVO ref : storeRefs) {
-                _snapshotStoreDao.expunge(ref.getId());
+                boolean refZoneIdMatch = false;
+                if (zoneId != null) {
+                    Long refZoneId = dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole());
+                    refZoneIdMatch = zoneId.equals(refZoneId);
+                }
+                if (zoneId == null || refZoneIdMatch) {
+                    _snapshotStoreDao.expunge(ref.getId());
+                    deletedRefs.add(ref.getId());
+                }
             }
-            _snapshotDao.remove(snapshotId);
+            if (deletedRefs.size() == storeRefs.size()) {
+                _snapshotDao.remove(snapshotId);
+            }
             return true;
         }
 
@@ -233,46 +314,26 @@
 
         if (!Snapshot.State.BackedUp.equals(snapshotVO.getState()) && !Snapshot.State.Error.equals(snapshotVO.getState()) &&
                 !Snapshot.State.Destroying.equals(snapshotVO.getState())) {
-            throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
+            throw new InvalidParameterValueException("Can't delete snapshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
         }
-
-        SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Image);
-        if (snapshotOnImage == null) {
-            log.debug("Can't find snapshot on backup storage, delete it in db");
-            _snapshotDao.remove(snapshotId);
-            return true;
+        List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
+        if (zoneId != null) {
+            storeRefs.removeIf(ref -> !zoneId.equals(dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())));
         }
-
-        SnapshotObject obj = (SnapshotObject)snapshotOnImage;
-        try {
-            obj.processEvent(Snapshot.Event.DestroyRequested);
-        } catch (NoTransitionException e) {
-            log.debug("Failed to set the state to destroying: ", e);
-            return false;
-        }
-
-        try {
-            boolean result = deleteSnapshotChain(snapshotOnImage);
-            obj.processEvent(Snapshot.Event.OperationSucceeded);
-            if (result) {
-                SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
-                if (snapshotOnPrimary != null) {
-                    snapshotOnPrimary.setState(State.Destroyed);
-                    _snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary);
-                }
+        for (SnapshotDataStoreVO ref : storeRefs) {
+            if (!deleteSnapshotOnImageAndPrimary(snapshotId, dataStoreMgr.getDataStore(ref.getDataStoreId(), ref.getRole()))) {
+                return false;
             }
-        } catch (Exception e) {
-            log.debug("Failed to delete snapshot: ", e);
-            try {
-                obj.processEvent(Snapshot.Event.OperationFailed);
-            } catch (NoTransitionException e1) {
-                log.debug("Failed to change snapshot state: " + e.toString());
-            }
-            return false;
+        }
+        if (zoneId != null) {
+            snapshotZoneDao.removeSnapshotFromZone(snapshotVO.getId(), zoneId);
+        } else {
+            snapshotZoneDao.removeSnapshotFromZones(snapshotVO.getId());
         }
         return true;
     }
 
+
     @Override
     public SnapshotInfo takeSnapshot(SnapshotInfo snapshot) {
         return null;
diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java
index 1172600..489f64f 100644
--- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java
+++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolVMSnapshotStrategy.java
@@ -335,7 +335,7 @@
                 }
                 VolumeInfo vinfo = volFactory.getVolume(volumeObjectTO.getId());
                 if (vinfo.getMaxIops() != null) {
-                    resp = StorPoolUtil.volumeUpdateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
+                    resp = StorPoolUtil.volumeUpdateIopsAndTags(volumeName, null, vinfo.getMaxIops(), conn, null);
 
                     if (resp.getError() != null) {
                         StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s",
diff --git a/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java b/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java
index 356cac9..b862695 100644
--- a/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java
+++ b/plugins/storage/volume/storpool/src/test/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriverTest.java
@@ -47,18 +47,16 @@
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
-import org.powermock.core.classloader.annotations.PrepareForTest;
 
 import java.util.UUID;
 
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.mock;
 
 
 @RunWith(MockitoJUnitRunner.class)
-@PrepareForTest(StorPoolUtil.class)
 public class StorPoolPrimaryDataStoreDriverTest {
 
     @Mock
diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml
index 2bb800e..01ccd16 100644
--- a/plugins/user-authenticators/ldap/pom.xml
+++ b/plugins/user-authenticators/ldap/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
@@ -149,7 +149,7 @@
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
+            <artifactId>mockito-inline</artifactId>
             <version>${cs.mockito.version}</version>
             <scope>compile</scope>
             <exclusions>
diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java
index 0ba5c0c..0c70c4d 100644
--- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java
+++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java
@@ -376,7 +376,7 @@
     }
 
     /**
-     * @return true unless the the user is imported in the specified cloudstack domain from LDAP
+     * @return true unless the user is imported in the specified cloudstack domain from LDAP
      */
     private boolean isNotAlreadyImportedInTheCurrentDomain(LdapUserResponse user) {
         UserResponse cloudstackUser = getCloudstackUser(user);
diff --git a/plugins/user-authenticators/ldap/src/main/resources/META-INF/cloudstack/ldap/module.properties b/plugins/user-authenticators/ldap/src/main/resources/META-INF/cloudstack/ldap/module.properties
index 4659ab5..7194ac4 100644
--- a/plugins/user-authenticators/ldap/src/main/resources/META-INF/cloudstack/ldap/module.properties
+++ b/plugins/user-authenticators/ldap/src/main/resources/META-INF/cloudstack/ldap/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=ldap
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy b/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy
index 36b37ca..769f623 100644
--- a/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy
+++ b/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy
@@ -107,4 +107,4 @@
 	where: "The username is set to "
 	domain << ["", null, "engineering"]
     }
-}
\ No newline at end of file
+}
diff --git a/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy b/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy
index 4c0cc4b..93c91e3 100644
--- a/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy
+++ b/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy
@@ -27,4 +27,4 @@
         where: "The username is set to "
         query << ["", null, "murp*"]
     }
-}
\ No newline at end of file
+}
diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java
index 6bc81b7..b96a007 100644
--- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java
+++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapCreateAccountCmdTest.java
@@ -32,8 +32,8 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class LdapCreateAccountCmdTest implements LdapConfigurationChanger {
diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java
index 55310f9..594c23f 100644
--- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java
+++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapImportUsersCmdTest.java
@@ -40,8 +40,8 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class LdapImportUsersCmdTest implements LdapConfigurationChanger {
diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapListUsersCmdTest.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapListUsersCmdTest.java
index 6203c53..11d99f5 100644
--- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapListUsersCmdTest.java
+++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LdapListUsersCmdTest.java
@@ -31,14 +31,14 @@
 import org.apache.cloudstack.ldap.LdapUser;
 import org.apache.cloudstack.ldap.NoLdapUserMatchingQueryException;
 import org.apache.cloudstack.query.QueryService;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -50,16 +50,13 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.doReturn;
-import static org.powermock.api.mockito.PowerMockito.doThrow;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.when;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
-@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class LdapListUsersCmdTest implements LdapConfigurationChanger {
 
     public static final String LOCAL_DOMAIN_ID = "12345678-90ab-cdef-fedc-ba0987654321";
@@ -76,23 +73,30 @@
 
     Domain localDomain;
 
+    MockedStatic<CallContext> callContextMocked;
+
     @Before
     public void setUp() throws NoSuchFieldException, IllegalAccessException {
         ldapListUsersCmd = new LdapListUsersCmd(ldapManager, queryService);
         cmdSpy = spy(ldapListUsersCmd);
 
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Account accountMock = PowerMockito.mock(Account.class);
-        PowerMockito.when(accountMock.getDomainId()).thenReturn(1l);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
+        callContextMocked = Mockito.mockStatic(CallContext.class);
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+        Account accountMock = Mockito.mock(Account.class);
+        when(accountMock.getDomainId()).thenReturn(1l);
+        when(callContextMock.getCallingAccount()).thenReturn(accountMock);
 
         ldapListUsersCmd._domainService = domainService;
 
 // no need to        setHiddenField(ldapListUsersCmd, .... );
     }
 
+    @After
+    public void tearDown() throws Exception {
+        callContextMocked.close();
+    }
+
     /**
      * given: "We have an LdapManager, QueryService and LdapListUsersCmd"
      *  when: "Get entity owner id is called"
@@ -114,7 +118,7 @@
      */
     @Test
     public void successfulEmptyResponseFromExecute() throws NoLdapUserMatchingQueryException {
-        doThrow(new NoLdapUserMatchingQueryException("")).when(ldapManager).getUsers(null);
+        Mockito.doThrow(new NoLdapUserMatchingQueryException("")).when(ldapManager).getUsers(null);
         ldapListUsersCmd.execute();
         assertEquals(0, ((ListResponse)ldapListUsersCmd.getResponseObject()).getResponses().size());
     }
diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java
index 1a00a05..e355d77 100644
--- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java
+++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkAccountToLdapCmdTest.java
@@ -36,7 +36,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class LinkAccountToLdapCmdTest implements LdapConfigurationChanger {
diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java
index 04594e2..204e985 100644
--- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java
+++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/api/command/LinkDomainToLdapCmdTest.java
@@ -35,7 +35,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class LinkDomainToLdapCmdTest implements LdapConfigurationChanger
diff --git a/plugins/user-authenticators/ldap/src/test/resources/cloudstack.org.ldif b/plugins/user-authenticators/ldap/src/test/resources/cloudstack.org.ldif
index 4078372..ad677bc 100644
--- a/plugins/user-authenticators/ldap/src/test/resources/cloudstack.org.ldif
+++ b/plugins/user-authenticators/ldap/src/test/resources/cloudstack.org.ldif
@@ -308,4 +308,3 @@
 mail: cpetri@cloudstack.org
 uid: cpetri
 userpassword:: cGFzc3dvcmQ=
-
diff --git a/plugins/user-authenticators/ldap/src/test/resources/ldapunit.ldif b/plugins/user-authenticators/ldap/src/test/resources/ldapunit.ldif
index a6c1da1..e2e7f99 100644
--- a/plugins/user-authenticators/ldap/src/test/resources/ldapunit.ldif
+++ b/plugins/user-authenticators/ldap/src/test/resources/ldapunit.ldif
@@ -148,4 +148,4 @@
 sn: User
 givenName: demo
 mail: d@b.c
-uid: noadmin
\ No newline at end of file
+uid: noadmin
diff --git a/plugins/user-authenticators/ldap/src/test/resources/minimal.ldif b/plugins/user-authenticators/ldap/src/test/resources/minimal.ldif
index 46e87c2..035c216 100644
--- a/plugins/user-authenticators/ldap/src/test/resources/minimal.ldif
+++ b/plugins/user-authenticators/ldap/src/test/resources/minimal.ldif
@@ -240,4 +240,3 @@
 inetUserStatus: Active
 mail: d@b.c
 uid: noadmin
-
diff --git a/plugins/user-authenticators/ldap/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/plugins/user-authenticators/ldap/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/plugins/user-authenticators/ldap/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/plugins/user-authenticators/ldap/src/test/resources/unboundid.ldif b/plugins/user-authenticators/ldap/src/test/resources/unboundid.ldif
index 4078372..ad677bc 100644
--- a/plugins/user-authenticators/ldap/src/test/resources/unboundid.ldif
+++ b/plugins/user-authenticators/ldap/src/test/resources/unboundid.ldif
@@ -308,4 +308,3 @@
 mail: cpetri@cloudstack.org
 uid: cpetri
 userpassword:: cGFzc3dvcmQ=
-
diff --git a/plugins/user-authenticators/md5/pom.xml b/plugins/user-authenticators/md5/pom.xml
index 2fc98c6..70ff72c 100644
--- a/plugins/user-authenticators/md5/pom.xml
+++ b/plugins/user-authenticators/md5/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/module.properties b/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/module.properties
index 03ba739..c79f1b6 100644
--- a/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/module.properties
+++ b/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=md5
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/user-authenticators/oauth2/pom.xml b/plugins/user-authenticators/oauth2/pom.xml
new file mode 100644
index 0000000..8d6bb66
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/pom.xml
@@ -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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-user-authenticator-oauth2</artifactId>
+    <name>Apache CloudStack Plugin - User Authenticator OAuth2</name>
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.19.1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-utils</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-framework-config</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.apis</groupId>
+            <artifactId>google-api-services-docs</artifactId>
+            <version>v1-rev20220609-1.32.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.apis</groupId>
+            <artifactId>google-api-services-oauth2</artifactId>
+            <version>v2-rev20200213-1.32.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.oauth-client</groupId>
+            <artifactId>google-oauth-client-servlet</artifactId>
+            <version>1.34.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.http-client</groupId>
+            <artifactId>google-http-client-jackson2</artifactId>
+            <version>1.20.0</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.java
new file mode 100644
index 0000000..ece012d
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManager.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.oauth2;
+
+import com.cloud.utils.component.PluggableService;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+
+import java.util.List;
+
+public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableService {
+    public static ConfigKey<Boolean> OAuth2IsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "oauth2.enabled", "false",
+            "Indicates whether OAuth plugin is enabled or not", false);
+    public static final ConfigKey<String> OAuth2Plugins = new ConfigKey<String>("Advanced", String.class, "oauth2.plugins", "google,github",
+            "List of OAuth plugins", true);
+    public static final ConfigKey<String> OAuth2PluginsExclude = new ConfigKey<String>("Advanced", String.class, "oauth2.plugins.exclude", "",
+            "List of OAuth plugins which are excluded", true);
+
+    /**
+     * Lists user OAuth2 provider plugins
+     * @return list of providers
+     */
+    List<UserOAuth2Authenticator> listUserOAuth2AuthenticationProviders();
+
+    /**
+     * Finds user OAuth2 provider by name
+     * @param providerName name of the provider
+     * @return OAuth2 provider
+     */
+    UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(final String providerName);
+
+    String verifyCodeAndFetchEmail(String code, String provider);
+
+    OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd);
+
+    List<OauthProviderVO> listOauthProviders(String provider, String uuid);
+
+    boolean deleteOauthProvider(Long id);
+
+    OauthProviderVO updateOauthProvider(UpdateOAuthProviderCmd cmd);
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java
new file mode 100644
index 0000000..8573065
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImpl.java
@@ -0,0 +1,233 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package org.apache.cloudstack.oauth2;
+
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.component.Manager;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.oauth2.api.command.DeleteOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.api.command.ListOAuthProvidersCmd;
+import org.apache.cloudstack.oauth2.api.command.OauthLoginAPIAuthenticatorCmd;
+import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.api.command.VerifyOAuthCodeAndGetUserCmd;
+import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthManager, Manager, Configurable {
+    private static final Logger s_logger = Logger.getLogger(OAuth2AuthManagerImpl.class);
+    @Inject
+    private UserDao _userDao;
+
+    @Inject
+    protected OauthProviderDao _oauthProviderDao;
+
+    protected static Map<String, UserOAuth2Authenticator> userOAuth2AuthenticationProvidersMap = new HashMap<>();
+
+    private List<UserOAuth2Authenticator> userOAuth2AuthenticationProviders;
+
+    @Override
+    public List<Class<?>> getAuthCommands() {
+        List<Class<?>> cmdList = new ArrayList<Class<?>>();
+        cmdList.add(OauthLoginAPIAuthenticatorCmd.class);
+        cmdList.add(ListOAuthProvidersCmd.class);
+        cmdList.add(VerifyOAuthCodeAndGetUserCmd.class);
+        return cmdList;
+    }
+
+    @Override
+    public boolean start() {
+        if (isOAuthPluginEnabled()) {
+            s_logger.info("OAUTH plugin loaded");
+            initializeUserOAuth2AuthenticationProvidersMap();
+        } else {
+            s_logger.info("OAUTH plugin not enabled so not loading");
+        }
+        return true;
+    }
+
+    protected boolean isOAuthPluginEnabled() {
+        return OAuth2IsPluginEnabled.value();
+    }
+
+    @Override
+    public boolean stop() {
+        return false;
+    }
+
+    @Override
+    public List<Class<?>> getCommands() {
+        List<Class<?>> cmdList = new ArrayList<Class<?>>();
+        cmdList.add(RegisterOAuthProviderCmd.class);
+        cmdList.add(DeleteOAuthProviderCmd.class);
+        cmdList.add(UpdateOAuthProviderCmd.class);
+
+        return cmdList;
+    }
+
+    @Override
+    public List<UserOAuth2Authenticator> listUserOAuth2AuthenticationProviders() {
+        return userOAuth2AuthenticationProviders;
+    }
+
+    @Override
+    public UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(String providerName) {
+        if (StringUtils.isEmpty(providerName)) {
+            throw new CloudRuntimeException("OAuth2 authentication provider name is empty");
+        }
+        if (!userOAuth2AuthenticationProvidersMap.containsKey(providerName.toLowerCase())) {
+            throw new CloudRuntimeException(String.format("Failed to find OAuth2 authentication provider by the name: %s.", providerName));
+        }
+        return userOAuth2AuthenticationProvidersMap.get(providerName.toLowerCase());
+    }
+
+    public List<UserOAuth2Authenticator> getUserOAuth2AuthenticationProviders() {
+        return userOAuth2AuthenticationProviders;
+    }
+
+    public void setUserOAuth2AuthenticationProviders(final List<UserOAuth2Authenticator> userOAuth2AuthenticationProviders) {
+        this.userOAuth2AuthenticationProviders = userOAuth2AuthenticationProviders;
+    }
+
+    protected void initializeUserOAuth2AuthenticationProvidersMap() {
+        if (userOAuth2AuthenticationProviders != null) {
+            for (final UserOAuth2Authenticator userOAuth2Authenticator : userOAuth2AuthenticationProviders) {
+                userOAuth2AuthenticationProvidersMap.put(userOAuth2Authenticator.getName().toLowerCase(), userOAuth2Authenticator);
+            }
+        }
+    }
+
+    @Override
+    public String verifyCodeAndFetchEmail(String code, String provider) {
+        UserOAuth2Authenticator authenticator = getUserOAuth2AuthenticationProvider(provider);
+        String email = authenticator.verifyCodeAndFetchEmail(code);
+
+        return email;
+    }
+
+    @Override
+    public OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd) {
+        String description = cmd.getDescription();
+        String provider = cmd.getProvider();
+        String clientId = cmd.getClientId();
+        String redirectUri = cmd.getRedirectUri();
+        String secretKey = cmd.getSecretKey();
+
+        if (!isOAuthPluginEnabled()) {
+            throw new CloudRuntimeException("OAuth is not enabled, please enable to register");
+        }
+        OauthProviderVO providerVO = _oauthProviderDao.findByProvider(provider);
+        if (providerVO != null) {
+            throw new CloudRuntimeException(String.format("Provider with the name %s is already registered", provider));
+        }
+
+        return saveOauthProvider(provider, description, clientId, secretKey, redirectUri);
+    }
+
+    @Override
+    public List<OauthProviderVO> listOauthProviders(String provider, String uuid) {
+        List<OauthProviderVO> providers;
+        if (uuid != null) {
+            providers = Collections.singletonList(_oauthProviderDao.findByUuid(uuid));
+        } else if (StringUtils.isNotBlank(provider)) {
+            providers = Collections.singletonList(_oauthProviderDao.findByProvider(provider));
+        } else {
+            providers = _oauthProviderDao.listAll();
+        }
+        return providers;
+    }
+
+    @Override
+    public OauthProviderVO updateOauthProvider(UpdateOAuthProviderCmd cmd) {
+        Long id = cmd.getId();
+        String description = cmd.getDescription();
+        String clientId = cmd.getClientId();
+        String redirectUri = cmd.getRedirectUri();
+        String secretKey = cmd.getSecretKey();
+        Boolean enabled = cmd.getEnabled();
+
+        OauthProviderVO providerVO = _oauthProviderDao.findById(id);
+        if (providerVO == null) {
+            throw new CloudRuntimeException("Provider with the given id is not there");
+        }
+
+        if (StringUtils.isNotEmpty(description)) {
+            providerVO.setDescription(description);
+        }
+        if (StringUtils.isNotEmpty(clientId)) {
+            providerVO.setClientId(clientId);
+        }
+        if (StringUtils.isNotEmpty(redirectUri)) {
+            providerVO.setRedirectUri(redirectUri);
+        }
+        if (StringUtils.isNotEmpty(secretKey)) {
+            providerVO.setSecretKey(secretKey);
+        }
+        if (enabled != null) {
+            providerVO.setEnabled(enabled);
+        }
+
+        _oauthProviderDao.update(id, providerVO);
+
+        return _oauthProviderDao.findById(id);
+    }
+
+    private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri) {
+        final OauthProviderVO oauthProviderVO = new OauthProviderVO();
+
+        oauthProviderVO.setProvider(provider);
+        oauthProviderVO.setDescription(description);
+        oauthProviderVO.setClientId(clientId);
+        oauthProviderVO.setSecretKey(secretKey);
+        oauthProviderVO.setRedirectUri(redirectUri);
+        oauthProviderVO.setEnabled(true);
+
+        _oauthProviderDao.persist(oauthProviderVO);
+
+        return oauthProviderVO;
+    }
+
+    @Override
+    public boolean deleteOauthProvider(Long id) {
+        return _oauthProviderDao.remove(id);
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return "OAUTH2-PLUGIN";
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {OAuth2IsPluginEnabled, OAuth2Plugins, OAuth2PluginsExclude};
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.java
new file mode 100644
index 0000000..8484a5e
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticator.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.oauth2;
+
+import com.cloud.user.User;
+import com.cloud.user.UserAccount;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.AdapterBase;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.auth.UserAuthenticator;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.Map;
+
+public class OAuth2UserAuthenticator extends AdapterBase implements UserAuthenticator {
+    public static final Logger s_logger = Logger.getLogger(OAuth2UserAuthenticator.class);
+
+    @Inject
+    private UserAccountDao _userAccountDao;
+    @Inject
+    private UserDao _userDao;
+
+    @Inject
+    private OAuth2AuthManager _userOAuth2mgr;
+
+    @Override
+    public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Trying OAuth2 auth for user: " + username);
+        }
+
+        final UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
+        if (userAccount == null) {
+            s_logger.debug("Unable to find user with " + username + " in domain " + domainId + ", or user source is not OAUTH2");
+            return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
+        } else {
+            User user = _userDao.getUser(userAccount.getId());
+            final String[] provider = (String[])requestParameters.get(ApiConstants.PROVIDER);
+            final String[] emailArray = (String[])requestParameters.get(ApiConstants.EMAIL);
+            final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
+            String oauthProvider = ((provider == null) ? null : provider[0]);
+            String email = ((emailArray == null) ? null : emailArray[0]);
+            String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
+
+            UserOAuth2Authenticator authenticator = _userOAuth2mgr.getUserOAuth2AuthenticationProvider(oauthProvider);
+            if (user != null && authenticator.verifyUser(email, secretCode)) {
+                return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
+            }
+        }
+        // Deny all by default
+        return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
+    }
+
+    @Override
+    public String encode(String password) {
+        return null;
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/DeleteOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/DeleteOAuthProviderCmd.java
new file mode 100644
index 0000000..6cd3156
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/DeleteOAuthProviderCmd.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.oauth2.api.command;
+
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
+import org.apache.log4j.Logger;
+
+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.context.CallContext;
+
+import javax.inject.Inject;
+
+@APICommand(name = "deleteOauthProvider", description = "Deletes the registered OAuth provider", responseObject = SuccessResponse.class,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0")
+public class DeleteOAuthProviderCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(DeleteOAuthProviderCmd.class.getName());
+
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = OauthProviderResponse.class, required = true, description = "id of the OAuth provider to be deleted")
+    private Long id;
+
+    @Inject
+    OAuth2AuthManager _oauthMgr;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return id;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.User;
+    }
+
+    @Override
+    public void execute() {
+        boolean result = _oauthMgr.deleteOauthProvider(getId());
+        if (result) {
+            SuccessResponse response = new SuccessResponse(getCommandName());
+            this.setResponseObject(response);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete the OAuth provider");
+        }
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.java
new file mode 100644
index 0000000..597283a
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/ListOAuthProvidersCmd.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 org.apache.cloudstack.oauth2.api.command;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.api.response.ApiResponseSerializer;
+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.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.log4j.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@APICommand(name = "listOauthProvider", description = "List OAuth providers registered", responseObject = OauthProviderResponse.class, entityType = {},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0")
+public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticator {
+    public static final Logger s_logger = Logger.getLogger(ListOAuthProvidersCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = OauthProviderResponse.class, description = "the ID of the OAuth provider")
+    private String id;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider")
+    private String provider;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+    public String getId() {
+        return id;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    OAuth2AuthManager _oauth2mgr;
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.Type.NORMAL.ordinal();
+    }
+
+    @Override
+    public void execute() throws ServerApiException {
+        // We should never reach here
+        throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+    }
+
+    @Override
+    public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, HttpServletRequest req, HttpServletResponse resp) throws ServerApiException {
+        final String[] idArray = (String[])params.get(ApiConstants.ID);
+        final String[] providerArray = (String[])params.get(ApiConstants.PROVIDER);
+        if (ArrayUtils.isNotEmpty(idArray)) {
+            id = idArray[0];
+        }
+        if (ArrayUtils.isNotEmpty(providerArray)) {
+            provider = providerArray[0];
+        }
+
+        List<OauthProviderVO> resultList = _oauth2mgr.listOauthProviders(provider, id);
+        List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauth2mgr.listUserOAuth2AuthenticationProviders();
+        List<String> authenticatorPluginNames = new ArrayList<>();
+        for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) {
+            String name = authenticator.getName();
+            authenticatorPluginNames.add(name);
+        }
+        List<OauthProviderResponse> responses = new ArrayList<>();
+        for (OauthProviderVO result : resultList) {
+            OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
+                    result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri());
+            if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
+                r.setEnabled(true);
+            } else {
+                r.setEnabled(false);
+            }
+            r.setObjectName(ApiConstants.OAUTH_PROVIDER);
+            responses.add(r);
+        }
+
+        ListResponse<OauthProviderResponse> response = new ListResponse<>();
+        response.setResponses(responses, resultList.size());
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+
+        return ApiResponseSerializer.toSerializedString(response, responseType);
+    }
+
+    @Override
+    public APIAuthenticationType getAPIType() {
+        return null;
+    }
+
+    @Override
+    public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
+        for (PluggableAPIAuthenticator authManager: authenticators) {
+            if (authManager != null && authManager instanceof OAuth2AuthManager) {
+                _oauth2mgr = (OAuth2AuthManager) authManager;
+            }
+        }
+        if (_oauth2mgr == null) {
+            s_logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers");
+        }
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java
new file mode 100644
index 0000000..928fa76
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmd.java
@@ -0,0 +1,234 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.oauth2.api.command;
+
+import com.cloud.api.ApiServlet;
+import com.cloud.domain.Domain;
+import com.cloud.user.User;
+import com.cloud.user.UserAccount;
+import org.apache.cloudstack.api.ApiServerService;
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.exception.CloudAuthenticationException;
+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.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.response.LoginCmdResponse;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.jetbrains.annotations.Nullable;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.List;
+import java.util.Map;
+import java.net.InetAddress;
+
+import static org.apache.cloudstack.oauth2.OAuth2AuthManager.OAuth2IsPluginEnabled;
+
+@APICommand(name = "oauthlogin", description = "Logs a user into the CloudStack after successful verification of OAuth secret code from the particular provider." +
+        "A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.",
+        requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {}, since = "4.19.0")
+public class OauthLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
+
+    public static final Logger s_logger = Logger.getLogger(OauthLoginAPIAuthenticatorCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider", required = true)
+    private String provider;
+
+    @Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "Email id with which user tried to login using OAuth provider", required = true)
+    private String email;
+
+    @Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING, description = "Path of the domain that the user belongs to. Example: domain=/com/cloud/internal. If no domain is passed in, the ROOT (/) domain is assumed.")
+    private String domain;
+
+    @Parameter(name = ApiConstants.DOMAIN__ID, type = CommandType.LONG, description = "The id of the domain that the user belongs to. If both domain and domainId are passed in, \"domainId\" parameter takes precedence.")
+    private Long domainId;
+
+    @Parameter(name = ApiConstants.SECRET_CODE, type = CommandType.STRING, description = "Code that is provided by OAuth provider (Eg. google, github) after successful login")
+    private String secretCode;
+
+    @Inject
+    ApiServerService _apiServer;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public String getDomainName() {
+        return domain;
+    }
+
+    public Long getDomainId() {
+        return domainId;
+    }
+
+    public String getSecretCode() {
+        return secretCode;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.Type.NORMAL.ordinal();
+    }
+
+    @Override
+    public void execute() throws ServerApiException {
+        // We should never reach here
+        throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+    }
+
+    @Override
+    public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
+        if (!OAuth2IsPluginEnabled.value()) {
+            throw new CloudAuthenticationException("OAuth is not enabled in CloudStack, users cannot login using OAuth");
+        }
+        final String[] provider = (String[])params.get(ApiConstants.PROVIDER);
+        final String[] emailArray = (String[])params.get(ApiConstants.EMAIL);
+        final String[] secretCodeArray = (String[])params.get(ApiConstants.SECRET_CODE);
+
+        String oauthProvider = ((provider == null) ? null : provider[0]);
+        String email = ((emailArray == null) ? null : emailArray[0]);
+        String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
+        if (StringUtils.isAnyEmpty(oauthProvider, email, secretCode)) {
+            throw new CloudAuthenticationException("OAuth provider, email, secretCode any of these cannot be null");
+        }
+
+        Long domainId = getDomainIdFromParams(params, auditTrailSb, responseType);
+        final String[] domainName = (String[])params.get(ApiConstants.DOMAIN);
+        String domain = getDomainName(auditTrailSb, domainName);
+
+        return doOauthAuthentication(session, domainId, domain, email, params, remoteAddress, responseType, auditTrailSb);
+    }
+
+    private String doOauthAuthentication(HttpSession session, Long domainId, String domain, String email, Map<String, Object[]> params, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb) {
+        String serializedResponse = null;
+
+        try {
+            final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain);
+            if (userDomain != null) {
+                domainId = userDomain.getId();
+            } else {
+                throw new CloudAuthenticationException("Unable to find the domain from the path " + domain);
+            }
+            final List<UserAccount> userAccounts = _accountService.getActiveUserAccountByEmail(email, domainId);
+            if (CollectionUtils.isEmpty(userAccounts)) {
+                throw new CloudAuthenticationException("User not found in CloudStack to login. If user belongs to any domain, please provide it.");
+            }
+            if (userAccounts.size() > 1) {
+                throw new CloudAuthenticationException("Multiple Users found in CloudStack. If user belongs to any specific domain, please provide it.");
+            }
+            UserAccount userAccount = userAccounts.get(0);
+            if (userAccount != null && User.Source.SAML2 == userAccount.getSource()) {
+                throw new CloudAuthenticationException("User is not allowed CloudStack login");
+            }
+            return ApiResponseSerializer.toSerializedString(_apiServer.loginUser(session, userAccount.getUsername(), null, domainId, domain, remoteAddress, params),
+                    responseType);
+        } catch (final CloudAuthenticationException ex) {
+            ApiServlet.invalidateHttpSession(session, "fall through to API key,");
+            String msg = String.format("%s", ex.getMessage() != null ?
+                    ex.getMessage() :
+                    "failed to authenticate user, check if username/password are correct");
+            auditTrailSb.append(" " + ApiErrorCode.ACCOUNT_ERROR + " " + msg);
+            serializedResponse = _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), msg, params, responseType);
+            if (s_logger.isTraceEnabled()) {
+                s_logger.trace(msg);
+            }
+        }
+
+        // We should not reach here and if we do we throw an exception
+        throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, serializedResponse);
+    }
+
+    protected Long getDomainIdFromParams(Map<String, Object[]> params, StringBuilder auditTrailSb, String responseType) {
+        String[] domainIdArr = (String[])params.get(ApiConstants.DOMAIN_ID);
+
+        if (domainIdArr == null) {
+            domainIdArr = (String[])params.get(ApiConstants.DOMAIN__ID);
+        }
+        Long domainId = null;
+        if ((domainIdArr != null) && (domainIdArr.length > 0)) {
+            try {
+                //check if UUID is passed in for domain
+                domainId = _apiServer.fetchDomainId(domainIdArr[0]);
+                if (domainId == null) {
+                    domainId = Long.parseLong(domainIdArr[0]);
+                }
+                auditTrailSb.append(" domainid=" + domainId);// building the params for POST call
+            } catch (final NumberFormatException e) {
+                s_logger.warn("Invalid domain id entered by user");
+                auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one");
+                throw new ServerApiException(ApiErrorCode.UNAUTHORIZED,
+                        _apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid domain id entered, please enter a valid one", params,
+                                responseType));
+            }
+        }
+        return domainId;
+    }
+
+    @Nullable
+    protected String getDomainName(StringBuilder auditTrailSb, String[] domainName) {
+        String domain = null;
+        if (domainName != null) {
+            domain = domainName[0];
+            auditTrailSb.append(" domain=" + domain);
+            if (domain != null) {
+                // ensure domain starts with '/' and ends with '/'
+                if (!domain.endsWith("/")) {
+                    domain += '/';
+                }
+                if (!domain.startsWith("/")) {
+                    domain = "/" + domain;
+                }
+            }
+        }
+        return domain;
+    }
+
+    @Override
+    public APIAuthenticationType getAPIType() {
+        return APIAuthenticationType.LOGIN_API;
+    }
+
+    @Override
+    public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.java
new file mode 100644
index 0000000..b31cbde
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmd.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.oauth2.api.command;
+
+import javax.inject.Inject;
+import javax.persistence.EntityExistsException;
+
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.apache.commons.collections.MapUtils;
+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.context.CallContext;
+
+import com.cloud.exception.ConcurrentOperationException;
+
+import java.util.Collection;
+import java.util.Map;
+
+@APICommand(name = "registerOauthProvider", responseObject = SuccessResponse.class, description = "Register the OAuth2 provider in CloudStack", since = "4.19.0")
+public class RegisterOAuthProviderCmd extends BaseCmd {
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, description = "Description of the OAuth Provider")
+    private String description;
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider from the list of OAuth providers supported in CloudStack", required = true)
+    private String provider;
+
+    @Parameter(name = ApiConstants.CLIENT_ID, type = CommandType.STRING, description = "Client ID pre-registered in the specific OAuth provider", required = true)
+    private String clientId;
+
+    @Parameter(name = ApiConstants.OAUTH_SECRET_KEY, type = CommandType.STRING, description = "Secret Key pre-registered in the specific OAuth provider", required = true)
+    private String secretKey;
+
+    @Parameter(name = ApiConstants.REDIRECT_URI, type = CommandType.STRING, description = "Redirect URI pre-registered in the specific OAuth provider", required = true)
+    private String redirectUri;
+
+    @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
+            description = "Any OAuth provider details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].clientsecret=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")
+    protected Map details;
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public Map getDetails() {
+        if (MapUtils.isEmpty(details)) {
+            return null;
+        }
+        Collection paramsCollection = this.details.values();
+        return (Map) (paramsCollection.toArray())[0];
+    }
+
+    @Inject
+    OAuth2AuthManager _oauth2mgr;
+
+    @Override
+    public void execute() throws ServerApiException, ConcurrentOperationException, EntityExistsException {
+        OauthProviderVO provider = _oauth2mgr.registerOauthProvider(this);
+
+        OauthProviderResponse response = new OauthProviderResponse(provider.getUuid(), provider.getProvider(),
+                provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri());
+        response.setResponseName(getCommandName());
+        response.setObjectName(ApiConstants.OAUTH_PROVIDER);
+        setResponseObject(response);
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java
new file mode 100644
index 0000000..b38423f
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/UpdateOAuthProviderCmd.java
@@ -0,0 +1,141 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.oauth2.api.command;
+
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.apache.log4j.Logger;
+
+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.context.CallContext;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+@APICommand(name = "updateOauthProvider", description = "Updates the registered OAuth provider details", responseObject = OauthProviderResponse.class,
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0")
+public final class UpdateOAuthProviderCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(UpdateOAuthProviderCmd.class.getName());
+
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = OauthProviderResponse.class, required = true, description = "id of the OAuth provider to be updated")
+    private Long id;
+
+    @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "Description of the OAuth Provider")
+    private String description;
+
+    @Parameter(name = ApiConstants.CLIENT_ID, type = CommandType.STRING, description = "Client ID pre-registered in the specific OAuth provider")
+    private String clientId;
+
+    @Parameter(name = ApiConstants.OAUTH_SECRET_KEY, type = CommandType.STRING, description = "Secret Key pre-registered in the specific OAuth provider")
+    private String secretKey;
+
+    @Parameter(name = ApiConstants.REDIRECT_URI, type = CommandType.STRING, description = "Redirect URI pre-registered in the specific OAuth provider")
+    private String redirectUri;
+
+    @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "OAuth provider will be enabled or disabled based on this value")
+    private Boolean enabled;
+
+    @Inject
+    OAuth2AuthManager _oauthMgr;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+
+    @Override
+    public Long getApiResourceId() {
+        return id;
+    }
+
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.User;
+    }
+
+    @Override
+    public void execute() {
+        OauthProviderVO result = _oauthMgr.updateOauthProvider(this);
+        if (result != null) {
+            OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
+                    result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri());
+
+            List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauthMgr.listUserOAuth2AuthenticationProviders();
+            List<String> authenticatorPluginNames = new ArrayList<>();
+            for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) {
+                String name = authenticator.getName();
+                authenticatorPluginNames.add(name);
+            }
+            if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
+                r.setEnabled(true);
+            } else {
+                r.setEnabled(false);
+            }
+
+            r.setObjectName(ApiConstants.OAUTH_PROVIDER);
+            r.setResponseName(getCommandName());
+            this.setResponseObject(r);
+        } else {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update OAuth provider");
+        }
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java
new file mode 100644
index 0000000..5dbeef1
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmd.java
@@ -0,0 +1,130 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.oauth2.api.command;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.api.response.ApiResponseSerializer;
+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.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.response.UserResponse;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.log4j.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@APICommand(name = "verifyOAuthCodeAndGetUser", description = "Verify the OAuth Code and fetch the corresponding user from provider", responseObject = OauthProviderResponse.class, entityType = {},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0")
+public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuthenticator {
+    public static final Logger s_logger = Logger.getLogger(VerifyOAuthCodeAndGetUserCmd.class.getName());
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider", required = true)
+    private String provider;
+
+    @Parameter(name = ApiConstants.SECRET_CODE, type = CommandType.STRING, description = "Code that is provided by OAuth provider (Eg. google, github) after successful login")
+    private String secretCode;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public String getSecretCode() {
+        return secretCode;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    protected OAuth2AuthManager _oauth2mgr;
+
+    @Override
+    public long getEntityOwnerId() {
+        return Account.Type.NORMAL.ordinal();
+    }
+
+    @Override
+    public void execute() throws ServerApiException {
+        // We should never reach here
+        throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+    }
+
+    @Override
+    public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, HttpServletRequest req, HttpServletResponse resp) throws ServerApiException {
+        final String[] secretcodeArray = (String[])params.get(ApiConstants.SECRET_CODE);
+        final String[] providerArray = (String[])params.get(ApiConstants.PROVIDER);
+        if (ArrayUtils.isNotEmpty(secretcodeArray)) {
+            secretCode = secretcodeArray[0];
+        }
+        if (ArrayUtils.isNotEmpty(providerArray)) {
+            provider = providerArray[0];
+        }
+
+        String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider);
+        if (email != null) {
+            UserResponse response = new UserResponse();
+            response.setEmail(email);
+            response.setResponseName(getCommandName());
+            response.setObjectName("oauthemail");
+
+            return ApiResponseSerializer.toSerializedString(response, responseType);
+        }
+
+        throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to verify the code provided");
+    }
+
+    @Override
+    public APIAuthenticationType getAPIType() {
+        return null;
+    }
+
+    @Override
+    public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
+        for (PluggableAPIAuthenticator authManager: authenticators) {
+            if (authManager != null && authManager instanceof OAuth2AuthManager) {
+                _oauth2mgr = (OAuth2AuthManager) authManager;
+            }
+        }
+        if (_oauth2mgr == null) {
+            s_logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers");
+        }
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java
new file mode 100644
index 0000000..e0c40be
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/api/response/OauthProviderResponse.java
@@ -0,0 +1,127 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.oauth2.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.oauth2.vo.OauthProviderVO;
+
+@EntityReference(value = OauthProviderVO.class)
+public class OauthProviderResponse extends BaseResponse {
+
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "ID of the provider")
+    private String id;
+
+    @SerializedName(ApiConstants.PROVIDER)
+    @Param(description = "Name of the provider")
+    private String provider;
+
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "Name of the provider")
+    private String name;
+
+    @SerializedName(ApiConstants.DESCRIPTION)
+    @Param(description = "Description of the provider registered")
+    private String description;
+
+    @SerializedName(ApiConstants.CLIENT_ID)
+    @Param(description = "Client ID registered in the OAuth provider")
+    private String clientId;
+
+    @SerializedName(ApiConstants.OAUTH_SECRET_KEY)
+    @Param(description = "Secret key registered in the OAuth provider")
+    private String secretKey;
+
+    @SerializedName(ApiConstants.REDIRECT_URI)
+    @Param(description = "Redirect URI registered in the OAuth provider")
+    private String redirectUri;
+
+    @SerializedName(ApiConstants.ENABLED)
+    @Param(description = "Whether the OAuth provider is enabled or not")
+    private boolean enabled;
+
+    public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri) {
+        this.id = id;
+        this.provider = provider;
+        this.name = provider;
+        this.description = description;
+        this.clientId = clientId;
+        this.secretKey = secretKey;
+        this.redirectUri =  redirectUri;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public void setProvider(String provider) {
+        this.provider = provider;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public void setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java
new file mode 100644
index 0000000..31738ac
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDao.java
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.oauth2.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+
+public interface OauthProviderDao extends GenericDao<OauthProviderVO, Long> {
+
+    public OauthProviderVO findByProvider(String provider);
+
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.java
new file mode 100644
index 0000000..27eea4d
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/dao/OauthProviderDaoImpl.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 org.apache.cloudstack.oauth2.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+
+public class OauthProviderDaoImpl extends GenericDaoBase<OauthProviderVO, Long> implements OauthProviderDao {
+
+    private final SearchBuilder<OauthProviderVO> oauthProviderSearchByName;
+
+    public OauthProviderDaoImpl() {
+        super();
+
+        oauthProviderSearchByName = createSearchBuilder();
+        oauthProviderSearchByName.and("provider", oauthProviderSearchByName.entity().getProvider(), SearchCriteria.Op.EQ);
+        oauthProviderSearchByName.done();
+    }
+
+    @Override
+    public OauthProviderVO findByProvider(String provider) {
+        SearchCriteria<OauthProviderVO> sc = oauthProviderSearchByName.create();
+        sc.setParameters("provider", provider);
+
+        return findOneBy(sc);
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.java
new file mode 100644
index 0000000..e4a7fae
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/github/GithubOAuth2Provider.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
+//the License.  You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied.  See the License for the
+//specific language governing permissions and limitations
+//under the License.
+package org.apache.cloudstack.oauth2.github;
+
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.inject.Inject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GithubOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
+
+    @Inject
+    OauthProviderDao _oauthProviderDao;
+
+    private String accessToken = null;
+
+    @Override
+    public String getName() {
+        return "github";
+    }
+
+    @Override
+    public String getDescription() {
+        return "Github OAuth2 Provider Plugin";
+    }
+
+    @Override
+    public boolean verifyUser(String email, String secretCode) {
+        if (StringUtils.isAnyEmpty(email, secretCode)) {
+            throw new CloudRuntimeException(String.format("Either email or secretcode should not be null/empty"));
+        }
+
+        OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName());
+        if (providerVO == null) {
+            throw new CloudRuntimeException("Github provider is not registered, so user cannot be verified");
+        }
+
+        String verifiedEmail = getUserEmailAddress();
+        if (verifiedEmail == null || !email.equals(verifiedEmail)) {
+            throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
+        }
+
+        clearAccessToken();
+
+        return true;
+    }
+
+    @Override
+    public String verifyCodeAndFetchEmail(String secretCode) {
+        String accessToken = getAccessToken(secretCode);
+        if (accessToken == null) {
+            return null;
+        }
+        return getUserEmailAddress();
+    }
+
+    protected String getAccessToken(String secretCode) throws CloudRuntimeException {
+        OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName());
+        String tokenUrl = "https://github.com/login/oauth/access_token";
+        String generatedAccessToken = null;
+        try {
+            URL url = new URL(tokenUrl);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("Content-Type", "application/json");
+            connection.setDoOutput(true);
+
+            String jsonParams = "{\"client_id\":\"" + githubProvider.getClientId() + "\",\"client_secret\":\"" + githubProvider.getSecretKey() + "\",\"code\":\"" + secretCode + "\"}";
+
+            try (OutputStream os = connection.getOutputStream()) {
+                byte[] input = jsonParams.getBytes("utf-8");
+                os.write(input, 0, input.length);
+            }
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode == HttpURLConnection.HTTP_OK) {
+                try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+                    String inputLine;
+                    StringBuilder response = new StringBuilder();
+                    while ((inputLine = in.readLine()) != null) {
+                        response.append(inputLine);
+                    }
+                    String regexPattern = "access_token=([^&]+)";
+                    Pattern pattern = Pattern.compile(regexPattern);
+                    Matcher matcher = pattern.matcher(response);
+                    if (matcher.find()) {
+                        generatedAccessToken = matcher.group(1);
+                    } else {
+                        throw new CloudRuntimeException("Could not fetch access token from the given code");
+                    }
+                }
+            } else {
+                throw new CloudRuntimeException("HTTP Request while fetching access token from github failed with error code: " + responseCode);
+            }
+        } catch (IOException e) {
+            throw new CloudRuntimeException(String.format("Error while trying to fetch the github access token : %s", e.getMessage()));
+        }
+
+        accessToken = generatedAccessToken;
+        return accessToken;
+    }
+
+    public String getUserEmailAddress() throws CloudRuntimeException {
+        if (accessToken == null) {
+            throw new CloudRuntimeException("Access Token not found to fetch the email address");
+        }
+
+        String apiUrl = "https://api.github.com/user/emails";
+        String email = null;
+        try {
+            URL url = new URL(apiUrl);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setRequestProperty("Authorization", "token " + accessToken);
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode == HttpURLConnection.HTTP_OK) {
+                try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+                    String inputLine;
+                    StringBuilder response = new StringBuilder();
+                    while ((inputLine = in.readLine()) != null) {
+                        response.append(inputLine);
+                    }
+
+                    try {
+                        ObjectMapper objectMapper = new ObjectMapper();
+                        JsonNode jsonNode = objectMapper.readTree(response.toString());
+                        if (jsonNode != null  && jsonNode.isArray()) {
+                            JsonNode firstObject = jsonNode.get(0);
+                            email = firstObject.get("email").asText();
+                        } else {
+                            throw new CloudRuntimeException("Invalid JSON format found while accessing email from github");
+                        }
+                    } catch (Exception e) {
+                        throw new CloudRuntimeException(String.format("Error occurred while accessing email from github: %s", e.getMessage()));
+                    }                }
+            } else {
+                throw new CloudRuntimeException(String.format("HTTP Request Failed with error code: %s", responseCode));
+            }
+        } catch (IOException e) {
+            throw new CloudRuntimeException(String.format("Error while trying to fetch email from github : %s", e.getMessage()));
+        }
+
+        return email;
+    }
+
+    private void clearAccessToken() {
+        accessToken = null;
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java
new file mode 100644
index 0000000..aa0fc93
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2Provider.java
@@ -0,0 +1,141 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements.  See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership.  The 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.oauth2.google;
+
+import com.cloud.exception.CloudAuthenticationException;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
+import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.oauth2.Oauth2;
+import com.google.api.services.oauth2.model.Userinfo;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
+    private static final Logger s_logger = Logger.getLogger(GoogleOAuth2Provider.class);
+
+    protected String accessToken = null;
+    protected String refreshToken = null;
+
+    @Inject
+    OauthProviderDao _oauthProviderDao;
+
+    @Override
+    public String getName() {
+        return "google";
+    }
+
+    @Override
+    public String getDescription() {
+        return "Google OAuth2 Provider Plugin";
+    }
+
+    @Override
+    public boolean verifyUser(String email, String secretCode) {
+        if (StringUtils.isAnyEmpty(email, secretCode)) {
+            throw new CloudAuthenticationException("Either email or secret code should not be null/empty");
+        }
+
+        OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName());
+        if (providerVO == null) {
+            throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified");
+        }
+
+        String verifiedEmail = verifyCodeAndFetchEmail(secretCode);
+        if (verifiedEmail == null || !email.equals(verifiedEmail)) {
+            throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
+        }
+        clearAccessAndRefreshTokens();
+
+        return true;
+    }
+
+    @Override
+    public String verifyCodeAndFetchEmail(String secretCode) {
+        OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName());
+        String clientId = githubProvider.getClientId();
+        String secret = githubProvider.getSecretKey();
+        String redirectURI = githubProvider.getRedirectUri();
+        GoogleClientSecrets clientSecrets = new GoogleClientSecrets()
+                .setWeb(new GoogleClientSecrets.Details()
+                        .setClientId(clientId)
+                        .setClientSecret(secret));
+
+        NetHttpTransport httpTransport = new NetHttpTransport();
+        JsonFactory jsonFactory = new JacksonFactory();
+        List<String> scopes = Arrays.asList(
+                                "https://www.googleapis.com/auth/userinfo.profile",
+                                "https://www.googleapis.com/auth/userinfo.email");
+        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
+                httpTransport, jsonFactory, clientSecrets, scopes)
+                .build();
+
+        if (StringUtils.isAnyEmpty(accessToken, refreshToken)) {
+            GoogleTokenResponse tokenResponse = null;
+            try {
+                tokenResponse = flow.newTokenRequest(secretCode)
+                        .setRedirectUri(redirectURI)
+                        .execute();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            accessToken = tokenResponse.getAccessToken();
+            refreshToken = tokenResponse.getRefreshToken();
+        }
+
+        GoogleCredential credential = new GoogleCredential.Builder()
+                .setTransport(httpTransport)
+                .setJsonFactory(jsonFactory)
+                .setClientSecrets(clientSecrets)
+                .build()
+                .setAccessToken(accessToken)
+                .setRefreshToken(refreshToken);
+
+        Oauth2 oauth2 = new Oauth2.Builder(httpTransport, jsonFactory, credential).build();
+        Userinfo userinfo = null;
+        try {
+            userinfo = oauth2.userinfo().get().execute();
+        } catch (IOException e) {
+            throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s" + e.getMessage()));
+        }
+        return userinfo.getEmail();
+    }
+
+    protected void clearAccessAndRefreshTokens() {
+        accessToken = null;
+        refreshToken = null;
+    }
+
+    @Override
+    public String getUserEmailAddress() throws CloudRuntimeException {
+        return null;
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.java
new file mode 100644
index 0000000..efd6004
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/java/org/apache/cloudstack/oauth2/vo/OauthProviderVO.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.oauth2.vo;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "oauth_provider")
+public class OauthProviderVO implements Identity, InternalIdentity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "provider")
+    private String provider;
+
+    @Column(name = "client_id")
+    private String clientId;
+
+    @Column(name = "secret_key")
+    private String secretKey;
+
+    @Column(name = "redirect_uri")
+    private String redirectUri;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    private Date created;
+
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    private Date removed;
+
+    @Column(name = "enabled")
+    private boolean enabled = true;
+
+    public OauthProviderVO () {
+        uuid = UUID.randomUUID().toString();
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public void setProvider(String provider) {
+        this.provider = provider;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public void setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/main/resources/META-INF/cloudstack/oauth2/module.properties b/plugins/user-authenticators/oauth2/src/main/resources/META-INF/cloudstack/oauth2/module.properties
new file mode 100644
index 0000000..17844de
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/resources/META-INF/cloudstack/oauth2/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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=oauth2
+parent=api
diff --git a/plugins/user-authenticators/oauth2/src/main/resources/META-INF/cloudstack/oauth2/spring-oauth2-context.xml b/plugins/user-authenticators/oauth2/src/main/resources/META-INF/cloudstack/oauth2/spring-oauth2-context.xml
new file mode 100644
index 0000000..04a6c8d
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/main/resources/META-INF/cloudstack/oauth2/spring-oauth2-context.xml
@@ -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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
+    xmlns:aop="http://www.springframework.org/schema/aop"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+                      http://www.springframework.org/schema/context
+                      http://www.springframework.org/schema/context/spring-context.xsd">
+
+    <bean id="OauthProviderDao" class="org.apache.cloudstack.oauth2.dao.OauthProviderDaoImpl" />
+    <bean id="OAuth2UserAuthenticator" class="org.apache.cloudstack.oauth2.OAuth2UserAuthenticator">
+        <property name="name" value="oauth2"/>
+    </bean>
+    <bean id="GoogleOAuth2Provider" class="org.apache.cloudstack.oauth2.google.GoogleOAuth2Provider">
+        <property name="name" value="google" />
+    </bean>
+    <bean id="GithubOAuth2Provider" class="org.apache.cloudstack.oauth2.github.GithubOAuth2Provider">
+        <property name="name" value="github" />
+    </bean>
+
+    <bean id="OAuth2AuthManager" class="org.apache.cloudstack.oauth2.OAuth2AuthManagerImpl">
+        <property name="name" value="OAUTH2Auth" />
+        <property name="userOAuth2AuthenticationProviders" value="#{userOAuth2AuthenticatorsRegistry.registered}" />
+    </bean>
+
+    <bean id="userOAuth2AuthenticatorsRegistry"
+          class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
+        <property name="orderConfigKey" value="user.oauth2.providers.order" />
+        <property name="excludeKey" value="oauth2.plugins.exclude" />
+        <property name="orderConfigDefault" value="google,github" />
+    </bean>
+
+    <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
+        <property name="registry" ref="userOAuth2AuthenticatorsRegistry" />
+        <property name="typeClass"
+                  value="org.apache.cloudstack.auth.UserOAuth2Authenticator" />
+    </bean>
+</beans>
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.java
new file mode 100644
index 0000000..3fd5636
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2AuthManagerImplTest.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 org.apache.cloudstack.oauth2;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.oauth2.api.command.DeleteOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
+import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+public class OAuth2AuthManagerImplTest {
+
+    @Spy
+    @InjectMocks
+    private OAuth2AuthManagerImpl _authManager;
+
+    @Mock
+    OauthProviderDao _oauthProviderDao;
+
+    AutoCloseable closeable;
+    @Before
+    public void setUp() {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testRegisterOauthProvider() {
+        when(_authManager.isOAuthPluginEnabled()).thenReturn(false);
+        RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
+        try {
+            _authManager.registerOauthProvider(cmd);
+            Assert.fail("Expected CloudRuntimeException was not thrown");
+        } catch (CloudRuntimeException e) {
+            assertEquals("OAuth is not enabled, please enable to register", e.getMessage());
+        }
+
+        // Test when provider is already registered
+        when(_authManager.isOAuthPluginEnabled()).thenReturn(true);
+        OauthProviderVO providerVO = new OauthProviderVO();
+        providerVO.setProvider("testProvider");
+        when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO);
+        when(cmd.getProvider()).thenReturn("testProvider");
+
+        try {
+            _authManager.registerOauthProvider(cmd);
+            Assert.fail("Expected CloudRuntimeException was not thrown");
+        } catch (CloudRuntimeException e) {
+            assertEquals("Provider with the name testProvider is already registered", e.getMessage());
+        }
+
+        // Test when provider is github and secret key is not null
+        when(cmd.getSecretKey()).thenReturn("testSecretKey");
+        providerVO = null;
+        when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO);
+        OauthProviderVO savedProviderVO = new OauthProviderVO();
+        when(cmd.getProvider()).thenReturn("github");
+        when(_authManager._oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenReturn(savedProviderVO);
+        OauthProviderVO result = _authManager.registerOauthProvider(cmd);
+        assertEquals("github", result.getProvider());
+        assertEquals("testSecretKey", result.getSecretKey());
+    }
+
+    @Test
+    public void testUpdateOauthProvider() {
+        Long id = 1L;
+        String description = "updated description";
+        String clientId = "updated client id";
+        String redirectUri = "updated redirect uri";
+        String secretKey = "updated secret key";
+
+        UpdateOAuthProviderCmd cmd = Mockito.mock(UpdateOAuthProviderCmd.class);
+        when(cmd.getId()).thenReturn(id);
+        when(cmd.getDescription()).thenReturn(description);
+        when(cmd.getClientId()).thenReturn(clientId);
+        when(cmd.getRedirectUri()).thenReturn(redirectUri);
+        when(cmd.getSecretKey()).thenReturn(secretKey);
+
+        OauthProviderVO providerVO = new OauthProviderVO();
+        providerVO.setDescription("old description");
+        providerVO.setClientId("old client id");
+        providerVO.setRedirectUri("old redirect uri");
+        providerVO.setSecretKey("old secret key");
+
+        when(_oauthProviderDao.findById(id)).thenReturn(providerVO);
+
+        OauthProviderVO updatedProviderVO = new OauthProviderVO();
+        updatedProviderVO.setDescription(description);
+        updatedProviderVO.setClientId(clientId);
+        updatedProviderVO.setRedirectUri(redirectUri);
+        updatedProviderVO.setSecretKey(secretKey);
+
+        when(_oauthProviderDao.update(id, providerVO)).thenReturn(true);
+
+        OauthProviderVO result = _authManager.updateOauthProvider(cmd);
+
+        assertEquals(description, result.getDescription());
+        assertEquals(clientId, result.getClientId());
+        assertEquals(redirectUri, result.getRedirectUri());
+        assertEquals(secretKey, result.getSecretKey());
+    }
+
+    @Test
+    public void testListOauthProviders() {
+        String uuid = "1234-5678-9101";
+        String provider = "testProvider";
+        OauthProviderVO providerVO = new OauthProviderVO();
+        providerVO.setProvider(provider);
+        List<OauthProviderVO> providerList = Collections.singletonList(providerVO);
+
+        // Test when uuid is not null
+        when(_oauthProviderDao.findByUuid(uuid)).thenReturn(providerVO);
+        List<OauthProviderVO> result = _authManager.listOauthProviders(null, uuid);
+        assertEquals(providerList, result);
+
+        // Test when provider is not blank
+        when(_oauthProviderDao.findByProvider(provider)).thenReturn(providerVO);
+        result = _authManager.listOauthProviders(provider, null);
+        assertEquals(providerList, result);
+
+        // Test when both uuid and provider are null
+        when(_oauthProviderDao.listAll()).thenReturn(providerList);
+        result = _authManager.listOauthProviders(null, null);
+        assertEquals(providerList, result);
+    }
+
+    @Test
+    public void testGetCommands() {
+        List<Class<?>> expectedCmdList = new ArrayList<>();
+        expectedCmdList.add(RegisterOAuthProviderCmd.class);
+        expectedCmdList.add(DeleteOAuthProviderCmd.class);
+        expectedCmdList.add(UpdateOAuthProviderCmd.class);
+
+        List<Class<?>> cmdList = _authManager.getCommands();
+
+        assertEquals(expectedCmdList, cmdList);
+    }
+
+    @Test
+    public void testStart() {
+        when(_authManager.isOAuthPluginEnabled()).thenReturn(true);
+        doNothing().when(_authManager).initializeUserOAuth2AuthenticationProvidersMap();
+        boolean result = _authManager.start();
+        assertTrue(result);
+
+        when(_authManager.isOAuthPluginEnabled()).thenReturn(false);
+        result = _authManager.start();
+        assertTrue(result);
+    }
+
+}
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java
new file mode 100644
index 0000000..06aa04d
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/OAuth2UserAuthenticatorTest.java
@@ -0,0 +1,153 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package org.apache.cloudstack.oauth2;
+
+import com.cloud.user.UserAccount;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.Pair;
+import org.apache.cloudstack.auth.UserOAuth2Authenticator;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class OAuth2UserAuthenticatorTest {
+
+    @Mock
+    private UserAccountDao userAccountDao;
+
+    @Mock
+    private UserDao userDao;
+
+    @Mock
+    private OAuth2AuthManager userOAuth2mgr;
+
+    @InjectMocks
+    private OAuth2UserAuthenticator authenticator;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testAuthenticateWithValidCredentials() {
+        String username = "testuser";
+        Long domainId = 1L;
+        String[] provider = {"testprovider"};
+        String[] email = {"testemail"};
+        String[] secretCode = {"testsecretcode"};
+
+        UserAccount userAccount = mock(UserAccount.class);
+        UserVO user = mock(UserVO.class);
+        UserOAuth2Authenticator userOAuth2Authenticator = mock(UserOAuth2Authenticator.class);
+
+        when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount);
+        when(userDao.getUser(userAccount.getId())).thenReturn(user);
+        when(userOAuth2mgr.getUserOAuth2AuthenticationProvider(provider[0])).thenReturn(userOAuth2Authenticator);
+        when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0])).thenReturn(true);
+
+        Map<String, Object[]> requestParameters = new HashMap<>();
+        requestParameters.put("provider", provider);
+        requestParameters.put("email", email);
+        requestParameters.put("secretcode", secretCode);
+
+        Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result = authenticator.authenticate(username, null, domainId, requestParameters);
+
+        verify(userAccountDao).getUserAccount(username, domainId);
+        verify(userDao).getUser(userAccount.getId());
+        verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]);
+        verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]);
+
+        assertEquals(true, result.first().booleanValue());
+        assertEquals(null, result.second());
+    }
+
+    @Test
+    public void testAuthenticateWithInvalidCredentials() {
+        String username = "testuser";
+        Long domainId = 1L;
+        String[] provider = {"testprovider"};
+        String[] email = {"testemail"};
+        String[] secretCode = {"testsecretcode"};
+
+        UserAccount userAccount = mock(UserAccount.class);
+        UserVO user = mock(UserVO.class);
+        UserOAuth2Authenticator userOAuth2Authenticator = mock(UserOAuth2Authenticator.class);
+
+        when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount);
+        when(userDao.getUser(userAccount.getId())).thenReturn( user);
+        when(userOAuth2mgr.getUserOAuth2AuthenticationProvider(provider[0])).thenReturn(userOAuth2Authenticator);
+        when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0])).thenReturn(false);
+
+        Map<String, Object[]> requestParameters = new HashMap<>();
+        requestParameters.put("provider", provider);
+        requestParameters.put("email", email);
+        requestParameters.put("secretcode", secretCode);
+
+        Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result = authenticator.authenticate(username, null, domainId, requestParameters);
+
+        verify(userAccountDao).getUserAccount(username, domainId);
+        verify(userDao).getUser(userAccount.getId());
+        verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]);
+        verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]);
+
+        assertEquals(false, result.first().booleanValue());
+        assertEquals(OAuth2UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT, result.second());
+    }
+
+    @Test
+    public void testAuthenticateWithInvalidUserAccount() {
+        String username = "testuser";
+        Long domainId = 1L;
+        String[] provider = {"testprovider"};
+        String[] email = {"testemail"};
+        String[] secretCode = {"testsecretcode"};
+
+        when(userAccountDao.getUserAccount(username, domainId)).thenReturn(null);
+
+        Map<String, Object[]> requestParameters = new HashMap<>();
+        requestParameters.put("provider", provider);
+        requestParameters.put("email", email);
+        requestParameters.put("secretcode", secretCode);
+
+        Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result = authenticator.authenticate(username, null, domainId, requestParameters);
+
+        verify(userAccountDao).getUserAccount(username, domainId);
+        verify(userDao, never()).getUser(anyLong());
+        verify(userOAuth2mgr, never()).getUserOAuth2AuthenticationProvider(anyString());
+
+        assertEquals(false, result.first().booleanValue());
+        assertEquals(null, result.second());
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/DeleteOAuthProviderCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/DeleteOAuthProviderCmdTest.java
new file mode 100644
index 0000000..be8670c
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/DeleteOAuthProviderCmdTest.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.oauth2.api.command;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+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.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DeleteOAuthProviderCmdTest {
+
+    @Mock
+    private OAuth2AuthManager _oauthMgr;
+
+    @InjectMocks
+    private DeleteOAuthProviderCmd cmd;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testExecuteFailure() {
+        when(_oauthMgr.deleteOauthProvider(cmd.getId())).thenReturn(false);
+        cmd.execute();
+    }
+
+    @Test
+    public void testExecuteSuccess() {
+        when(_oauthMgr.deleteOauthProvider(cmd.getId())).thenReturn(true);
+        cmd.execute();
+    }
+
+    @Test
+    public void testGetApiResourceType() {
+        assert (cmd.getApiResourceType() == org.apache.cloudstack.api.ApiCommandResourceType.User);
+    }
+
+    @Test
+    public void testDeleteOAuthProvider() {
+        when(_oauthMgr.deleteOauthProvider(null)).thenReturn(true);
+        cmd.execute();
+
+        assertTrue(cmd.getResponseObject() instanceof SuccessResponse);
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testDeleteOAuthProviderExpectFailure() {
+        when(_oauthMgr.deleteOauthProvider(null)).thenReturn(false);
+        cmd.execute();
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmdTest.java
new file mode 100644
index 0000000..07df66f
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/OauthLoginAPIAuthenticatorCmdTest.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.oauth2.api.command;
+
+import com.cloud.api.ApiServer;
+import org.apache.cloudstack.api.ApiConstants;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class OauthLoginAPIAuthenticatorCmdTest {
+    @InjectMocks
+    private OauthLoginAPIAuthenticatorCmd cmd;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+    @Test
+    public void testGetDomainNameWhenDomainNameIsNull() {
+        StringBuilder auditTrailSb = new StringBuilder();
+        String[] domainName = null;
+        String domain = cmd.getDomainName(auditTrailSb, domainName);
+        assertNull(domain);
+        assertEquals("", auditTrailSb.toString());
+    }
+
+    @Test
+    public void testGetDomainNameWithStartingSlash() {
+        StringBuilder auditTrailSb = new StringBuilder();
+        String[] domainName = {"/example"};
+        String domain = cmd.getDomainName(auditTrailSb, domainName);
+        assertEquals("/example/", domain);
+        assertEquals(" domain=/example", auditTrailSb.toString());
+    }
+
+    @Test
+    public void testGetDomainNameWithEndingSlash() {
+        StringBuilder auditTrailSb = new StringBuilder();
+        String[] domainName = {"example/"};
+        String domain = cmd.getDomainName(auditTrailSb, domainName);
+        assertEquals("/example/", domain);
+        assertEquals(" domain=example/", auditTrailSb.toString());
+    }
+
+    @Test
+    public void testGetDomainIdFromParams() {
+        StringBuilder auditTrailSb = new StringBuilder();
+        String responseType = "json";
+        Map<String, Object[]> params = new HashMap<>();
+        params.put(ApiConstants.DOMAIN_ID, new String[]{"1234"});
+        ApiServer apiServer = mock(ApiServer.class);
+        cmd._apiServer = apiServer;
+        when(apiServer.fetchDomainId("1234")).thenReturn(5678L);
+
+        Long domainId = cmd.getDomainIdFromParams(params, auditTrailSb, responseType);
+
+        assertEquals(Long.valueOf(5678), domainId);
+        assertEquals(" domainid=5678", auditTrailSb.toString());
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.java
new file mode 100644
index 0000000..987c7a5
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/RegisterOAuthProviderCmdTest.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.oauth2.api.command;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+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.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RegisterOAuthProviderCmdTest {
+
+    @Mock
+    private OAuth2AuthManager _oauth2mgr;
+
+    @InjectMocks
+    private RegisterOAuthProviderCmd _cmd;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testExecute() throws ServerApiException {
+        OauthProviderVO provider = mock(OauthProviderVO.class);
+        when(_oauth2mgr.registerOauthProvider(_cmd)).thenReturn(provider);
+
+        _cmd.execute();
+        assertEquals(ApiConstants.OAUTH_PROVIDER, ((OauthProviderResponse)_cmd.getResponseObject()).getObjectName());
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.java
new file mode 100644
index 0000000..59245a4
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/api/command/VerifyOAuthCodeAndGetUserCmdTest.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.oauth2.api.command;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.oauth2.OAuth2AuthManager;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class VerifyOAuthCodeAndGetUserCmdTest {
+
+    private VerifyOAuthCodeAndGetUserCmd cmd;
+    private OAuth2AuthManager oauth2mgr;
+    private HttpSession session;
+    private InetAddress remoteAddress;
+    private StringBuilder auditTrailSb;
+    private HttpServletRequest req;
+    private HttpServletResponse resp;
+
+    @Before
+    public void setUp() {
+        cmd = new VerifyOAuthCodeAndGetUserCmd();
+        oauth2mgr = mock(OAuth2AuthManager.class);
+        session = mock(HttpSession.class);
+        remoteAddress = mock(InetAddress.class);
+        auditTrailSb = new StringBuilder();
+        req = mock(HttpServletRequest.class);
+        resp = mock(HttpServletResponse.class);
+        cmd._oauth2mgr = oauth2mgr;
+    }
+
+    @Test
+    public void testAuthenticate() {
+        final String[] secretcodeArray = new String[] { "secretcode" };
+        final String[] providerArray = new String[] { "provider" };
+        final String responseType = "json";
+
+        Map<String, Object[]> params = new HashMap<>();
+        params.put("secretcode", secretcodeArray);
+        params.put("provider", providerArray);
+
+        when(oauth2mgr.verifyCodeAndFetchEmail("secretcode", "provider")).thenReturn("test@example.com");
+
+        String response = cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp);
+
+        Assert.assertNotNull(response);
+        Assert.assertTrue(response.contains("test@example.com"));
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testAuthenticateWithInvalidCode() throws Exception {
+        final String[] secretcodeArray = new String[] { "invalidcode" };
+        final String[] providerArray = new String[] { "provider" };
+        final String responseType = "json";
+
+        Map<String, Object[]> params = new HashMap<>();
+        params.put("secretcode", secretcodeArray);
+        params.put("provider", providerArray);
+
+        when(oauth2mgr.verifyCodeAndFetchEmail("invalidcode", "provider")).thenReturn(null);
+
+        cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp);
+    }
+
+    @Test
+    public void testSetAuthenticators() {
+        VerifyOAuthCodeAndGetUserCmd cmd = new VerifyOAuthCodeAndGetUserCmd();
+        OAuth2AuthManager oauth2mgr = mock(OAuth2AuthManager.class);
+        List<PluggableAPIAuthenticator> authenticators = new ArrayList<>();
+        authenticators.add(mock(PluggableAPIAuthenticator.class));
+        authenticators.add(oauth2mgr);
+        authenticators.add(null);
+        cmd.setAuthenticators(authenticators);
+        Assert.assertEquals(oauth2mgr, cmd._oauth2mgr);
+    }
+}
diff --git a/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java
new file mode 100644
index 0000000..b8b1abc
--- /dev/null
+++ b/plugins/user-authenticators/oauth2/src/test/java/org/apache/cloudstack/oauth2/google/GoogleOAuth2ProviderTest.java
@@ -0,0 +1,148 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements.  See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership.  The 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.oauth2.google;
+
+import com.cloud.exception.CloudAuthenticationException;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.api.services.oauth2.Oauth2;
+import com.google.api.services.oauth2.model.Userinfo;
+import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
+import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class GoogleOAuth2ProviderTest {
+
+    @Mock
+    private OauthProviderDao _oauthProviderDao;
+
+    @Spy
+    @InjectMocks
+    private GoogleOAuth2Provider _googleOAuth2Provider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test(expected = CloudAuthenticationException.class)
+    public void testVerifyUserWithNullEmail() {
+        _googleOAuth2Provider.verifyUser(null, "secretCode");
+    }
+
+    @Test(expected = CloudAuthenticationException.class)
+    public void testVerifyUserWithNullSecretCode() {
+        _googleOAuth2Provider.verifyUser("email@example.com", null);
+    }
+
+    @Test(expected = CloudAuthenticationException.class)
+    public void testVerifyUserWithUnregisteredProvider() {
+        when(_oauthProviderDao.findByProvider(anyString())).thenReturn(null);
+        _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testVerifyUserWithInvalidSecretCode() throws IOException {
+        OauthProviderVO providerVO = mock(OauthProviderVO.class);
+        when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
+        when(providerVO.getProvider()).thenReturn("testProvider");
+        when(providerVO.getSecretKey()).thenReturn("testSecret");
+        when(providerVO.getClientId()).thenReturn("testClientid");
+        _googleOAuth2Provider.accessToken = "testAccessToken";
+        _googleOAuth2Provider.refreshToken = "testRefreshToken";
+        Oauth2 oauth2 = mock(Oauth2.class);
+        try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
+                (mock, context) -> when(mock.build()).thenReturn(oauth2))) {
+            Userinfo userinfo = mock(Userinfo.class);
+            Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
+            when(oauth2.userinfo()).thenReturn(userinfo1);
+            Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
+            when(userinfo1.get()).thenReturn(userinfoGet);
+            when(userinfoGet.execute()).thenReturn(userinfo);
+            when(userinfo.getEmail()).thenReturn(null);
+
+            _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+        }
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testVerifyUserWithMismatchedEmail() throws IOException {
+        OauthProviderVO providerVO = mock(OauthProviderVO.class);
+        when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
+        when(providerVO.getProvider()).thenReturn("testProvider");
+        when(providerVO.getSecretKey()).thenReturn("testSecret");
+        when(providerVO.getClientId()).thenReturn("testClientid");
+        _googleOAuth2Provider.accessToken = "testAccessToken";
+        _googleOAuth2Provider.refreshToken = "testRefreshToken";
+        Oauth2 oauth2 = mock(Oauth2.class);
+        try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
+                (mock, context) -> when(mock.build()).thenReturn(oauth2))) {
+            Userinfo userinfo = mock(Userinfo.class);
+            Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
+            when(oauth2.userinfo()).thenReturn(userinfo1);
+            Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
+            when(userinfo1.get()).thenReturn(userinfoGet);
+            when(userinfoGet.execute()).thenReturn(userinfo);
+            when(userinfo.getEmail()).thenReturn("otheremail@example.com");
+
+            _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+        }
+    }
+
+    @Test
+    public void testVerifyUserEmail() throws IOException {
+        OauthProviderVO providerVO = mock(OauthProviderVO.class);
+        when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
+        when(providerVO.getProvider()).thenReturn("testProvider");
+        when(providerVO.getSecretKey()).thenReturn("testSecret");
+        when(providerVO.getClientId()).thenReturn("testClientid");
+        _googleOAuth2Provider.accessToken = "testAccessToken";
+        _googleOAuth2Provider.refreshToken = "testRefreshToken";
+        Oauth2 oauth2 = mock(Oauth2.class);
+        try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
+                (mock, context) -> when(mock.build()).thenReturn(oauth2))) {
+            Userinfo userinfo = mock(Userinfo.class);
+            Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
+            when(oauth2.userinfo()).thenReturn(userinfo1);
+            Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
+            when(userinfo1.get()).thenReturn(userinfoGet);
+            when(userinfoGet.execute()).thenReturn(userinfo);
+            when(userinfo.getEmail()).thenReturn("email@example.com");
+
+            boolean result = _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
+
+            assertTrue(result);
+            assertNull(_googleOAuth2Provider.accessToken);
+            assertNull(_googleOAuth2Provider.refreshToken);
+        }
+    }
+}
diff --git a/plugins/user-authenticators/pbkdf2/pom.xml b/plugins/user-authenticators/pbkdf2/pom.xml
index f22e813..fc1211e 100644
--- a/plugins/user-authenticators/pbkdf2/pom.xml
+++ b/plugins/user-authenticators/pbkdf2/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/user-authenticators/plain-text/pom.xml b/plugins/user-authenticators/plain-text/pom.xml
index 5a087b9..1059528 100644
--- a/plugins/user-authenticators/plain-text/pom.xml
+++ b/plugins/user-authenticators/plain-text/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/module.properties b/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/module.properties
index 5a29563..1ff77da 100644
--- a/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/module.properties
+++ b/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=plaintext
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml
index 93340b4..6a72761 100644
--- a/plugins/user-authenticators/saml2/pom.xml
+++ b/plugins/user-authenticators/saml2/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/plugins/user-authenticators/sha256salted/pom.xml b/plugins/user-authenticators/sha256salted/pom.xml
index b1fe03d..4f1ab61 100644
--- a/plugins/user-authenticators/sha256salted/pom.xml
+++ b/plugins/user-authenticators/sha256salted/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/module.properties b/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/module.properties
index c70a2f5..ae81f5a 100644
--- a/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/module.properties
+++ b/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=sha256salted
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/user-two-factor-authenticators/static-pin/pom.xml b/plugins/user-two-factor-authenticators/static-pin/pom.xml
index 0f45033..eeee9a2 100644
--- a/plugins/user-two-factor-authenticators/static-pin/pom.xml
+++ b/plugins/user-two-factor-authenticators/static-pin/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
-</project>
\ No newline at end of file
+</project>
diff --git a/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/staticpin/module.properties b/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/staticpin/module.properties
index 14deddd..2c92233 100644
--- a/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/staticpin/module.properties
+++ b/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/staticpin/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=staticpin
-parent=api
\ No newline at end of file
+parent=api
diff --git a/plugins/user-two-factor-authenticators/totp/pom.xml b/plugins/user-two-factor-authenticators/totp/pom.xml
index b0532ad..1d6bfab 100644
--- a/plugins/user-two-factor-authenticators/totp/pom.xml
+++ b/plugins/user-two-factor-authenticators/totp/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-plugins</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
-</project>
\ No newline at end of file
+</project>
diff --git a/plugins/user-two-factor-authenticators/totp/src/main/resources/META-INF/cloudstack/totp/module.properties b/plugins/user-two-factor-authenticators/totp/src/main/resources/META-INF/cloudstack/totp/module.properties
index 1b735ac..9b37b69 100644
--- a/plugins/user-two-factor-authenticators/totp/src/main/resources/META-INF/cloudstack/totp/module.properties
+++ b/plugins/user-two-factor-authenticators/totp/src/main/resources/META-INF/cloudstack/totp/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=totp
-parent=api
\ No newline at end of file
+parent=api
diff --git a/pom.xml b/pom.xml
index bbbb3d5..3151c9f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <groupId>org.apache.cloudstack</groupId>
     <artifactId>cloudstack</artifactId>
-    <version>4.18.3.0-SNAPSHOT</version>
+    <version>4.19.1.0-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>Apache CloudStack</name>
     <description>Apache CloudStack is an IaaS ("Infrastructure as a Service") cloud orchestration platform.</description>
@@ -50,7 +50,7 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <project.systemvm.template.location>https://download.cloudstack.org/systemvm</project.systemvm.template.location>
-        <project.systemvm.template.version>4.18.1.0</project.systemvm.template.version>
+        <project.systemvm.template.version>4.19.0.0</project.systemvm.template.version>
         <sonar.organization>apache</sonar.organization>
         <sonar.host.url>https://sonarcloud.io</sonar.host.url>
 
@@ -64,7 +64,7 @@
         <cs.antrun-plugin.version>1.8</cs.antrun-plugin.version>
         <cs.builder-helper-plugin.version>3.0.0</cs.builder-helper-plugin.version>
         <cs.checkstyle-plugin.version>3.1.0</cs.checkstyle-plugin.version>
-        <cs.jacoco-plugin.version>0.8.8</cs.jacoco-plugin.version>
+        <cs.jacoco-plugin.version>0.8.11</cs.jacoco-plugin.version>
         <cs.compiler-plugin.version>3.8.1</cs.compiler-plugin.version>
         <cs.dependency-plugin.version>3.1.1</cs.dependency-plugin.version>
         <cs.failsafe-plugin.version>2.22.2</cs.failsafe-plugin.version>
@@ -114,8 +114,7 @@
         <cs.junit.dataprovider.version>1.13.1</cs.junit.dataprovider.version>
         <cs.junit.jupiter.version>5.9.1</cs.junit.jupiter.version>
         <cs.guava-testlib.version>18.0</cs.guava-testlib.version>
-        <cs.mockito.version>3.2.4</cs.mockito.version>
-        <cs.powermock.version>2.0.5</cs.powermock.version>
+        <cs.mockito.version>3.12.4</cs.mockito.version>
         <cs.selenium.server.version>1.0-20081010.060147</cs.selenium.server.version>
         <cs.selenium-java-client-driver.version>1.0.1</cs.selenium-java-client-driver.version>
         <cs.testng.version>7.1.0</cs.testng.version>
@@ -134,6 +133,7 @@
         <cs.bcprov.version>1.70</cs.bcprov.version>
         <cs.cglib.version>3.3.0</cs.cglib.version>
         <cs.checkstyle-lib.version>8.18</cs.checkstyle-lib.version>
+        <cs.cron-utils.version>9.2.0</cs.cron-utils.version>
         <cs.cxf.version>3.2.14</cs.cxf.version>
         <cs.ehcache.version>2.6.11</cs.ehcache.version>
         <cs.globodns-client.version>0.0.27</cs.globodns-client.version>
@@ -164,7 +164,7 @@
         <cs.kafka-clients.version>2.7.0</cs.kafka-clients.version>
         <cs.libvirt-java.version>0.5.3</cs.libvirt-java.version>
         <cs.mail.version>1.5.0-b01</cs.mail.version>
-        <cs.mysql.version>8.0.19</cs.mysql.version>
+        <cs.mysql.version>8.0.33</cs.mysql.version>
         <cs.neethi.version>2.0.4</cs.neethi.version>
         <cs.nitro.version>10.1</cs.nitro.version>
         <cs.opensaml.version>2.6.6</cs.opensaml.version>
@@ -453,8 +453,8 @@
                 <version>${cs.reload4j.version}</version>
             </dependency>
             <dependency>
-                <groupId>mysql</groupId>
-                <artifactId>mysql-connector-java</artifactId>
+                <groupId>com.mysql</groupId>
+                <artifactId>mysql-connector-j</artifactId>
                 <version>${cs.mysql.version}</version>
                 <scope>test</scope>
             </dependency>
@@ -699,6 +699,11 @@
                 <artifactId>xml-apis</artifactId>
                 <version>2.0.2</version>
             </dependency>
+            <dependency>
+                <groupId>com.linbit.linstor.api</groupId>
+                <artifactId>java-linstor</artifactId>
+                <version>${cs.java-linstor.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -741,24 +746,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.powermock</groupId>
-            <artifactId>powermock-core</artifactId>
-            <version>${cs.powermock.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.powermock</groupId>
-            <artifactId>powermock-module-junit4</artifactId>
-            <version>${cs.powermock.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.powermock</groupId>
-            <artifactId>powermock-api-mockito2</artifactId>
-            <version>${cs.powermock.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-test</artifactId>
             <version>${org.springframework.version}</version>
@@ -1063,6 +1050,7 @@
                             <exclude>ui/public/**</exclude>
                             <exclude>ui/legacy/**</exclude>
                             <exclude>utils/testsmallfileinactive</exclude>
+                            <exclude>**/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker</exclude>
                         </excludes>
                     </configuration>
                 </plugin>
diff --git a/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
index 3921484..4944fc0 100755
--- a/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
+++ b/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
@@ -93,4 +93,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
index 23ec8f3..28d5a11 100755
--- a/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
+++ b/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
@@ -93,4 +93,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in b/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in
index 51b4f58..de68534 100755
--- a/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in
+++ b/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in
@@ -113,4 +113,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
index 23ec8f3..28d5a11 100644
--- a/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
+++ b/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in
@@ -93,4 +93,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in b/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in
index 51b4f58..de68534 100755
--- a/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in
+++ b/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in
@@ -113,4 +113,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in b/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in
index e2cb361..4acb11a 100755
--- a/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in
+++ b/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in
@@ -107,4 +107,3 @@
 esac
 
 exit $RETVAL
-
diff --git a/quickcloud/pom.xml b/quickcloud/pom.xml
index 001de0c..028b1b3 100644
--- a/quickcloud/pom.xml
+++ b/quickcloud/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 </project>
diff --git a/requirements.txt b/requirements.txt
index 187beba..96f8c9c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,10 +15,9 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Install the latest version of cloudmonkey
-cloudmonkey
-
 # Marvin dependencies are installed via its bundle
 
+pre-commit
+
 # Install the SolidFire SDK for Python
 solidfire-sdk-python
diff --git a/scripts/installer/cloudstack-help-text b/scripts/installer/cloudstack-help-text
index 75327cf..1231464 100755
--- a/scripts/installer/cloudstack-help-text
+++ b/scripts/installer/cloudstack-help-text
@@ -21,7 +21,7 @@
 
 case $1 in
     management)
-        PACKAGE="Managment Server"
+        PACKAGE="Management Server"
         ;;
     usage)
         PACKAGE="Usage Server"
diff --git a/scripts/installer/export-templates.sh b/scripts/installer/export-templates.sh
index 9371f1c..dbd560e 100644
--- a/scripts/installer/export-templates.sh
+++ b/scripts/installer/export-templates.sh
@@ -189,4 +189,3 @@
 else
   echo "Conversion of template to $1's compatible format not supported "
 fi
-
diff --git a/scripts/network/juniper/dest-nat-rule-getone.xml b/scripts/network/juniper/dest-nat-rule-getone.xml
index 992dc0e..9dd2a1b 100644
--- a/scripts/network/juniper/dest-nat-rule-getone.xml
+++ b/scripts/network/juniper/dest-nat-rule-getone.xml
@@ -34,5 +34,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
-
diff --git a/scripts/network/juniper/firewall-filter-bytes-getall.xml b/scripts/network/juniper/firewall-filter-bytes-getall.xml
index 9384daf..c5a3728 100644
--- a/scripts/network/juniper/firewall-filter-bytes-getall.xml
+++ b/scripts/network/juniper/firewall-filter-bytes-getall.xml
@@ -20,4 +20,3 @@
 <get-firewall-information>
 </get-firewall-information>
 </rpc>
-
diff --git a/scripts/network/juniper/src-nat-rule-add.xml b/scripts/network/juniper/src-nat-rule-add.xml
index 4093af7..58faf1c 100644
--- a/scripts/network/juniper/src-nat-rule-add.xml
+++ b/scripts/network/juniper/src-nat-rule-add.xml
@@ -44,5 +44,3 @@
 </configuration>
 </load-configuration>
 </rpc>
-
-
diff --git a/scripts/network/juniper/src-nat-rule-getall.xml b/scripts/network/juniper/src-nat-rule-getall.xml
index e04378b..d42d4b6 100644
--- a/scripts/network/juniper/src-nat-rule-getall.xml
+++ b/scripts/network/juniper/src-nat-rule-getall.xml
@@ -28,4 +28,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
diff --git a/scripts/network/juniper/src-nat-rule-getone.xml b/scripts/network/juniper/src-nat-rule-getone.xml
index 999969b..80bcbbc 100644
--- a/scripts/network/juniper/src-nat-rule-getone.xml
+++ b/scripts/network/juniper/src-nat-rule-getone.xml
@@ -34,5 +34,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
-
diff --git a/scripts/network/juniper/static-nat-rule-add.xml b/scripts/network/juniper/static-nat-rule-add.xml
index 8316e63..bc8d53c 100644
--- a/scripts/network/juniper/static-nat-rule-add.xml
+++ b/scripts/network/juniper/static-nat-rule-add.xml
@@ -45,5 +45,3 @@
 </configuration>
 </load-configuration>
 </rpc>
-
-
diff --git a/scripts/network/juniper/static-nat-rule-getall.xml b/scripts/network/juniper/static-nat-rule-getall.xml
index a5d0f0a..11755f3 100644
--- a/scripts/network/juniper/static-nat-rule-getall.xml
+++ b/scripts/network/juniper/static-nat-rule-getall.xml
@@ -28,5 +28,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
-
diff --git a/scripts/network/juniper/static-nat-rule-getone.xml b/scripts/network/juniper/static-nat-rule-getone.xml
index bbec6c3..e2b9102 100644
--- a/scripts/network/juniper/static-nat-rule-getone.xml
+++ b/scripts/network/juniper/static-nat-rule-getone.xml
@@ -34,5 +34,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
-
diff --git a/scripts/network/juniper/test.xml b/scripts/network/juniper/test.xml
index 16bbd79..84fe3a4 100644
--- a/scripts/network/juniper/test.xml
+++ b/scripts/network/juniper/test.xml
@@ -31,4 +31,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
diff --git a/scripts/network/juniper/zone-interface-add.xml b/scripts/network/juniper/zone-interface-add.xml
index 4b93ba3..9b2d372 100644
--- a/scripts/network/juniper/zone-interface-add.xml
+++ b/scripts/network/juniper/zone-interface-add.xml
@@ -32,4 +32,3 @@
 </configuration>
 </load-configuration>
 </rpc>
-
diff --git a/scripts/network/juniper/zone-interface-getone.xml b/scripts/network/juniper/zone-interface-getone.xml
index b0ead3c..4bc5c4b 100644
--- a/scripts/network/juniper/zone-interface-getone.xml
+++ b/scripts/network/juniper/zone-interface-getone.xml
@@ -32,4 +32,3 @@
 </configuration>
 </get-configuration>
 </rpc>
-
diff --git a/scripts/storage/multipath/cleanStaleMaps.sh b/scripts/storage/multipath/cleanStaleMaps.sh
new file mode 100755
index 0000000..90b9bef
--- /dev/null
+++ b/scripts/storage/multipath/cleanStaleMaps.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#############################################################################################
+#
+# Clean old multipath maps that have 0 paths available
+#
+#############################################################################################
+
+cd $(dirname $0)
+
+for WWID in $(multipathd list maps status | awk '{ if ($4 == 0) { print substr($1,2); }}'); do
+  ./removeVolume.sh ${WWID}
+done
+
+exit 0
diff --git a/scripts/storage/multipath/connectVolume.sh b/scripts/storage/multipath/connectVolume.sh
new file mode 100755
index 0000000..fb8387e
--- /dev/null
+++ b/scripts/storage/multipath/connectVolume.sh
@@ -0,0 +1,133 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#####################################################################################
+#
+# Given a lun # and a WWID for a volume provisioned externally, find the volume
+# through the SCSI bus and make sure its visable via multipath
+#
+#####################################################################################
+
+
+LUN=${1:?"LUN required"}
+WWID=${2:?"WWID required"}
+
+WWID=$(echo $WWID | tr '[:upper:]' '[:lower:]')
+
+systemctl is-active multipathd || systemctl restart multipathd || {
+   echo "$(date): Multipathd is NOT running and cannot be started.  This must be corrected before this host can access this storage volume."
+   logger -t "CS_SCSI_VOL_FIND" "${WWID} cannot be mapped to this host because multipathd is not currently running and cannot be started"
+   exit 1
+}
+
+echo "$(date): Looking for ${WWID} on lun ${LUN}"
+
+# get vendor OUI.  we will only delete a device on the designated lun if it matches the
+# incoming WWN OUI value.  This is because multiple storage arrays may be mapped to the
+# host on different fiber channel hosts with the same LUN
+INCOMING_OUI=$(echo ${WWID} | cut -c2-7)
+echo "$(date): Incoming OUI: ${INCOMING_OUI}"
+
+# first we need to check if any stray references are left from a previous use of this lun
+for fchost in $(ls /sys/class/fc_host | sed -e 's/host//g'); do
+   lingering_devs=$(lsscsi -w "${fchost}:*:*:${LUN}" | grep /dev | awk '{if (NF > 6) { printf("%s:%s ", $NF, $(NF-1));} }' | sed -e 's/0x/3/g')
+
+   if [ ! -z "${lingering_devs}" ]; then
+     for dev in ${lingering_devs}; do
+       LSSCSI_WWID=$(echo $dev | awk -F: '{print $2}' | sed -e 's/0x/3/g')
+       FOUND_OUI=$(echo ${LSSCSI_WWID} | cut -c3-8)
+       if [ "${INCOMING_OUI}" != "${FOUND_OUI}" ]; then
+           continue;
+       fi
+       dev=$(echo $dev | awk -F: '{ print $1}')
+       logger -t "CS_SCSI_VOL_FIND" "${WWID} processing identified a lingering device ${dev} from previous lun use, attempting to clean up"
+       MP_WWID=$(multipath -l ${dev} | head -1 | awk '{print $1}')
+       MP_WWID=${MP_WWID:1} # strip first character (3) off
+       # don't do this if the WWID passed in matches the WWID from multipath
+       if [ ! -z "${MP_WWID}" ] && [ "${MP_WWID}" != "${WWID}" ]; then
+          # run full removal again so all devices and multimap are cleared
+          $(dirname $0)/disconnectVolume.sh ${MP_WWID}
+       # we don't have a multimap but we may still have some stranded devices to clean up
+       elif [ "${LSSCSI_WWID}" != "${WWID}" ]; then
+           echo "1" > /sys/block/$(echo ${dev} | awk -F'/' '{print $NF}')/device/delete
+       fi
+     done
+     sleep 3
+   fi
+done
+
+logger -t "CS_SCSI_VOL_FIND" "${WWID} awaiting disk path at /dev/mapper/3${WWID}"
+
+# wait for multipath to map the new lun to the WWID
+echo "$(date): Waiting for multipath entry to show up for the WWID"
+while true; do
+   ls /dev/mapper/3${WWID} >/dev/null 2>&1
+   if [ $? == 0 ]; then
+      break
+   fi
+
+   logger -t "CS_SCSI_VOL_FIND" "${WWID} not available yet, triggering scan"
+
+   # instruct bus to scan for new lun
+   for fchost in $(ls /sys/class/fc_host); do
+      echo "   --> Scanning ${fchost}"
+      echo "- - ${LUN}" > /sys/class/scsi_host/${fchost}/scan
+   done
+
+   multipath -v2 2>/dev/null
+
+   ls /dev/mapper/3${WWID} >/dev/null 2>&1
+   if [ $? == 0 ]; then
+      break
+   fi
+
+   sleep 5
+done
+
+echo "$(date): Doing a recan to make sure we have proper current size locally"
+for device in $(multipath -ll 3${WWID} | egrep '^  ' | awk '{print $2}'); do
+    echo "1" > /sys/bus/scsi/drivers/sd/${device}/rescan;
+done
+
+sleep 3
+
+multipathd reconfigure
+
+sleep 3
+
+# cleanup any old/faulty paths
+delete_needed=false
+multipath -l 3${WWID}
+for dev in $(multipath -l 3${WWID} 2>/dev/null| grep failed  | awk '{print $3}' ); do
+   logger -t "CS_SCSI_VOL_FIND" "${WWID} multipath contains faulty path ${dev}, removing"
+   echo 1 > /sys/block/${dev}/device/delete;
+   delete_needed=true
+done
+
+if [ "${delete_needed}" == "true" ]; then
+    sleep 10
+    multipath -v2 >/dev/null
+fi
+
+multipath -l 3${WWID}
+
+logger -t "CS_SCSI_VOL_FIND" "${WWID} successfully discovered and available"
+
+echo "$(date): Complete - found mapped LUN at /dev/mapper/3${WWID}"
+
+exit 0
diff --git a/scripts/storage/multipath/copyVolume.sh b/scripts/storage/multipath/copyVolume.sh
new file mode 100755
index 0000000..d169198
--- /dev/null
+++ b/scripts/storage/multipath/copyVolume.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+OUTPUT_FORMAT=${1:?"Output format is required"}
+INPUT_FILE=${2:?"Input file/path is required"}
+OUTPUT_FILE=${3:?"Output file/path is required"}
+
+echo "$(date): qemu-img convert -n -p -W -t none -O ${OUTPUT_FORMAT} ${INPUT_FILE} ${OUTPUT_FILE}"
+
+qemu-img convert -n -p -W -t none -O ${OUTPUT_FORMAT} ${INPUT_FILE} ${OUTPUT_FILE} && {
+   # if its a block device make sure we flush caches before exiting
+   lsblk ${OUTPUT_FILE} >/dev/null 2>&1 && {
+      blockdev --flushbufs ${OUTPUT_FILE}
+      hdparm -F ${OUTPUT_FILE}
+   }
+   exit 0
+}
diff --git a/scripts/storage/multipath/disconnectVolume.sh b/scripts/storage/multipath/disconnectVolume.sh
new file mode 100755
index 0000000..067e561
--- /dev/null
+++ b/scripts/storage/multipath/disconnectVolume.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#########################################################################################
+#
+# Given a WWID, cleanup/remove any multipath and devices associated with this WWID.  This
+# may not always have lasting result because if the storage array still has the volume
+# visable to the host, it may be rediscovered.  The cleanupStaleMaps.sh script should
+# catch those cases
+#
+#########################################################################################
+
+WWID=${1:?"WWID required"}
+WWID=$(echo $WWID | tr '[:upper:]' '[:lower:]')
+
+echo "$(date): Removing ${WWID}"
+
+systemctl is-active multipathd || systemctl restart multipathd || {
+   echo "$(date): Multipathd is NOT running and cannot be started.  This must be corrected before this host can access this storage volume."
+   logger -t "CS_SCSI_VOL_REMOVE" "${WWID} cannot be disconnected from this host because multipathd is not currently running and cannot be started"
+   exit 1
+}
+
+# first get dm- name
+DM_NAME=$(ls -lrt /dev/mapper/3${WWID} | awk '{ print $NF }' | awk -F'/' '{print $NF}')
+SLAVE_DEVS=""
+if [ -z "${DM_NAME}" ]; then
+   logger -t CS_SCSI_VOL_REMOVE "${WWID} has no active multimap so no removal performed"
+   logger -t CS_SCSI_VOL_REMOVE "WARN: dm name could not be found for ${WWID}"
+   dmsetup remove /dev/mapper/*${WWID}
+   logger -t CS_SCSI_VOL_REMOVE "${WWID} removal via dmsetup remove /dev/mapper/${WWID} finished with return code $?"
+else
+   # now look for slave devices and save for deletion
+   for dev in $(ls /sys/block/${DM_NAME}/slaves/ 2>/dev/null); do
+      SLAVE_DEVS="${SLAVE_DEVS} ${dev}"
+   done
+fi
+
+# delete the path map last
+multipath -f 3${WWID}
+
+# now delete slave devices
+# https://bugzilla.redhat.com/show_bug.cgi?id=1949369
+if [ ! -z "${SLAVE_DEVS}" ]; then
+  for dev in ${SLAVE_DEVS}; do
+     multipathd del path /dev/${dev}
+     echo "1" > /sys/block/${dev}/device/delete
+     logger -t CS_SCSI_VOL_REMOVE "${WWID} removal of device ${dev} complete"
+  done
+fi
+
+logger -t CS_SCSI_VOL_REMOVE "${WWID} successfully purged from multipath along with slave devices"
+
+echo "$(date): ${WWID} removed"
+
+exit 0
diff --git a/scripts/storage/multipath/resizeVolume.sh b/scripts/storage/multipath/resizeVolume.sh
new file mode 100755
index 0000000..1b44a71
--- /dev/null
+++ b/scripts/storage/multipath/resizeVolume.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+notifyqemu() {
+  if `virsh help 2>/dev/null | grep -q blockresize`
+  then
+    if `virsh domstate $VMNAME >/dev/null 2>&1`
+    then
+      sizeinkb=$(($NEWSIZE/1024))
+      devicepath=$(virsh domblklist $VMNAME | grep ${WWID} | awk '{print $1}')
+      virsh blockresize --path $devicepath --size $sizeinkb ${VMNAME} >/dev/null 2>&1
+      retval=$?
+      if [ -z $retval ] || [ $retval -ne 0 ]
+      then
+        log "failed to live resize $path to size of $sizeinkb kb" 1
+      else
+        liveresize='true'
+      fi
+    fi
+  fi
+}
+
+WWID=${1:?"WWID required"}
+VMNAME=${2:?"VMName required"}
+NEWSIZE=${3:?"New size required in bytes"}
+
+WWID=$(echo $WWID | tr '[:upper:]' '[:lower:]')
+
+export WWID VMNAME NEWSIZE
+
+systemctl is-active multipathd || systemctl restart multipathd || {
+   echo "$(date): Multipathd is NOT running and cannot be started.  This must be corrected before this host can access this storage volume."
+   logger -t "CS_SCSI_VOL_RESIZE" "Unable to notify running VM of resize for ${WWID} because multipathd is not currently running and cannot be started"
+   exit 1
+}
+
+logger -t "CS_SCSI_VOL_RESIZE" "${WWID} resizing disk path at /dev/mapper/3${WWID} STARTING"
+
+for device in $(multipath -ll 3${WWID} | egrep '^  ' | awk '{print $2}'); do
+    echo "1" > /sys/bus/scsi/drivers/sd/${device}/rescan;
+done
+
+sleep 3
+
+multipathd reconfigure
+
+sleep 3
+
+multipath -ll 3${WWID}
+
+notifyqemu
+
+logger -t "CS_SCSI_VOL_RESIZE" "${WWID} resizing disk path at /dev/mapper/3${WWID} COMPLETE"
+
+exit 0
diff --git a/scripts/storage/qcow2/managevolume.sh b/scripts/storage/qcow2/managevolume.sh
index abf8dd6..1f53047 100755
--- a/scripts/storage/qcow2/managevolume.sh
+++ b/scripts/storage/qcow2/managevolume.sh
@@ -176,5 +176,3 @@
 fi
 
 exit 0
-
-
diff --git a/scripts/storage/secondary/createvolume.sh b/scripts/storage/secondary/createvolume.sh
index 91370df..e5838ae 100755
--- a/scripts/storage/secondary/createvolume.sh
+++ b/scripts/storage/secondary/createvolume.sh
@@ -18,8 +18,8 @@
 
  
 
-# $Id: createtmplt.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/secondary/createtmplt.sh $
-# createtmplt.sh -- install a volume
+# $Id: createvolume.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/secondary/createvolume.sh $
+# createvolume.sh -- install a volume
 
 usage() {
   printf "Usage: %s: -t <volume-fs> -n <volumename> -f <root disk file> -c <md5 cksum> -d <descr> -h  [-u] [-v]\n" $(basename $0) >&2
diff --git a/scripts/storage/secondary/setup-sysvm-tmplt b/scripts/storage/secondary/setup-sysvm-tmplt
index 905d0d5..8b65662 100755
--- a/scripts/storage/secondary/setup-sysvm-tmplt
+++ b/scripts/storage/secondary/setup-sysvm-tmplt
@@ -171,4 +171,4 @@
 fi
 
 echo "Successfully installed system VM template $tmpltimg and template.properties to $destdir"
-exit 0
\ No newline at end of file
+exit 0
diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import
index c4ec3be..a7523ca 100755
--- a/scripts/util/keystore-cert-import
+++ b/scripts/util/keystore-cert-import
@@ -49,7 +49,7 @@
 KS_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null  | sed 's/keystore.passphrase=//g' 2>/dev/null)
 
 if [ -z "${KS_PASS// }" ]; then
-    echo "Failed to find keystore passphrase from file: $PROPS_FILE, quiting!"
+    echo "Failed to find keystore passphrase from file: $PROPS_FILE, quitting!"
     exit 1
 fi
 
diff --git a/scripts/vm/hypervisor/kvm/kvmspheartbeat.sh b/scripts/vm/hypervisor/kvm/kvmspheartbeat.sh
new file mode 100755
index 0000000..3cb459e
--- /dev/null
+++ b/scripts/vm/hypervisor/kvm/kvmspheartbeat.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+help() {
+  printf "Usage: $0
+                    -c cleanup"
+  exit 1
+}
+#set -x
+cflag=0
+
+while getopts 'c' OPTION
+do
+  case $OPTION in
+  c)
+    cflag=1
+     ;;
+  *)
+     help
+     ;;
+  esac
+done
+
+
+#delete VMs on this mountpoint
+deleteVMs() {
+  vmPids=$(ps aux| grep qemu | grep 'storpool-byid' | awk '{print $2}' 2> /dev/null)
+  if [ $? -gt 0 ]
+  then
+     return
+  fi
+
+  if [ -z "$vmPids" ]
+  then
+     return
+  fi
+
+  for pid in $vmPids
+  do
+     kill -9 $pid &> /dev/null
+  done
+}
+
+if [ "$cflag" == "1" ]
+then
+  /usr/bin/logger -t heartbeat "kvmspheartbeat.sh will reboot system because it was unable to write the heartbeat to the storage."
+  sync &
+  sleep 5
+  echo b > /proc/sysrq-trigger
+  exit $?
+fi
diff --git a/scripts/vm/hypervisor/kvm/nsrkvmbackup.sh b/scripts/vm/hypervisor/kvm/nsrkvmbackup.sh
index c6e115f..2fcfa0b 100755
--- a/scripts/vm/hypervisor/kvm/nsrkvmbackup.sh
+++ b/scripts/vm/hypervisor/kvm/nsrkvmbackup.sh
@@ -257,4 +257,4 @@
 
  backup_domain "$kvmDName" "$snapPrefix$kvmDName"
 
- exit 0
\ No newline at end of file
+ exit 0
diff --git a/scripts/vm/hypervisor/kvm/nsrkvmrestore.sh b/scripts/vm/hypervisor/kvm/nsrkvmrestore.sh
index ad3865d..40a9021 100755
--- a/scripts/vm/hypervisor/kvm/nsrkvmrestore.sh
+++ b/scripts/vm/hypervisor/kvm/nsrkvmrestore.sh
@@ -220,4 +220,4 @@
         sanity_checks
         restore_all_volumes
 fi
-exit 0
\ No newline at end of file
+exit 0
diff --git a/scripts/vm/hypervisor/xenserver/add_to_vcpus_params_live.sh b/scripts/vm/hypervisor/xenserver/add_to_vcpus_params_live.sh
index 0fadcd8..b44fec9 100644
--- a/scripts/vm/hypervisor/xenserver/add_to_vcpus_params_live.sh
+++ b/scripts/vm/hypervisor/xenserver/add_to_vcpus_params_live.sh
@@ -30,4 +30,3 @@
 then
     xe vm-param-set VCPUs-params:cap=$value uuid=$uuid
 fi
-
diff --git a/scripts/vm/hypervisor/xenserver/cloud-plugin-storage b/scripts/vm/hypervisor/xenserver/cloud-plugin-storage
index bc90947..01a4cdd 100644
--- a/scripts/vm/hypervisor/xenserver/cloud-plugin-storage
+++ b/scripts/vm/hypervisor/xenserver/cloud-plugin-storage
@@ -105,7 +105,7 @@
     return
 
 
-def checkVolumeAvailablility(path):
+def checkVolumeAvailability(path):
     try:
         if not isVolumeAvailable(path):
             # The VHD file is not available on XenSever. The volume is probably
@@ -172,7 +172,7 @@
 
     baseCopyUuid = ''
     if isISCSI:
-        checkVolumeAvailablility(snapshotPath)
+        checkVolumeAvailability(snapshotPath)
         baseCopyUuid = scanParent(snapshotPath)
     else:
         baseCopyUuid = getParent(snapshotPath, isISCSI)
@@ -302,4 +302,3 @@
         "umountNfsSecondaryStorage":umountNfsSecondaryStorage,
         "makeDirectory":makeDirectory})
     
-
diff --git a/scripts/vm/hypervisor/xenserver/cloud-prepare-upgrade.sh b/scripts/vm/hypervisor/xenserver/cloud-prepare-upgrade.sh
index 4f80f72..3a1f88b 100755
--- a/scripts/vm/hypervisor/xenserver/cloud-prepare-upgrade.sh
+++ b/scripts/vm/hypervisor/xenserver/cloud-prepare-upgrade.sh
@@ -98,4 +98,3 @@
     echo "Warning : Don't know how to handle VM $vm, it is in $state state"
   fi
 done
-
diff --git a/scripts/vm/hypervisor/xenserver/cloud-setup-bonding.sh b/scripts/vm/hypervisor/xenserver/cloud-setup-bonding.sh
index 3b806b5..699d1d3 100755
--- a/scripts/vm/hypervisor/xenserver/cloud-setup-bonding.sh
+++ b/scripts/vm/hypervisor/xenserver/cloud-setup-bonding.sh
@@ -107,4 +107,3 @@
   fi
 done
 echo "#check is successful, you can add these hosts to CloudStack."
-
diff --git a/scripts/vm/hypervisor/xenserver/cloudlog b/scripts/vm/hypervisor/xenserver/cloudlog
index ed7e690..bf0202e 100644
--- a/scripts/vm/hypervisor/xenserver/cloudlog
+++ b/scripts/vm/hypervisor/xenserver/cloudlog
@@ -34,4 +34,3 @@
     size 1M
     rotate 2
 }
-
diff --git a/scripts/vm/hypervisor/xenserver/copy_vhd_from_secondarystorage.sh b/scripts/vm/hypervisor/xenserver/copy_vhd_from_secondarystorage.sh
index 61c65ea..6c78613 100755
--- a/scripts/vm/hypervisor/xenserver/copy_vhd_from_secondarystorage.sh
+++ b/scripts/vm/hypervisor/xenserver/copy_vhd_from_secondarystorage.sh
@@ -114,7 +114,7 @@
       dd if=$srcvhd of=$desvhd bs=512 seek=$(($(($vsize/512))-1)) count=1
       $VHDUTIL modify -s $vsize -n $desvhd
       if [ $? -ne 0 ]; then
-        echo "32#failed to set new vhd physical size for vdi vdi $uuid"
+        echo "32#failed to set new vhd physical size for vdi $uuid"
         cleanup
         exit 0
       fi
diff --git a/scripts/vm/hypervisor/xenserver/make_migratable.sh b/scripts/vm/hypervisor/xenserver/make_migratable.sh
index aada316..a7ae262 100755
--- a/scripts/vm/hypervisor/xenserver/make_migratable.sh
+++ b/scripts/vm/hypervisor/xenserver/make_migratable.sh
@@ -78,5 +78,3 @@
     exit 3
     ;;
 esac
-
-
diff --git a/scripts/vm/hypervisor/xenserver/network_info.sh b/scripts/vm/hypervisor/xenserver/network_info.sh
index 96df382..ff4805a 100755
--- a/scripts/vm/hypervisor/xenserver/network_info.sh
+++ b/scripts/vm/hypervisor/xenserver/network_info.sh
@@ -55,4 +55,3 @@
 fi
 
 [ -n "$gflag" ] && echo $gateway && exit 0
-
diff --git a/scripts/vm/hypervisor/xenserver/ovstunnel b/scripts/vm/hypervisor/xenserver/ovstunnel
index 72855cd..f349873 100755
--- a/scripts/vm/hypervisor/xenserver/ovstunnel
+++ b/scripts/vm/hypervisor/xenserver/ovstunnel
@@ -356,4 +356,4 @@
                            "getLabel": getLabel,
                            "setup_ovs_bridge_for_distributed_routing": setup_ovs_bridge_for_distributed_routing,
                            "configure_ovs_bridge_for_network_topology": configure_ovs_bridge_for_network_topology,
-                           "configure_ovs_bridge_for_routing_policies": configure_ovs_bridge_for_routing_policies})
\ No newline at end of file
+                           "configure_ovs_bridge_for_routing_policies": configure_ovs_bridge_for_routing_policies})
diff --git a/scripts/vm/hypervisor/xenserver/setup_iscsi.sh b/scripts/vm/hypervisor/xenserver/setup_iscsi.sh
index 660a92f..096e161 100755
--- a/scripts/vm/hypervisor/xenserver/setup_iscsi.sh
+++ b/scripts/vm/hypervisor/xenserver/setup_iscsi.sh
@@ -50,4 +50,3 @@
   exit 1
 fi
 printf "=======> DONE <======\n"
-
diff --git a/scripts/vm/hypervisor/xenserver/setupxenserver.sh b/scripts/vm/hypervisor/xenserver/setupxenserver.sh
index 6c850c6..ef1f68f 100755
--- a/scripts/vm/hypervisor/xenserver/setupxenserver.sh
+++ b/scripts/vm/hypervisor/xenserver/setupxenserver.sh
@@ -62,4 +62,3 @@
 rm -f /opt/xensource/packages/iso/systemvm-premium.iso
 
 echo "success"
-
diff --git a/scripts/vm/hypervisor/xenserver/upgrade_vnc_config.sh b/scripts/vm/hypervisor/xenserver/upgrade_vnc_config.sh
index e65b9ec..c6a1467 100755
--- a/scripts/vm/hypervisor/xenserver/upgrade_vnc_config.sh
+++ b/scripts/vm/hypervisor/xenserver/upgrade_vnc_config.sh
@@ -21,4 +21,3 @@
 # remove listening vnc on all interface
 sed -i 's/0\.0\.0\.0/127\.0\.0\.1/' /opt/xensource/libexec/vncterm-wrapper 2>&1
 sed -i 's/0\.0\.0\.0/127\.0\.0\.1/' /opt/xensource/libexec/qemu-dm-wrapper 2>&1
-
diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops
index 0d82a9d..4f78a3c 100755
--- a/scripts/vm/hypervisor/xenserver/vmops
+++ b/scripts/vm/hypervisor/xenserver/vmops
@@ -769,7 +769,7 @@
             logging.debug("vm ip " + ip)
             util.pread2(['ipset', action, ipsetname, ip])
         except:
-            logging.debug("vm ip alreday in ip set" + ip)
+            logging.debug("vm ip already in ip set" + ip)
             continue
 
     return result
@@ -1023,7 +1023,7 @@
     [vm_ip, vm_mac] = get_vm_mac_ip_from_log(vmchain)
     default_arp_antispoof(vmchain, vifs, vm_ip, vm_mac)
 
-    #check wether the vm has secondary ips
+    #check whether the vm has secondary ips
     if is_secondary_ips_set(vm_name) == True:
         vmips = get_vm_sec_ips(vm_name)
         #add arp rules for the secondaryp ip
diff --git a/scripts/vm/hypervisor/xenserver/vmopsSnapshot b/scripts/vm/hypervisor/xenserver/vmopsSnapshot
index b74a855..0d5fcc1 100755
--- a/scripts/vm/hypervisor/xenserver/vmopsSnapshot
+++ b/scripts/vm/hypervisor/xenserver/vmopsSnapshot
@@ -190,7 +190,7 @@
     errMsg = ''
     exists = True
     if isISCSI:
-        exists = checkVolumeAvailablility(path)
+        exists = checkVolumeAvailability(path)
     else:
         exists = os.path.isfile(path)
         
@@ -269,7 +269,7 @@
 
     baseCopyUuid = ''
     if isISCSI:
-        checkVolumeAvailablility(snapshotPath)
+        checkVolumeAvailability(snapshotPath)
         baseCopyUuid = scanParent(snapshotPath)
     else:
         baseCopyUuid = getParent(snapshotPath, isISCSI)
@@ -439,7 +439,7 @@
     return
 
 
-def checkVolumeAvailablility(path):
+def checkVolumeAvailability(path):
     try:
         if not isVolumeAvailable(path):
             # The VHD file is not available on XenSever. The volume is probably
@@ -621,4 +621,3 @@
 if __name__ == "__main__":
     XenAPIPlugin.dispatch({"getVhdParent":getVhdParent,  "create_secondary_storage_folder":create_secondary_storage_folder, "delete_secondary_storage_folder":delete_secondary_storage_folder, "post_create_private_template":post_create_private_template, "backupSnapshot": backupSnapshot, "deleteSnapshotBackup": deleteSnapshotBackup, "unmountSnapshotsDir": unmountSnapshotsDir, "revert_memory_snapshot":revert_memory_snapshot, "getSnapshotSize":getSnapshotSize})
     
-
diff --git a/scripts/vm/hypervisor/xenserver/xcposs/patch b/scripts/vm/hypervisor/xenserver/xcposs/patch
index 1edd35a..f516251 100644
--- a/scripts/vm/hypervisor/xenserver/xcposs/patch
+++ b/scripts/vm/hypervisor/xenserver/xcposs/patch
@@ -60,4 +60,4 @@
 cloudstack_plugins.conf=..,0644,/etc/xensource
 cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins
 cloudlog=..,0644,/etc/logrotate.d
-update_host_passwd.sh=../..,0755,/opt/cloud/bin
\ No newline at end of file
+update_host_passwd.sh=../..,0755,/opt/cloud/bin
diff --git a/scripts/vm/hypervisor/xenserver/xcpserver/patch b/scripts/vm/hypervisor/xenserver/xcpserver/patch
index 8bb1ead..e08fe4d 100644
--- a/scripts/vm/hypervisor/xenserver/xcpserver/patch
+++ b/scripts/vm/hypervisor/xenserver/xcpserver/patch
@@ -61,4 +61,4 @@
 cloudstack_plugins.conf=..,0644,/etc/xensource
 cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins
 cloudlog=..,0644,/etc/logrotate.d
-update_host_passwd.sh=../..,0755,/opt/cloud/bin
\ No newline at end of file
+update_host_passwd.sh=../..,0755,/opt/cloud/bin
diff --git a/scripts/vm/hypervisor/xenserver/xenheartbeat.sh b/scripts/vm/hypervisor/xenserver/xenheartbeat.sh
index b4ef56e..355f06f 100755
--- a/scripts/vm/hypervisor/xenserver/xenheartbeat.sh
+++ b/scripts/vm/hypervisor/xenserver/xenheartbeat.sh
@@ -109,4 +109,4 @@
 done
 
 /usr/bin/logger -t heartbeat "Problem with $hb: not reachable for $(($(date +%s) - $lastdate)) seconds, rebooting system!"
-echo b > /proc/sysrq-trigger
\ No newline at end of file
+echo b > /proc/sysrq-trigger
diff --git a/scripts/vm/hypervisor/xenserver/xenserver60/patch b/scripts/vm/hypervisor/xenserver/xenserver60/patch
index 2652c30..67475de 100644
--- a/scripts/vm/hypervisor/xenserver/xenserver60/patch
+++ b/scripts/vm/hypervisor/xenserver/xenserver60/patch
@@ -71,4 +71,4 @@
 ovs-get-bridge.sh=..,0755,/opt/cloud/bin
 cloudlog=..,0644,/etc/logrotate.d
 update_host_passwd.sh=../..,0755,/opt/cloud/bin
-logrotate=..,0755,/etc/cron.hourly
\ No newline at end of file
+logrotate=..,0755,/etc/cron.hourly
diff --git a/scripts/vm/hypervisor/xenserver/xenserver62/patch b/scripts/vm/hypervisor/xenserver/xenserver62/patch
index f18a325..e225105 100644
--- a/scripts/vm/hypervisor/xenserver/xenserver62/patch
+++ b/scripts/vm/hypervisor/xenserver/xenserver62/patch
@@ -67,4 +67,4 @@
 ovs-get-bridge.sh=..,0755,/opt/cloud/bin
 cloudlog=..,0644,/etc/logrotate.d
 update_host_passwd.sh=../..,0755,/opt/cloud/bin
-logrotate=..,0755,/etc/cron.hourly
\ No newline at end of file
+logrotate=..,0755,/etc/cron.hourly
diff --git a/scripts/vm/hypervisor/xenserver/xenserver65/patch b/scripts/vm/hypervisor/xenserver/xenserver65/patch
index f18a325..e225105 100644
--- a/scripts/vm/hypervisor/xenserver/xenserver65/patch
+++ b/scripts/vm/hypervisor/xenserver/xenserver65/patch
@@ -67,4 +67,4 @@
 ovs-get-bridge.sh=..,0755,/opt/cloud/bin
 cloudlog=..,0644,/etc/logrotate.d
 update_host_passwd.sh=../..,0755,/opt/cloud/bin
-logrotate=..,0755,/etc/cron.hourly
\ No newline at end of file
+logrotate=..,0755,/etc/cron.hourly
diff --git a/scripts/vm/hypervisor/xenserver/xs_cleanup.sh b/scripts/vm/hypervisor/xenserver/xs_cleanup.sh
index 7ea3836..d2efa86 100755
--- a/scripts/vm/hypervisor/xenserver/xs_cleanup.sh
+++ b/scripts/vm/hypervisor/xenserver/xs_cleanup.sh
@@ -66,4 +66,3 @@
 
 echo "======> DONE <======"
 exit 0
-
diff --git a/scripts/vm/network/ovs-pvlan-cleanup.sh b/scripts/vm/network/ovs-pvlan-cleanup.sh
index 7493bed..b79c2bd 100755
--- a/scripts/vm/network/ovs-pvlan-cleanup.sh
+++ b/scripts/vm/network/ovs-pvlan-cleanup.sh
@@ -20,4 +20,3 @@
 
 ovs-ofctl del-flows xenbr0
 ovs-ofctl add-flow xenbr0 priority=0,actions=NORMAL
-
diff --git a/scripts/vm/network/ovs-pvlan-vm.sh b/scripts/vm/network/ovs-pvlan-vm.sh
index 06e41fe..c306faa 100755
--- a/scripts/vm/network/ovs-pvlan-vm.sh
+++ b/scripts/vm/network/ovs-pvlan-vm.sh
@@ -97,4 +97,3 @@
     ovs-ofctl del-flows --strict $br priority=50,dl_vlan=0xffff,dl_src=$vm_mac
     ovs-ofctl del-flows --strict $br priority=60,dl_vlan=$sec_iso_vlan,dl_src=$vm_mac
 fi
-
diff --git a/scripts/vm/network/tungsten/create_tap_device.sh b/scripts/vm/network/tungsten/create_tap_device.sh
index c10a0e7..90c24d6 100755
--- a/scripts/vm/network/tungsten/create_tap_device.sh
+++ b/scripts/vm/network/tungsten/create_tap_device.sh
@@ -16,5 +16,5 @@
 # specific language governing permissions and limitations
 # under the License.
 
-ip tuntap add dev $1 mode tap
-ip link set $1 up
\ No newline at end of file
+#ip tuntap add dev $1 mode tap multi_queue
+#ip link set $1 up
diff --git a/scripts/vm/network/tungsten/delete_tap_device.sh b/scripts/vm/network/tungsten/delete_tap_device.sh
index 37d668a..8a36772 100755
--- a/scripts/vm/network/tungsten/delete_tap_device.sh
+++ b/scripts/vm/network/tungsten/delete_tap_device.sh
@@ -16,4 +16,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-ip tuntap del dev $1 mode tap
\ No newline at end of file
+ip tuntap del dev $1 mode tap multi_queue 2>/dev/null
+if [ $? -ne 0 ];then
+  ip tuntap del dev $1 mode tap
+fi
diff --git a/scripts/vm/network/tungsten/setup_tungsten_vrouter.sh b/scripts/vm/network/tungsten/setup_tungsten_vrouter.sh
index e041eac..9d19af3 100755
--- a/scripts/vm/network/tungsten/setup_tungsten_vrouter.sh
+++ b/scripts/vm/network/tungsten/setup_tungsten_vrouter.sh
@@ -21,4 +21,4 @@
 then
     container=$(docker ps | grep contrail-vrouter-agent | awk '{print $1}')
     docker exec $container python /opt/contrail/utils/provision_vgw_interface.py --oper $1 --interface $2 --subnets "$3" --routes "$4" --vrf $5
-fi
\ No newline at end of file
+fi
diff --git a/server/conf/cloudstack-catalina.logrotate b/server/conf/cloudstack-catalina.logrotate
index 498f690..3fd67c9 100644
--- a/server/conf/cloudstack-catalina.logrotate
+++ b/server/conf/cloudstack-catalina.logrotate
@@ -23,4 +23,4 @@
     compress
     missingok
     create 0644 cloud cloud
-}
\ No newline at end of file
+}
diff --git a/server/pom.xml b/server/pom.xml
index 47116cb..152cd43 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <repositories>
         <repository>
@@ -35,8 +35,8 @@
 
     <dependencies>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
index df6ea74..862e8ac 100644
--- a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
+++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
@@ -25,6 +25,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -182,7 +183,6 @@
 
                 if (hasSvcOfferingTag && hasTemplateTag) {
                     hostsMatchingOfferingTag.retainAll(hostsMatchingTemplateTag);
-                    clusterHosts = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate);
                     if (s_logger.isDebugEnabled()) {
                         s_logger.debug("Found " + hostsMatchingOfferingTag.size() + " Hosts satisfying both tags, host ids are:" + hostsMatchingOfferingTag);
                     }
@@ -202,6 +202,13 @@
             clusterHosts.retainAll(hostsMatchingUefiTag);
         }
 
+        clusterHosts.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering));
+
+
+        if (clusterHosts.isEmpty()) {
+            s_logger.error(String.format("No suitable host found for vm [%s] with tags [%s].", vmProfile, hostTagOnOffering));
+            throw new CloudRuntimeException(String.format("No suitable host found for vm [%s].", vmProfile));
+        }
         // add all hosts that we are not considering to the avoid list
         List<HostVO> allhostsInCluster = _hostDao.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId, null);
         allhostsInCluster.removeAll(clusterHosts);
@@ -267,6 +274,8 @@
             }
         }
 
+        hostsCopy.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering));
+
         if (!hostsCopy.isEmpty()) {
             suitableHosts = allocateTo(plan, offering, template, avoid, hostsCopy, returnUpTo, considerReservedCapacity, account);
         }
diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java
index ca26de5..f550d80 100644
--- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java
+++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java
@@ -727,7 +727,7 @@
         if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM)
                 && (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY)
                 && (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC)
-                && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) && (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED)
+                && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE) && (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED)
                 && (alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) && (alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR)
                 && (alertType != AlertManager.AlertType.ALERT_TYPE_HA_ACTION) && (alertType != AlertManager.AlertType.ALERT_TYPE_CA_CERT)) {
             alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId);
diff --git a/server/src/main/java/com/cloud/alert/ClusterAlertAdapter.java b/server/src/main/java/com/cloud/alert/ClusterAlertAdapter.java
index 16e87b4..4d5246b 100644
--- a/server/src/main/java/com/cloud/alert/ClusterAlertAdapter.java
+++ b/server/src/main/java/com/cloud/alert/ClusterAlertAdapter.java
@@ -70,7 +70,7 @@
                     s_logger.debug("Management server node " + mshost.getServiceIP() + " is up, send alert");
                 }
 
-                _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE, 0, new Long(0), "Management server node " + mshost.getServiceIP() + " is up", "");
+                _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE, 0, new Long(0), "Management server node " + mshost.getServiceIP() + " is up", "");
                 break;
             }
         }
@@ -90,7 +90,7 @@
                     if (s_logger.isDebugEnabled()) {
                         s_logger.debug("Detected management server node " + mshost.getServiceIP() + " is down, send alert");
                     }
-                    _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE, 0, new Long(0), "Management server node " + mshost.getServiceIP() + " is down",
+                    _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE, 0, new Long(0), "Management server node " + mshost.getServiceIP() + " is down",
                         "");
                 } else {
                     if (s_logger.isDebugEnabled()) {
diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java
index c4d7f3c..97ecd98 100644
--- a/server/src/main/java/com/cloud/api/ApiDBUtils.java
+++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java
@@ -16,6 +16,81 @@
 // under the License.
 package com.cloud.api;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.RoleService;
+import org.apache.cloudstack.affinity.AffinityGroup;
+import org.apache.cloudstack.affinity.AffinityGroupResponse;
+import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiConstants.DomainDetails;
+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.response.AccountResponse;
+import org.apache.cloudstack.api.response.AsyncJobResponse;
+import org.apache.cloudstack.api.response.BackupOfferingResponse;
+import org.apache.cloudstack.api.response.BackupResponse;
+import org.apache.cloudstack.api.response.BackupScheduleResponse;
+import org.apache.cloudstack.api.response.DiskOfferingResponse;
+import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.DomainRouterResponse;
+import org.apache.cloudstack.api.response.EventResponse;
+import org.apache.cloudstack.api.response.HostForMigrationResponse;
+import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.HostTagResponse;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.api.response.InstanceGroupResponse;
+import org.apache.cloudstack.api.response.NetworkOfferingResponse;
+import org.apache.cloudstack.api.response.ProjectAccountResponse;
+import org.apache.cloudstack.api.response.ProjectInvitationResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
+import org.apache.cloudstack.api.response.ResourceTagResponse;
+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.StoragePoolResponse;
+import org.apache.cloudstack.api.response.StorageTagResponse;
+import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.api.response.UserResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.api.response.VpcOfferingResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.backup.Backup;
+import org.apache.cloudstack.backup.BackupOffering;
+import org.apache.cloudstack.backup.BackupSchedule;
+import org.apache.cloudstack.backup.dao.BackupDao;
+import org.apache.cloudstack.backup.dao.BackupOfferingDao;
+import org.apache.cloudstack.backup.dao.BackupScheduleDao;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.jobs.AsyncJob;
+import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
+import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO;
+import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
+import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+
 import com.cloud.agent.api.VgpuTypesInfo;
 import com.cloud.api.query.dao.AccountJoinDao;
 import com.cloud.api.query.dao.AffinityGroupJoinDao;
@@ -35,6 +110,7 @@
 import com.cloud.api.query.dao.ResourceTagJoinDao;
 import com.cloud.api.query.dao.SecurityGroupJoinDao;
 import com.cloud.api.query.dao.ServiceOfferingJoinDao;
+import com.cloud.api.query.dao.SnapshotJoinDao;
 import com.cloud.api.query.dao.StoragePoolJoinDao;
 import com.cloud.api.query.dao.TemplateJoinDao;
 import com.cloud.api.query.dao.UserAccountJoinDao;
@@ -60,6 +136,7 @@
 import com.cloud.api.query.vo.ResourceTagJoinVO;
 import com.cloud.api.query.vo.SecurityGroupJoinVO;
 import com.cloud.api.query.vo.ServiceOfferingJoinVO;
+import com.cloud.api.query.vo.SnapshotJoinVO;
 import com.cloud.api.query.vo.StoragePoolJoinVO;
 import com.cloud.api.query.vo.TemplateJoinVO;
 import com.cloud.api.query.vo.UserAccountJoinVO;
@@ -220,6 +297,7 @@
 import com.cloud.storage.Volume.Type;
 import com.cloud.storage.VolumeStats;
 import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.BucketDao;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.GuestOSCategoryDao;
 import com.cloud.storage.dao.GuestOSDao;
@@ -274,72 +352,10 @@
 import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.VMSnapshot;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
-import org.apache.cloudstack.acl.Role;
-import org.apache.cloudstack.acl.RoleService;
-import org.apache.cloudstack.affinity.AffinityGroup;
-import org.apache.cloudstack.affinity.AffinityGroupResponse;
-import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
-import org.apache.cloudstack.api.ApiCommandResourceType;
-import org.apache.cloudstack.api.ApiConstants.DomainDetails;
-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.response.AccountResponse;
-import org.apache.cloudstack.api.response.AsyncJobResponse;
-import org.apache.cloudstack.api.response.BackupOfferingResponse;
-import org.apache.cloudstack.api.response.BackupResponse;
-import org.apache.cloudstack.api.response.BackupScheduleResponse;
-import org.apache.cloudstack.api.response.DiskOfferingResponse;
-import org.apache.cloudstack.api.response.DomainResponse;
-import org.apache.cloudstack.api.response.DomainRouterResponse;
-import org.apache.cloudstack.api.response.EventResponse;
-import org.apache.cloudstack.api.response.HostForMigrationResponse;
-import org.apache.cloudstack.api.response.HostResponse;
-import org.apache.cloudstack.api.response.HostTagResponse;
-import org.apache.cloudstack.api.response.ImageStoreResponse;
-import org.apache.cloudstack.api.response.InstanceGroupResponse;
-import org.apache.cloudstack.api.response.NetworkOfferingResponse;
-import org.apache.cloudstack.api.response.ProjectAccountResponse;
-import org.apache.cloudstack.api.response.ProjectInvitationResponse;
-import org.apache.cloudstack.api.response.ProjectResponse;
-import org.apache.cloudstack.api.response.ResourceIconResponse;
-import org.apache.cloudstack.api.response.ResourceTagResponse;
-import org.apache.cloudstack.api.response.SecurityGroupResponse;
-import org.apache.cloudstack.api.response.ServiceOfferingResponse;
-import org.apache.cloudstack.api.response.StoragePoolResponse;
-import org.apache.cloudstack.api.response.StorageTagResponse;
-import org.apache.cloudstack.api.response.TemplateResponse;
-import org.apache.cloudstack.api.response.UserResponse;
-import org.apache.cloudstack.api.response.UserVmResponse;
-import org.apache.cloudstack.api.response.VolumeResponse;
-import org.apache.cloudstack.api.response.VpcOfferingResponse;
-import org.apache.cloudstack.api.response.ZoneResponse;
-import org.apache.cloudstack.backup.Backup;
-import org.apache.cloudstack.backup.BackupOffering;
-import org.apache.cloudstack.backup.BackupSchedule;
-import org.apache.cloudstack.backup.dao.BackupDao;
-import org.apache.cloudstack.backup.dao.BackupOfferingDao;
-import org.apache.cloudstack.backup.dao.BackupScheduleDao;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.jobs.AsyncJob;
-import org.apache.cloudstack.framework.jobs.AsyncJobManager;
-import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
-import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 
-import javax.annotation.PostConstruct;
-import javax.inject.Inject;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
 
 public class ApiDBUtils {
     private static ManagementServer s_ms;
@@ -441,6 +457,7 @@
     static AccountJoinDao s_accountJoinDao;
     static AsyncJobJoinDao s_jobJoinDao;
     static TemplateJoinDao s_templateJoinDao;
+    static SnapshotJoinDao s_snapshotJoinDao;
 
     static PhysicalNetworkTrafficTypeDao s_physicalNetworkTrafficTypeDao;
     static PhysicalNetworkServiceProviderDao s_physicalNetworkServiceProviderDao;
@@ -471,6 +488,10 @@
     static BackupOfferingDao s_backupOfferingDao;
     static NicDao s_nicDao;
     static ResourceManagerUtil s_resourceManagerUtil;
+    static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao;
+    static ObjectStoreDao s_objectStoreDao;
+
+    static BucketDao s_bucketDao;
 
     @Inject
     private ManagementServer ms;
@@ -662,6 +683,8 @@
     private AsyncJobJoinDao jobJoinDao;
     @Inject
     private TemplateJoinDao templateJoinDao;
+    @Inject
+    private SnapshotJoinDao snapshotJoinDao;
 
     @Inject
     private PhysicalNetworkTrafficTypeDao physicalNetworkTrafficTypeDao;
@@ -725,6 +748,13 @@
     private ResourceIconDao resourceIconDao;
     @Inject
     private ResourceManagerUtil resourceManagerUtil;
+    @Inject
+    SnapshotPolicyDetailsDao snapshotPolicyDetailsDao;
+
+    @Inject
+    private ObjectStoreDao objectStoreDao;
+    @Inject
+    private BucketDao bucketDao;
 
     @PostConstruct
     void init() {
@@ -820,6 +850,7 @@
         s_accountJoinDao = accountJoinDao;
         s_jobJoinDao = jobJoinDao;
         s_templateJoinDao = templateJoinDao;
+        s_snapshotJoinDao = snapshotJoinDao;
 
         s_physicalNetworkTrafficTypeDao = physicalNetworkTrafficTypeDao;
         s_physicalNetworkServiceProviderDao = physicalNetworkServiceProviderDao;
@@ -832,6 +863,7 @@
         s_vpcOfferingDao = vpcOfferingDao;
         s_vpcOfferingJoinDao = vpcOfferingJoinDao;
         s_snapshotPolicyDao = snapshotPolicyDao;
+        s_snapshotPolicyDetailsDao = snapshotPolicyDetailsDao;
         s_asyncJobDao = asyncJobDao;
         s_hostDetailsDao = hostDetailsDao;
         s_clusterDetailsDao = clusterDetailsDao;
@@ -855,6 +887,8 @@
         s_backupOfferingDao = backupOfferingDao;
         s_resourceIconDao = resourceIconDao;
         s_resourceManagerUtil = resourceManagerUtil;
+        s_objectStoreDao = objectStoreDao;
+        s_bucketDao = bucketDao;
     }
 
     // ///////////////////////////////////////////////////////////
@@ -1263,7 +1297,7 @@
                 type = HypervisorType.Hyperv;
             }
         } if (format == ImageFormat.RAW) {
-            // Currently, KVM only supports RBD and PowerFlex images of type RAW.
+            // Currently, KVM only supports RBD, PowerFlex, and FiberChannel images of type RAW.
             // This results in a weird collision with OVM volumes which
             // can only be raw, thus making KVM RBD volumes show up as OVM
             // rather than RBD. This block of code can (hopefully) by checking to
@@ -1275,14 +1309,19 @@
             ListIterator<StoragePoolVO> itr = pools.listIterator();
             while(itr.hasNext()) {
                 StoragePoolVO pool = itr.next();
-                if(pool.getPoolType() == StoragePoolType.RBD ||
-                    pool.getPoolType() == StoragePoolType.PowerFlex ||
-                    pool.getPoolType() == StoragePoolType.CLVM ||
-                    pool.getPoolType() == StoragePoolType.Linstor) {
+
+                if(List.of(StoragePoolType.RBD,
+                           StoragePoolType.PowerFlex,
+                           StoragePoolType.CLVM,
+                           StoragePoolType.Linstor,
+                           StoragePoolType.FiberChannel).contains(pool.getPoolType())) {
                   // This case will note the presence of non-qcow2 primary stores, suggesting KVM without NFS. Otherwse,
                   // If this check is not passed, the hypervisor type will remain OVM.
                   type = HypervisorType.KVM;
                   break;
+                } else if (pool.getHypervisor() == HypervisorType.Custom) {
+                    type = HypervisorType.Custom;
+                    break;
                 }
             }
         }
@@ -1514,6 +1553,10 @@
         return s_ipAddressDao.findByAssociatedVmId(vmId);
     }
 
+    public static IpAddress findIpByAssociatedVmIdAndNetworkId(long vmId, long networkId) {
+        return s_ipAddressDao.findByVmIdAndNetworkId(networkId, vmId);
+    }
+
     public static String getHaTag() {
         return s_haMgr.getHaTag();
     }
@@ -1646,6 +1689,20 @@
         return s_snapshotPolicyDao.findById(policyId);
     }
 
+    public static List<DataCenterVO> findSnapshotPolicyZones(SnapshotPolicy policy, Volume volume) {
+        List<SnapshotPolicyDetailVO> zoneDetails = s_snapshotPolicyDetailsDao.findDetails(policy.getId(), ApiConstants.ZONE_ID);
+        List<Long> zoneIds = new ArrayList<>();
+        for (SnapshotPolicyDetailVO detail : zoneDetails) {
+            try {
+                zoneIds.add(Long.valueOf(detail.getValue()));
+            } catch (NumberFormatException ignored) {}
+        }
+        if (volume != null && !zoneIds.contains(volume.getDataCenterId())) {
+            zoneIds.add(0, volume.getDataCenterId());
+        }
+        return s_zoneDao.listByIds(zoneIds);
+    }
+
     public static VpcOffering findVpcOfferingById(long offeringId) {
         return s_vpcOfferingDao.findById(offeringId);
     }
@@ -1831,6 +1888,10 @@
         return s_userVmJoinDao.newUserVmView(userVms);
     }
 
+    public static List<UserVmJoinVO> newUserVmView(VirtualMachine... vms) {
+        return s_userVmJoinDao.newUserVmView(vms);
+    }
+
     public static SecurityGroupResponse newSecurityGroupResponse(SecurityGroupJoinVO vsg, Account caller) {
         return s_securityGroupJoinDao.newSecurityGroupResponse(vsg, caller);
     }
@@ -2024,6 +2085,29 @@
         return response;
     }
 
+    public static List<AccountResponse> newAccountResponses(ResponseView view, EnumSet<DomainDetails> details, AccountJoinVO... accounts) {
+        List<AccountResponse> responseList = new ArrayList<>();
+
+        List<Long> roleIdList = Arrays.stream(accounts).map(AccountJoinVO::getRoleId).collect(Collectors.toList());
+        Map<Long,Role> roleIdMap = s_roleService.findRoles(roleIdList, false).stream().collect(Collectors.toMap(Role::getId, Function.identity()));
+
+        for (AccountJoinVO account : accounts) {
+            AccountResponse response = s_accountJoinDao.newAccountResponse(view, details, account);
+            // Populate account role information
+            if (account.getRoleId() != null) {
+                Role role = roleIdMap.get(account.getRoleId());
+                if (role != null) {
+                    response.setRoleId(role.getUuid());
+                    response.setRoleType(role.getRoleType());
+                    response.setRoleName(role.getName());
+                }
+            }
+            responseList.add(response);
+        }
+
+        return responseList;
+    }
+
     public static AccountJoinVO newAccountView(Account e) {
         return s_accountJoinDao.newAccountView(e);
     }
@@ -2080,14 +2164,22 @@
         return s_templateJoinDao.newTemplateResponse(detailsView, view, vr);
     }
 
-    public static TemplateResponse newIsoResponse(TemplateJoinVO vr) {
-        return s_templateJoinDao.newIsoResponse(vr);
+    public static TemplateResponse newIsoResponse(TemplateJoinVO vr, ResponseView view) {
+        return s_templateJoinDao.newIsoResponse(vr, view);
+    }
+
+    public static SnapshotResponse newSnapshotResponse(ResponseView view, boolean isShowUnique, SnapshotJoinVO vr) {
+        return s_snapshotJoinDao.newSnapshotResponse(view, isShowUnique, vr);
     }
 
     public static TemplateResponse fillTemplateDetails(EnumSet<DomainDetails> detailsView, ResponseView view, TemplateResponse vrData, TemplateJoinVO vr) {
         return s_templateJoinDao.setTemplateResponse(detailsView, view, vrData, vr);
     }
 
+    public static SnapshotResponse fillSnapshotDetails(SnapshotResponse vrData, SnapshotJoinVO vr) {
+        return s_snapshotJoinDao.setSnapshotResponse(vrData, vr);
+    }
+
     public static List<TemplateJoinVO> newTemplateView(VirtualMachineTemplate vr) {
         return s_templateJoinDao.newTemplateView(vr);
     }
@@ -2159,4 +2251,12 @@
     public static NicSecondaryIpVO findSecondaryIpByIp4AddressAndNetworkId(String ip4Address, long networkId) {
         return s_nicSecondaryIpDao.findByIp4AddressAndNetworkId(ip4Address, networkId);
     }
+
+    public static ObjectStoreResponse newObjectStoreResponse(ObjectStoreVO store) {
+        return s_objectStoreDao.newObjectStoreResponse(store);
+    }
+
+    public static ObjectStoreResponse fillObjectStoreDetails(ObjectStoreResponse storeData, ObjectStoreVO store) {
+        return s_objectStoreDao.setObjectStoreResponse(storeData, store);
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/ApiDispatcher.java b/server/src/main/java/com/cloud/api/ApiDispatcher.java
index 3880f2a..09a7a92 100644
--- a/server/src/main/java/com/cloud/api/ApiDispatcher.java
+++ b/server/src/main/java/com/cloud/api/ApiDispatcher.java
@@ -35,6 +35,7 @@
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.jobs.AsyncJob;
 import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.framework.jobs.impl.AsyncJobManagerImpl;
 import org.apache.log4j.Logger;
 
 import com.cloud.api.dispatch.DispatchChain;
@@ -44,6 +45,7 @@
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
 
 public class ApiDispatcher {
     private static final Logger s_logger = Logger.getLogger(ApiDispatcher.class.getName());
@@ -63,6 +65,9 @@
     @Inject()
     protected DispatchChainFactory dispatchChainFactory;
 
+    @Inject
+    AsyncJobManagerImpl asyncJobManager;
+
     protected DispatchChain standardDispatchChain;
 
     protected DispatchChain asyncCreationDispatchChain;
@@ -85,7 +90,11 @@
     }
 
     public void dispatchCreateCmd(final BaseAsyncCreateCmd cmd, final Map<String, String> params) throws Exception {
-        asyncCreationDispatchChain.dispatch(new DispatchTask(cmd, params));
+        if (asyncJobManager.isAsyncJobsEnabled()) {
+            asyncCreationDispatchChain.dispatch(new DispatchTask(cmd, params));
+        } else {
+            throw new CloudRuntimeException("A shutdown has been triggered. Can not accept new jobs");
+        }
     }
 
     private void doAccessChecks(BaseCmd cmd, Map<Object, AccessType> entitiesToAccess) {
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 17c465d..6d66da4 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -33,16 +33,20 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.BucketVO;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiConstants.DomainDetails;
 import org.apache.cloudstack.api.ApiConstants.HostDetails;
 import org.apache.cloudstack.api.ApiConstants.VMDetails;
@@ -61,6 +65,7 @@
 import org.apache.cloudstack.api.response.BackupOfferingResponse;
 import org.apache.cloudstack.api.response.BackupResponse;
 import org.apache.cloudstack.api.response.BackupScheduleResponse;
+import org.apache.cloudstack.api.response.BucketResponse;
 import org.apache.cloudstack.api.response.CapabilityResponse;
 import org.apache.cloudstack.api.response.CapacityResponse;
 import org.apache.cloudstack.api.response.ClusterResponse;
@@ -91,11 +96,14 @@
 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.HypervisorGuestOsResponse;
 import org.apache.cloudstack.api.response.IPAddressResponse;
 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.IpRangeResponse;
 import org.apache.cloudstack.api.response.Ipv6RouteResponse;
 import org.apache.cloudstack.api.response.IsolationMethodResponse;
@@ -114,6 +122,7 @@
 import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse;
 import org.apache.cloudstack.api.response.NicResponse;
 import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
 import org.apache.cloudstack.api.response.OvsProviderResponse;
 import org.apache.cloudstack.api.response.PhysicalNetworkResponse;
 import org.apache.cloudstack.api.response.PodResponse;
@@ -135,6 +144,7 @@
 import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
 import org.apache.cloudstack.api.response.SSHKeyPairResponse;
+import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.SecurityGroupRuleResponse;
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
@@ -154,6 +164,8 @@
 import org.apache.cloudstack.api.response.TemplateResponse;
 import org.apache.cloudstack.api.response.TrafficMonitorResponse;
 import org.apache.cloudstack.api.response.TrafficTypeResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
 import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse;
 import org.apache.cloudstack.api.response.UsageRecordResponse;
 import org.apache.cloudstack.api.response.UserDataResponse;
@@ -191,14 +203,21 @@
 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.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
 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.object.Bucket;
+import org.apache.cloudstack.storage.object.ObjectStore;
 import org.apache.cloudstack.usage.Usage;
 import org.apache.cloudstack.usage.UsageService;
 import org.apache.cloudstack.usage.UsageTypes;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
@@ -276,6 +295,7 @@
 import com.cloud.network.PhysicalNetwork;
 import com.cloud.network.PhysicalNetworkServiceProvider;
 import com.cloud.network.PhysicalNetworkTrafficType;
+import com.cloud.network.PublicIpQuarantine;
 import com.cloud.network.RemoteAccessVpn;
 import com.cloud.network.RouterHealthCheckResult;
 import com.cloud.network.Site2SiteCustomerGateway;
@@ -291,6 +311,7 @@
 import com.cloud.network.as.Condition;
 import com.cloud.network.as.ConditionVO;
 import com.cloud.network.as.Counter;
+import com.cloud.network.dao.FirewallRulesDao;
 import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.IPAddressVO;
 import com.cloud.network.dao.LoadBalancerVO;
@@ -457,8 +478,13 @@
     @Inject
     NetworkServiceMapDao ntwkSrvcDao;
     @Inject
+    FirewallRulesDao firewallRulesDao;
+    @Inject
     UserDataDao userDataDao;
 
+    @Inject
+    ObjectStoreDao _objectStoreDao;
+
     @Override
     public UserResponse createUserResponse(User user) {
         UserAccountJoinVO vUser = ApiDBUtils.newUserView(user);
@@ -637,6 +663,7 @@
             DataCenter zone = ApiDBUtils.findZoneById(volume.getDataCenterId());
             if (zone != null) {
                 snapshotResponse.setZoneId(zone.getUuid());
+                snapshotResponse.setZoneName(zone.getName());
             }
 
             if (volume.getVolumeType() == Volume.Type.ROOT && volume.getInstanceId() != null) {
@@ -664,7 +691,7 @@
         } else {
             DataStoreRole dataStoreRole = getDataStoreRole(snapshot, _snapshotStoreDao, _dataStoreMgr);
 
-            snapshotInfo = snapshotfactory.getSnapshot(snapshot.getId(), dataStoreRole);
+            snapshotInfo = snapshotfactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, volume.getDataCenterId());
         }
 
         if (snapshotInfo == null) {
@@ -672,7 +699,7 @@
             snapshotResponse.setRevertable(false);
         } else {
         snapshotResponse.setRevertable(snapshotInfo.isRevertable());
-        snapshotResponse.setPhysicaSize(snapshotInfo.getPhysicalSize());
+        snapshotResponse.setPhysicalSize(snapshotInfo.getPhysicalSize());
         }
 
         // set tag information
@@ -691,7 +718,7 @@
     }
 
     public static DataStoreRole getDataStoreRole(Snapshot snapshot, SnapshotDataStoreDao snapshotStoreDao, DataStoreManager dataStoreMgr) {
-        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+        SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
 
         if (snapshotStore == null) {
             return DataStoreRole.Image;
@@ -730,7 +757,7 @@
         if (vm != null) {
             vmSnapshotResponse.setVirtualMachineId(vm.getUuid());
             vmSnapshotResponse.setVirtualMachineName(StringUtils.isEmpty(vm.getDisplayName()) ? vm.getHostName() : vm.getDisplayName());
-            vmSnapshotResponse.setHypervisor(vm.getHypervisorType());
+            vmSnapshotResponse.setHypervisor(vm.getHypervisorType().getHypervisorDisplayName());
             DataCenterVO datacenter = ApiDBUtils.findZoneById(vm.getDataCenterId());
             if (datacenter != null) {
                 vmSnapshotResponse.setZoneId(datacenter.getUuid());
@@ -798,6 +825,16 @@
             CollectionUtils.addIgnoreNull(tagResponses, tagResponse);
         }
         policyResponse.setTags(new HashSet<>(tagResponses));
+        List<ZoneResponse> zoneResponses = new ArrayList<>();
+        List<DataCenterVO> zones = ApiDBUtils.findSnapshotPolicyZones(policy, vol);
+        for (DataCenterVO zone : zones) {
+            ZoneResponse zoneResponse = new ZoneResponse();
+            zoneResponse.setId(zone.getUuid());
+            zoneResponse.setName(zone.getName());
+            zoneResponse.setTags(null);
+            zoneResponses.add(zoneResponse);
+        }
+        policyResponse.setZones(new HashSet<>(zoneResponses));
 
         return policyResponse;
     }
@@ -938,6 +975,42 @@
         return userIpAddresVO != null ? userIpAddresVO.isForSystemVms() : false;
     }
 
+    private void addVmDetailsInIpResponse(IPAddressResponse response, IpAddress ipAddress) {
+        if (ipAddress.getAllocatedToAccountId() != null && ipAddress.getAllocatedToAccountId() == Account.ACCOUNT_ID_SYSTEM) {
+            NicVO nic = ApiDBUtils.findByIp4AddressAndNetworkId(ipAddress.getAddress().toString(), ipAddress.getNetworkId());
+            if (nic != null) {
+                addSystemVmInfoToIpResponse(nic, response);
+            }
+        }
+        if (ipAddress.getAssociatedWithVmId() != null) {
+            addUserVmDetailsInIpResponse(response, ipAddress);
+        }
+    }
+
+    private void addSystemVmInfoToIpResponse(NicVO nic, IPAddressResponse ipResponse) {
+        final boolean isAdmin = Account.Type.ADMIN.equals(CallContext.current().getCallingAccount().getType());
+        if (!isAdmin) {
+            return;
+        }
+        VirtualMachine vm = ApiDBUtils.findVMInstanceById(nic.getInstanceId());
+        if (vm == null) {
+            return;
+        }
+        ipResponse.setVirtualMachineId(vm.getUuid());
+        ipResponse.setVirtualMachineName(vm.getHostName());
+        ipResponse.setVirtualMachineType(vm.getType().toString());
+    }
+
+    private void addUserVmDetailsInIpResponse(IPAddressResponse response, IpAddress ipAddress) {
+        UserVm userVm = ApiDBUtils.findUserVmById(ipAddress.getAssociatedWithVmId());
+        if (userVm != null) {
+            response.setVirtualMachineId(userVm.getUuid());
+            response.setVirtualMachineName(userVm.getHostName());
+            response.setVirtualMachineType(userVm.getType().toString());
+            response.setVirtualMachineDisplayName(ObjectUtils.firstNonNull(userVm.getDisplayName(), userVm.getHostName()));
+        }
+    }
+
     @Override
     public IPAddressResponse createIPAddressResponse(ResponseView view, IpAddress ipAddr) {
         VlanVO vlan = ApiDBUtils.findVlanById(ipAddr.getVlanId());
@@ -966,18 +1039,7 @@
         ipResponse.setForVirtualNetwork(forVirtualNetworks);
         ipResponse.setStaticNat(ipAddr.isOneToOneNat());
 
-        if (ipAddr.getAssociatedWithVmId() != null) {
-            UserVm vm = ApiDBUtils.findUserVmById(ipAddr.getAssociatedWithVmId());
-            if (vm != null) {
-                ipResponse.setVirtualMachineId(vm.getUuid());
-                ipResponse.setVirtualMachineName(vm.getHostName());
-                if (vm.getDisplayName() != null) {
-                    ipResponse.setVirtualMachineDisplayName(vm.getDisplayName());
-                } else {
-                    ipResponse.setVirtualMachineDisplayName(vm.getHostName());
-                }
-            }
-        }
+        addVmDetailsInIpResponse(ipResponse, ipAddr);
         if (ipAddr.getVmIp() != null) {
             ipResponse.setVirtualMachineIp(ipAddr.getVmIp());
         }
@@ -990,13 +1052,9 @@
             }
         }
 
-        if (ipAddr.getVpcId() != null) {
-            Vpc vpc = ApiDBUtils.findVpcById(ipAddr.getVpcId());
-            if (vpc != null) {
-                ipResponse.setVpcId(vpc.getUuid());
-                ipResponse.setVpcName(vpc.getName());
-            }
-        }
+
+        setVpcIdInResponse(ipAddr.getVpcId(), ipResponse::setVpcId, ipResponse::setVpcName);
+
 
         // Network id the ip is associated with (if associated networkId is
         // null, try to get this information from vlan)
@@ -1061,10 +1119,27 @@
         ipResponse.setHasAnnotation(annotationDao.hasAnnotations(ipAddr.getUuid(), AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(),
                 _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
 
+        ipResponse.setHasRules(firewallRulesDao.countRulesByIpId(ipAddr.getId()) > 0);
         ipResponse.setObjectName("ipaddress");
         return ipResponse;
     }
 
+
+    private void setVpcIdInResponse(Long vpcId, Consumer<String> vpcUuidSetter, Consumer<String> vpcNameSetter) {
+        if (vpcId != null) {
+            Vpc vpc = ApiDBUtils.findVpcById(vpcId);
+            if (vpc != null) {
+                try {
+                    _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, false, vpc);
+                    vpcUuidSetter.accept(vpc.getUuid());
+                } catch (PermissionDeniedException e) {
+                    s_logger.debug("Not setting the vpcId to the response because the caller does not have access to the VPC");
+                }
+                vpcNameSetter.accept(vpc.getName());
+            }
+        }
+    }
+
     private void showVmInfoForSharedNetworks(boolean forVirtualNetworks, IpAddress ipAddr, IPAddressResponse ipResponse) {
         if (!forVirtualNetworks) {
             NicVO nic = ApiDBUtils.findByIp4AddressAndNetworkId(ipAddr.getAddress().toString(), ipAddr.getNetworkId());
@@ -1095,8 +1170,9 @@
                         ipResponse.setVirtualMachineDisplayName(vm.getHostName());
                     }
                 }
-            } else if (nic.getVmType() == VirtualMachine.Type.DomainRouter) {
+            } else if (nic.getVmType().isUsedBySystem()) {
                 ipResponse.setIsSystem(true);
+                addSystemVmInfoToIpResponse(nic, ipResponse);
             }
         }
     }
@@ -1393,7 +1469,7 @@
             clusterResponse.setZoneId(dc.getUuid());
             clusterResponse.setZoneName(dc.getName());
         }
-        clusterResponse.setHypervisorType(cluster.getHypervisorType().toString());
+        clusterResponse.setHypervisorType(cluster.getHypervisorType().getHypervisorDisplayName());
         clusterResponse.setClusterType(cluster.getClusterType().toString());
         clusterResponse.setAllocationState(cluster.getAllocationState().toString());
         clusterResponse.setManagedState(cluster.getManagedState().toString());
@@ -1589,7 +1665,13 @@
                 vmResponse.setTemplateName(template.getName());
             }
             vmResponse.setCreated(vm.getCreated());
-            vmResponse.setHypervisor(vm.getHypervisorType().toString());
+            vmResponse.setHypervisor(vm.getHypervisorType().getHypervisorDisplayName());
+
+            ServiceOffering serviceOffering = ApiDBUtils.findServiceOfferingById(vm.getServiceOfferingId());
+            if (serviceOffering != null) {
+                vmResponse.setServiceOfferingId(serviceOffering.getUuid());
+                vmResponse.setServiceOfferingName(serviceOffering.getName());
+            }
 
             if (vm.getHostId() != null) {
                 Host host = ApiDBUtils.findHostById(vm.getHostId());
@@ -1882,7 +1964,7 @@
             // it seems that the volume can actually be removed from the DB at some point if it's deleted
             // if volume comes back null, use another technique to try to discover the zone
             if (volume == null) {
-                SnapshotDataStoreVO snapshotStore = _snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+                SnapshotDataStoreVO snapshotStore = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
 
                 if (snapshotStore != null) {
                     long storagePoolId = snapshotStore.getDataStoreId();
@@ -2518,6 +2600,10 @@
                 Domain domain = ApiDBUtils.findDomainById(domainNetworkDetails.first());
                 if (domain != null) {
                     response.setDomainId(domain.getUuid());
+
+                    StringBuilder domainPath = new StringBuilder("ROOT");
+                    (domainPath.append(domain.getPath())).deleteCharAt(domainPath.length() - 1);
+                    response.setDomainPath(domainPath.toString());
                 }
             }
             response.setSubdomainAccess(domainNetworkDetails.second());
@@ -2534,13 +2620,9 @@
         }
 
         response.setSpecifyIpRanges(network.getSpecifyIpRanges());
-        if (network.getVpcId() != null) {
-            Vpc vpc = ApiDBUtils.findVpcById(network.getVpcId());
-            if (vpc != null) {
-                response.setVpcId(vpc.getUuid());
-                response.setVpcName(vpc.getName());
-            }
-        }
+
+
+        setVpcIdInResponse(network.getVpcId(), response::setVpcId, response::setVpcName);
 
         setResponseAssociatedNetworkInformation(response, network.getId());
 
@@ -2752,7 +2834,7 @@
     public HypervisorCapabilitiesResponse createHypervisorCapabilitiesResponse(HypervisorCapabilities hpvCapabilities) {
         HypervisorCapabilitiesResponse hpvCapabilitiesResponse = new HypervisorCapabilitiesResponse();
         hpvCapabilitiesResponse.setId(hpvCapabilities.getUuid());
-        hpvCapabilitiesResponse.setHypervisor(hpvCapabilities.getHypervisorType());
+        hpvCapabilitiesResponse.setHypervisor(hpvCapabilities.getHypervisorType().getHypervisorDisplayName());
         hpvCapabilitiesResponse.setHypervisorVersion(hpvCapabilities.getHypervisorVersion());
         hpvCapabilitiesResponse.setIsSecurityGroupEnabled(hpvCapabilities.isSecurityGroupEnabled());
         hpvCapabilitiesResponse.setMaxGuestsLimit(hpvCapabilities.getMaxGuestsLimit());
@@ -2783,6 +2865,23 @@
         response.setDomainName(domain.getName());
     }
 
+    private void populateOwner(ControlledViewEntityResponse response, ControlledEntity object) {
+        Account account = ApiDBUtils.findAccountById(object.getAccountId());
+
+        if (account.getType() == Account.Type.PROJECT) {
+            // find the project
+            Project project = ApiDBUtils.findProjectByProjectAccountId(account.getId());
+            response.setProjectId(project.getUuid());
+            response.setProjectName(project.getName());
+        } else {
+            response.setAccountName(account.getAccountName());
+        }
+
+        Domain domain = ApiDBUtils.findDomainById(object.getDomainId());
+        response.setDomainId(domain.getUuid());
+        response.setDomainName(domain.getName());
+    }
+
     public static void populateOwner(ControlledViewEntityResponse response, ControlledViewEntity object) {
 
         if (object.getAccountType() == Account.Type.PROJECT) {
@@ -3307,10 +3406,12 @@
     }
 
     @Override
-    public PrivateGatewayResponse createPrivateGatewayResponse(PrivateGateway result) {
+    public PrivateGatewayResponse createPrivateGatewayResponse(ResponseView view, PrivateGateway result) {
         PrivateGatewayResponse response = new PrivateGatewayResponse();
         response.setId(result.getUuid());
-        response.setBroadcastUri(result.getBroadcastUri());
+        if (view == ResponseView.Full) {
+            response.setBroadcastUri(result.getBroadcastUri());
+        }
         response.setGateway(result.getGateway());
         response.setNetmask(result.getNetmask());
         if (result.getVpcId() != null) {
@@ -3643,6 +3744,7 @@
     @Override
     public GuestOSResponse createGuestOSResponse(GuestOS guestOS) {
         GuestOSResponse response = new GuestOSResponse();
+        response.setName(guestOS.getDisplayName());
         response.setDescription(guestOS.getDisplayName());
         response.setId(guestOS.getUuid());
         response.setIsUserDefined(guestOS.getIsUserDefined());
@@ -3650,6 +3752,7 @@
         GuestOSCategoryVO category = ApiDBUtils.findGuestOsCategoryById(guestOS.getCategoryId());
         if (category != null) {
             response.setOsCategoryId(category.getUuid());
+            response.setOsCategoryName(category.getName());
         }
 
         response.setObjectName("ostype");
@@ -3660,7 +3763,7 @@
     public GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor guestOSHypervisor) {
         GuestOsMappingResponse response = new GuestOsMappingResponse();
         response.setId(guestOSHypervisor.getUuid());
-        response.setHypervisor(guestOSHypervisor.getHypervisorType());
+        response.setHypervisor(Hypervisor.HypervisorType.getType(guestOSHypervisor.getHypervisorType()).getHypervisorDisplayName());
         response.setHypervisorVersion(guestOSHypervisor.getHypervisorVersion());
         response.setOsNameForHypervisor((guestOSHypervisor.getGuestOsName()));
         response.setIsUserDefined(Boolean.valueOf(guestOSHypervisor.getIsUserDefined()).toString());
@@ -3675,6 +3778,28 @@
     }
 
     @Override
+    public HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(List<Pair<String, String>> hypervisorGuestOsNames) {
+        HypervisorGuestOsNamesResponse response = new HypervisorGuestOsNamesResponse();
+        List<HypervisorGuestOsResponse> hypervisorGuestOsResponses = new ArrayList<>();
+        for (Pair<String, String> hypervisorGuestOsName : hypervisorGuestOsNames) {
+            HypervisorGuestOsResponse hypervisorGuestOsResponse = createHypervisorGuestOsResponse(hypervisorGuestOsName);
+            hypervisorGuestOsResponses.add(hypervisorGuestOsResponse);
+        }
+        response.setGuestOSList(hypervisorGuestOsResponses);
+        response.setGuestOSCount(hypervisorGuestOsResponses.size());
+        response.setObjectName("hypervisorguestosnames");
+        return response;
+    }
+
+    private HypervisorGuestOsResponse createHypervisorGuestOsResponse(Pair<String, String> hypervisorGuestOsName) {
+        HypervisorGuestOsResponse hypervisorGuestOsResponse = new HypervisorGuestOsResponse();
+        hypervisorGuestOsResponse.setOsStdName(hypervisorGuestOsName.first());
+        hypervisorGuestOsResponse.setOsNameForHypervisor(hypervisorGuestOsName.second());
+        hypervisorGuestOsResponse.setObjectName(ApiConstants.GUEST_OS_LIST);
+        return hypervisorGuestOsResponse;
+    }
+
+    @Override
     public SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule snapshotSchedule) {
         SnapshotScheduleResponse response = new SnapshotScheduleResponse();
         response.setId(snapshotSchedule.getUuid());
@@ -4199,6 +4324,10 @@
                 }
                 usageRecResponse.setDescription(builder.toString());
             }
+        } else if (usageRecord.getUsageType() == UsageTypes.BUCKET) {
+            BucketVO bucket = _entityMgr.findByIdIncludingRemoved(BucketVO.class, usageRecord.getUsageId().toString());
+            usageRecResponse.setUsageId(bucket.getUuid());
+            usageRecResponse.setResourceName(bucket.getName());
         }
         if(resourceTagResponseMap != null && resourceTagResponseMap.get(resourceId + ":" + resourceType) != null) {
              usageRecResponse.setTags(resourceTagResponseMap.get(resourceId + ":" + resourceType));
@@ -4689,12 +4818,7 @@
     @Override
     public UserDataResponse createUserDataResponse(UserData userData) {
         UserDataResponse response = new UserDataResponse(userData.getUuid(), userData.getName(), userData.getUserData(), userData.getParams());
-        Account account = ApiDBUtils.findAccountById(userData.getAccountId());
-        response.setAccountId(account.getUuid());
-        response.setAccountName(account.getAccountName());
-        Domain domain = ApiDBUtils.findDomainById(userData.getDomainId());
-        response.setDomainId(domain.getUuid());
-        response.setDomainName(domain.getName());
+        populateOwner(response, userData);
         response.setHasAnnotation(annotationDao.hasAnnotations(userData.getUuid(), AnnotationService.EntityType.USER_DATA.name(),
                 _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
         return response;
@@ -4888,7 +5012,7 @@
         response.setId(certificate.getUuid());
         response.setAlias(certificate.getAlias());
         handleCertificateResponse(certificate.getCertificate(), response);
-        response.setHypervisor(certificate.getHypervisorType().name());
+        response.setHypervisor(certificate.getHypervisorType().getHypervisorDisplayName());
         response.setObjectName("directdownloadcertificate");
         return response;
     }
@@ -4980,4 +5104,139 @@
         response.setObjectName("firewallrule");
         return response;
     }
+
+    @Override
+    public UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) {
+        UnmanagedInstanceResponse response = new UnmanagedInstanceResponse();
+        response.setName(instance.getName());
+        if (cluster != null) {
+            response.setClusterId(cluster.getUuid());
+            response.setClusterName(cluster.getName());
+        } else if (instance.getClusterName() != null) {
+            response.setClusterName(instance.getClusterName());
+        }
+        if (host != null) {
+            response.setHostId(host.getUuid());
+            response.setHostName(host.getName());
+        } else if (instance.getHostName() != null) {
+            response.setHostName(instance.getHostName());
+        }
+        response.setPowerState(instance.getPowerState().toString());
+        response.setCpuCores(instance.getCpuCores());
+        response.setCpuSpeed(instance.getCpuSpeed());
+        response.setCpuCoresPerSocket(instance.getCpuCoresPerSocket());
+        response.setMemory(instance.getMemory());
+        response.setOperatingSystemId(instance.getOperatingSystemId());
+        response.setOperatingSystem(instance.getOperatingSystem());
+        response.setObjectName("unmanagedinstance");
+
+        if (instance.getDisks() != null) {
+            for (UnmanagedInstanceTO.Disk disk : instance.getDisks()) {
+                UnmanagedInstanceDiskResponse diskResponse = new UnmanagedInstanceDiskResponse();
+                diskResponse.setDiskId(disk.getDiskId());
+                if (StringUtils.isNotEmpty(disk.getLabel())) {
+                    diskResponse.setLabel(disk.getLabel());
+                }
+                diskResponse.setCapacity(disk.getCapacity());
+                diskResponse.setController(disk.getController());
+                diskResponse.setControllerUnit(disk.getControllerUnit());
+                diskResponse.setPosition(disk.getPosition());
+                diskResponse.setImagePath(disk.getImagePath());
+                diskResponse.setDatastoreName(disk.getDatastoreName());
+                diskResponse.setDatastoreHost(disk.getDatastoreHost());
+                diskResponse.setDatastorePath(disk.getDatastorePath());
+                diskResponse.setDatastoreType(disk.getDatastoreType());
+                response.addDisk(diskResponse);
+            }
+        }
+
+        if (instance.getNics() != null) {
+            for (UnmanagedInstanceTO.Nic nic : instance.getNics()) {
+                NicResponse nicResponse = new NicResponse();
+                nicResponse.setId(nic.getNicId());
+                nicResponse.setNetworkName(nic.getNetwork());
+                nicResponse.setMacAddress(nic.getMacAddress());
+                if (StringUtils.isNotEmpty(nic.getAdapterType())) {
+                    nicResponse.setAdapterType(nic.getAdapterType());
+                }
+                if (!CollectionUtils.isEmpty(nic.getIpAddress())) {
+                    nicResponse.setIpAddresses(nic.getIpAddress());
+                }
+                nicResponse.setVlanId(nic.getVlan());
+                nicResponse.setIsolatedPvlanId(nic.getPvlan());
+                nicResponse.setIsolatedPvlanType(nic.getPvlanType());
+                response.addNic(nicResponse);
+            }
+        }
+        return response;
+    }
+
+    @Override
+    public SecondaryStorageHeuristicsResponse createSecondaryStorageSelectorResponse(Heuristic heuristic) {
+        String zoneUuid = ApiDBUtils.findZoneById(heuristic.getZoneId()).getUuid();
+        SecondaryStorageHeuristicsResponse secondaryStorageHeuristicsResponse = new SecondaryStorageHeuristicsResponse(heuristic.getUuid(), heuristic.getName(),
+                heuristic.getDescription(), zoneUuid, heuristic.getType(), heuristic.getHeuristicRule(), heuristic.getCreated(), heuristic.getRemoved());
+        secondaryStorageHeuristicsResponse.setResponseName("secondarystorageheuristics");
+
+        return secondaryStorageHeuristicsResponse;
+    }
+
+    @Override
+    public IpQuarantineResponse createQuarantinedIpsResponse(PublicIpQuarantine quarantinedIp) {
+        IpQuarantineResponse quarantinedIpsResponse = new IpQuarantineResponse();
+        String ipAddress = userIpAddressDao.findById(quarantinedIp.getPublicIpAddressId()).getAddress().toString();
+        Account previousOwner = _accountMgr.getAccount(quarantinedIp.getPreviousOwnerId());
+
+        quarantinedIpsResponse.setId(quarantinedIp.getUuid());
+        quarantinedIpsResponse.setPublicIpAddress(ipAddress);
+        quarantinedIpsResponse.setPreviousOwnerId(previousOwner.getUuid());
+        quarantinedIpsResponse.setPreviousOwnerName(previousOwner.getName());
+        quarantinedIpsResponse.setCreated(quarantinedIp.getCreated());
+        quarantinedIpsResponse.setRemoved(quarantinedIp.getRemoved());
+        quarantinedIpsResponse.setEndDate(quarantinedIp.getEndDate());
+        quarantinedIpsResponse.setRemovalReason(quarantinedIp.getRemovalReason());
+        if (quarantinedIp.getRemoverAccountId() != null) {
+            Account removerAccount = _accountMgr.getAccount(quarantinedIp.getRemoverAccountId());
+            quarantinedIpsResponse.setRemoverAccountId(removerAccount.getUuid());
+        }
+        quarantinedIpsResponse.setResponseName("quarantinedip");
+
+        return quarantinedIpsResponse;
+    }
+
+    public ObjectStoreResponse createObjectStoreResponse(ObjectStore os) {
+        ObjectStoreResponse objectStoreResponse = new ObjectStoreResponse();
+        objectStoreResponse.setId(os.getUuid());
+        objectStoreResponse.setName(os.getName());
+        objectStoreResponse.setProviderName(os.getProviderName());
+        objectStoreResponse.setObjectName("objectstore");
+        return objectStoreResponse;
+    }
+
+    @Override
+    public BucketResponse createBucketResponse(Bucket bucket) {
+        BucketResponse bucketResponse = new BucketResponse();
+        bucketResponse.setName(bucket.getName());
+        bucketResponse.setId(bucket.getUuid());
+        bucketResponse.setCreated(bucket.getCreated());
+        bucketResponse.setState(bucket.getState());
+        bucketResponse.setSize(bucket.getSize());
+        if(bucket.getQuota() != null) {
+            bucketResponse.setQuota(bucket.getQuota());
+        }
+        bucketResponse.setVersioning(bucket.isVersioning());
+        bucketResponse.setEncryption(bucket.isEncryption());
+        bucketResponse.setObjectLock(bucket.isObjectLock());
+        bucketResponse.setPolicy(bucket.getPolicy());
+        bucketResponse.setBucketURL(bucket.getBucketURL());
+        bucketResponse.setAccessKey(bucket.getAccessKey());
+        bucketResponse.setSecretKey(bucket.getSecretKey());
+        ObjectStoreVO objectStoreVO = _objectStoreDao.findById(bucket.getObjectStoreId());
+        bucketResponse.setObjectStoragePoolId(objectStoreVO.getUuid());
+        bucketResponse.setObjectStoragePool(objectStoreVO.getName());
+        bucketResponse.setObjectName("bucket");
+        bucketResponse.setProvider(objectStoreVO.getProviderName());
+        populateAccount(bucketResponse, bucket.getAccountId());
+        return bucketResponse;
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java
index 4a7259c..76a436f 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -394,7 +394,7 @@
         try {
             eventBus.publish(event);
         } catch (EventBusException evx) {
-            String errMsg = "Failed to publish async job event on the the event bus.";
+            String errMsg = "Failed to publish async job event on the event bus.";
             s_logger.warn(errMsg, evx);
         }
     }
diff --git a/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java b/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
index 6ec9ff9..1b8c268 100644
--- a/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java
@@ -82,7 +82,6 @@
         cmdList.add(ValidateUserTwoFactorAuthenticationCodeCmd.class);
         cmdList.add(SetupUserTwoFactorAuthenticationCmd.class);
 
-
         for (PluggableAPIAuthenticator apiAuthenticator: _apiAuthenticators) {
             List<Class<?>> commands = apiAuthenticator.getAuthCommands();
             if (commands != null) {
diff --git a/server/src/main/java/com/cloud/api/dispatch/ParamGenericValidationWorker.java b/server/src/main/java/com/cloud/api/dispatch/ParamGenericValidationWorker.java
index 45d4ed7..009d88a 100644
--- a/server/src/main/java/com/cloud/api/dispatch/ParamGenericValidationWorker.java
+++ b/server/src/main/java/com/cloud/api/dispatch/ParamGenericValidationWorker.java
@@ -92,7 +92,9 @@
                     break;
                 }
             }
-            if (!matchedCurrentParam && !((String)actualParamName).equalsIgnoreCase("expires") && !((String)actualParamName).equalsIgnoreCase("signatureversion")) {
+            if (!matchedCurrentParam && !((String)actualParamName).equalsIgnoreCase("expires") &&
+                !((String)actualParamName).equalsIgnoreCase("signatureversion") &&
+                !((String)actualParamName).equalsIgnoreCase("projectid")) {
                 errorMsg.append(" ").append(actualParamName);
                 foundUnknownParam= true;
             }
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index 51aed5a..d72e476 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -29,12 +29,42 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.StoragePoolHostVO;
+import com.cloud.event.EventVO;
+import com.cloud.event.dao.EventDao;
+import com.cloud.host.HostVO;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.service.ServiceOfferingDetailsVO;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
+import com.cloud.network.as.AutoScaleVmGroupVmMapVO;
+import com.cloud.network.as.dao.AutoScaleVmGroupDao;
+import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.dao.PublicIpQuarantineDao;
+import com.cloud.network.PublicIpQuarantine;
+import com.cloud.network.vo.PublicIpQuarantineVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.AccountVO;
+import com.cloud.user.SSHKeyPairVO;
+import com.cloud.user.dao.SSHKeyPairDao;
+import com.cloud.vm.InstanceGroupVMMapVO;
+import com.cloud.vm.NicVO;
+import com.cloud.vm.UserVmDetailVO;
+import com.cloud.vm.dao.InstanceGroupVMMapDao;
+import com.cloud.vm.dao.NicDao;
+import com.cloud.vm.dao.UserVmDetailsDao;
+import com.cloud.storage.VolumeVO;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.acl.SecurityChecker;
@@ -61,15 +91,19 @@
 import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd;
 import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd;
 import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd;
+import org.apache.cloudstack.api.command.admin.snapshot.ListSnapshotsCmdByAdmin;
 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.ListStoragePoolsCmd;
 import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd;
 import org.apache.cloudstack.api.command.admin.template.ListTemplatesCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
 import org.apache.cloudstack.api.command.admin.zone.ListZonesCmdByAdmin;
 import org.apache.cloudstack.api.command.user.account.ListAccountsCmd;
 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.event.ListEventsCmd;
 import org.apache.cloudstack.api.command.user.iso.ListIsosCmd;
@@ -80,8 +114,11 @@
 import org.apache.cloudstack.api.command.user.project.ListProjectsCmd;
 import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
 import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
+import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
+import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
 import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
 import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
+import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd;
 import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
 import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd;
 import org.apache.cloudstack.api.command.user.volume.ListResourceDetailsCmd;
@@ -98,8 +135,10 @@
 import org.apache.cloudstack.api.response.HostTagResponse;
 import org.apache.cloudstack.api.response.ImageStoreResponse;
 import org.apache.cloudstack.api.response.InstanceGroupResponse;
+import org.apache.cloudstack.api.response.IpQuarantineResponse;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.ManagementServerResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
 import org.apache.cloudstack.api.response.ProjectAccountResponse;
 import org.apache.cloudstack.api.response.ProjectInvitationResponse;
 import org.apache.cloudstack.api.response.ProjectResponse;
@@ -107,8 +146,10 @@
 import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
+import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
 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.StoragePoolResponse;
 import org.apache.cloudstack.api.response.StorageTagResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
@@ -116,6 +157,8 @@
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.backup.BackupOfferingVO;
+import org.apache.cloudstack.backup.dao.BackupOfferingDao;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
@@ -125,13 +168,25 @@
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
+import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementVO;
+import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao;
 import org.apache.cloudstack.query.QueryService;
+import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
 import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
+import org.apache.cloudstack.secstorage.HeuristicVO;
+import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
 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.StoragePoolDetailVO;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 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.log4j.Logger;
@@ -155,6 +210,7 @@
 import com.cloud.api.query.dao.ResourceTagJoinDao;
 import com.cloud.api.query.dao.SecurityGroupJoinDao;
 import com.cloud.api.query.dao.ServiceOfferingJoinDao;
+import com.cloud.api.query.dao.SnapshotJoinDao;
 import com.cloud.api.query.dao.StoragePoolJoinDao;
 import com.cloud.api.query.dao.TemplateJoinDao;
 import com.cloud.api.query.dao.UserAccountJoinDao;
@@ -179,11 +235,14 @@
 import com.cloud.api.query.vo.ResourceTagJoinVO;
 import com.cloud.api.query.vo.SecurityGroupJoinVO;
 import com.cloud.api.query.vo.ServiceOfferingJoinVO;
+import com.cloud.api.query.vo.SnapshotJoinVO;
 import com.cloud.api.query.vo.StoragePoolJoinVO;
 import com.cloud.api.query.vo.TemplateJoinVO;
 import com.cloud.api.query.vo.UserAccountJoinVO;
 import com.cloud.api.query.vo.UserVmJoinVO;
 import com.cloud.api.query.vo.VolumeJoinVO;
+import com.cloud.cluster.ManagementServerHostVO;
+import com.cloud.cluster.dao.ManagementServerHostDao;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DedicatedResourceVO;
 import com.cloud.dc.dao.DedicatedResourceDao;
@@ -195,10 +254,10 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.PermissionDeniedException;
 import com.cloud.ha.HighAvailabilityManager;
-import com.cloud.host.Host;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.network.RouterHealthCheckResult;
+import com.cloud.network.VNF;
 import com.cloud.network.VpcVirtualNetworkApplianceService;
 import com.cloud.network.dao.RouterHealthCheckResultDao;
 import com.cloud.network.dao.RouterHealthCheckResultVO;
@@ -224,9 +283,12 @@
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.service.dao.ServiceOfferingDetailsDao;
+import com.cloud.storage.BucketVO;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.ScopeType;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage.TemplateType;
@@ -235,10 +297,10 @@
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeApiServiceImpl;
+import com.cloud.storage.dao.BucketDao;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.StoragePoolTagsDao;
 import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.storage.dao.VolumeDao;
 import com.cloud.tags.ResourceTagVO;
 import com.cloud.tags.dao.ResourceTagDao;
 import com.cloud.template.VirtualMachineTemplate.State;
@@ -271,6 +333,10 @@
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd;
+import org.apache.cloudstack.api.response.BucketResponse;
+
+import static com.cloud.vm.VmDetailConstants.SSH_PUBLIC_KEY;
 
 @Component
 public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable {
@@ -280,7 +346,7 @@
     private static final String ID_FIELD = "id";
 
     @Inject
-    private AccountManager _accountMgr;
+    private AccountManager accountMgr;
 
     @Inject
     private ProjectManager _projectMgr;
@@ -295,6 +361,9 @@
     private UserAccountJoinDao _userAccountJoinDao;
 
     @Inject
+    private EventDao eventDao;
+
+    @Inject
     private EventJoinDao _eventJoinDao;
 
     @Inject
@@ -307,7 +376,7 @@
     private UserVmJoinDao _userVmJoinDao;
 
     @Inject
-    private UserVmDao _userVmDao;
+    private UserVmDao userVmDao;
 
     @Inject
     private VMInstanceDao _vmInstanceDao;
@@ -316,7 +385,7 @@
     private SecurityGroupJoinDao _securityGroupJoinDao;
 
     @Inject
-    private SecurityGroupVMMapDao _securityGroupVMMapDao;
+    private SecurityGroupVMMapDao securityGroupVMMapDao;
 
     @Inject
     private DomainRouterJoinDao _routerJoinDao;
@@ -337,7 +406,7 @@
     private ProjectAccountJoinDao _projectAccountJoinDao;
 
     @Inject
-    private HostJoinDao _hostJoinDao;
+    private HostJoinDao hostJoinDao;
 
     @Inject
     private VolumeJoinDao _volumeJoinDao;
@@ -420,7 +489,7 @@
     private AffinityGroupDomainMapDao _affinityGroupDomainMapDao;
 
     @Inject
-    private ResourceTagDao _resourceTagDao;
+    private ResourceTagDao resourceTagDao;
 
     @Inject
     private DataStoreManager dataStoreManager;
@@ -438,7 +507,7 @@
     private RouterHealthCheckResultDao routerHealthCheckResultDao;
 
     @Inject
-    private PrimaryDataStoreDao _storagePoolDao;
+    private PrimaryDataStoreDao storagePoolDao;
 
     @Inject
     private StoragePoolDetailsDao _storagePoolDetailsDao;
@@ -447,10 +516,20 @@
     private ProjectInvitationDao projectInvitationDao;
 
     @Inject
+    private TemplateDataStoreDao templateDataStoreDao;
+
+    @Inject
+    private VMTemplatePoolDao templatePoolDao;
+
+    @Inject
+    private SnapshotDataStoreDao snapshotDataStoreDao;
+
+    @Inject
     private UserDao userDao;
 
     @Inject
     private VirtualMachineManager virtualMachineManager;
+
     @Inject
     private VolumeDao volumeDao;
 
@@ -458,8 +537,62 @@
     private ResourceIconDao resourceIconDao;
 
     @Inject
+    private ManagementServerHostDao msHostDao;
+
+    @Inject
+    private SecondaryStorageHeuristicDao secondaryStorageHeuristicDao;
+
+    @Inject
+    private NetworkDao networkDao;
+
+    @Inject
+    private NicDao nicDao;
+
+    @Inject
+    private HostDao hostDao;
+
+    @Inject
+    private OutOfBandManagementDao outOfBandManagementDao;
+
+    @Inject
+    private InstanceGroupVMMapDao instanceGroupVMMapDao;
+
+    @Inject
+    private AffinityGroupVMMapDao affinityGroupVMMapDao;
+
+    @Inject
+    private UserVmDetailsDao userVmDetailsDao;
+
+    @Inject
+    private SSHKeyPairDao sshKeyPairDao;
+
+    @Inject
+    private BackupOfferingDao backupOfferingDao;
+
+    @Inject
+    private AutoScaleVmGroupDao autoScaleVmGroupDao;
+
+    @Inject
+    private AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDao;
+
+    @Inject
+    private SnapshotJoinDao snapshotJoinDao;
+
+    @Inject
+    private ObjectStoreDao objectStoreDao;
+
+    @Inject
+    private BucketDao bucketDao;
+
+    @Inject
     EntityManager entityManager;
 
+    @Inject
+    private PublicIpQuarantineDao publicIpQuarantineDao;
+
+    @Inject
+    private StoragePoolHostDao storagePoolHostDao;
+
     private SearchCriteria<ServiceOfferingJoinVO> getMinimumCpuServiceOfferingJoinSearchCriteria(int cpu) {
         SearchCriteria<ServiceOfferingJoinVO> sc = _srvOfferingJoinDao.createSearchCriteria();
         SearchCriteria<ServiceOfferingJoinVO> sc1 = _srvOfferingJoinDao.createSearchCriteria();
@@ -579,13 +712,13 @@
     private Pair<List<UserAccountJoinVO>, Integer> getUserListInternal(Account caller, List<Long> permittedAccounts, boolean listAll, Long id, Object username, Object type,
             String accountName, Object state, String keyword, Long domainId, boolean recursive, Filter searchFilter) {
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, recursive, null);
-        _accountMgr.buildACLSearchParameters(caller, id, accountName, null, permittedAccounts, domainIdRecursiveListProject, listAll, false);
+        accountMgr.buildACLSearchParameters(caller, id, accountName, null, permittedAccounts, domainIdRecursiveListProject, listAll, false);
         domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
 
         SearchBuilder<UserAccountJoinVO> sb = _userAccountJoinDao.createSearchBuilder();
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
         sb.and("username", sb.entity().getUsername(), Op.LIKE);
         if (id != null && id == 1) {
             // system user should NOT be searchable
@@ -611,7 +744,7 @@
         SearchCriteria<UserAccountJoinVO> sc = sb.create();
 
         // building ACL condition
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (keyword != null) {
             SearchCriteria<UserAccountJoinVO> ssc = _userAccountJoinDao.createSearchCriteria();
@@ -670,9 +803,23 @@
     }
 
     private Pair<List<EventJoinVO>, Integer> searchForEventsInternal(ListEventsCmd cmd) {
+        Pair<List<Long>, Integer> eventIdPage = searchForEventIdsAndCount(cmd);
+
+        Integer count = eventIdPage.second();
+        Long[] idArray = eventIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<EventJoinVO> events = _eventJoinDao.searchByIds(idArray);
+        return new Pair<>(events, count);
+    }
+
+    private Pair<List<Long>, Integer> searchForEventIdsAndCount(ListEventsCmd cmd) {
         Account caller = CallContext.current().getCallingAccount();
-        boolean isRootAdmin = _accountMgr.isRootAdmin(caller.getId());
-        List<Long> permittedAccounts = new ArrayList<Long>();
+        boolean isRootAdmin = accountMgr.isRootAdmin(caller.getId());
+        List<Long> permittedAccounts = new ArrayList<>();
 
         Long id = cmd.getId();
         String type = cmd.getType();
@@ -711,45 +858,53 @@
             }
             if (!isRootAdmin && object instanceof ControlledEntity) {
                 ControlledEntity entity = (ControlledEntity)object;
-                _accountMgr.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.ListEntry, entity.getAccountId() == caller.getId(), entity);
+                accountMgr.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.ListEntry, entity.getAccountId() == caller.getId(), entity);
             }
         }
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
 
-        Filter searchFilter = new Filter(EventJoinVO.class, "createDate", false, cmd.getStartIndex(), cmd.getPageSizeVal());
+        Filter searchFilter = new Filter(EventVO.class, "createDate", false, cmd.getStartIndex(), cmd.getPageSizeVal());
         // additional order by since createdDate does not have milliseconds
         // and two events, created within one second can be incorrectly ordered (for example VM.CREATE Completed before Scheduled)
-        searchFilter.addOrderBy(EventJoinVO.class, "id", false);
+        searchFilter.addOrderBy(EventVO.class, "id", false);
 
-        SearchBuilder<EventJoinVO> sb = _eventJoinDao.createSearchBuilder();
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        SearchBuilder<EventVO> eventSearchBuilder = eventDao.createSearchBuilder();
+        eventSearchBuilder.select(null, Func.DISTINCT, eventSearchBuilder.entity().getId());
+        accountMgr.buildACLSearchBuilder(eventSearchBuilder, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
-        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
-        sb.and("levelL", sb.entity().getLevel(), SearchCriteria.Op.LIKE);
-        sb.and("levelEQ", sb.entity().getLevel(), SearchCriteria.Op.EQ);
-        sb.and("type", sb.entity().getType(), SearchCriteria.Op.EQ);
-        sb.and("createDateB", sb.entity().getCreateDate(), SearchCriteria.Op.BETWEEN);
-        sb.and("createDateG", sb.entity().getCreateDate(), SearchCriteria.Op.GTEQ);
-        sb.and("createDateL", sb.entity().getCreateDate(), SearchCriteria.Op.LTEQ);
-        sb.and("state", sb.entity().getState(), SearchCriteria.Op.NEQ);
-        sb.or("startId", sb.entity().getStartId(), SearchCriteria.Op.EQ);
-        sb.and("createDate", sb.entity().getCreateDate(), SearchCriteria.Op.BETWEEN);
-        sb.and("displayEvent", sb.entity().getDisplay(), SearchCriteria.Op.EQ);
-        sb.and("archived", sb.entity().getArchived(), SearchCriteria.Op.EQ);
-        sb.and("resourceId", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
-        sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("id", eventSearchBuilder.entity().getId(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("levelL", eventSearchBuilder.entity().getLevel(), SearchCriteria.Op.LIKE);
+        eventSearchBuilder.and("levelEQ", eventSearchBuilder.entity().getLevel(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("type", eventSearchBuilder.entity().getType(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("createDateB", eventSearchBuilder.entity().getCreateDate(), SearchCriteria.Op.BETWEEN);
+        eventSearchBuilder.and("createDateG", eventSearchBuilder.entity().getCreateDate(), SearchCriteria.Op.GTEQ);
+        eventSearchBuilder.and("createDateL", eventSearchBuilder.entity().getCreateDate(), SearchCriteria.Op.LTEQ);
+        eventSearchBuilder.and("state", eventSearchBuilder.entity().getState(), SearchCriteria.Op.NEQ);
+        eventSearchBuilder.or("startId", eventSearchBuilder.entity().getStartId(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("createDate", eventSearchBuilder.entity().getCreateDate(), SearchCriteria.Op.BETWEEN);
+        eventSearchBuilder.and("displayEvent", eventSearchBuilder.entity().isDisplay(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("archived", eventSearchBuilder.entity().getArchived(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("resourceId", eventSearchBuilder.entity().getResourceId(), SearchCriteria.Op.EQ);
+        eventSearchBuilder.and("resourceType", eventSearchBuilder.entity().getResourceType(), SearchCriteria.Op.EQ);
 
-        SearchCriteria<EventJoinVO> sc = sb.create();
+        if (keyword != null) {
+            eventSearchBuilder.and().op("keywordType", eventSearchBuilder.entity().getType(), SearchCriteria.Op.LIKE);
+            eventSearchBuilder.or("keywordDescription", eventSearchBuilder.entity().getDescription(), SearchCriteria.Op.LIKE);
+            eventSearchBuilder.or("keywordLevel", eventSearchBuilder.entity().getLevel(), SearchCriteria.Op.LIKE);
+            eventSearchBuilder.cp();
+        }
+
+        SearchCriteria<EventVO> sc = eventSearchBuilder.create();
         // building ACL condition
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         // For end users display only enabled events
-        if (!_accountMgr.isRootAdmin(caller.getId())) {
+        if (!accountMgr.isRootAdmin(caller.getId())) {
             sc.setParameters("displayEvent", true);
         }
 
@@ -765,11 +920,9 @@
         }
 
         if (keyword != null) {
-            SearchCriteria<EventJoinVO> ssc = _eventJoinDao.createSearchCriteria();
-            ssc.addOr("type", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("description", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("level", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            sc.addAnd("level", SearchCriteria.Op.SC, ssc);
+            sc.setParameters("keywordType", "%" + keyword + "%");
+            sc.setParameters("keywordDescription", "%" + keyword + "%");
+            sc.setParameters("keywordLevel", "%" + keyword + "%");
         }
 
         if (level != null) {
@@ -796,9 +949,11 @@
             sc.setParameters("resourceType", resourceType.toString());
         }
 
-        sc.setParameters("archived", false);
+        if (id == null) {
+            sc.setParameters("archived", cmd.getArchived());
+        }
 
-        Pair<List<EventJoinVO>, Integer> eventPair = null;
+        Pair<List<Long>, Integer> eventPair = null;
         // event_view will not have duplicate rows for each event, so
         // searchAndCount should be good enough.
         if ((entryTime != null) && (duration != null)) {
@@ -822,11 +977,14 @@
              * _eventDao.findCompletedEvent(event.getId()); if (completedEvent
              * == null) { pendingEvents.add(event); } } return pendingEvents;
              */
+            eventPair = new Pair<>(new ArrayList<>(), 0);
         } else {
-            eventPair = _eventJoinDao.searchAndCount(sc, searchFilter);
+            Pair<List<EventVO>, Integer> uniqueEventPair = eventDao.searchAndCount(sc, searchFilter);
+            Integer count = uniqueEventPair.second();
+            List<Long> eventIds = uniqueEventPair.first().stream().map(EventVO::getId).collect(Collectors.toList());
+            eventPair = new Pair<>(eventIds, count);
         }
         return eventPair;
-
     }
 
     @Override
@@ -862,14 +1020,14 @@
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
 
-        _accountMgr.buildACLSearchParameters(caller, null, cmd.getAccountName(), projectId, permittedAccounts, domainIdRecursiveListProject, listAll, false);
+        accountMgr.buildACLSearchParameters(caller, null, cmd.getAccountName(), projectId, permittedAccounts, domainIdRecursiveListProject, listAll, false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
         Filter searchFilter = new Filter(ResourceTagJoinVO.class, "resourceType", false, cmd.getStartIndex(), cmd.getPageSizeVal());
 
         SearchBuilder<ResourceTagJoinVO> sb = _resourceTagJoinDao.createSearchBuilder();
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         sb.and("key", sb.entity().getKey(), SearchCriteria.Op.EQ);
         sb.and("value", sb.entity().getValue(), SearchCriteria.Op.EQ);
@@ -884,7 +1042,7 @@
 
         // now set the SC criteria...
         SearchCriteria<ResourceTagJoinVO> sc = sb.create();
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (key != null) {
             sc.setParameters("key", key);
@@ -934,7 +1092,7 @@
         List<Long> permittedAccounts = new ArrayList<Long>();
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
@@ -942,13 +1100,13 @@
         Filter searchFilter = new Filter(InstanceGroupJoinVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
 
         SearchBuilder<InstanceGroupJoinVO> sb = _vmGroupJoinDao.createSearchBuilder();
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
         sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
 
         SearchCriteria<InstanceGroupJoinVO> sc = sb.create();
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (keyword != null) {
             SearchCriteria<InstanceGroupJoinVO> ssc = _vmGroupJoinDao.createSearchCriteria();
@@ -971,9 +1129,15 @@
     public ListResponse<UserVmResponse> searchForUserVMs(ListVMsCmd cmd) {
         Pair<List<UserVmJoinVO>, Integer> result = searchForUserVMsInternal(cmd);
         ListResponse<UserVmResponse> response = new ListResponse<>();
+
+        if (cmd.getRetrieveOnlyResourceCount()) {
+            response.setResponses(new ArrayList<>(), result.second());
+            return response;
+        }
+
         ResponseView respView = ResponseView.Restricted;
         Account caller = CallContext.current().getCallingAccount();
-        if (_accountMgr.isRootAdmin(caller.getId())) {
+        if (accountMgr.isRootAdmin(caller.getId())) {
             respView = ResponseView.Full;
         }
         List<UserVmResponse> vmResponses = ViewResponseHelper.createUserVmResponse(respView, "virtualmachine", cmd.getDetails(), cmd.getAccumulate(), cmd.getShowUserData(),
@@ -995,21 +1159,79 @@
     }
 
     private Pair<List<UserVmJoinVO>, Integer> searchForUserVMsInternal(ListVMsCmd cmd) {
+        Pair<List<Long>, Integer> vmIdPage = searchForUserVMIdsAndCount(cmd);
+
+        Integer count = vmIdPage.second();
+        Long[] idArray = vmIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        // search vm details by ids
+        List<UserVmJoinVO> vms = _userVmJoinDao.searchByIds(idArray);
+        return new Pair<>(vms, count);
+    }
+
+    private Pair<List<Long>, Integer> searchForUserVMIdsAndCount(ListVMsCmd cmd) {
         Account caller = CallContext.current().getCallingAccount();
         List<Long> permittedAccounts = new ArrayList<>();
-
         boolean listAll = cmd.listAll();
         Long id = cmd.getId();
+        Boolean display = cmd.getDisplay();
+        String hypervisor = cmd.getHypervisor();
+        String state = cmd.getState();
+        Long zoneId = cmd.getZoneId();
+        Long templateId = cmd.getTemplateId();
+        Long serviceOfferingId = cmd.getServiceOfferingId();
+        Boolean isHaEnabled = cmd.getHaEnabled();
+        String keyword = cmd.getKeyword();
+        Long networkId = cmd.getNetworkId();
+        Long isoId = cmd.getIsoId();
+        String vmHostName = cmd.getName();
+        Long hostId = null;
+        Long podId = null;
+        Long clusterId = null;
+        Long groupId = cmd.getGroupId();
+        Long vpcId = cmd.getVpcId();
+        Long affinityGroupId = cmd.getAffinityGroupId();
+        String keyPairName = cmd.getKeyPairName();
+        Long securityGroupId = cmd.getSecurityGroupId();
+        Long autoScaleVmGroupId = cmd.getAutoScaleVmGroupId();
+        Long backupOfferingId = cmd.getBackupOfferingId();
+        Long storageId = null;
+        StoragePoolVO pool = null;
         Long userId = cmd.getUserId();
         Map<String, String> tags = cmd.getTags();
-        Boolean display = cmd.getDisplay();
+
+        boolean isAdmin = false;
+        boolean isRootAdmin = false;
+
+        if (accountMgr.isAdmin(caller.getId())) {
+            isAdmin = true;
+        }
+
+        if (accountMgr.isRootAdmin(caller.getId())) {
+            isRootAdmin = true;
+            podId = (Long) getObjectPossibleMethodValue(cmd, "getPodId");
+            clusterId = (Long) getObjectPossibleMethodValue(cmd, "getClusterId");
+            hostId = (Long) getObjectPossibleMethodValue(cmd, "getHostId");
+            storageId = (Long) getObjectPossibleMethodValue(cmd, "getStorageId");
+            if (storageId != null) {
+                pool = storagePoolDao.findById( storageId);
+                if (pool == null) {
+                    throw new InvalidParameterValueException("Unable to find specified storage pool");
+                }
+            }
+        }
+
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, listAll, false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, listAll, false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
 
-        Filter searchFilter = new Filter(UserVmJoinVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
+        Filter searchFilter = new Filter(UserVmVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
 
         List<Long> ids = null;
         if (cmd.getId() != null) {
@@ -1022,302 +1244,336 @@
             ids = cmd.getIds();
         }
 
-        // first search distinct vm id by using query criteria and pagination
-        SearchBuilder<UserVmJoinVO> sb = _userVmJoinDao.createSearchBuilder();
-        sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct ids
+        SearchBuilder<UserVmVO> userVmSearchBuilder = userVmDao.createSearchBuilder();
+        userVmSearchBuilder.select(null, Func.DISTINCT, userVmSearchBuilder.entity().getId());
+        accountMgr.buildACLSearchBuilder(userVmSearchBuilder, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
-
-        String hypervisor = cmd.getHypervisor();
-        Object name = cmd.getName();
-        String state = cmd.getState();
-        Object zoneId = cmd.getZoneId();
-        Object keyword = cmd.getKeyword();
-        boolean isAdmin = false;
-        boolean isRootAdmin = false;
-        if (_accountMgr.isAdmin(caller.getId())) {
-            isAdmin = true;
-        }
-        if (_accountMgr.isRootAdmin(caller.getId())) {
-            isRootAdmin = true;
-        }
-
-        Object groupId = cmd.getGroupId();
-        Object networkId = cmd.getNetworkId();
         if (HypervisorType.getType(hypervisor) == HypervisorType.None && hypervisor != null) {
             // invalid hypervisor type input
             throw new InvalidParameterValueException("Invalid HypervisorType " + hypervisor);
         }
-        Object templateId = cmd.getTemplateId();
-        Object isoId = cmd.getIsoId();
-        Object vpcId = cmd.getVpcId();
-        Object affinityGroupId = cmd.getAffinityGroupId();
-        Object keyPairName = cmd.getKeyPairName();
-        Object serviceOffId = cmd.getServiceOfferingId();
-        Object securityGroupId = cmd.getSecurityGroupId();
-        Object backupOfferingId = cmd.getBackupOfferingId();
-        Object isHaEnabled = cmd.getHaEnabled();
-        Object autoScaleVmGroupId = cmd.getAutoScaleVmGroupId();
-        Object pod = null;
-        Object clusterId = null;
-        Object hostId = null;
-        Object storageId = null;
-        if (_accountMgr.isRootAdmin(caller.getId())) {
-            pod = getObjectPossibleMethodValue(cmd, "getPodId");
-            clusterId = getObjectPossibleMethodValue(cmd, "getClusterId");
-            hostId = getObjectPossibleMethodValue(cmd, "getHostId");
-            storageId = getObjectPossibleMethodValue(cmd, "getStorageId");
-        }
-
-        sb.and("displayName", sb.entity().getDisplayName(), SearchCriteria.Op.LIKE);
-        sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
-        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
-        sb.and("stateEQ", sb.entity().getState(), SearchCriteria.Op.EQ);
-        sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ);
-        sb.and("stateNIN", sb.entity().getState(), SearchCriteria.Op.NIN);
-        sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ);
-        sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ);
-        if (clusterId != null) {
-            sb.and().op("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ);
-            sb.or("clusterHostId", sb.entity().getHostId(), Op.IN);
-            sb.or("clusterLastHostId", sb.entity().getLastHostId(), Op.IN);
-            sb.cp();
-        }
-        sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
-        sb.and("hostIdEQ", sb.entity().getHostId(), SearchCriteria.Op.EQ);
-        sb.and("templateId", sb.entity().getTemplateId(), SearchCriteria.Op.EQ);
-        sb.and("isoId", sb.entity().getIsoId(), SearchCriteria.Op.EQ);
-        sb.and("instanceGroupId", sb.entity().getInstanceGroupId(), SearchCriteria.Op.EQ);
-
-        if (serviceOffId != null) {
-            sb.and("serviceOfferingId", sb.entity().getServiceOfferingId(), SearchCriteria.Op.EQ);
-        }
-
-        if (backupOfferingId != null) {
-            sb.and("backupOfferingId", sb.entity().getBackupOfferingId(), SearchCriteria.Op.EQ);
-        }
-
-        if (display != null) {
-            sb.and("display", sb.entity().isDisplayVm(), SearchCriteria.Op.EQ);
-        }
-
-        if (isHaEnabled != null) {
-            sb.and("haEnabled", sb.entity().isHaEnabled(), SearchCriteria.Op.EQ);
-        }
-
-        if (groupId != null && (Long)groupId != -1) {
-            sb.and("instanceGroupId", sb.entity().getInstanceGroupId(), SearchCriteria.Op.EQ);
-        }
-
-        if (userId != null) {
-            sb.and("userId", sb.entity().getUserId(), SearchCriteria.Op.EQ);
-        }
-
-        if (networkId != null) {
-            sb.and("networkId", sb.entity().getNetworkId(), SearchCriteria.Op.EQ);
-        }
-
-        if (vpcId != null && networkId == null) {
-            sb.and("vpcId", sb.entity().getVpcId(), SearchCriteria.Op.EQ);
-        }
-
-        if (storageId != null) {
-            StoragePoolVO poolVO = _storagePoolDao.findById((Long) storageId);
-            if (poolVO.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
-                sb.and("poolId", sb.entity().getPoolId(), SearchCriteria.Op.IN);
-            } else {
-                sb.and("poolId", sb.entity().getPoolId(), SearchCriteria.Op.EQ);
-            }
-        }
-
-        if (affinityGroupId != null) {
-            sb.and("affinityGroupId", sb.entity().getAffinityGroupId(), SearchCriteria.Op.EQ);
-        }
-
-        if (keyPairName != null) {
-            sb.and("keyPairName", sb.entity().getKeypairNames(), SearchCriteria.Op.FIND_IN_SET);
-        }
-
-        if (!isRootAdmin) {
-            sb.and("displayVm", sb.entity().isDisplayVm(), SearchCriteria.Op.EQ);
-        }
-
-        if (securityGroupId != null) {
-            sb.and("securityGroupId", sb.entity().getSecurityGroupId(), SearchCriteria.Op.EQ);
-        }
-
-        if (autoScaleVmGroupId != null) {
-            sb.and("autoScaleVmGroupId", sb.entity().getAutoScaleVmGroupId(), SearchCriteria.Op.EQ);
-        }
-
-        // populate the search criteria with the values passed in
-        SearchCriteria<UserVmJoinVO> sc = sb.create();
-
-        // building ACL condition
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
-
-        if (tags != null && !tags.isEmpty()) {
-            SearchCriteria<UserVmJoinVO> tagSc = _userVmJoinDao.createSearchCriteria();
-            for (Map.Entry<String, String> entry : tags.entrySet()) {
-                SearchCriteria<UserVmJoinVO> tsc = _userVmJoinDao.createSearchCriteria();
-                tsc.addAnd("tagKey", SearchCriteria.Op.EQ, entry.getKey());
-                tsc.addAnd("tagValue", SearchCriteria.Op.EQ, entry.getValue());
-                tagSc.addOr("tagKey", SearchCriteria.Op.SC, tsc);
-            }
-            sc.addAnd("tagKey", SearchCriteria.Op.SC, tagSc);
-        }
-
-        if (groupId != null && (Long)groupId != -1) {
-            sc.setParameters("instanceGroupId", groupId);
-        }
-
-        if (keyword != null) {
-            SearchCriteria<UserVmJoinVO> ssc = _userVmJoinDao.createSearchCriteria();
-            String likeKeyword = String.format("%%%s%%", keyword);
-            ssc.addOr("displayName", SearchCriteria.Op.LIKE, likeKeyword);
-            ssc.addOr("name", SearchCriteria.Op.LIKE, likeKeyword);
-            if (isRootAdmin) {
-                ssc.addOr("instanceName", SearchCriteria.Op.LIKE, likeKeyword);
-            }
-            ssc.addOr("ipAddress", SearchCriteria.Op.LIKE, likeKeyword);
-            ssc.addOr("publicIpAddress", SearchCriteria.Op.LIKE, likeKeyword);
-            ssc.addOr("ip6Address", SearchCriteria.Op.LIKE, likeKeyword);
-            ssc.addOr("state", SearchCriteria.Op.EQ, keyword);
-            sc.addAnd("displayName", SearchCriteria.Op.SC, ssc);
-        }
-
-        if (serviceOffId != null) {
-            sc.setParameters("serviceOfferingId", serviceOffId);
-        }
-
-        if (backupOfferingId != null) {
-            sc.setParameters("backupOfferingId", backupOfferingId);
-        }
-
-        if (securityGroupId != null) {
-            sc.setParameters("securityGroupId", securityGroupId);
-        }
-
-        if (autoScaleVmGroupId != null) {
-            sc.setParameters("autoScaleVmGroupId", autoScaleVmGroupId);
-        }
-
-        if (display != null) {
-            sc.setParameters("display", display);
-        }
-
-        if (isHaEnabled != null) {
-            sc.setParameters("haEnabled", isHaEnabled);
-        }
 
         if (ids != null && !ids.isEmpty()) {
-            sc.setParameters("idIN", ids.toArray());
+            userVmSearchBuilder.and("idIN", userVmSearchBuilder.entity().getId(), Op.IN);
+        }
+
+        userVmSearchBuilder.and("displayName", userVmSearchBuilder.entity().getDisplayName(), Op.LIKE);
+        userVmSearchBuilder.and("stateEQ", userVmSearchBuilder.entity().getState(), Op.EQ);
+        userVmSearchBuilder.and("stateNEQ", userVmSearchBuilder.entity().getState(), Op.NEQ);
+        userVmSearchBuilder.and("stateNIN", userVmSearchBuilder.entity().getState(), Op.NIN);
+
+        if (hostId != null) {
+            userVmSearchBuilder.and("hostId", userVmSearchBuilder.entity().getHostId(), Op.EQ);
+        }
+
+        if (zoneId != null) {
+            userVmSearchBuilder.and("dataCenterId", userVmSearchBuilder.entity().getDataCenterId(), Op.EQ);
         }
 
         if (templateId != null) {
-            sc.setParameters("templateId", templateId);
+            userVmSearchBuilder.and("templateId", userVmSearchBuilder.entity().getTemplateId(), Op.EQ);
+        }
+
+        if (hypervisor != null) {
+            userVmSearchBuilder.and("hypervisorType", userVmSearchBuilder.entity().getHypervisorType(), Op.EQ);
+        }
+
+        if (vmHostName != null) {
+            userVmSearchBuilder.and("name", userVmSearchBuilder.entity().getHostName(), Op.EQ);
+        }
+
+        if (serviceOfferingId != null) {
+            userVmSearchBuilder.and("serviceOfferingId", userVmSearchBuilder.entity().getServiceOfferingId(), Op.EQ);
+        }
+        if (display != null) {
+            userVmSearchBuilder.and("display", userVmSearchBuilder.entity().isDisplayVm(), Op.EQ);
+        }
+
+        if (!isRootAdmin) {
+            userVmSearchBuilder.and("displayVm", userVmSearchBuilder.entity().isDisplayVm(), Op.EQ);
+        }
+
+        if (isHaEnabled != null) {
+            userVmSearchBuilder.and("haEnabled", userVmSearchBuilder.entity().isHaEnabled(), Op.EQ);
         }
 
         if (isoId != null) {
-            sc.setParameters("isoId", isoId);
+            userVmSearchBuilder.and("isoId", userVmSearchBuilder.entity().getIsoId(), Op.EQ);
         }
 
         if (userId != null) {
-            sc.setParameters("userId", userId);
+            userVmSearchBuilder.and("userId", userVmSearchBuilder.entity().getUserId(), Op.EQ);
         }
 
-        if (networkId != null) {
-            sc.setParameters("networkId", networkId);
+        if (podId != null) {
+            userVmSearchBuilder.and("podId", userVmSearchBuilder.entity().getPodIdToDeployIn(), Op.EQ);
         }
 
-        if (vpcId != null && networkId == null) {
-            sc.setParameters("vpcId", vpcId);
+        if (networkId != null || vpcId != null) {
+            SearchBuilder<NicVO> nicSearch = nicDao.createSearchBuilder();
+            nicSearch.and("networkId", nicSearch.entity().getNetworkId(), Op.EQ);
+            if (vpcId != null) {
+                SearchBuilder<NetworkVO> networkSearch = networkDao.createSearchBuilder();
+                networkSearch.and("vpcId", networkSearch.entity().getVpcId(), Op.EQ);
+                nicSearch.join("vpc", networkSearch, networkSearch.entity().getId(), nicSearch.entity().getNetworkId(), JoinBuilder.JoinType.INNER);
+            }
+            userVmSearchBuilder.join("nic", nicSearch, nicSearch.entity().getInstanceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
         }
 
-        if (name != null) {
-            sc.setParameters("name", name);
+        if (clusterId != null) {
+            userVmSearchBuilder.and().op("hostIdIn", userVmSearchBuilder.entity().getHostId(), Op.IN);
+            userVmSearchBuilder.or().op("lastHostIdIn", userVmSearchBuilder.entity().getLastHostId(), Op.IN);
+            userVmSearchBuilder.and(userVmSearchBuilder.entity().getState(), Op.EQ).values(VirtualMachine.State.Stopped);
+            userVmSearchBuilder.cp().cp();
+        }
+
+        if (groupId != null && groupId != -1) {
+            SearchBuilder<InstanceGroupVMMapVO> instanceGroupSearch = instanceGroupVMMapDao.createSearchBuilder();
+            instanceGroupSearch.and("groupId", instanceGroupSearch.entity().getGroupId(), Op.EQ);
+            userVmSearchBuilder.join("instanceGroup", instanceGroupSearch, instanceGroupSearch.entity().getInstanceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (affinityGroupId != null && affinityGroupId != -1) {
+            SearchBuilder<AffinityGroupVMMapVO> affinityGroupSearch = affinityGroupVMMapDao.createSearchBuilder();
+            affinityGroupSearch.and("affinityGroupId", affinityGroupSearch.entity().getAffinityGroupId(), Op.EQ);
+            userVmSearchBuilder.join("affinityGroup", affinityGroupSearch, affinityGroupSearch.entity().getInstanceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (securityGroupId != null && securityGroupId != -1) {
+            SearchBuilder<SecurityGroupVMMapVO> securityGroupSearch = securityGroupVMMapDao.createSearchBuilder();
+            securityGroupSearch.and("securityGroupId", securityGroupSearch.entity().getSecurityGroupId(), Op.EQ);
+            userVmSearchBuilder.join("securityGroup", securityGroupSearch, securityGroupSearch.entity().getInstanceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (storageId != null) {
+            SearchBuilder<VolumeVO> volumeSearch = volumeDao.createSearchBuilder();
+            if (pool.getPoolType().equals(Storage.StoragePoolType.DatastoreCluster)) {
+                volumeSearch.and("storagePoolId", volumeSearch.entity().getPoolId(), Op.IN);
+            } else {
+                volumeSearch.and("storagePoolId", volumeSearch.entity().getPoolId(), Op.EQ);
+            }
+            userVmSearchBuilder.join("volume", volumeSearch, volumeSearch.entity().getInstanceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (tags != null && !tags.isEmpty()) {
+            SearchBuilder<ResourceTagVO> resourceTagSearch = resourceTagDao.createSearchBuilder();
+            resourceTagSearch.and("resourceType", resourceTagSearch.entity().getResourceType(), Op.EQ);
+            resourceTagSearch.and().op();
+            for (int count = 0; count < tags.size(); count++) {
+                if (count == 0) {
+                    resourceTagSearch.op("tagKey" + String.valueOf(count), resourceTagSearch.entity().getKey(), Op.EQ);
+                } else {
+                    resourceTagSearch.or().op("tagKey" + String.valueOf(count), resourceTagSearch.entity().getKey(), Op.EQ);
+                }
+                resourceTagSearch.and("tagValue" + String.valueOf(count), resourceTagSearch.entity().getValue(), Op.EQ);
+                resourceTagSearch.cp();
+            }
+            resourceTagSearch.cp();
+
+            userVmSearchBuilder.join("tags", resourceTagSearch, resourceTagSearch.entity().getResourceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (keyPairName != null) {
+            SearchBuilder<UserVmDetailVO> vmDetailSearchKeys = userVmDetailsDao.createSearchBuilder();
+            SearchBuilder<UserVmDetailVO> vmDetailSearchVmIds = userVmDetailsDao.createSearchBuilder();
+            vmDetailSearchKeys.and(vmDetailSearchKeys.entity().getName(), Op.EQ).values(SSH_PUBLIC_KEY);
+
+            SearchBuilder<SSHKeyPairVO> sshKeyPairSearch = sshKeyPairDao.createSearchBuilder();
+            sshKeyPairSearch.and("keyPairName", sshKeyPairSearch.entity().getName(), Op.EQ);
+
+            sshKeyPairSearch.join("keyPairToDetailValueJoin", vmDetailSearchKeys, vmDetailSearchKeys.entity().getValue(), sshKeyPairSearch.entity().getPublicKey(), JoinBuilder.JoinType.INNER);
+            userVmSearchBuilder.join("userVmToDetailJoin", vmDetailSearchVmIds, vmDetailSearchVmIds.entity().getResourceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+            userVmSearchBuilder.join("userVmToKeyPairJoin", sshKeyPairSearch, sshKeyPairSearch.entity().getAccountId(), userVmSearchBuilder.entity().getAccountId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (keyword != null) {
+            userVmSearchBuilder.and().op("keywordDisplayName", userVmSearchBuilder.entity().getDisplayName(), Op.LIKE);
+            userVmSearchBuilder.or("keywordName", userVmSearchBuilder.entity().getHostName(), Op.LIKE);
+            userVmSearchBuilder.or("keywordState", userVmSearchBuilder.entity().getState(), Op.EQ);
+            if (isRootAdmin) {
+                userVmSearchBuilder.or("keywordInstanceName", userVmSearchBuilder.entity().getInstanceName(), Op.LIKE );
+            }
+            userVmSearchBuilder.cp();
+        }
+
+        if (backupOfferingId != null) {
+            SearchBuilder<BackupOfferingVO> backupOfferingSearch = backupOfferingDao.createSearchBuilder();
+            backupOfferingSearch.and("backupOfferingId", backupOfferingSearch.entity().getId(), Op.EQ);
+            userVmSearchBuilder.join("backupOffering", backupOfferingSearch, backupOfferingSearch.entity().getId(), userVmSearchBuilder.entity().getBackupOfferingId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (autoScaleVmGroupId != null) {
+            SearchBuilder<AutoScaleVmGroupVmMapVO> autoScaleMapSearch = autoScaleVmGroupVmMapDao.createSearchBuilder();
+            autoScaleMapSearch.and("autoScaleVmGroupId", autoScaleMapSearch.entity().getVmGroupId(), Op.EQ);
+            userVmSearchBuilder.join("autoScaleVmGroup", autoScaleMapSearch, autoScaleMapSearch.entity().getInstanceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
+        Boolean isVnf = cmd.getVnf();
+        if (isVnf != null) {
+            SearchBuilder<VMTemplateVO> templateSearch = _templateDao.createSearchBuilder();
+            templateSearch.and("templateTypeEQ", templateSearch.entity().getTemplateType(), Op.EQ);
+            templateSearch.and("templateTypeNEQ", templateSearch.entity().getTemplateType(), Op.NEQ);
+
+            userVmSearchBuilder.join("vmTemplate", templateSearch, templateSearch.entity().getId(), userVmSearchBuilder.entity().getTemplateId(), JoinBuilder.JoinType.INNER);
+        }
+
+        SearchCriteria<UserVmVO> userVmSearchCriteria = userVmSearchBuilder.create();
+        accountMgr.buildACLSearchCriteria(userVmSearchCriteria, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+
+        if (serviceOfferingId != null) {
+            userVmSearchCriteria.setParameters("serviceOfferingId", serviceOfferingId);
         }
 
         if (state != null) {
             if (state.equalsIgnoreCase("present")) {
-                sc.setParameters("stateNIN", "Destroyed", "Expunging");
+                userVmSearchCriteria.setParameters("stateNIN", "Destroyed", "Expunging");
             } else {
-                sc.setParameters("stateEQ", state);
+                userVmSearchCriteria.setParameters("stateEQ", state);
             }
         }
 
         if (hypervisor != null) {
-            sc.setParameters("hypervisorType", hypervisor);
+            userVmSearchCriteria.setParameters("hypervisorType", hypervisor);
         }
 
         // Don't show Destroyed and Expunging vms to the end user if the AllowUserViewDestroyedVM flag is not set.
         if (!isAdmin && !AllowUserViewDestroyedVM.valueIn(caller.getAccountId())) {
-            sc.setParameters("stateNIN", "Destroyed", "Expunging");
+            userVmSearchCriteria.setParameters("stateNIN", "Destroyed", "Expunging");
         }
 
         if (zoneId != null) {
-            sc.setParameters("dataCenterId", zoneId);
+            userVmSearchCriteria.setParameters("dataCenterId", zoneId);
         }
 
-        if (affinityGroupId != null) {
-            sc.setParameters("affinityGroupId", affinityGroupId);
+        if (templateId != null) {
+            userVmSearchCriteria.setParameters("templateId", templateId);
+        }
+
+        if (display != null) {
+            userVmSearchCriteria.setParameters("display", display);
+        }
+
+        if (isHaEnabled != null) {
+            userVmSearchCriteria.setParameters("haEnabled", isHaEnabled);
+        }
+
+        if (isoId != null) {
+            userVmSearchCriteria.setParameters("isoId", isoId);
+        }
+
+        if (ids != null && !ids.isEmpty()) {
+            userVmSearchCriteria.setParameters("idIN", ids.toArray());
+        }
+
+        if (vmHostName != null) {
+            userVmSearchCriteria.setParameters("name", vmHostName);
+        }
+
+        if (groupId != null && groupId != -1) {
+            userVmSearchCriteria.setJoinParameters("instanceGroup","groupId", groupId);
+        }
+
+        if (affinityGroupId != null && affinityGroupId != -1) {
+            userVmSearchCriteria.setJoinParameters("affinityGroup", "affinityGroupId", affinityGroupId);
+        }
+
+        if (securityGroupId != null && securityGroupId != -1) {
+            userVmSearchCriteria.setJoinParameters("securityGroup","securityGroupId", securityGroupId);
+        }
+
+        if (keyword != null) {
+            String keywordMatch = "%" + keyword + "%";
+            userVmSearchCriteria.setParameters("keywordDisplayName", keywordMatch);
+            userVmSearchCriteria.setParameters("keywordName", keywordMatch);
+            userVmSearchCriteria.setParameters("keywordState", keyword);
+            if (isRootAdmin) {
+                userVmSearchCriteria.setParameters("keywordInstanceName", keywordMatch);
+            }
+        }
+
+        if (tags != null && !tags.isEmpty()) {
+            int count = 0;
+            userVmSearchCriteria.setJoinParameters("tags","resourceType", ResourceObjectType.UserVm);
+            for (Map.Entry<String, String> entry : tags.entrySet()) {
+                userVmSearchCriteria.setJoinParameters("tags", "tagKey" + String.valueOf(count), entry.getKey());
+                userVmSearchCriteria.setJoinParameters("tags", "tagValue" + String.valueOf(count), entry.getValue());
+                count++;
+            }
         }
 
         if (keyPairName != null) {
-            sc.setParameters("keyPairName", keyPairName);
+            userVmSearchCriteria.setJoinParameters("userVmToKeyPairJoin", "keyPairName", keyPairName);
         }
 
-        if (_accountMgr.isRootAdmin(caller.getId())) {
-            if (pod != null) {
-                sc.setParameters("podId", pod);
+        if (networkId != null) {
+            userVmSearchCriteria.setJoinParameters("nic", "networkId", networkId);
+        }
 
+        if (vpcId != null) {
+            userVmSearchCriteria.getJoin("nic").setJoinParameters("vpc", "vpcId", vpcId);
+        }
+
+        if (userId != null) {
+            userVmSearchCriteria.setParameters("userId", userId);
+        }
+
+        if (backupOfferingId != null) {
+            userVmSearchCriteria.setJoinParameters("backupOffering", "backupOfferingId", backupOfferingId);
+        }
+
+        if (autoScaleVmGroupId != null) {
+            userVmSearchCriteria.setJoinParameters("autoScaleVmGroup", "autoScaleVmGroupId", autoScaleVmGroupId);
+        }
+
+        if (isVnf != null) {
+            if (isVnf) {
+                userVmSearchCriteria.setJoinParameters("vmTemplate", "templateTypeEQ", TemplateType.VNF);
+            } else {
+                userVmSearchCriteria.setJoinParameters("vmTemplate", "templateTypeNEQ", TemplateType.VNF);
+            }
+        }
+
+        if (isRootAdmin) {
+            if (podId != null) {
+                userVmSearchCriteria.setParameters("podId", podId);
                 if (state == null) {
-                    sc.setParameters("stateNEQ", "Destroyed");
+                    userVmSearchCriteria.setParameters("stateNEQ", "Destroyed");
                 }
             }
 
             if (clusterId != null) {
-                sc.setParameters("clusterId", clusterId);
-                List<HostJoinVO> hosts = _hostJoinDao.findByClusterId((Long)clusterId, Host.Type.Routing);
+                List<HostJoinVO> hosts = hostJoinDao.findByClusterId(clusterId, Host.Type.Routing);
+                if (CollectionUtils.isEmpty(hosts)) {
+                    // cluster has no hosts, so we cannot find VMs, cancel search.
+                    return new Pair<>(new ArrayList<>(), 0);
+                }
                 List<Long> hostIds = hosts.stream().map(HostJoinVO::getId).collect(Collectors.toList());
-                sc.setParameters("clusterHostId", hostIds.toArray());
-                sc.setParameters("clusterLastHostId", hostIds.toArray());
+                userVmSearchCriteria.setParameters("hostIdIn", hostIds.toArray());
+                userVmSearchCriteria.setParameters("lastHostIdIn", hostIds.toArray());
             }
 
             if (hostId != null) {
-                sc.setParameters("hostIdEQ", hostId);
+                userVmSearchCriteria.setParameters("hostId", hostId);
             }
 
-            if (storageId != null) {
-                StoragePoolVO poolVO = _storagePoolDao.findById((Long) storageId);
-                if (poolVO.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
-                    List<StoragePoolVO> childDatastores = _storagePoolDao.listChildStoragePoolsInDatastoreCluster((Long) storageId);
+            if (storageId != null && pool != null) {
+                if (pool.getPoolType().equals(Storage.StoragePoolType.DatastoreCluster)) {
+                    List<StoragePoolVO> childDatastores = storagePoolDao.listChildStoragePoolsInDatastoreCluster(storageId);
                     List<Long> childDatastoreIds = childDatastores.stream().map(mo -> mo.getId()).collect(Collectors.toList());
-                    sc.setParameters("poolId", childDatastoreIds.toArray());
+                    userVmSearchCriteria.setJoinParameters("volume", "storagePoolId", childDatastoreIds.toArray());
                 } else {
-                    sc.setParameters("poolId", storageId);
+                    userVmSearchCriteria.setJoinParameters("volume", "storagePoolId", storageId);
                 }
             }
+        } else {
+            userVmSearchCriteria.setParameters("displayVm", 1);
         }
 
-        if (!isRootAdmin) {
-            sc.setParameters("displayVm", 1);
-        }
-        // search vm details by ids
-        Pair<List<UserVmJoinVO>, Integer> uniqueVmPair = _userVmJoinDao.searchAndDistinctCount(sc, searchFilter);
+        Pair<List<UserVmVO>, Integer> uniqueVmPair = userVmDao.searchAndDistinctCount(userVmSearchCriteria, searchFilter, new String[]{"vm_instance.id"});
         Integer count = uniqueVmPair.second();
-        if (count.intValue() == 0) {
-            // handle empty result cases
-            return uniqueVmPair;
-        }
-        List<UserVmJoinVO> uniqueVms = uniqueVmPair.first();
-        Long[] vmIds = new Long[uniqueVms.size()];
-        int i = 0;
-        for (UserVmJoinVO v : uniqueVms) {
-            vmIds[i++] = v.getId();
-        }
-        List<UserVmJoinVO> vms = _userVmJoinDao.searchByIds(vmIds);
-        return new Pair<>(vms, count);
+
+        List<Long> vmIds = uniqueVmPair.first().stream().map(VMInstanceVO::getId).collect(Collectors.toList());
+        return new Pair<>(vmIds, count);
     }
 
     @Override
@@ -1339,16 +1595,16 @@
         Map<String, String> tags = cmd.getTags();
 
         if (instanceId != null) {
-            UserVmVO userVM = _userVmDao.findById(instanceId);
+            UserVmVO userVM = userVmDao.findById(instanceId);
             if (userVM == null) {
                 throw new InvalidParameterValueException("Unable to list network groups for virtual machine instance " + instanceId + "; instance not found.");
             }
-            _accountMgr.checkAccess(caller, null, true, userVM);
+            accountMgr.checkAccess(caller, null, true, userVM);
             return listSecurityGroupRulesByVM(instanceId.longValue(), cmd.getStartIndex(), cmd.getPageSizeVal());
         }
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
@@ -1357,13 +1613,13 @@
         SearchBuilder<SecurityGroupJoinVO> sb = _securityGroupJoinDao.createSearchBuilder();
         sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct
         // ids
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
         sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
 
         SearchCriteria<SecurityGroupJoinVO> sc = sb.create();
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (id != null) {
             sc.setParameters("id", id);
@@ -1411,7 +1667,7 @@
 
     private Pair<List<SecurityGroupJoinVO>, Integer> listSecurityGroupRulesByVM(long vmId, long pageInd, long pageSize) {
         Filter sf = new Filter(SecurityGroupVMMapVO.class, null, true, pageInd, pageSize);
-        Pair<List<SecurityGroupVMMapVO>, Integer> sgVmMappingPair = _securityGroupVMMapDao.listByInstanceId(vmId, sf);
+        Pair<List<SecurityGroupVMMapVO>, Integer> sgVmMappingPair = securityGroupVMMapDao.listByInstanceId(vmId, sf);
         Integer count = sgVmMappingPair.second();
         if (count.intValue() == 0) {
             // handle empty result cases
@@ -1475,7 +1731,7 @@
         List<Long> permittedAccounts = new ArrayList<Long>();
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
@@ -1487,7 +1743,7 @@
         // number of
         // records with
         // pagination
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         sb.and("name", sb.entity().getInstanceName(), SearchCriteria.Op.EQ);
         sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
@@ -1535,7 +1791,7 @@
         }
 
         SearchCriteria<DomainRouterJoinVO> sc = sb.create();
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (keyword != null) {
             SearchCriteria<DomainRouterJoinVO> ssc = _routerJoinDao.createSearchCriteria();
@@ -1650,17 +1906,17 @@
         sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct
         // ids
 
-        if (_accountMgr.isAdmin(caller.getId())) {
+        if (accountMgr.isAdmin(caller.getId())) {
             if (domainId != null) {
                 DomainVO domain = _domainDao.findById(domainId);
                 if (domain == null) {
                     throw new InvalidParameterValueException("Domain id=" + domainId + " doesn't exist in the system");
                 }
 
-                _accountMgr.checkAccess(caller, domain);
+                accountMgr.checkAccess(caller, domain);
 
                 if (accountName != null) {
-                    Account owner = _accountMgr.getActiveAccountByName(accountName, domainId);
+                    Account owner = accountMgr.getActiveAccountByName(accountName, domainId);
                     if (owner == null) {
                         throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId);
                     }
@@ -1701,10 +1957,10 @@
             userId = user.getId();
         }
 
-        if (domainId == null && accountId == null && (_accountMgr.isNormalUser(caller.getId()) || !listAll)) {
+        if (domainId == null && accountId == null && (accountMgr.isNormalUser(caller.getId()) || !listAll)) {
             accountId = caller.getId();
             userId = user.getId();
-        } else if (_accountMgr.isDomainAdmin(caller.getId()) || (isRecursive && !listAll)) {
+        } else if (accountMgr.isDomainAdmin(caller.getId()) || (isRecursive && !listAll)) {
             DomainVO domain = _domainDao.findById(caller.getDomainId());
             path = domain.getPath();
         }
@@ -1815,14 +2071,14 @@
         List<Long> permittedAccounts = new ArrayList<Long>();
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, isRecursive, null);
-        _accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccounts, domainIdRecursiveListProject, listAll, true);
+        accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccounts, domainIdRecursiveListProject, listAll, true);
         domainId = domainIdRecursiveListProject.first();
         isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
 
         Filter searchFilter = new Filter(ProjectInvitationJoinVO.class, "id", true, startIndex, pageSizeVal);
         SearchBuilder<ProjectInvitationJoinVO> sb = _projectInvitationJoinDao.createSearchBuilder();
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
         ProjectInvitation invitation = projectInvitationDao.findByUserIdProjectId(callingUser.getId(), callingUser.getAccountId(), projectId == null ? -1 : projectId);
         sb.and("projectId", sb.entity().getProjectId(), SearchCriteria.Op.EQ);
         sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
@@ -1830,7 +2086,7 @@
         sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
 
         SearchCriteria<ProjectInvitationJoinVO> sc = sb.create();
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (projectId != null) {
             sc.setParameters("projectId", projectId);
@@ -1893,7 +2149,7 @@
 
         // verify permissions - only accounts belonging to the project can list
         // project's account
-        if (!_accountMgr.isAdmin(caller.getId()) && _projectAccountDao.findByProjectIdUserId(projectId, callingUser.getAccountId(), callingUser.getId()) == null &&
+        if (!accountMgr.isAdmin(caller.getId()) && _projectAccountDao.findByProjectIdUserId(projectId, callingUser.getAccountId(), callingUser.getId()) == null &&
         _projectAccountDao.findByProjectIdAccountId(projectId, caller.getAccountId()) == null) {
             throw new PermissionDeniedException("Account " + caller + " is not authorized to list users of the project id=" + projectId);
         }
@@ -1948,8 +2204,20 @@
     }
 
     public Pair<List<HostJoinVO>, Integer> searchForServersInternal(ListHostsCmd cmd) {
+        Pair<List<Long>, Integer> serverIdPage = searchForServerIdsAndCount(cmd);
 
-        Long zoneId = _accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
+        Integer count = serverIdPage.second();
+        Long[] idArray = serverIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<HostJoinVO> servers = hostJoinDao.searchByIds(idArray);
+        return new Pair<>(servers, count);
+    }
+    public Pair<List<Long>, Integer> searchForServerIdsAndCount(ListHostsCmd cmd) {
+        Long zoneId = accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
         Object name = cmd.getHostName();
         Object type = cmd.getType();
         Object state = cmd.getState();
@@ -1965,44 +2233,55 @@
         Long pageSize = cmd.getPageSizeVal();
         Hypervisor.HypervisorType hypervisorType = cmd.getHypervisor();
 
-        Filter searchFilter = new Filter(HostJoinVO.class, "id", Boolean.TRUE, startIndex, pageSize);
+        Filter searchFilter = new Filter(HostVO.class, "id", Boolean.TRUE, startIndex, pageSize);
 
-        SearchBuilder<HostJoinVO> sb = _hostJoinDao.createSearchBuilder();
-        sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct
+        SearchBuilder<HostVO> hostSearchBuilder = hostDao.createSearchBuilder();
+        hostSearchBuilder.select(null, Func.DISTINCT, hostSearchBuilder.entity().getId()); // select distinct
         // ids
-        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
-        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
-        sb.and("type", sb.entity().getType(), SearchCriteria.Op.LIKE);
-        sb.and("status", sb.entity().getStatus(), SearchCriteria.Op.EQ);
-        sb.and("dataCenterId", sb.entity().getZoneId(), SearchCriteria.Op.EQ);
-        sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ);
-        sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ);
-        sb.and("oobmEnabled", sb.entity().isOutOfBandManagementEnabled(), SearchCriteria.Op.EQ);
-        sb.and("powerState", sb.entity().getOutOfBandManagementPowerState(), SearchCriteria.Op.EQ);
-        sb.and("resourceState", sb.entity().getResourceState(), SearchCriteria.Op.EQ);
-        sb.and("hypervisor_type", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("id", hostSearchBuilder.entity().getId(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("name", hostSearchBuilder.entity().getName(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("type", hostSearchBuilder.entity().getType(), SearchCriteria.Op.LIKE);
+        hostSearchBuilder.and("status", hostSearchBuilder.entity().getStatus(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("dataCenterId", hostSearchBuilder.entity().getDataCenterId(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("podId", hostSearchBuilder.entity().getPodId(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("clusterId", hostSearchBuilder.entity().getClusterId(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("resourceState", hostSearchBuilder.entity().getResourceState(), SearchCriteria.Op.EQ);
+        hostSearchBuilder.and("hypervisor_type", hostSearchBuilder.entity().getHypervisorType(), SearchCriteria.Op.EQ);
+
+        if (keyword != null) {
+            hostSearchBuilder.and().op("keywordName", hostSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE);
+            hostSearchBuilder.or("keywordStatus", hostSearchBuilder.entity().getStatus(), SearchCriteria.Op.LIKE);
+            hostSearchBuilder.or("keywordType", hostSearchBuilder.entity().getType(), SearchCriteria.Op.LIKE);
+            hostSearchBuilder.cp();
+        }
+
+        if (outOfBandManagementEnabled != null || powerState != null) {
+            SearchBuilder<OutOfBandManagementVO> oobmSearch = outOfBandManagementDao.createSearchBuilder();
+            oobmSearch.and("oobmEnabled", oobmSearch.entity().isEnabled(), SearchCriteria.Op.EQ);
+            oobmSearch.and("powerState", oobmSearch.entity().getPowerState(), SearchCriteria.Op.EQ);
+
+            hostSearchBuilder.join("oobmSearch", oobmSearch, hostSearchBuilder.entity().getId(), oobmSearch.entity().getHostId(), JoinBuilder.JoinType.INNER);
+        }
 
         String haTag = _haMgr.getHaTag();
         if (haHosts != null && haTag != null && !haTag.isEmpty()) {
+            SearchBuilder<HostTagVO> hostTagSearchBuilder = _hostTagDao.createSearchBuilder();
             if ((Boolean)haHosts) {
-                sb.and("tag", sb.entity().getTag(), SearchCriteria.Op.EQ);
+                hostTagSearchBuilder.and("tag", hostTagSearchBuilder.entity().getName(), SearchCriteria.Op.EQ);
             } else {
-                sb.and().op("tag", sb.entity().getTag(), SearchCriteria.Op.NEQ);
-                sb.or("tagNull", sb.entity().getTag(), SearchCriteria.Op.NULL);
-                sb.cp();
+                hostTagSearchBuilder.and().op("tag", hostTagSearchBuilder.entity().getName(), Op.NEQ);
+                hostTagSearchBuilder.or("tagNull", hostTagSearchBuilder.entity().getName(), Op.NULL);
+                hostTagSearchBuilder.cp();
             }
-
+            hostSearchBuilder.join("hostTagSearch", hostTagSearchBuilder, hostSearchBuilder.entity().getId(), hostTagSearchBuilder.entity().getHostId(), JoinBuilder.JoinType.LEFT);
         }
 
-        SearchCriteria<HostJoinVO> sc = sb.create();
+        SearchCriteria<HostVO> sc = hostSearchBuilder.create();
 
         if (keyword != null) {
-            SearchCriteria<HostJoinVO> ssc = _hostJoinDao.createSearchCriteria();
-            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("status", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("type", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-
-            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+            sc.setParameters("keywordName", "%" + keyword + "%");
+            sc.setParameters("keywordStatus", "%" + keyword + "%");
+            sc.setParameters("keywordType", "%" + keyword + "%");
         }
 
         if (id != null) {
@@ -2029,11 +2308,11 @@
         }
 
         if (outOfBandManagementEnabled != null) {
-            sc.setParameters("oobmEnabled", outOfBandManagementEnabled);
+            sc.setJoinParameters("oobmSearch", "oobmEnabled", outOfBandManagementEnabled);
         }
 
         if (powerState != null) {
-            sc.setParameters("powerState", powerState);
+            sc.setJoinParameters("oobmSearch", "powerState", powerState);
         }
 
         if (resourceState != null) {
@@ -2041,38 +2320,32 @@
         }
 
         if (haHosts != null && haTag != null && !haTag.isEmpty()) {
-            sc.setParameters("tag", haTag);
+            sc.setJoinParameters("hostTagSearch", "tag", haTag);
         }
 
         if (hypervisorType != HypervisorType.None && hypervisorType != HypervisorType.Any) {
             sc.setParameters("hypervisor_type", hypervisorType);
         }
-        // search host details by ids
-        Pair<List<HostJoinVO>, Integer> uniqueHostPair = _hostJoinDao.searchAndCount(sc, searchFilter);
-        Integer count = uniqueHostPair.second();
-        if (count.intValue() == 0) {
-            // handle empty result cases
-            return uniqueHostPair;
-        }
-        List<HostJoinVO> uniqueHosts = uniqueHostPair.first();
-        Long[] hostIds = new Long[uniqueHosts.size()];
-        int i = 0;
-        for (HostJoinVO v : uniqueHosts) {
-            hostIds[i++] = v.getId();
-        }
-        List<HostJoinVO> hosts = _hostJoinDao.searchByIds(hostIds);
-        return new Pair<List<HostJoinVO>, Integer>(hosts, count);
 
+        Pair<List<HostVO>, Integer> uniqueHostPair = hostDao.searchAndCount(sc, searchFilter);
+        Integer count = uniqueHostPair.second();
+        List<Long> hostIds = uniqueHostPair.first().stream().map(HostVO::getId).collect(Collectors.toList());
+        return new Pair<>(hostIds, count);
     }
 
     @Override
     public ListResponse<VolumeResponse> searchForVolumes(ListVolumesCmd cmd) {
         Pair<List<VolumeJoinVO>, Integer> result = searchForVolumesInternal(cmd);
-        ListResponse<VolumeResponse> response = new ListResponse<VolumeResponse>();
+        ListResponse<VolumeResponse> response = new ListResponse<>();
+
+        if (cmd.getRetrieveOnlyResourceCount()) {
+            response.setResponses(new ArrayList<>(), result.second());
+            return response;
+        }
 
         ResponseView respView = cmd.getResponseView();
         Account account = CallContext.current().getCallingAccount();
-        if (_accountMgr.isRootAdmin(account.getAccountId())) {
+        if (accountMgr.isRootAdmin(account.getAccountId())) {
             respView = ResponseView.Full;
         }
 
@@ -2108,6 +2381,19 @@
     }
 
     private Pair<List<VolumeJoinVO>, Integer> searchForVolumesInternal(ListVolumesCmd cmd) {
+        Pair<List<Long>, Integer> volumeIdPage = searchForVolumeIdsAndCount(cmd);
+
+        Integer count = volumeIdPage.second();
+        Long[] idArray = volumeIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<VolumeJoinVO> vms = _volumeJoinDao.searchByIds(idArray);
+        return new Pair<>(vms, count);
+    }
+    private Pair<List<Long>, Integer> searchForVolumeIdsAndCount(ListVolumesCmd cmd) {
 
         Account caller = CallContext.current().getCallingAccount();
         List<Long> permittedAccounts = new ArrayList<Long>();
@@ -2131,65 +2417,101 @@
         List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
-        Filter searchFilter = new Filter(VolumeJoinVO.class, "created", false, cmd.getStartIndex(), cmd.getPageSizeVal());
+        Filter searchFilter = new Filter(VolumeVO.class, "created", false, cmd.getStartIndex(), cmd.getPageSizeVal());
 
-        // hack for now, this should be done better but due to needing a join I
-        // opted to
-        // do this quickly and worry about making it pretty later
-        SearchBuilder<VolumeJoinVO> sb = _volumeJoinDao.createSearchBuilder();
-        sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct
-        // ids to get
-        // number of
-        // records with
-        // pagination
-        _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        SearchBuilder<VolumeVO> volumeSearchBuilder = volumeDao.createSearchBuilder();
+        volumeSearchBuilder.select(null, Func.DISTINCT, volumeSearchBuilder.entity().getId()); // select distinct
+        accountMgr.buildACLSearchBuilder(volumeSearchBuilder, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
-        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
-        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
-        sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
-        sb.and("volumeType", sb.entity().getVolumeType(), SearchCriteria.Op.LIKE);
-        sb.and("uuid", sb.entity().getUuid(), SearchCriteria.Op.NNULL);
-        sb.and("instanceId", sb.entity().getVmId(), SearchCriteria.Op.EQ);
-        sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ);
-        sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ);
+        if (CollectionUtils.isNotEmpty(ids)) {
+            volumeSearchBuilder.and("idIN", volumeSearchBuilder.entity().getId(), SearchCriteria.Op.IN);
+        }
+
+        volumeSearchBuilder.and("name", volumeSearchBuilder.entity().getName(), SearchCriteria.Op.EQ);
+        volumeSearchBuilder.and("volumeType", volumeSearchBuilder.entity().getVolumeType(), SearchCriteria.Op.LIKE);
+        volumeSearchBuilder.and("uuid", volumeSearchBuilder.entity().getUuid(), SearchCriteria.Op.NNULL);
+        volumeSearchBuilder.and("instanceId", volumeSearchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ);
+        volumeSearchBuilder.and("dataCenterId", volumeSearchBuilder.entity().getDataCenterId(), SearchCriteria.Op.EQ);
+
+        if (keyword != null) {
+            volumeSearchBuilder.and().op("keywordName", volumeSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE);
+            volumeSearchBuilder.or("keywordVolumeType", volumeSearchBuilder.entity().getVolumeType(), SearchCriteria.Op.LIKE);
+            volumeSearchBuilder.or("keywordState", volumeSearchBuilder.entity().getState(), SearchCriteria.Op.LIKE);
+            volumeSearchBuilder.cp();
+        }
+
+        StoragePoolVO poolVO = null;
         if (storageId != null) {
-            StoragePoolVO poolVO = _storagePoolDao.findByUuid(storageId);
-            if (poolVO.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
-                sb.and("storageId", sb.entity().getPoolUuid(), SearchCriteria.Op.IN);
+            poolVO = storagePoolDao.findByUuid(storageId);
+            if (poolVO == null) {
+                throw new InvalidParameterValueException("Unable to find storage pool by uuid " + storageId);
+            } else if (poolVO.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
+                volumeSearchBuilder.and("storageId", volumeSearchBuilder.entity().getPoolId(), SearchCriteria.Op.IN);
             } else {
-                sb.and("storageId", sb.entity().getPoolUuid(), SearchCriteria.Op.EQ);
+                volumeSearchBuilder.and("storageId", volumeSearchBuilder.entity().getPoolId(), SearchCriteria.Op.EQ);
             }
         }
-        sb.and("diskOfferingId", sb.entity().getDiskOfferingId(), SearchCriteria.Op.EQ);
-        sb.and("display", sb.entity().isDisplayVolume(), SearchCriteria.Op.EQ);
-        sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
-        sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ);
 
+        if (clusterId != null || podId != null) {
+            SearchBuilder<StoragePoolVO> storagePoolSearch = storagePoolDao.createSearchBuilder();
+            storagePoolSearch.and("clusterId", storagePoolSearch.entity().getClusterId(), SearchCriteria.Op.EQ);
+            storagePoolSearch.and("podId", storagePoolSearch.entity().getPodId(), SearchCriteria.Op.EQ);
+            volumeSearchBuilder.join("storagePoolSearch", storagePoolSearch, storagePoolSearch.entity().getId(), volumeSearchBuilder.entity().getPoolId(), JoinBuilder.JoinType.INNER);
+        }
+
+        volumeSearchBuilder.and("diskOfferingId", volumeSearchBuilder.entity().getDiskOfferingId(), SearchCriteria.Op.EQ);
+        volumeSearchBuilder.and("display", volumeSearchBuilder.entity().isDisplayVolume(), SearchCriteria.Op.EQ);
+        volumeSearchBuilder.and("state", volumeSearchBuilder.entity().getState(), SearchCriteria.Op.EQ);
+        volumeSearchBuilder.and("stateNEQ", volumeSearchBuilder.entity().getState(), SearchCriteria.Op.NEQ);
+
+        // Need to test thoroughly
         if (!shouldListSystemVms) {
-            sb.and().op("systemUse", sb.entity().isSystemUse(), SearchCriteria.Op.NEQ);
-            sb.or("nulltype", sb.entity().isSystemUse(), SearchCriteria.Op.NULL);
-            sb.cp();
+            SearchBuilder<VMInstanceVO> vmSearch = _vmInstanceDao.createSearchBuilder();
+            SearchBuilder<ServiceOfferingVO> serviceOfferingSearch = _srvOfferingDao.createSearchBuilder();
+            vmSearch.and().op("svmType", vmSearch.entity().getType(), SearchCriteria.Op.NIN);
+            vmSearch.or("vmSearchNulltype", vmSearch.entity().getType(), SearchCriteria.Op.NULL);
+            vmSearch.cp();
 
-            sb.and().op("type", sb.entity().getVmType(), SearchCriteria.Op.NIN);
-            sb.or("nulltype", sb.entity().getVmType(), SearchCriteria.Op.NULL);
-            sb.cp();
+            serviceOfferingSearch.and().op("systemUse", serviceOfferingSearch.entity().isSystemUse(), SearchCriteria.Op.NEQ);
+            serviceOfferingSearch.or("serviceOfferingSearchNulltype", serviceOfferingSearch.entity().isSystemUse(), SearchCriteria.Op.NULL);
+            serviceOfferingSearch.cp();
+
+            vmSearch.join("serviceOfferingSearch", serviceOfferingSearch, serviceOfferingSearch.entity().getId(), vmSearch.entity().getServiceOfferingId(), JoinBuilder.JoinType.LEFT);
+
+            volumeSearchBuilder.join("vmSearch", vmSearch, vmSearch.entity().getId(), volumeSearchBuilder.entity().getInstanceId(), JoinBuilder.JoinType.LEFT);
+
+        }
+
+        if (MapUtils.isNotEmpty(tags)) {
+            SearchBuilder<ResourceTagVO> resourceTagSearch = resourceTagDao.createSearchBuilder();
+            resourceTagSearch.and("resourceType", resourceTagSearch.entity().getResourceType(), Op.EQ);
+            resourceTagSearch.and().op();
+            for (int count = 0; count < tags.size(); count++) {
+                if (count == 0) {
+                    resourceTagSearch.op("tagKey" + count, resourceTagSearch.entity().getKey(), Op.EQ);
+                } else {
+                    resourceTagSearch.or().op("tagKey" + count, resourceTagSearch.entity().getKey(), Op.EQ);
+                }
+                resourceTagSearch.and("tagValue" + count, resourceTagSearch.entity().getValue(), Op.EQ);
+                resourceTagSearch.cp();
+            }
+            resourceTagSearch.cp();
+
+            volumeSearchBuilder.join("tags", resourceTagSearch, resourceTagSearch.entity().getResourceId(), volumeSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
         }
 
         // now set the SC criteria...
-        SearchCriteria<VolumeJoinVO> sc = sb.create();
-        _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        SearchCriteria<VolumeVO> sc = volumeSearchBuilder.create();
+        accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
 
         if (keyword != null) {
-            SearchCriteria<VolumeJoinVO> ssc = _volumeJoinDao.createSearchCriteria();
-            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("volumeType", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("state", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-
-            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+            sc.setParameters("keywordName", "%" + keyword + "%");
+            sc.setParameters("keywordVolumeType", "%" + keyword + "%");
+            sc.setParameters("keywordState", "%" + keyword + "%");
         }
 
         if (name != null) {
@@ -2203,19 +2525,18 @@
         setIdsListToSearchCriteria(sc, ids);
 
         if (!shouldListSystemVms) {
-            sc.setParameters("systemUse", 1);
-            sc.setParameters("type", VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.DomainRouter);
+            sc.setJoinParameters("vmSearch", "svmType", VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.DomainRouter);
+            sc.getJoin("vmSearch").setJoinParameters("serviceOfferingSearch", "systemUse", 1);
         }
 
-        if (tags != null && !tags.isEmpty()) {
-            SearchCriteria<VolumeJoinVO> tagSc = _volumeJoinDao.createSearchCriteria();
-            for (String key : tags.keySet()) {
-                SearchCriteria<VolumeJoinVO> tsc = _volumeJoinDao.createSearchCriteria();
-                tsc.addAnd("tagKey", SearchCriteria.Op.EQ, key);
-                tsc.addAnd("tagValue", SearchCriteria.Op.EQ, tags.get(key));
-                tagSc.addOr("tagKey", SearchCriteria.Op.SC, tsc);
+        if (MapUtils.isNotEmpty(tags)) {
+            int count = 0;
+            sc.setJoinParameters("tags", "resourceType", ResourceObjectType.Volume);
+            for (Map.Entry<String, String> entry  : tags.entrySet()) {
+                sc.setJoinParameters("tags", "tagKey" + count, entry.getKey());
+                sc.setJoinParameters("tags", "tagValue" + count, entry.getValue());
+                count++;
             }
-            sc.addAnd("tagKey", SearchCriteria.Op.SC, tagSc);
         }
 
         if (diskOffId != null) {
@@ -2235,50 +2556,38 @@
         if (zoneId != null) {
             sc.setParameters("dataCenterId", zoneId);
         }
-        if (podId != null) {
-            sc.setParameters("podId", podId);
-        }
 
         if (storageId != null) {
-            StoragePoolVO poolVO = _storagePoolDao.findByUuid(storageId);
             if (poolVO.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
-                List<StoragePoolVO> childDatastores = _storagePoolDao.listChildStoragePoolsInDatastoreCluster(poolVO.getId());
-                List<String> childDatastoreIds = childDatastores.stream().map(mo -> mo.getUuid()).collect(Collectors.toList());
-                sc.setParameters("storageId", childDatastoreIds.toArray());
+                List<StoragePoolVO> childDataStores = storagePoolDao.listChildStoragePoolsInDatastoreCluster(poolVO.getId());
+                sc.setParameters("storageId", childDataStores.stream().map(StoragePoolVO::getId).toArray());
             } else {
-                sc.setParameters("storageId", storageId);
+                sc.setParameters("storageId", poolVO.getId());
             }
         }
 
         if (clusterId != null) {
-            sc.setParameters("clusterId", clusterId);
+            sc.setJoinParameters("storagePoolSearch", "clusterId", clusterId);
+        }
+        if (podId != null) {
+            sc.setJoinParameters("storagePoolSearch", "podId", podId);
         }
 
         if (state != null) {
             sc.setParameters("state", state);
-        } else if (!_accountMgr.isAdmin(caller.getId())) {
+        } else if (!accountMgr.isAdmin(caller.getId())) {
             sc.setParameters("stateNEQ", Volume.State.Expunged);
         }
 
         // search Volume details by ids
-        Pair<List<VolumeJoinVO>, Integer> uniqueVolPair = _volumeJoinDao.searchAndCount(sc, searchFilter);
+        Pair<List<VolumeVO>, Integer> uniqueVolPair = volumeDao.searchAndCount(sc, searchFilter);
         Integer count = uniqueVolPair.second();
-        if (count.intValue() == 0) {
-            // empty result
-            return uniqueVolPair;
-        }
-        List<VolumeJoinVO> uniqueVols = uniqueVolPair.first();
-        Long[] vrIds = new Long[uniqueVols.size()];
-        int i = 0;
-        for (VolumeJoinVO v : uniqueVols) {
-            vrIds[i++] = v.getId();
-        }
-        List<VolumeJoinVO> vrs = _volumeJoinDao.searchByIds(vrIds);
-        return new Pair<List<VolumeJoinVO>, Integer>(vrs, count);
+        List<Long> vmIds = uniqueVolPair.first().stream().map(VolumeVO::getId).collect(Collectors.toList());
+        return new Pair<>(vmIds, count);
     }
 
     private boolean shouldListSystemVms(ListVolumesCmd cmd, Long callerId) {
-        return Boolean.TRUE.equals(cmd.getListSystemVms()) && _accountMgr.isRootAdmin(callerId);
+        return Boolean.TRUE.equals(cmd.getListSystemVms()) && accountMgr.isRootAdmin(callerId);
     }
 
     @Override
@@ -2297,6 +2606,20 @@
     }
 
     private Pair<List<DomainJoinVO>, Integer> searchForDomainsInternal(ListDomainsCmd cmd) {
+        Pair<List<Long>, Integer> domainIdPage = searchForDomainIdsAndCount(cmd);
+
+        Integer count = domainIdPage.second();
+        Long[] idArray = domainIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<DomainJoinVO> domains = _domainJoinDao.searchByIds(idArray);
+        return new Pair<>(domains, count);
+    }
+
+    private Pair<List<Long>, Integer> searchForDomainIdsAndCount(ListDomainsCmd cmd) {
         Account caller = CallContext.current().getCallingAccount();
         Long domainId = cmd.getId();
         boolean listAll = cmd.listAll();
@@ -2308,7 +2631,7 @@
             if (domain == null) {
                 throw new InvalidParameterValueException("Domain id=" + domainId + " doesn't exist");
             }
-            _accountMgr.checkAccess(caller, domain);
+            accountMgr.checkAccess(caller, domain);
         } else {
             if (caller.getType() != Account.Type.ADMIN) {
                 domainId = caller.getDomainId();
@@ -2318,24 +2641,27 @@
             }
         }
 
-        Filter searchFilter = new Filter(DomainJoinVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
+        Filter searchFilter = new Filter(DomainVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
         String domainName = cmd.getDomainName();
         Integer level = cmd.getLevel();
         Object keyword = cmd.getKeyword();
 
-        SearchBuilder<DomainJoinVO> sb = _domainJoinDao.createSearchBuilder();
-        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
-        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
-        sb.and("level", sb.entity().getLevel(), SearchCriteria.Op.EQ);
-        sb.and("path", sb.entity().getPath(), SearchCriteria.Op.LIKE);
-        sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
-
-        SearchCriteria<DomainJoinVO> sc = sb.create();
+        SearchBuilder<DomainVO> domainSearchBuilder = _domainDao.createSearchBuilder();
+        domainSearchBuilder.select(null, Func.DISTINCT, domainSearchBuilder.entity().getId()); // select distinct
+        domainSearchBuilder.and("id", domainSearchBuilder.entity().getId(), SearchCriteria.Op.EQ);
+        domainSearchBuilder.and("name", domainSearchBuilder.entity().getName(), SearchCriteria.Op.EQ);
+        domainSearchBuilder.and("level", domainSearchBuilder.entity().getLevel(), SearchCriteria.Op.EQ);
+        domainSearchBuilder.and("path", domainSearchBuilder.entity().getPath(), SearchCriteria.Op.LIKE);
+        domainSearchBuilder.and("state", domainSearchBuilder.entity().getState(), SearchCriteria.Op.EQ);
 
         if (keyword != null) {
-            SearchCriteria<DomainJoinVO> ssc = _domainJoinDao.createSearchCriteria();
-            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+            domainSearchBuilder.and("keywordName", domainSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE);
+        }
+
+        SearchCriteria<DomainVO> sc = domainSearchBuilder.create();
+
+        if (keyword != null) {
+            sc.setParameters("keywordName", "%" + keyword + "%");
         }
 
         if (domainName != null) {
@@ -2360,7 +2686,10 @@
         // return only Active domains to the API
         sc.setParameters("state", Domain.State.Active);
 
-        return _domainJoinDao.searchAndCount(sc, searchFilter);
+        Pair<List<DomainVO>, Integer> uniqueDomainPair = _domainDao.searchAndCount(sc, searchFilter);
+        Integer count = uniqueDomainPair.second();
+        List<Long> domainIds = uniqueDomainPair.first().stream().map(DomainVO::getId).collect(Collectors.toList());
+        return new Pair<>(domainIds, count);
     }
 
     @Override
@@ -2379,13 +2708,28 @@
     }
 
     private Pair<List<AccountJoinVO>, Integer> searchForAccountsInternal(ListAccountsCmd cmd) {
+
+        Pair<List<Long>, Integer> accountIdPage = searchForAccountIdsAndCount(cmd);
+
+        Integer count = accountIdPage.second();
+        Long[] idArray = accountIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<AccountJoinVO> accounts = _accountJoinDao.searchByIds(idArray);
+        return new Pair<>(accounts, count);
+    }
+
+    private Pair<List<Long>, Integer> searchForAccountIdsAndCount(ListAccountsCmd cmd) {
         Account caller = CallContext.current().getCallingAccount();
         Long domainId = cmd.getDomainId();
         Long accountId = cmd.getId();
         String accountName = cmd.getSearchName();
         boolean isRecursive = cmd.isRecursive();
         boolean listAll = cmd.listAll();
-        boolean callerIsAdmin = _accountMgr.isAdmin(caller.getId());
+        boolean callerIsAdmin = accountMgr.isAdmin(caller.getId());
         Account account;
         Domain domain = null;
 
@@ -2397,7 +2741,7 @@
                 throw new InvalidParameterValueException("Domain id=" + domainId + " doesn't exist");
             }
             // ... and check access rights.
-            _accountMgr.checkAccess(caller, domain);
+            accountMgr.checkAccess(caller, domain);
         }
 
         // if no "id" specified...
@@ -2420,7 +2764,7 @@
             if (account == null || account.getId() == Account.ACCOUNT_ID_SYSTEM) {
                 throw new InvalidParameterValueException("Unable to find account by name " + accountName + " in domain " + domainId);
             }
-            _accountMgr.checkAccess(caller, null, true, account);
+            accountMgr.checkAccess(caller, null, true, account);
         } else {
             // if they specified an "id"...
             if (domainId == null) {
@@ -2431,32 +2775,41 @@
             if (account == null || account.getId() == Account.ACCOUNT_ID_SYSTEM) {
                 throw new InvalidParameterValueException("Unable to find account by id " + accountId + (domainId == null ? "" : " in domain " + domainId));
             }
-            _accountMgr.checkAccess(caller, null, true, account);
+            accountMgr.checkAccess(caller, null, true, account);
         }
 
-        Filter searchFilter = new Filter(AccountJoinVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
+        Filter searchFilter = new Filter(AccountVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
 
         Object type = cmd.getAccountType();
         Object state = cmd.getState();
         Object isCleanupRequired = cmd.isCleanupRequired();
         Object keyword = cmd.getKeyword();
 
-        SearchBuilder<AccountJoinVO> sb = _accountJoinDao.createSearchBuilder();
-        sb.and("accountName", sb.entity().getAccountName(), SearchCriteria.Op.EQ);
-        sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ);
-        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
-        sb.and("type", sb.entity().getType(), SearchCriteria.Op.EQ);
-        sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
-        sb.and("needsCleanup", sb.entity().isNeedsCleanup(), SearchCriteria.Op.EQ);
-        sb.and("typeNEQ", sb.entity().getType(), SearchCriteria.Op.NEQ);
-        sb.and("idNEQ", sb.entity().getId(), SearchCriteria.Op.NEQ);
-        sb.and("type2NEQ", sb.entity().getType(), SearchCriteria.Op.NEQ);
+        SearchBuilder<AccountVO> accountSearchBuilder = _accountDao.createSearchBuilder();
+        accountSearchBuilder.select(null, Func.DISTINCT, accountSearchBuilder.entity().getId()); // select distinct
+        accountSearchBuilder.and("accountName", accountSearchBuilder.entity().getAccountName(), SearchCriteria.Op.EQ);
+        accountSearchBuilder.and("domainId", accountSearchBuilder.entity().getDomainId(), SearchCriteria.Op.EQ);
+        accountSearchBuilder.and("id", accountSearchBuilder.entity().getId(), SearchCriteria.Op.EQ);
+        accountSearchBuilder.and("type", accountSearchBuilder.entity().getType(), SearchCriteria.Op.EQ);
+        accountSearchBuilder.and("state", accountSearchBuilder.entity().getState(), SearchCriteria.Op.EQ);
+        accountSearchBuilder.and("needsCleanup", accountSearchBuilder.entity().getNeedsCleanup(), SearchCriteria.Op.EQ);
+        accountSearchBuilder.and("typeNEQ", accountSearchBuilder.entity().getType(), SearchCriteria.Op.NEQ);
+        accountSearchBuilder.and("idNEQ", accountSearchBuilder.entity().getId(), SearchCriteria.Op.NEQ);
+        accountSearchBuilder.and("type2NEQ", accountSearchBuilder.entity().getType(), SearchCriteria.Op.NEQ);
 
         if (domainId != null && isRecursive) {
-            sb.and("path", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE);
+            SearchBuilder<DomainVO> domainSearch = _domainDao.createSearchBuilder();
+            domainSearch.and("path", domainSearch.entity().getPath(), SearchCriteria.Op.LIKE);
+            accountSearchBuilder.join("domainSearch", domainSearch, domainSearch.entity().getId(), accountSearchBuilder.entity().getDomainId(), JoinBuilder.JoinType.INNER);
         }
 
-        SearchCriteria<AccountJoinVO> sc = sb.create();
+        if (keyword != null) {
+            accountSearchBuilder.and().op("keywordAccountName", accountSearchBuilder.entity().getAccountName(), SearchCriteria.Op.LIKE);
+            accountSearchBuilder.or("keywordState", accountSearchBuilder.entity().getState(), SearchCriteria.Op.LIKE);
+            accountSearchBuilder.cp();
+        }
+
+        SearchCriteria<AccountVO> sc = accountSearchBuilder.create();
 
         // don't return account of type project to the end user
         sc.setParameters("typeNEQ", Account.Type.PROJECT);
@@ -2470,10 +2823,8 @@
         }
 
         if (keyword != null) {
-            SearchCriteria<AccountJoinVO> ssc = _accountJoinDao.createSearchCriteria();
-            ssc.addOr("accountName", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("state", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            sc.addAnd("accountName", SearchCriteria.Op.SC, ssc);
+            sc.setParameters("keywordAccountName", "%" + keyword + "%");
+            sc.setParameters("keywordState", "%" + keyword + "%");
         }
 
         if (type != null) {
@@ -2502,13 +2853,16 @@
                 if (domain == null) {
                     domain = _domainDao.findById(domainId);
                 }
-                sc.setParameters("path", domain.getPath() + "%");
+                sc.setJoinParameters("domainSearch", "path", domain.getPath() + "%");
             } else {
                 sc.setParameters("domainId", domainId);
             }
         }
 
-        return _accountJoinDao.searchAndCount(sc, searchFilter);
+        Pair<List<AccountVO>, Integer> uniqueAccountPair = _accountDao.searchAndCount(sc, searchFilter);
+        Integer count = uniqueAccountPair.second();
+        List<Long> accountIds = uniqueAccountPair.first().stream().map(AccountVO::getId).collect(Collectors.toList());
+        return new Pair<>(accountIds, count);
     }
 
     @Override
@@ -2527,7 +2881,7 @@
         List<Long> permittedAccounts = new ArrayList<Long>();
 
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, null, cmd.getAccountName(), null, permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        accountMgr.buildACLSearchParameters(caller, null, cmd.getAccountName(), null, permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
         Long domainId = domainIdRecursiveListProject.first();
         Boolean isRecursive = domainIdRecursiveListProject.second();
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
@@ -2557,6 +2911,10 @@
             }
         }
 
+        if (cmd.getManagementServerId() != null) {
+            sb.and("executingMsid", sb.entity().getExecutingMsid(), SearchCriteria.Op.EQ);
+        }
+
         Object keyword = cmd.getKeyword();
         Object startDate = cmd.getStartDate();
 
@@ -2585,15 +2943,41 @@
             sc.addAnd("created", SearchCriteria.Op.GTEQ, startDate);
         }
 
+        if (cmd.getManagementServerId() != null) {
+            ManagementServerHostVO msHost = msHostDao.findById(cmd.getManagementServerId());
+            sc.setParameters("executingMsid", msHost.getMsid());
+        }
+
         return _jobJoinDao.searchAndCount(sc, searchFilter);
     }
 
     @Override
     public ListResponse<StoragePoolResponse> searchForStoragePools(ListStoragePoolsCmd cmd) {
-        Pair<List<StoragePoolJoinVO>, Integer> result = searchForStoragePoolsInternal(cmd);
-        ListResponse<StoragePoolResponse> response = new ListResponse<StoragePoolResponse>();
+        Pair<List<StoragePoolJoinVO>, Integer> result = (ScopeType.HOST.name().equalsIgnoreCase(cmd.getScope()) && cmd.getHostId() != null) ?
+                searchForLocalStorages(cmd) : searchForStoragePoolsInternal(cmd);
+        return createStoragesPoolResponse(result);
+    }
 
-        List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(result.first().toArray(new StoragePoolJoinVO[result.first().size()]));
+    private Pair<List<StoragePoolJoinVO>, Integer> searchForLocalStorages(ListStoragePoolsCmd cmd) {
+        long id = cmd.getHostId();
+        List<StoragePoolHostVO> localstoragePools = storagePoolHostDao.listByHostId(id);
+        Long[] poolIds = new Long[localstoragePools.size()];
+        int i = 0;
+        for(StoragePoolHostVO localstoragePool : localstoragePools) {
+            StoragePool storagePool = storagePoolDao.findById(localstoragePool.getPoolId());
+            if (storagePool != null && storagePool.isLocal()) {
+                poolIds[i++] = localstoragePool.getPoolId();
+            }
+        }
+        List<StoragePoolJoinVO> pools = _poolJoinDao.searchByIds(poolIds);
+        return new Pair<>(pools, pools.size());
+    }
+
+    private ListResponse<StoragePoolResponse> createStoragesPoolResponse(Pair<List<StoragePoolJoinVO>, Integer> storagePools) {
+        ListResponse<StoragePoolResponse> response = new ListResponse<>();
+
+        List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()]));
+        Map<String, Long> poolUuidToIdMap = storagePools.first().stream().collect(Collectors.toMap(StoragePoolJoinVO::getUuid, StoragePoolJoinVO::getId));
         for (StoragePoolResponse poolResponse : poolResponses) {
             DataStore store = dataStoreManager.getPrimaryDataStore(poolResponse.getId());
             if (store != null) {
@@ -2602,8 +2986,7 @@
                     Map<String, String> caps = driver.getCapabilities();
                     if (Storage.StoragePoolType.NetworkFilesystem.toString().equals(poolResponse.getType()) &&
                         HypervisorType.VMware.toString().equals(poolResponse.getHypervisor())) {
-                        StoragePoolVO pool = _storagePoolDao.findPoolByUUID(poolResponse.getId());
-                        StoragePoolDetailVO detail = _storagePoolDetailsDao.findDetail(pool.getId(), Storage.Capability.HARDWARE_ACCELERATION.toString());
+                        StoragePoolDetailVO detail = _storagePoolDetailsDao.findDetail(poolUuidToIdMap.get(poolResponse.getId()), Storage.Capability.HARDWARE_ACCELERATION.toString());
                         if (detail != null) {
                             caps.put(Storage.Capability.HARDWARE_ACCELERATION.toString(), detail.getValue());
                         }
@@ -2613,7 +2996,7 @@
             }
         }
 
-        response.setResponses(poolResponses, result.second());
+        response.setResponses(poolResponses, storagePools.second());
         return response;
     }
 
@@ -2621,7 +3004,7 @@
         ScopeType scopeType = ScopeType.validateAndGetScopeType(cmd.getScope());
         StoragePoolStatus status = StoragePoolStatus.validateAndGetStatus(cmd.getStatus());
 
-        Long zoneId = _accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
+        Long zoneId = accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
         Long id = cmd.getId();
         String name = cmd.getStoragePoolName();
         String path = cmd.getPath();
@@ -2629,29 +3012,18 @@
         Long cluster = cmd.getClusterId();
         String address = cmd.getIpAddress();
         String keyword = cmd.getKeyword();
+
         Long startIndex = cmd.getStartIndex();
         Long pageSize = cmd.getPageSizeVal();
 
-        Filter searchFilter = new Filter(StoragePoolJoinVO.class, "id", Boolean.TRUE, startIndex, pageSize);
+        Filter searchFilter = new Filter(StoragePoolVO.class, "id", Boolean.TRUE, startIndex, pageSize);
 
-        // search & count Pool details by ids
-        Pair<List<StoragePoolJoinVO>, Integer> uniquePoolPair = _poolJoinDao.searchAndCount(id, name, zoneId, path, pod,
+        Pair<List<Long>, Integer> uniquePoolPair = storagePoolDao.searchForIdsAndCount(id, name, zoneId, path, pod,
                 cluster, address, scopeType, status, keyword, searchFilter);
 
-        Integer count = uniquePoolPair.second();
-        if (count.intValue() == 0) {
-            // empty result
-            return uniquePoolPair;
-        }
-        List<StoragePoolJoinVO> uniquePools = uniquePoolPair.first();
-        Long[] vrIds = new Long[uniquePools.size()];
-        int i = 0;
-        for (StoragePoolJoinVO v : uniquePools) {
-            vrIds[i++] = v.getId();
-        }
-        List<StoragePoolJoinVO> vrs = _poolJoinDao.searchByIds(vrIds);
-        return new Pair<List<StoragePoolJoinVO>, Integer>(vrs, count);
+        List<StoragePoolJoinVO> storagePools = _poolJoinDao.searchByIds(uniquePoolPair.first().toArray(new Long[0]));
 
+        return new Pair<>(storagePools, uniquePoolPair.second());
     }
 
     @Override
@@ -2748,7 +3120,7 @@
 
     private Pair<List<ImageStoreJoinVO>, Integer> searchForImageStoresInternal(ListImageStoresCmd cmd) {
 
-        Long zoneId = _accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
+        Long zoneId = accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
         Object id = cmd.getId();
         Object name = cmd.getStoreName();
         String provider = cmd.getProvider();
@@ -2832,7 +3204,7 @@
 
     private Pair<List<ImageStoreJoinVO>, Integer> searchForCacheStoresInternal(ListSecondaryStagingStoresCmd cmd) {
 
-        Long zoneId = _accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
+        Long zoneId = accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), cmd.getZoneId());
         Object id = cmd.getId();
         Object name = cmd.getStoreName();
         String provider = cmd.getProvider();
@@ -2909,6 +3281,33 @@
     }
 
     private Pair<List<DiskOfferingJoinVO>, Integer> searchForDiskOfferingsInternal(ListDiskOfferingsCmd cmd) {
+        Ternary<List<Long>, Integer, String[]> diskOfferingIdPage = searchForDiskOfferingsIdsAndCount(cmd);
+
+        Integer count = diskOfferingIdPage.second();
+        Long[] idArray = diskOfferingIdPage.first().toArray(new Long[0]);
+        String[] requiredTagsArray = diskOfferingIdPage.third();
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<DiskOfferingJoinVO> diskOfferings = _diskOfferingJoinDao.searchByIds(idArray);
+
+        if (requiredTagsArray.length != 0) {
+            ListIterator<DiskOfferingJoinVO> iteratorForTagsChecking = diskOfferings.listIterator();
+            while (iteratorForTagsChecking.hasNext()) {
+                DiskOfferingJoinVO offering = iteratorForTagsChecking.next();
+                String offeringTags = offering.getTags();
+                String[] offeringTagsArray = (offeringTags == null || offeringTags.isEmpty()) ? new String[0] : offeringTags.split(",");
+                if (!CollectionUtils.isSubCollection(Arrays.asList(requiredTagsArray), Arrays.asList(offeringTagsArray))) {
+                    iteratorForTagsChecking.remove();
+                }
+            }
+        }
+        return new Pair<>(diskOfferings, count);
+    }
+
+    private Ternary<List<Long>, Integer, String[]> searchForDiskOfferingsIdsAndCount(ListDiskOfferingsCmd cmd) {
         // Note
         // The list method for offerings is being modified in accordance with
         // discussion with Will/Kevin
@@ -2919,36 +3318,60 @@
         // till
         // root
 
-        Filter searchFilter = new Filter(DiskOfferingJoinVO.class, "sortKey", SortKeyAscending.value(), cmd.getStartIndex(), cmd.getPageSizeVal());
-        searchFilter.addOrderBy(DiskOfferingJoinVO.class, "id", true);
-        SearchCriteria<DiskOfferingJoinVO> sc = _diskOfferingJoinDao.createSearchCriteria();
-        sc.addAnd("computeOnly", Op.EQ, false);
-
         Account account = CallContext.current().getCallingAccount();
         Object name = cmd.getDiskOfferingName();
         Object id = cmd.getId();
         Object keyword = cmd.getKeyword();
         Long domainId = cmd.getDomainId();
+        Boolean isRootAdmin = accountMgr.isRootAdmin(account.getAccountId());
         Long projectId = cmd.getProjectId();
         String accountName = cmd.getAccountName();
-        Boolean isRootAdmin = _accountMgr.isRootAdmin(account.getAccountId());
         Boolean isRecursive = cmd.isRecursive();
         Long zoneId = cmd.getZoneId();
         Long volumeId = cmd.getVolumeId();
         Long storagePoolId = cmd.getStoragePoolId();
         Boolean encrypt = cmd.getEncrypt();
+        String storageType = cmd.getStorageType();
+        DiskOffering.State state = cmd.getState();
+
+        Filter searchFilter = new Filter(DiskOfferingVO.class, "sortKey", SortKeyAscending.value(), cmd.getStartIndex(), cmd.getPageSizeVal());
+        searchFilter.addOrderBy(DiskOfferingVO.class, "id", true);
+        SearchBuilder<DiskOfferingVO> diskOfferingSearch = _diskOfferingDao.createSearchBuilder();
+        diskOfferingSearch.select(null, Func.DISTINCT, diskOfferingSearch.entity().getId()); // select distinct
+
+        diskOfferingSearch.and("computeOnly", diskOfferingSearch.entity().isComputeOnly(), Op.EQ);
+
+        if (state != null) {
+            diskOfferingSearch.and("state", diskOfferingSearch.entity().getState(), Op.EQ);
+        }
+
         // Keeping this logic consistent with domain specific zones
         // if a domainId is provided, we just return the disk offering
         // associated with this domain
         if (domainId != null && accountName == null) {
-            if (_accountMgr.isRootAdmin(account.getId()) || isPermissible(account.getDomainId(), domainId)) {
+            if (accountMgr.isRootAdmin(account.getId()) || isPermissible(account.getDomainId(), domainId)) {
                 // check if the user's domain == do's domain || user's domain is
                 // a child of so's domain for non-root users
-                sc.addAnd("domainId", Op.FIND_IN_SET, String.valueOf(domainId));
+                SearchBuilder<DiskOfferingDetailVO> domainDetailsSearch = _diskOfferingDetailsDao.createSearchBuilder();
+                domainDetailsSearch.and("domainId", domainDetailsSearch.entity().getValue(), Op.EQ);
+
+                diskOfferingSearch.join("domainDetailsSearch", domainDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                        diskOfferingSearch.entity().getId(), domainDetailsSearch.entity().getResourceId(),
+                        domainDetailsSearch.entity().getName(), diskOfferingSearch.entity().setString(ApiConstants.DOMAIN_ID));
+
                 if (!isRootAdmin) {
-                    sc.addAnd("displayOffering", SearchCriteria.Op.EQ, 1);
+                    diskOfferingSearch.and("displayOffering", diskOfferingSearch.entity().getDisplayOffering(), Op.EQ);
                 }
-                return _diskOfferingJoinDao.searchAndCount(sc, searchFilter);
+
+                SearchCriteria<DiskOfferingVO> sc = diskOfferingSearch.create();
+                sc.setParameters("computeOnly", false);
+                sc.setParameters("activeState", DiskOffering.State.Active);
+
+                sc.setJoinParameters("domainDetailsSearch", "domainId", domainId);
+
+                Pair<List<DiskOfferingVO>, Integer> uniquePairs = _diskOfferingDao.searchAndCount(sc, searchFilter);
+                List<Long> idsArray = uniquePairs.first().stream().map(DiskOfferingVO::getId).collect(Collectors.toList());
+                return new Ternary<>(idsArray, uniquePairs.second(), new String[0]);
             } else {
                 throw new PermissionDeniedException("The account:" + account.getAccountName() + " does not fall in the same domain hierarchy as the disk offering");
             }
@@ -2956,7 +3379,7 @@
 
         // For non-root users, only return all offerings for the user's domain,
         // and everything above till root
-        if ((_accountMgr.isNormalUser(account.getId()) || _accountMgr.isDomainAdmin(account.getId())) || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) {
+        if ((accountMgr.isNormalUser(account.getId()) || accountMgr.isDomainAdmin(account.getId())) || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) {
             if (isRecursive) { // domain + all sub-domains
                 if (account.getType() == Account.Type.NORMAL) {
                     throw new InvalidParameterValueException("Only ROOT admins and Domain admins can list disk offerings with isrecursive=true");
@@ -2969,102 +3392,150 @@
         }
 
         if (keyword != null) {
-            SearchCriteria<DiskOfferingJoinVO> ssc = _diskOfferingJoinDao.createSearchCriteria();
-            ssc.addOr("displayText", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-
-            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+            diskOfferingSearch.and().op("keywordDisplayText", diskOfferingSearch.entity().getDisplayText(), Op.LIKE);
+            diskOfferingSearch.or("keywordName", diskOfferingSearch.entity().getName(), Op.LIKE);
+            diskOfferingSearch.cp();
         }
 
         if (id != null) {
-            sc.addAnd("id", SearchCriteria.Op.EQ, id);
+            diskOfferingSearch.and("id", diskOfferingSearch.entity().getId(), Op.EQ);
         }
 
         if (name != null) {
-            sc.addAnd("name", SearchCriteria.Op.EQ, name);
+            diskOfferingSearch.and("name", diskOfferingSearch.entity().getName(), Op.EQ);
         }
 
         if (encrypt != null) {
-            sc.addAnd("encrypt", SearchCriteria.Op.EQ, encrypt);
+            diskOfferingSearch.and("encrypt", diskOfferingSearch.entity().getEncrypt(), Op.EQ);
+        }
+
+        if (storageType != null || zoneId != null) {
+            diskOfferingSearch.and("useLocalStorage", diskOfferingSearch.entity().isUseLocalStorage(), Op.EQ);
         }
 
         if (zoneId != null) {
-            SearchBuilder<DiskOfferingJoinVO> sb = _diskOfferingJoinDao.createSearchBuilder();
-            sb.and("zoneId", sb.entity().getZoneId(), Op.FIND_IN_SET);
-            sb.or("zId", sb.entity().getZoneId(), Op.NULL);
-            sb.done();
-            SearchCriteria<DiskOfferingJoinVO> zoneSC = sb.create();
-            zoneSC.setParameters("zoneId", String.valueOf(zoneId));
-            sc.addAnd("zoneId", SearchCriteria.Op.SC, zoneSC);
-            DataCenterJoinVO zone = _dcJoinDao.findById(zoneId);
-            if (DataCenter.Type.Edge.equals(zone.getType())) {
-                sc.addAnd("useLocalStorage", Op.EQ, true);
-            }
+            SearchBuilder<DiskOfferingDetailVO> zoneDetailSearch = _diskOfferingDetailsDao.createSearchBuilder();
+            zoneDetailSearch.and().op("zoneId", zoneDetailSearch.entity().getValue(), Op.EQ);
+            zoneDetailSearch.or("zoneIdNull", zoneDetailSearch.entity().getId(), Op.NULL);
+            zoneDetailSearch.cp();
+
+            diskOfferingSearch.join("zoneDetailSearch", zoneDetailSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                    diskOfferingSearch.entity().getId(), zoneDetailSearch.entity().getResourceId(),
+                    zoneDetailSearch.entity().getName(), diskOfferingSearch.entity().setString(ApiConstants.ZONE_ID));
         }
 
         DiskOffering currentDiskOffering = null;
+        Volume volume = null;
         if (volumeId != null) {
-            Volume volume = volumeDao.findById(volumeId);
+            volume = volumeDao.findById(volumeId);
             if (volume == null) {
                 throw new InvalidParameterValueException(String.format("Unable to find a volume with specified id %s", volumeId));
             }
             currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId());
             if (!currentDiskOffering.isComputeOnly() && currentDiskOffering.getDiskSizeStrictness()) {
-                SearchCriteria<DiskOfferingJoinVO> ssc = _diskOfferingJoinDao.createSearchCriteria();
-                ssc.addOr("diskSize", Op.EQ, volume.getSize());
-                ssc.addOr("customized", SearchCriteria.Op.EQ, true);
-                sc.addAnd("diskSizeOrCustomized", SearchCriteria.Op.SC, ssc);
+                diskOfferingSearch.and().op("diskSize", diskOfferingSearch.entity().getDiskSize(), Op.EQ);
+                diskOfferingSearch.or("customized", diskOfferingSearch.entity().isCustomized(), Op.EQ);
+                diskOfferingSearch.cp();
             }
-            sc.addAnd("id", SearchCriteria.Op.NEQ, currentDiskOffering.getId());
-            sc.addAnd("diskSizeStrictness", Op.EQ, currentDiskOffering.getDiskSizeStrictness());
+            diskOfferingSearch.and("idNEQ", diskOfferingSearch.entity().getId(), Op.NEQ);
+            diskOfferingSearch.and("diskSizeStrictness", diskOfferingSearch.entity().getDiskSizeStrictness(), Op.EQ);
+        }
+
+        account = accountMgr.finalizeOwner(account, accountName, domainId, projectId);
+        if (!Account.Type.ADMIN.equals(account.getType())) {
+            SearchBuilder<DiskOfferingDetailVO> domainDetailsSearch = _diskOfferingDetailsDao.createSearchBuilder();
+            domainDetailsSearch.and().op("domainIdIN", domainDetailsSearch.entity().getValue(), Op.IN);
+            domainDetailsSearch.or("domainIdNull", domainDetailsSearch.entity().getId(), Op.NULL);
+            domainDetailsSearch.cp();
+
+            diskOfferingSearch.join("domainDetailsSearch", domainDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                    diskOfferingSearch.entity().getId(), domainDetailsSearch.entity().getResourceId(),
+                    domainDetailsSearch.entity().getName(), diskOfferingSearch.entity().setString(ApiConstants.DOMAIN_ID));
+        }
+
+        SearchCriteria<DiskOfferingVO> sc = diskOfferingSearch.create();
+
+        sc.setParameters("computeOnly", false);
+
+        if (state != null) {
+            sc.setParameters("state", state);
+        }
+
+        if (keyword != null) {
+            sc.setParameters("keywordDisplayText", "%" + keyword + "%");
+            sc.setParameters("keywordName", "%" + keyword + "%");
+        }
+
+        if (id != null) {
+            sc.setParameters("id", id);
+        }
+
+        if (name != null) {
+            sc.setParameters("name", name);
+        }
+
+        if (encrypt != null) {
+            sc.setParameters("encrypt", encrypt);
+        }
+
+        if (storageType != null) {
+            if (storageType.equalsIgnoreCase(ServiceOffering.StorageType.local.toString())) {
+                sc.setParameters("useLocalStorage", true);
+
+            } else if (storageType.equalsIgnoreCase(ServiceOffering.StorageType.shared.toString())) {
+                sc.setParameters("useLocalStorage", false);
+            }
+        }
+
+        if (zoneId != null) {
+            sc.setJoinParameters("zoneDetailSearch", "zoneId", zoneId);
+
+            DataCenterJoinVO zone = _dcJoinDao.findById(zoneId);
+            if (DataCenter.Type.Edge.equals(zone.getType())) {
+                sc.setParameters("useLocalStorage", true);
+            }
+        }
+
+        if (volumeId != null) {
+            if (!currentDiskOffering.isComputeOnly() && currentDiskOffering.getDiskSizeStrictness()) {
+                sc.setParameters("diskSize", volume.getSize());
+                sc.setParameters("customized", true);
+            }
+            sc.setParameters("idNEQ", currentDiskOffering.getId());
+            sc.setParameters("diskSizeStrictness", currentDiskOffering.getDiskSizeStrictness());
         }
 
         // Filter offerings that are not associated with caller's domain
-        // Fetch the offering ids from the details table since theres no smart way to filter them in the join ... yet!
-        account = _accountMgr.finalizeOwner(account, accountName, domainId, projectId);
         if (!Account.Type.ADMIN.equals(account.getType())) {
             Domain callerDomain = _domainDao.findById(account.getDomainId());
             List<Long> domainIds = findRelatedDomainIds(callerDomain, isRecursive);
 
-            List<Long> ids = _diskOfferingDetailsDao.findOfferingIdsByDomainIds(domainIds);
-            SearchBuilder<DiskOfferingJoinVO> sb = _diskOfferingJoinDao.createSearchBuilder();
-            if (ids != null && !ids.isEmpty()) {
-                sb.and("id", sb.entity().getId(), Op.IN);
-            }
-            sb.or("domainId", sb.entity().getDomainId(), Op.NULL);
-            sb.done();
-
-            SearchCriteria<DiskOfferingJoinVO> scc = sb.create();
-            if (ids != null && !ids.isEmpty()) {
-                scc.setParameters("id", ids.toArray());
-            }
-            sc.addAnd("domainId", SearchCriteria.Op.SC, scc);
+            sc.setJoinParameters("domainDetailsSearch", "domainIdIN", domainIds.toArray());
         }
 
-        Pair<List<DiskOfferingJoinVO>, Integer> result = _diskOfferingJoinDao.searchAndCount(sc, searchFilter);
+        Pair<List<DiskOfferingVO>, Integer> uniquePairs = _diskOfferingDao.searchAndCount(sc, searchFilter);
         String[] requiredTagsArray = new String[0];
-        if (CollectionUtils.isNotEmpty(result.first()) && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(zoneId)) {
+        if (CollectionUtils.isNotEmpty(uniquePairs.first()) && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(zoneId)) {
             if (volumeId != null) {
-                Volume volume = volumeDao.findById(volumeId);
-                currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId());
                 requiredTagsArray = currentDiskOffering.getTagsArray();
             } else if (storagePoolId != null) {
                 requiredTagsArray = _storageTagDao.getStoragePoolTags(storagePoolId).toArray(new String[0]);
             }
         }
-        if (requiredTagsArray.length != 0) {
-            ListIterator<DiskOfferingJoinVO> iteratorForTagsChecking = result.first().listIterator();
-            while (iteratorForTagsChecking.hasNext()) {
-                DiskOfferingJoinVO offering = iteratorForTagsChecking.next();
-                String offeringTags = offering.getTags();
-                String[] offeringTagsArray = (offeringTags == null || offeringTags.isEmpty()) ? new String[0] : offeringTags.split(",");
-                if (!CollectionUtils.isSubCollection(Arrays.asList(requiredTagsArray), Arrays.asList(offeringTagsArray))) {
-                    iteratorForTagsChecking.remove();
-                }
+        List<Long> idsArray = uniquePairs.first().stream().map(DiskOfferingVO::getId).collect(Collectors.toList());
+
+        return new Ternary<>(idsArray, uniquePairs.second(), requiredTagsArray);
+    }
+
+    private void useStorageType(SearchCriteria<?> sc, String storageType) {
+        if (storageType != null) {
+            if (storageType.equalsIgnoreCase(ServiceOffering.StorageType.local.toString())) {
+                sc.addAnd("useLocalStorage", Op.EQ, true);
+
+            } else if (storageType.equalsIgnoreCase(ServiceOffering.StorageType.shared.toString())) {
+                sc.addAnd("useLocalStorage", Op.EQ, false);
             }
         }
-
-        return new Pair<>(result.first(), result.second());
     }
 
     private List<Long> findRelatedDomainIds(Domain domain, boolean isRecursive) {
@@ -3089,6 +3560,20 @@
     }
 
     private Pair<List<ServiceOfferingJoinVO>, Integer> searchForServiceOfferingsInternal(ListServiceOfferingsCmd cmd) {
+        Pair<List<Long>, Integer> offeringIdPage = searchForServiceOfferingIdsAndCount(cmd);
+
+        Integer count = offeringIdPage.second();
+        Long[] idArray = offeringIdPage.first().toArray(new Long[0]);
+
+        if (count == 0) {
+            return new Pair<>(new ArrayList<>(), count);
+        }
+
+        List<ServiceOfferingJoinVO> srvOfferings = _srvOfferingJoinDao.searchByIds(idArray);
+        return new Pair<>(srvOfferings, count);
+    }
+
+    private Pair<List<Long>, Integer> searchForServiceOfferingIdsAndCount(ListServiceOfferingsCmd cmd) {
         // Note
         // The filteredOfferings method for offerings is being modified in accordance with
         // discussion with Will/Kevin
@@ -3098,9 +3583,6 @@
         // their domains+parent domains ... all the way
         // till
         // root
-        Filter searchFilter = new Filter(ServiceOfferingJoinVO.class, "sortKey", SortKeyAscending.value(), cmd.getStartIndex(), cmd.getPageSizeVal());
-        searchFilter.addOrderBy(ServiceOfferingJoinVO.class, "id", true);
-
         Account caller = CallContext.current().getCallingAccount();
         Long projectId = cmd.getProjectId();
         String accountName = cmd.getAccountName();
@@ -3112,23 +3594,26 @@
         Boolean isSystem = cmd.getIsSystem();
         String vmTypeStr = cmd.getSystemVmType();
         ServiceOfferingVO currentVmOffering = null;
+        DiskOfferingVO diskOffering = null;
         Boolean isRecursive = cmd.isRecursive();
         Long zoneId = cmd.getZoneId();
         Integer cpuNumber = cmd.getCpuNumber();
         Integer memory = cmd.getMemory();
         Integer cpuSpeed = cmd.getCpuSpeed();
         Boolean encryptRoot = cmd.getEncryptRoot();
+        String storageType = cmd.getStorageType();
+        ServiceOffering.State state = cmd.getState();
 
-        final Account owner = _accountMgr.finalizeOwner(caller, accountName, domainId, projectId);
-        SearchCriteria<ServiceOfferingJoinVO> sc = _srvOfferingJoinDao.createSearchCriteria();
-        if (!_accountMgr.isRootAdmin(caller.getId()) && isSystem) {
+        final Account owner = accountMgr.finalizeOwner(caller, accountName, domainId, projectId);
+
+        if (!accountMgr.isRootAdmin(caller.getId()) && isSystem) {
             throw new InvalidParameterValueException("Only ROOT admins can access system offerings.");
         }
 
         // Keeping this logic consistent with domain specific zones
         // if a domainId is provided, we just return the so associated with this
         // domain
-        if (domainId != null && !_accountMgr.isRootAdmin(caller.getId())) {
+        if (domainId != null && !accountMgr.isRootAdmin(caller.getId())) {
             // check if the user's domain == so's domain || user's domain is a
             // child of so's domain
             if (!isPermissible(owner.getDomainId(), domainId)) {
@@ -3136,41 +3621,47 @@
             }
         }
 
+        VMInstanceVO vmInstance = null;
         if (vmId != null) {
-            VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId);
+            vmInstance = _vmInstanceDao.findById(vmId);
             if ((vmInstance == null) || (vmInstance.getRemoved() != null)) {
                 InvalidParameterValueException ex = new InvalidParameterValueException("unable to find a virtual machine with specified id");
                 ex.addProxyObject(vmId.toString(), "vmId");
                 throw ex;
             }
+            accountMgr.checkAccess(owner, null, true, vmInstance);
+        }
 
-            _accountMgr.checkAccess(owner, null, true, vmInstance);
+        Filter searchFilter = new Filter(ServiceOfferingVO.class, "sortKey", SortKeyAscending.value(), cmd.getStartIndex(), cmd.getPageSizeVal());
+        searchFilter.addOrderBy(ServiceOfferingVO.class, "id", true);
 
+        SearchBuilder<ServiceOfferingVO> serviceOfferingSearch = _srvOfferingDao.createSearchBuilder();
+        serviceOfferingSearch.select(null, Func.DISTINCT, serviceOfferingSearch.entity().getId()); // select distinct
+
+        if (state != null) {
+            serviceOfferingSearch.and("state", serviceOfferingSearch.entity().getState(), Op.EQ);
+        }
+
+        if (vmId != null) {
             currentVmOffering = _srvOfferingDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId());
-            if (! currentVmOffering.isDynamic()) {
-                sc.addAnd("id", SearchCriteria.Op.NEQ, currentVmOffering.getId());
+            diskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentVmOffering.getDiskOfferingId());
+            if (!currentVmOffering.isDynamic()) {
+                serviceOfferingSearch.and("idNEQ", serviceOfferingSearch.entity().getId(), SearchCriteria.Op.NEQ);
             }
 
             if (currentVmOffering.getDiskOfferingStrictness()) {
-                sc.addAnd("diskOfferingId", Op.EQ, currentVmOffering.getDiskOfferingId());
-                sc.addAnd("diskOfferingStrictness", Op.EQ, true);
-            } else {
-                sc.addAnd("diskOfferingStrictness", Op.EQ, false);
+                serviceOfferingSearch.and("diskOfferingId", serviceOfferingSearch.entity().getDiskOfferingId(), SearchCriteria.Op.EQ);
             }
+            serviceOfferingSearch.and("diskOfferingStrictness", serviceOfferingSearch.entity().getDiskOfferingStrictness(), SearchCriteria.Op.EQ);
 
-            boolean isRootVolumeUsingLocalStorage = virtualMachineManager.isRootVolumeOnLocalStorage(vmId);
-
-            // 1. Only return offerings with the same storage type than the storage pool where the VM's root volume is allocated
-            sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, isRootVolumeUsingLocalStorage);
-
-            // 2.In case vm is running return only offerings greater than equal to current offering compute and offering's dynamic scalability should match
+            // In case vm is running return only offerings greater than equal to current offering compute and offering's dynamic scalability should match
             if (vmInstance.getState() == VirtualMachine.State.Running) {
                 Integer vmCpu = currentVmOffering.getCpu();
                 Integer vmMemory = currentVmOffering.getRamSize();
                 Integer vmSpeed = currentVmOffering.getSpeed();
                 if ((vmCpu == null || vmMemory == null || vmSpeed == null) && VirtualMachine.Type.User.equals(vmInstance.getType())) {
-                    UserVmVO userVmVO = _userVmDao.findById(vmId);
-                    _userVmDao.loadDetails(userVmVO);
+                    UserVmVO userVmVO = userVmDao.findById(vmId);
+                    userVmDao.loadDetails(userVmVO);
                     Map<String, String> details = userVmVO.getDetails();
                     vmCpu = NumbersUtil.parseInt(details.get(ApiConstants.CPU_NUMBER), 0);
                     if (vmSpeed == null) {
@@ -3179,20 +3670,58 @@
                     vmMemory = NumbersUtil.parseInt(details.get(ApiConstants.MEMORY), 0);
                 }
                 if (vmCpu != null && vmCpu > 0) {
-                    sc.addAnd("cpu", Op.SC, getMinimumCpuServiceOfferingJoinSearchCriteria(vmCpu));
+                    /*
+                            (service_offering.cpu >= ?)
+                             OR (
+                                service_offering.cpu IS NULL
+                                AND (maxComputeDetailsSearch.value IS NULL OR  maxComputeDetailsSearch.value >= ?)
+                            )
+                     */
+                    SearchBuilder<ServiceOfferingDetailsVO> maxComputeDetailsSearch = _srvOfferingDetailsDao.createSearchBuilder();
+
+                    serviceOfferingSearch.join("maxComputeDetailsSearch", maxComputeDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                            serviceOfferingSearch.entity().getId(), maxComputeDetailsSearch.entity().getResourceId(),
+                            maxComputeDetailsSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.MAX_CPU_NUMBER));
+
+                    serviceOfferingSearch.and().op("vmCpu", serviceOfferingSearch.entity().getCpu(), Op.GTEQ);
+                    serviceOfferingSearch.or().op("vmCpuNull", serviceOfferingSearch.entity().getCpu(), Op.NULL);
+                    serviceOfferingSearch.and().op("maxComputeDetailsSearch", "vmMaxComputeNull", maxComputeDetailsSearch.entity().getValue(), Op.NULL);
+                    serviceOfferingSearch.or("maxComputeDetailsSearch", "vmMaxComputeGTEQ", maxComputeDetailsSearch.entity().getValue(), Op.GTEQ).cp();
+
+                    serviceOfferingSearch.cp().cp();
+
                 }
                 if (vmSpeed != null && vmSpeed > 0) {
-                    sc.addAnd("speed", Op.SC, getMinimumCpuSpeedServiceOfferingJoinSearchCriteria(vmSpeed));
+                    serviceOfferingSearch.and().op("speedNULL", serviceOfferingSearch.entity().getSpeed(), Op.NULL);
+                    serviceOfferingSearch.or("speedGTEQ", serviceOfferingSearch.entity().getSpeed(), Op.GTEQ);
+                    serviceOfferingSearch.cp();
                 }
                 if (vmMemory != null && vmMemory > 0) {
-                    sc.addAnd("ramSize", Op.SC, getMinimumMemoryServiceOfferingJoinSearchCriteria(vmMemory));
+                    /*
+                        (service_offering.ram_size >= ?)
+                        OR (
+                          service_offering.ram_size IS NULL
+                          AND (max_memory_details.value IS NULL OR max_memory_details.value >= ?)
+                        )
+                     */
+                    SearchBuilder<ServiceOfferingDetailsVO> maxMemoryDetailsSearch = _srvOfferingDetailsDao.createSearchBuilder();
+
+                    serviceOfferingSearch.join("maxMemoryDetailsSearch", maxMemoryDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                            serviceOfferingSearch.entity().getId(), maxMemoryDetailsSearch.entity().getResourceId(),
+                            maxMemoryDetailsSearch.entity().getName(), serviceOfferingSearch.entity().setString("maxmemory"));
+
+                    serviceOfferingSearch.and().op("vmMemory", serviceOfferingSearch.entity().getRamSize(), Op.GTEQ);
+                    serviceOfferingSearch.or().op("vmMemoryNull", serviceOfferingSearch.entity().getRamSize(), Op.NULL);
+                    serviceOfferingSearch.and().op("maxMemoryDetailsSearch", "vmMaxMemoryNull", maxMemoryDetailsSearch.entity().getValue(), Op.NULL);
+                    serviceOfferingSearch.and("maxMemoryDetailsSearch", "vmMaxMemoryGTEQ", maxMemoryDetailsSearch.entity().getValue(), Op.GTEQ).cp();
+
+                    serviceOfferingSearch.cp().cp();
                 }
-                sc.addAnd("dynamicScalingEnabled", Op.EQ, currentVmOffering.isDynamicScalingEnabled());
+                serviceOfferingSearch.and("dynamicScalingEnabled", serviceOfferingSearch.entity().isDynamicScalingEnabled(), SearchCriteria.Op.EQ);
             }
         }
 
-        // boolean includePublicOfferings = false;
-        if ((_accountMgr.isNormalUser(owner.getId()) || _accountMgr.isDomainAdmin(owner.getId())) || owner.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) {
+        if ((accountMgr.isNormalUser(owner.getId()) || accountMgr.isDomainAdmin(owner.getId())) || owner.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) {
             // For non-root users.
             if (isSystem) {
                 throw new InvalidParameterValueException("Only root admins can access system's offering");
@@ -3205,150 +3734,327 @@
         } else {
             // for root users
             if (owner.getDomainId() != 1 && isSystem) { // NON ROOT admin
-                throw new InvalidParameterValueException("Non ROOT admins cannot access system's offering.");
+                throw new InvalidParameterValueException("Non ROOT admins cannot access system's offering");
             }
             if (domainId != null && accountName == null) {
-                sc.addAnd("domainId", Op.FIND_IN_SET, String.valueOf(domainId));
+                SearchBuilder<ServiceOfferingDetailsVO> srvOffrDomainDetailSearch = _srvOfferingDetailsDao.createSearchBuilder();
+                srvOffrDomainDetailSearch.and("domainId", srvOffrDomainDetailSearch.entity().getValue(), Op.EQ);
+                serviceOfferingSearch.join("domainDetailSearch", srvOffrDomainDetailSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                        serviceOfferingSearch.entity().getId(), srvOffrDomainDetailSearch.entity().getResourceId(),
+                        srvOffrDomainDetailSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.DOMAIN_ID));
             }
         }
 
         if (keyword != null) {
-            SearchCriteria<ServiceOfferingJoinVO> ssc = _srvOfferingJoinDao.createSearchCriteria();
-            ssc.addOr("displayText", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-
-            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+            serviceOfferingSearch.and().op("keywordName", serviceOfferingSearch.entity().getName(), SearchCriteria.Op.LIKE);
+            serviceOfferingSearch.or("keywordDisplayText", serviceOfferingSearch.entity().getDisplayText(), SearchCriteria.Op.LIKE);
+            serviceOfferingSearch.cp();
         }
 
         if (id != null) {
-            sc.addAnd("id", SearchCriteria.Op.EQ, id);
+            serviceOfferingSearch.and("id", serviceOfferingSearch.entity().getId(), SearchCriteria.Op.EQ);
         }
 
         if (isSystem != null) {
             // note that for non-root users, isSystem is always false when
             // control comes to here
-            sc.addAnd("systemUse", SearchCriteria.Op.EQ, isSystem);
-        }
-
-        if (encryptRoot != null) {
-            sc.addAnd("encryptRoot", SearchCriteria.Op.EQ, encryptRoot);
+            serviceOfferingSearch.and("systemUse", serviceOfferingSearch.entity().isSystemUse(), SearchCriteria.Op.EQ);
         }
 
         if (name != null) {
-            sc.addAnd("name", SearchCriteria.Op.EQ, name);
+            serviceOfferingSearch.and("name", serviceOfferingSearch.entity().getName(), SearchCriteria.Op.EQ);
         }
 
         if (vmTypeStr != null) {
-            sc.addAnd("vmType", SearchCriteria.Op.EQ, vmTypeStr);
+            serviceOfferingSearch.and("svmType", serviceOfferingSearch.entity().getVmType(), SearchCriteria.Op.EQ);
+        }
+        DataCenterJoinVO zone = null;
+        if (zoneId != null) {
+            SearchBuilder<ServiceOfferingDetailsVO> srvOffrZoneDetailSearch = _srvOfferingDetailsDao.createSearchBuilder();
+            srvOffrZoneDetailSearch.and().op("zoneId", srvOffrZoneDetailSearch.entity().getValue(), Op.EQ);
+            srvOffrZoneDetailSearch.or("idNull", srvOffrZoneDetailSearch.entity().getId(), Op.NULL);
+            srvOffrZoneDetailSearch.cp();
+
+            serviceOfferingSearch.join("ZoneDetailSearch", srvOffrZoneDetailSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                    serviceOfferingSearch.entity().getId(), srvOffrZoneDetailSearch.entity().getResourceId(),
+                    srvOffrZoneDetailSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.ZONE_ID));
+            zone = _dcJoinDao.findById(zoneId);
         }
 
-        if (zoneId != null) {
-            SearchBuilder<ServiceOfferingJoinVO> sb = _srvOfferingJoinDao.createSearchBuilder();
-            sb.and("zoneId", sb.entity().getZoneId(), Op.FIND_IN_SET);
-            sb.or("zId", sb.entity().getZoneId(), Op.NULL);
-            sb.done();
-            SearchCriteria<ServiceOfferingJoinVO> zoneSC = sb.create();
-            zoneSC.setParameters("zoneId", String.valueOf(zoneId));
-            sc.addAnd("zoneId", SearchCriteria.Op.SC, zoneSC);
-            DataCenterJoinVO zone = _dcJoinDao.findById(zoneId);
-            if (DataCenter.Type.Edge.equals(zone.getType())) {
-                sc.addAnd("useLocalStorage", Op.EQ, true);
+        if (encryptRoot != null || vmId != null || (zone != null && DataCenter.Type.Edge.equals(zone.getType()))) {
+            SearchBuilder<DiskOfferingVO> diskOfferingSearch = _diskOfferingDao.createSearchBuilder();
+            diskOfferingSearch.and("useLocalStorage", diskOfferingSearch.entity().isUseLocalStorage(), SearchCriteria.Op.EQ);
+            diskOfferingSearch.and("encrypt", diskOfferingSearch.entity().getEncrypt(), SearchCriteria.Op.EQ);
+
+            if (diskOffering != null) {
+                List<String> storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags());
+                if (!storageTags.isEmpty() && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.value()) {
+                    for (String tag : storageTags) {
+                        diskOfferingSearch.and(tag, diskOfferingSearch.entity().getTags(), Op.EQ);
+                    }
+                    diskOfferingSearch.done();
+                }
             }
+
+            serviceOfferingSearch.join("diskOfferingSearch", diskOfferingSearch, JoinBuilder.JoinType.INNER, JoinBuilder.JoinCondition.AND,
+                    serviceOfferingSearch.entity().getDiskOfferingId(), diskOfferingSearch.entity().getId(),
+                    serviceOfferingSearch.entity().setString("Active"), diskOfferingSearch.entity().getState());
         }
 
         if (cpuNumber != null) {
-            SearchCriteria<ServiceOfferingJoinVO> cpuConstraintSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
-            cpuConstraintSearchCriteria.addAnd("minCpu", Op.LTEQ, cpuNumber);
-            cpuConstraintSearchCriteria.addAnd("maxCpu", Op.GTEQ, cpuNumber);
+            SearchBuilder<ServiceOfferingDetailsVO> maxComputeDetailsSearch = (SearchBuilder<ServiceOfferingDetailsVO>) serviceOfferingSearch.getJoinSB("maxComputeDetailsSearch");
+            if (maxComputeDetailsSearch == null) {
+                maxComputeDetailsSearch = _srvOfferingDetailsDao.createSearchBuilder();
+                serviceOfferingSearch.join("maxComputeDetailsSearch", maxComputeDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                        serviceOfferingSearch.entity().getId(), maxComputeDetailsSearch.entity().getResourceId(),
+                        maxComputeDetailsSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.MAX_CPU_NUMBER));
+            }
 
-            SearchCriteria<ServiceOfferingJoinVO> cpuSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
-            cpuSearchCriteria.addOr("minCpu", Op.NULL);
-            cpuSearchCriteria.addOr("constraints", Op.SC, cpuConstraintSearchCriteria);
-            cpuSearchCriteria.addOr("minCpu", Op.GTEQ, cpuNumber);
+            SearchBuilder<ServiceOfferingDetailsVO> minComputeDetailsSearch = _srvOfferingDetailsDao.createSearchBuilder();
 
-            sc.addAnd("cpuConstraints", SearchCriteria.Op.SC, cpuSearchCriteria);
+            serviceOfferingSearch.join("minComputeDetailsSearch", minComputeDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                    serviceOfferingSearch.entity().getId(), minComputeDetailsSearch.entity().getResourceId(),
+                    minComputeDetailsSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.MIN_CPU_NUMBER));
+
+            /*
+                (min_cpu IS NULL AND cpu IS NULL AND max_cpu IS NULL)
+                OR (cpu = X)
+                OR (min_cpu <= X AND max_cpu >= X)
+
+                AND (
+                    (min_compute_details.value is NULL AND cpu is NULL)
+                    OR (min_compute_details.value is NULL AND cpu >= X)
+                    OR min_compute_details.value >= X
+                    OR (
+                        ((min_compute_details.value is NULL AND cpu <= X) OR min_compute_details.value <= X)
+                        AND ((max_compute_details.value is NULL AND cpu >= X) OR max_compute_details.value >= X)
+                    )
+                )
+             */
+            serviceOfferingSearch.and().op().op("minComputeDetailsSearch", "cpuConstraintMinComputeNull", minComputeDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("cpuConstraintNull", serviceOfferingSearch.entity().getCpu(), Op.NULL).cp();
+
+            serviceOfferingSearch.or().op("minComputeDetailsSearch", "cpuConstraintMinComputeNull", minComputeDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("cpuNumber", serviceOfferingSearch.entity().getCpu(), Op.GTEQ).cp();
+            serviceOfferingSearch.or("cpuNumber", serviceOfferingSearch.entity().getCpu(), Op.GTEQ);
+
+            serviceOfferingSearch.or().op().op();
+            serviceOfferingSearch.op("minComputeDetailsSearch", "cpuConstraintMinComputeNull", minComputeDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("cpuNumber", serviceOfferingSearch.entity().getCpu(), Op.LTEQ).cp();
+            serviceOfferingSearch.or("minComputeDetailsSearch", "cpuNumber", minComputeDetailsSearch.entity().getValue(), Op.LTEQ).cp();
+            serviceOfferingSearch.and().op().op("maxComputeDetailsSearch", "cpuConstraintMaxComputeNull", maxComputeDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("cpuNumber", serviceOfferingSearch.entity().getCpu(), Op.GTEQ).cp();
+            serviceOfferingSearch.or("maxComputeDetailsSearch", "cpuNumber", maxComputeDetailsSearch.entity().getValue(), Op.GTEQ).cp();
+            serviceOfferingSearch.cp().cp();
         }
 
         if (memory != null) {
-            SearchCriteria<ServiceOfferingJoinVO> memoryConstraintSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
-            memoryConstraintSearchCriteria.addAnd("minMemory", Op.LTEQ, memory);
-            memoryConstraintSearchCriteria.addAnd("maxMemory", Op.GTEQ, memory);
+            SearchBuilder<ServiceOfferingDetailsVO> maxMemoryDetailsSearch = (SearchBuilder<ServiceOfferingDetailsVO>) serviceOfferingSearch.getJoinSB("maxMemoryDetailsSearch");
+            if (maxMemoryDetailsSearch == null) {
+                maxMemoryDetailsSearch = _srvOfferingDetailsDao.createSearchBuilder();
+                serviceOfferingSearch.join("maxMemoryDetailsSearch", maxMemoryDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                        serviceOfferingSearch.entity().getId(), maxMemoryDetailsSearch.entity().getResourceId(),
+                        maxMemoryDetailsSearch.entity().getName(), serviceOfferingSearch.entity().setString("maxmemory"));
+            }
 
-            SearchCriteria<ServiceOfferingJoinVO> memSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
-            memSearchCriteria.addOr("minMemory", Op.NULL);
-            memSearchCriteria.addOr("memconstraints", Op.SC, memoryConstraintSearchCriteria);
-            memSearchCriteria.addOr("minMemory", Op.GTEQ, memory);
+            SearchBuilder<ServiceOfferingDetailsVO> minMemoryDetailsSearch = _srvOfferingDetailsDao.createSearchBuilder();
 
-            sc.addAnd("memoryConstraints", SearchCriteria.Op.SC, memSearchCriteria);
+            serviceOfferingSearch.join("minMemoryDetailsSearch", minMemoryDetailsSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                    serviceOfferingSearch.entity().getId(), minMemoryDetailsSearch.entity().getResourceId(),
+                    minMemoryDetailsSearch.entity().getName(), serviceOfferingSearch.entity().setString("minmemory"));
+
+            /*
+                (min_ram_size IS NULL AND ram_size IS NULL AND max_ram_size IS NULL)
+                OR (ram_size = X)
+                OR (min_ram_size <= X AND max_ram_size >= X)
+             */
+
+            serviceOfferingSearch.and().op().op("minMemoryDetailsSearch", "memoryConstraintMinMemoryNull", minMemoryDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("memoryConstraintNull", serviceOfferingSearch.entity().getRamSize(), Op.NULL).cp();
+
+            serviceOfferingSearch.or().op("minMemoryDetailsSearch", "memoryConstraintMinMemoryNull", minMemoryDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("memory", serviceOfferingSearch.entity().getRamSize(), Op.GTEQ).cp();
+            serviceOfferingSearch.or("memory", serviceOfferingSearch.entity().getRamSize(), Op.GTEQ);
+
+            serviceOfferingSearch.or().op().op();
+            serviceOfferingSearch.op("minMemoryDetailsSearch", "memoryConstraintMinMemoryNull", minMemoryDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("memory", serviceOfferingSearch.entity().getRamSize(), Op.LTEQ).cp();
+            serviceOfferingSearch.or("minMemoryDetailsSearch", "memory", minMemoryDetailsSearch.entity().getValue(), Op.LTEQ).cp();
+            serviceOfferingSearch.and().op().op("maxMemoryDetailsSearch", "memoryConstraintMaxMemoryNull", maxMemoryDetailsSearch.entity().getValue(), Op.NULL);
+            serviceOfferingSearch.and("memory", serviceOfferingSearch.entity().getRamSize(), Op.GTEQ).cp();
+            serviceOfferingSearch.or("maxMemoryDetailsSearch", "memory", maxMemoryDetailsSearch.entity().getValue(), Op.GTEQ).cp();
+            serviceOfferingSearch.cp().cp();
         }
 
         if (cpuSpeed != null) {
-            SearchCriteria<ServiceOfferingJoinVO> cpuSpeedSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
-            cpuSpeedSearchCriteria.addOr("speed", Op.NULL);
-            cpuSpeedSearchCriteria.addOr("speed", Op.GTEQ, cpuSpeed);
-            sc.addAnd("cpuspeedconstraints", SearchCriteria.Op.SC, cpuSpeedSearchCriteria);
+            serviceOfferingSearch.and().op("speedNull", serviceOfferingSearch.entity().getSpeed(), Op.NULL);
+            serviceOfferingSearch.or("speedGTEQ", serviceOfferingSearch.entity().getSpeed(), Op.GTEQ);
+            serviceOfferingSearch.cp();
         }
 
         // Filter offerings that are not associated with caller's domain
         // Fetch the offering ids from the details table since theres no smart way to filter them in the join ... yet!
         if (owner.getType() != Account.Type.ADMIN) {
-            Domain callerDomain = _domainDao.findById(owner.getDomainId());
-            List<Long> domainIds = findRelatedDomainIds(callerDomain, isRecursive);
-
-            List<Long> ids = _srvOfferingDetailsDao.findOfferingIdsByDomainIds(domainIds);
-            SearchBuilder<ServiceOfferingJoinVO> sb = _srvOfferingJoinDao.createSearchBuilder();
-            if (ids != null && !ids.isEmpty()) {
-                sb.and("id", sb.entity().getId(), Op.IN);
-            }
-            sb.or("domainId", sb.entity().getDomainId(), Op.NULL);
-            sb.done();
-
-            SearchCriteria<ServiceOfferingJoinVO> scc = sb.create();
-            if (ids != null && !ids.isEmpty()) {
-                scc.setParameters("id", ids.toArray());
-            }
-            sc.addAnd("domainId", SearchCriteria.Op.SC, scc);
+            SearchBuilder<ServiceOfferingDetailsVO> srvOffrDomainDetailSearch = _srvOfferingDetailsDao.createSearchBuilder();
+            srvOffrDomainDetailSearch.and().op("domainIdIN", srvOffrDomainDetailSearch.entity().getValue(), Op.IN);
+            srvOffrDomainDetailSearch.or("idNull", srvOffrDomainDetailSearch.entity().getValue(), Op.NULL);
+            srvOffrDomainDetailSearch.cp();
+            serviceOfferingSearch.join("domainDetailSearchNormalUser", srvOffrDomainDetailSearch, JoinBuilder.JoinType.LEFT, JoinBuilder.JoinCondition.AND,
+                    serviceOfferingSearch.entity().getId(), srvOffrDomainDetailSearch.entity().getResourceId(),
+                    srvOffrDomainDetailSearch.entity().getName(), serviceOfferingSearch.entity().setString(ApiConstants.DOMAIN_ID));
         }
 
         if (currentVmOffering != null) {
-            DiskOfferingVO diskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentVmOffering.getDiskOfferingId());
-            List<String> storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags());
-            if (!storageTags.isEmpty() && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.value()) {
-                SearchBuilder<ServiceOfferingJoinVO> sb = _srvOfferingJoinDao.createSearchBuilder();
-                for(String tag : storageTags) {
-                    sb.and(tag, sb.entity().getTags(), Op.FIND_IN_SET);
-                }
-                sb.done();
+            List<String> hostTags = com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getHostTag());
+            if (!hostTags.isEmpty()) {
 
-                SearchCriteria<ServiceOfferingJoinVO> scc = sb.create();
-                for(String tag : storageTags) {
-                    scc.setParameters(tag, tag);
+                serviceOfferingSearch.and().op("hostTag", serviceOfferingSearch.entity().getHostTag(), Op.NULL);
+                serviceOfferingSearch.or().op();
+
+                for(String tag : hostTags) {
+                    serviceOfferingSearch.and(tag, serviceOfferingSearch.entity().getHostTag(), Op.EQ);
                 }
-                sc.addAnd("storageTags", SearchCriteria.Op.SC, scc);
+                serviceOfferingSearch.cp().cp().done();
+            }
+        }
+
+        SearchCriteria<ServiceOfferingVO> sc = serviceOfferingSearch.create();
+        if (state != null) {
+            sc.setParameters("state", state);
+        }
+
+        if (vmId != null) {
+            if (!currentVmOffering.isDynamic()) {
+                sc.setParameters("idNEQ", currentVmOffering.getId());
+            }
+
+            if (currentVmOffering.getDiskOfferingStrictness()) {
+                sc.setParameters("diskOfferingId", currentVmOffering.getDiskOfferingId());
+                sc.setParameters("diskOfferingStrictness", true);
+            } else {
+                sc.setParameters("diskOfferingStrictness", false);
+            }
+
+            boolean isRootVolumeUsingLocalStorage = virtualMachineManager.isRootVolumeOnLocalStorage(vmId);
+
+            // 1. Only return offerings with the same storage type than the storage pool where the VM's root volume is allocated
+            sc.setJoinParameters("diskOfferingSearch", "useLocalStorage", isRootVolumeUsingLocalStorage);
+
+            // 2.In case vm is running return only offerings greater than equal to current offering compute and offering's dynamic scalability should match
+            if (vmInstance.getState() == VirtualMachine.State.Running) {
+                Integer vmCpu = currentVmOffering.getCpu();
+                Integer vmMemory = currentVmOffering.getRamSize();
+                Integer vmSpeed = currentVmOffering.getSpeed();
+                if ((vmCpu == null || vmMemory == null || vmSpeed == null) && VirtualMachine.Type.User.equals(vmInstance.getType())) {
+                    UserVmVO userVmVO = userVmDao.findById(vmId);
+                    userVmDao.loadDetails(userVmVO);
+                    Map<String, String> details = userVmVO.getDetails();
+                    vmCpu = NumbersUtil.parseInt(details.get(ApiConstants.CPU_NUMBER), 0);
+                    if (vmSpeed == null) {
+                        vmSpeed = NumbersUtil.parseInt(details.get(ApiConstants.CPU_SPEED), 0);
+                    }
+                    vmMemory = NumbersUtil.parseInt(details.get(ApiConstants.MEMORY), 0);
+                }
+                if (vmCpu != null && vmCpu > 0) {
+                    sc.setParameters("vmCpu", vmCpu);
+                    sc.setParameters("vmMaxComputeGTEQ", vmCpu);
+                }
+                if (vmSpeed != null && vmSpeed > 0) {
+                    sc.setParameters("speedGTEQ", vmSpeed);
+                }
+                if (vmMemory != null && vmMemory > 0) {
+                    sc.setParameters("vmMemory", vmMemory);
+                    sc.setParameters("vmMaxMemoryGTEQ", vmMemory);
+                }
+                sc.setParameters("dynamicScalingEnabled", currentVmOffering.isDynamicScalingEnabled());
+            }
+        }
+
+        if ((!accountMgr.isNormalUser(caller.getId()) && !accountMgr.isDomainAdmin(caller.getId())) && caller.getType() != Account.Type.RESOURCE_DOMAIN_ADMIN) {
+            if (domainId != null && accountName == null) {
+                sc.setJoinParameters("domainDetailSearch", "domainId", domainId);
+            }
+        }
+
+        if (keyword != null) {
+            sc.setParameters("keywordName", "%" + keyword + "%");
+            sc.setParameters("keywordDisplayText", "%" + keyword + "%");
+        }
+
+        if (id != null) {
+            sc.setParameters("id", id);
+        }
+
+        if (isSystem != null) {
+            // note that for non-root users, isSystem is always false when
+            // control comes to here
+            sc.setParameters("systemUse", isSystem);
+        }
+
+        if (encryptRoot != null) {
+            sc.setJoinParameters("diskOfferingSearch", "encrypt", encryptRoot);
+        }
+
+        if (name != null) {
+            sc.setParameters("name", name);
+        }
+
+        if (vmTypeStr != null) {
+            sc.setParameters("svmType", vmTypeStr);
+        }
+
+        useStorageType(sc, storageType);
+
+        if (zoneId != null) {
+            sc.setJoinParameters("ZoneDetailSearch", "zoneId", zoneId);
+
+            if (DataCenter.Type.Edge.equals(zone.getType())) {
+                sc.setJoinParameters("diskOfferingSearch", "useLocalStorage", true);
+            }
+        }
+
+        if (cpuNumber != null) {
+            sc.setParameters("cpuNumber", cpuNumber);
+        }
+
+        if (memory != null) {
+            sc.setParameters("memory", memory);
+        }
+
+        if (cpuSpeed != null) {
+            sc.setParameters("speedGTEQ", cpuSpeed);
+        }
+
+        if (owner.getType() != Account.Type.ADMIN) {
+            Domain callerDomain = _domainDao.findById(owner.getDomainId());
+            List<Long> domainIds = findRelatedDomainIds(callerDomain, isRecursive);
+
+            sc.setJoinParameters("domainDetailSearchNormalUser", "domainIdIN", domainIds.toArray());
+        }
+
+        if (currentVmOffering != null) {
+
+            if (diskOffering != null) {
+                List<String> storageTags = com.cloud.utils.StringUtils.csvTagsToList(diskOffering.getTags());
+                if (!storageTags.isEmpty() && VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.value()) {
+                    for(String tag : storageTags) {
+                        sc.setJoinParameters("diskOfferingSearch", tag, tag);
+                    }
+                }
             }
 
             List<String> hostTags = com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getHostTag());
             if (!hostTags.isEmpty()) {
-                SearchBuilder<ServiceOfferingJoinVO> hostTagsSearchBuilder = _srvOfferingJoinDao.createSearchBuilder();
                 for(String tag : hostTags) {
-                    hostTagsSearchBuilder.and(tag, hostTagsSearchBuilder.entity().getHostTag(), Op.FIND_IN_SET);
+                    sc.setParameters(tag, tag);
                 }
-                hostTagsSearchBuilder.done();
-
-                SearchCriteria<ServiceOfferingJoinVO> hostTagsSearchCriteria = hostTagsSearchBuilder.create();
-                for(String tag : hostTags) {
-                    hostTagsSearchCriteria.setParameters(tag, tag);
-                }
-
-                SearchCriteria<ServiceOfferingJoinVO> finalHostTagsSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
-                finalHostTagsSearchCriteria.addOr("hostTag", Op.NULL);
-                finalHostTagsSearchCriteria.addOr("hostTag", Op.SC, hostTagsSearchCriteria);
-
-                sc.addAnd("hostTagsConstraint", SearchCriteria.Op.SC, finalHostTagsSearchCriteria);
             }
         }
 
-        return _srvOfferingJoinDao.searchAndCount(sc, searchFilter);
+        Pair<List<ServiceOfferingVO>, Integer> uniquePair = _srvOfferingDao.searchAndCount(sc, searchFilter);
+        Integer count = uniquePair.second();
+        List<Long> offeringIds = uniquePair.first().stream().map(ServiceOfferingVO::getId).collect(Collectors.toList());
+        return new Pair<>(offeringIds, count);
     }
 
     @Override
@@ -3370,6 +4076,7 @@
         Account account = CallContext.current().getCallingAccount();
         Long domainId = cmd.getDomainId();
         Long id = cmd.getId();
+        List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
         String keyword = cmd.getKeyword();
         String name = cmd.getName();
         String networkType = cmd.getNetworkType();
@@ -3377,7 +4084,7 @@
 
         SearchBuilder<DataCenterJoinVO> sb = _dcJoinDao.createSearchBuilder();
         if (resourceTags != null && !resourceTags.isEmpty()) {
-            SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
+            SearchBuilder<ResourceTagVO> tagSearch = resourceTagDao.createSearchBuilder();
             for (int count = 0; count < resourceTags.size(); count++) {
                 tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
                 tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
@@ -3396,6 +4103,10 @@
             sc.addAnd("networkType", SearchCriteria.Op.EQ, networkType);
         }
 
+        if (CollectionUtils.isNotEmpty(ids)) {
+            sc.addAnd("id", SearchCriteria.Op.IN, ids.toArray());
+        }
+
         if (id != null) {
             sc.addAnd("id", SearchCriteria.Op.EQ, id);
         } else if (name != null) {
@@ -3417,7 +4128,7 @@
                 // only list zones associated // with this domain, private zone
                 sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId);
 
-                if (_accountMgr.isNormalUser(account.getId())) {
+                if (accountMgr.isNormalUser(account.getId())) {
                     // accountId == null (zones dedicated to a domain) or
                     // accountId = caller
                     SearchCriteria<DataCenterJoinVO> sdc = _dcJoinDao.createSearchCriteria();
@@ -3427,7 +4138,7 @@
                     sc.addAnd("accountId", SearchCriteria.Op.SC, sdc);
                 }
 
-            } else if (_accountMgr.isNormalUser(account.getId())) {
+            } else if (accountMgr.isNormalUser(account.getId())) {
                 // it was decided to return all zones for the user's domain, and
                 // everything above till root
                 // list all zones belonging to this domain, and all of its
@@ -3472,7 +4183,7 @@
                     sdc.addAnd("id", SearchCriteria.Op.NIN, dedicatedZoneIds.toArray(new Object[dedicatedZoneIds.size()]));
                 }
 
-            } else if (_accountMgr.isDomainAdmin(account.getId()) || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) {
+            } else if (accountMgr.isDomainAdmin(account.getId()) || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN) {
                 // it was decided to return all zones for the domain admin, and
                 // everything above till root, as well as zones till the domain
                 // leaf
@@ -3619,24 +4330,44 @@
         }
 
         List<Long> permittedAccountIds = new ArrayList<Long>();
-        Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccountIds, domainIdRecursiveListProject, listAll, false);
+        Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
+        accountMgr.buildACLSearchParameters(
+                caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccountIds,
+                domainIdRecursiveListProject, listAll, false
+        );
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
         List<Account> permittedAccounts = new ArrayList<Account>();
         for (Long accountId : permittedAccountIds) {
-            permittedAccounts.add(_accountMgr.getAccount(accountId));
+            permittedAccounts.add(accountMgr.getAccount(accountId));
         }
 
         boolean showDomr = ((templateFilter != TemplateFilter.selfexecutable) && (templateFilter != TemplateFilter.featured));
         HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor());
 
-        return searchForTemplatesInternal(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false, null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType,
-                showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique());
+        String templateType = cmd.getTemplateType();
+        if (cmd instanceof ListVnfTemplatesCmd) {
+            if (templateType == null) {
+                templateType = TemplateType.VNF.name();
+            } else if (!TemplateType.VNF.name().equals(templateType)) {
+                throw new InvalidParameterValueException("Template type must be VNF when list VNF templates");
+            }
+        }
+        Boolean isVnf = cmd.getVnf();
+
+        return searchForTemplatesInternal(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false,
+                null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(),
+                cmd.getImageStoreId(), hypervisorType, showDomr, cmd.listInReadyState(), permittedAccounts, caller,
+                listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(),
+                templateType, isVnf);
     }
 
-    private Pair<List<TemplateJoinVO>, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, TemplateFilter templateFilter, boolean isIso, Boolean bootable, Long pageSize,
-            Long startIndex, Long zoneId, HypervisorType hyperType, boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller,
-            ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags, boolean showRemovedTmpl, List<Long> ids, Long parentTemplateId, Boolean showUnique) {
+    private Pair<List<TemplateJoinVO>, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword,
+            TemplateFilter templateFilter, boolean isIso, Boolean bootable, Long pageSize,
+            Long startIndex, Long zoneId, Long storagePoolId, Long imageStoreId, HypervisorType hyperType,
+            boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller,
+            ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags,
+            boolean showRemovedTmpl, List<Long> ids, Long parentTemplateId, Boolean showUnique, String templateType,
+            Boolean isVnf) {
 
         // check if zone is configured, if not, just return empty list
         List<HypervisorType> hypers = null;
@@ -3661,8 +4392,23 @@
         if (ids != null && !ids.isEmpty()) {
             sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
         }
+
+        if (storagePoolId != null) {
+            SearchBuilder<VMTemplateStoragePoolVO> storagePoolSb = templatePoolDao.createSearchBuilder();
+            storagePoolSb.and("pool_id", storagePoolSb.entity().getPoolId(), SearchCriteria.Op.EQ);
+            sb.join("storagePool", storagePoolSb, storagePoolSb.entity().getTemplateId(), sb.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
         SearchCriteria<TemplateJoinVO> sc = sb.create();
 
+        if (imageStoreId != null) {
+            sc.addAnd("dataStoreId", SearchCriteria.Op.EQ, imageStoreId);
+        }
+
+        if (storagePoolId != null) {
+            sc.setJoinParameters("storagePool", "pool_id", storagePoolId);
+        }
+
         // verify templateId parameter and specially handle it
         if (templateId != null) {
             template = _templateDao.findByIdIncludingRemoved(templateId); // Done for backward compatibility - Bug-5221
@@ -3682,16 +4428,16 @@
                 throw ex;
             }
             if (!template.isPublicTemplate() && caller.getType() == Account.Type.DOMAIN_ADMIN) {
-                Account template_acc = _accountMgr.getAccount(template.getAccountId());
+                Account template_acc = accountMgr.getAccount(template.getAccountId());
                 DomainVO domain = _domainDao.findById(template_acc.getDomainId());
-                _accountMgr.checkAccess(caller, domain);
+                accountMgr.checkAccess(caller, domain);
             }
 
             // if template is not public, perform permission check here
             else if (!template.isPublicTemplate() && caller.getType() != Account.Type.ADMIN) {
-                _accountMgr.checkAccess(caller, null, false, template);
+                accountMgr.checkAccess(caller, null, false, template);
             } else if (template.isPublicTemplate()) {
-                _accountMgr.checkAccess(caller, null, false, template);
+                accountMgr.checkAccess(caller, null, false, template);
             }
 
             // if templateId is specified, then we will just use the id to
@@ -3744,7 +4490,7 @@
                     }
 
                     // get all child domain ID's
-                    if (_accountMgr.isAdmin(account.getId()) || publicTemplates) {
+                    if (accountMgr.isAdmin(account.getId()) || publicTemplates) {
                         List<DomainVO> allChildDomains = _domainDao.findAllChildren(domainTreeNode.getPath(), domainTreeNode.getId());
                         for (DomainVO childDomain : allChildDomains) {
                             relatedDomainIds.add(childDomain.getId());
@@ -3800,14 +4546,64 @@
             }
         }
 
-        return templateChecks(isIso, hypers, tags, name, keyword, hyperType, onlyReady, bootable, zoneId, showDomr, caller,
-                showRemovedTmpl, parentTemplateId, showUnique, searchFilter, sc);
+        applyPublicTemplateSharingRestrictions(sc, caller);
 
+        return templateChecks(isIso, hypers, tags, name, keyword, hyperType, onlyReady, bootable, zoneId, showDomr, caller,
+                showRemovedTmpl, parentTemplateId, showUnique, templateType, isVnf, searchFilter, sc);
+    }
+
+    /**
+     * If the caller is not a root admin, restricts the search to return only public templates from the domain which
+     * the caller belongs to and domains with the setting 'share.public.templates.with.other.domains' enabled.
+     */
+    protected void applyPublicTemplateSharingRestrictions(SearchCriteria<TemplateJoinVO> sc, Account caller) {
+        if (caller.getType() == Account.Type.ADMIN) {
+            s_logger.debug(String.format("Account [%s] is a root admin. Therefore, it has access to all public templates.", caller));
+            return;
+        }
+
+        List<TemplateJoinVO> publicTemplates = _templateJoinDao.listPublicTemplates();
+
+        Set<Long> unsharableDomainIds = new HashSet<>();
+        for (TemplateJoinVO template : publicTemplates) {
+            addDomainIdToSetIfDomainDoesNotShareTemplates(template.getDomainId(), caller, unsharableDomainIds);
+        }
+
+        if (!unsharableDomainIds.isEmpty()) {
+            s_logger.info(String.format("The public templates belonging to the domains [%s] will not be listed to account [%s] as they have the configuration [%s] marked as 'false'.", unsharableDomainIds, caller, QueryService.SharePublicTemplatesWithOtherDomains.key()));
+            sc.addAnd("domainId", SearchCriteria.Op.NOTIN, unsharableDomainIds.toArray());
+        }
+    }
+
+    /**
+     * Adds the provided domain ID to the set if the domain does not share templates with the account. That is, if:
+     * (1) the template does not belong to the domain of the account AND
+     * (2) the domain of the template has the setting 'share.public.templates.with.other.domains' disabled.
+     */
+    protected void addDomainIdToSetIfDomainDoesNotShareTemplates(long domainId, Account account, Set<Long> unsharableDomainIds) {
+        if (domainId == account.getDomainId()) {
+            s_logger.trace(String.format("Domain [%s] will not be added to the set of domains with unshared templates since the account [%s] belongs to it.", domainId, account));
+            return;
+        }
+
+        if (unsharableDomainIds.contains(domainId)) {
+            s_logger.trace(String.format("Domain [%s] is already on the set of domains with unshared templates.", domainId));
+            return;
+        }
+
+        if (!checkIfDomainSharesTemplates(domainId)) {
+            s_logger.debug(String.format("Domain [%s] will be added to the set of domains with unshared templates as configuration [%s] is false.", domainId, QueryService.SharePublicTemplatesWithOtherDomains.key()));
+            unsharableDomainIds.add(domainId);
+        }
+    }
+
+    protected boolean checkIfDomainSharesTemplates(Long domainId) {
+        return QueryService.SharePublicTemplatesWithOtherDomains.valueIn(domainId);
     }
 
     private Pair<List<TemplateJoinVO>, Integer> templateChecks(boolean isIso, List<HypervisorType> hypers, Map<String, String> tags, String name, String keyword,
                                                                HypervisorType hyperType, boolean onlyReady, Boolean bootable, Long zoneId, boolean showDomr, Account caller,
-                                                               boolean showRemovedTmpl, Long parentTemplateId, Boolean showUnique,
+                                                               boolean showRemovedTmpl, Long parentTemplateId, Boolean showUnique, String templateType, Boolean isVnf,
                                                                Filter searchFilter, SearchCriteria<TemplateJoinVO> sc) {
         if (!isIso) {
             // add hypervisor criteria for template case
@@ -3889,6 +4685,18 @@
             sc.addAnd("parentTemplateId", SearchCriteria.Op.EQ, parentTemplateId);
         }
 
+        if (templateType != null) {
+            sc.addAnd("templateType", SearchCriteria.Op.EQ, templateType);
+        }
+
+        if (isVnf != null) {
+            if (isVnf) {
+                sc.addAnd("templateType", SearchCriteria.Op.EQ, TemplateType.VNF);
+            } else {
+                sc.addAnd("templateType", SearchCriteria.Op.NEQ, TemplateType.VNF);
+            }
+        }
+
         // don't return removed template, this should not be needed since we
         // changed annotation for removed field in TemplateJoinVO.
         // sc.addAnd("removed", SearchCriteria.Op.NULL);
@@ -3900,7 +4708,7 @@
         } else {
             sc.addAnd("templateState", SearchCriteria.Op.IN, new State[] {State.Active, State.UploadAbandoned, State.UploadError, State.NotUploaded, State.UploadInProgress});
             if (showUnique) {
-                final String[] distinctColumns = {"id"};
+                final String[] distinctColumns = {"template_view.id"};
                 uniqueTmplPair = _templateJoinDao.searchAndDistinctCount(sc, searchFilter, distinctColumns);
             } else {
                 final String[] distinctColumns = {"temp_zone_pair"};
@@ -3934,27 +4742,9 @@
             templates = _templateJoinDao.searchByTemplateZonePair(showRemoved, templateZonePairs);
         }
 
-        if(caller.getType() != Account.Type.ADMIN) {
-            templates = applyPublicTemplateRestriction(templates, caller);
-            count = templates.size();
-        }
-
         return new Pair<List<TemplateJoinVO>, Integer>(templates, count);
     }
 
-    private List<TemplateJoinVO> applyPublicTemplateRestriction(List<TemplateJoinVO> templates, Account caller){
-        List<Long> unsharableDomainIds = templates.stream()
-                .map(TemplateJoinVO::getDomainId)
-                .distinct()
-                .filter(domainId -> domainId != caller.getDomainId())
-                .filter(Predicate.not(QueryService.SharePublicTemplatesWithOtherDomains::valueIn))
-                .collect(Collectors.toList());
-
-        return templates.stream()
-                .filter(Predicate.not(t -> unsharableDomainIds.contains(t.getDomainId())))
-                .collect(Collectors.toList());
-    }
-
     @Override
     public ListResponse<TemplateResponse> listIsos(ListIsosCmd cmd) {
         Pair<List<TemplateJoinVO>, Integer> result = searchForIsosInternal(cmd);
@@ -3985,19 +4775,22 @@
             listAll = true;
         }
 
+
         List<Long> permittedAccountIds = new ArrayList<>();
         Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
-        _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccountIds, domainIdRecursiveListProject, listAll, false);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccountIds, domainIdRecursiveListProject, listAll, false);
         ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
         List<Account> permittedAccounts = new ArrayList<>();
         for (Long accountId : permittedAccountIds) {
-            permittedAccounts.add(_accountMgr.getAccount(accountId));
+            permittedAccounts.add(accountMgr.getAccount(accountId));
         }
 
         HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor());
 
-        return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(),
-                hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null, cmd.getShowUnique());
+        return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(),
+                cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(),
+                hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria,
+                tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null);
     }
 
     @Override
@@ -4017,6 +4810,9 @@
                 }
                 fillVMOrTemplateDetailOptions(options, hypervisorType);
                 break;
+            case VnfTemplate:
+                fillVnfTemplateDetailOptions(options);
+                return new DetailOptionsResponse(options);
             default:
                 throw new CloudRuntimeException("Resource type not supported.");
         }
@@ -4040,6 +4836,19 @@
         return responses;
     }
 
+    private void fillVnfTemplateDetailOptions(final Map<String, List<String>> options) {
+        for (VNF.AccessDetail detail : VNF.AccessDetail.values()) {
+            if (VNF.AccessDetail.ACCESS_METHODS.equals(detail)) {
+                options.put(detail.name().toLowerCase(), Arrays.stream(VNF.AccessMethod.values()).map(method -> method.toString()).sorted().collect(Collectors.toList()));
+            } else {
+                options.put(detail.name().toLowerCase(), Collections.emptyList());
+            }
+        }
+        for (VNF.VnfDetail detail : VNF.VnfDetail.values()) {
+            options.put(detail.name().toLowerCase(), Collections.emptyList());
+        }
+    }
+
     private void fillVMOrTemplateDetailOptions(final Map<String, List<String>> options, final HypervisorType hypervisorType) {
         if (options == null) {
             throw new CloudRuntimeException("Invalid/null detail-options response object passed");
@@ -4058,6 +4867,8 @@
             options.put(VmDetailConstants.VIDEO_RAM, Collections.emptyList());
             options.put(VmDetailConstants.IO_POLICY, Arrays.asList("threads", "native", "io_uring", "storage_specific"));
             options.put(VmDetailConstants.IOTHREADS, Arrays.asList("enabled"));
+            options.put(VmDetailConstants.NIC_MULTIQUEUE_NUMBER, Collections.emptyList());
+            options.put(VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, Arrays.asList("true", "false"));
         }
 
         if (HypervisorType.VMware.equals(hypervisorType)) {
@@ -4097,18 +4908,18 @@
         Account caller = CallContext.current().getCallingAccount();
 
         if (vmId != null) {
-            UserVmVO userVM = _userVmDao.findById(vmId);
+            UserVmVO userVM = userVmDao.findById(vmId);
             if (userVM == null) {
                 throw new InvalidParameterValueException("Unable to list affinity groups for virtual machine instance " + vmId + "; instance not found.");
             }
-            _accountMgr.checkAccess(caller, null, true, userVM);
+            accountMgr.checkAccess(caller, null, true, userVM);
             return listAffinityGroupsByVM(vmId.longValue(), startIndex, pageSize);
         }
 
         List<Long> permittedAccounts = new ArrayList<Long>();
         Ternary<Long, Boolean, ListProjectResourcesCriteria> ternary = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, isRecursive, null);
 
-        _accountMgr.buildACLSearchParameters(caller, affinityGroupId, accountName, projectId, permittedAccounts, ternary, listAll, false);
+        accountMgr.buildACLSearchParameters(caller, affinityGroupId, accountName, projectId, permittedAccounts, ternary, listAll, false);
 
         domainId = ternary.first();
         isRecursive = ternary.second();
@@ -4440,6 +5251,433 @@
     }
 
     @Override
+    public ListResponse<SecondaryStorageHeuristicsResponse> listSecondaryStorageSelectors(ListSecondaryStorageSelectorsCmd cmd) {
+        ListResponse<SecondaryStorageHeuristicsResponse> response = new ListResponse<>();
+        Pair<List<HeuristicVO>, Integer> result = listSecondaryStorageSelectorsInternal(cmd.getZoneId(), cmd.getType(), cmd.isShowRemoved());
+        List<SecondaryStorageHeuristicsResponse> listOfSecondaryStorageHeuristicsResponses = new ArrayList<>();
+
+        for (Heuristic heuristic : result.first()) {
+            SecondaryStorageHeuristicsResponse secondaryStorageHeuristicsResponse = responseGenerator.createSecondaryStorageSelectorResponse(heuristic);
+            listOfSecondaryStorageHeuristicsResponses.add(secondaryStorageHeuristicsResponse);
+        }
+
+        response.setResponses(listOfSecondaryStorageHeuristicsResponses);
+        return response;
+    }
+
+    private Pair<List<HeuristicVO>, Integer> listSecondaryStorageSelectorsInternal(Long zoneId, String type, boolean showRemoved) {
+        SearchBuilder<HeuristicVO> searchBuilder = secondaryStorageHeuristicDao.createSearchBuilder();
+
+        searchBuilder.and("zoneId", searchBuilder.entity().getZoneId(), SearchCriteria.Op.EQ);
+        searchBuilder.and("type", searchBuilder.entity().getType(), SearchCriteria.Op.EQ);
+
+        searchBuilder.done();
+
+        SearchCriteria<HeuristicVO> searchCriteria = searchBuilder.create();
+        searchCriteria.setParameters("zoneId", zoneId);
+        searchCriteria.setParametersIfNotNull("type", type);
+
+        return secondaryStorageHeuristicDao.searchAndCount(searchCriteria, null, showRemoved);
+    }
+
+    @Override
+    public ListResponse<IpQuarantineResponse> listQuarantinedIps(ListQuarantinedIpsCmd cmd) {
+        ListResponse<IpQuarantineResponse> response = new ListResponse<>();
+        Pair<List<PublicIpQuarantineVO>, Integer> result = listQuarantinedIpsInternal(cmd.isShowRemoved(), cmd.isShowInactive());
+        List<IpQuarantineResponse> ipsQuarantinedResponses = new ArrayList<>();
+
+        for (PublicIpQuarantine quarantinedIp : result.first()) {
+            IpQuarantineResponse ipsInQuarantineResponse = responseGenerator.createQuarantinedIpsResponse(quarantinedIp);
+            ipsQuarantinedResponses.add(ipsInQuarantineResponse);
+        }
+
+        response.setResponses(ipsQuarantinedResponses);
+        return response;
+    }
+
+    /**
+     * It lists the quarantine IPs that the caller account is allowed to see by filtering the domain path of the caller account.
+     * Furthermore, it lists inactive and removed quarantined IPs according to the command parameters.
+     */
+    private Pair<List<PublicIpQuarantineVO>, Integer> listQuarantinedIpsInternal(boolean showRemoved, boolean showInactive) {
+        String callingAccountDomainPath = _domainDao.findById(CallContext.current().getCallingAccount().getDomainId()).getPath();
+
+        SearchBuilder<AccountJoinVO> filterAllowedOnly = _accountJoinDao.createSearchBuilder();
+        filterAllowedOnly.and("path", filterAllowedOnly.entity().getDomainPath(), SearchCriteria.Op.LIKE);
+
+        SearchBuilder<PublicIpQuarantineVO> listAllPublicIpsInQuarantineAllowedToTheCaller = publicIpQuarantineDao.createSearchBuilder();
+        listAllPublicIpsInQuarantineAllowedToTheCaller.join("listQuarantinedJoin", filterAllowedOnly,
+                listAllPublicIpsInQuarantineAllowedToTheCaller.entity().getPreviousOwnerId(),
+                filterAllowedOnly.entity().getId(), JoinBuilder.JoinType.INNER);
+
+        if (!showInactive) {
+            listAllPublicIpsInQuarantineAllowedToTheCaller.and("endDate", listAllPublicIpsInQuarantineAllowedToTheCaller.entity().getEndDate(), SearchCriteria.Op.GT);
+        }
+
+        filterAllowedOnly.done();
+        listAllPublicIpsInQuarantineAllowedToTheCaller.done();
+
+        SearchCriteria<PublicIpQuarantineVO> searchCriteria = listAllPublicIpsInQuarantineAllowedToTheCaller.create();
+        searchCriteria.setJoinParameters("listQuarantinedJoin", "path", callingAccountDomainPath + "%");
+        searchCriteria.setParametersIfNotNull("endDate", new Date());
+
+        return publicIpQuarantineDao.searchAndCount(searchCriteria, null, showRemoved);
+    }
+
+    public ListResponse<SnapshotResponse> listSnapshots(ListSnapshotsCmd cmd) {
+        Account caller = CallContext.current().getCallingAccount();
+        Pair<List<SnapshotJoinVO>, Integer> result = searchForSnapshotsWithParams(cmd.getId(), cmd.getIds(),
+                cmd.getVolumeId(), cmd.getSnapshotName(), cmd.getKeyword(), cmd.getTags(),
+                cmd.getSnapshotType(), cmd.getIntervalType(), cmd.getZoneId(), cmd.getLocationType(),
+                cmd.isShowUnique(), cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId(), cmd.getStoragePoolId(),
+                cmd.getImageStoreId(), cmd.getStartIndex(), cmd.getPageSizeVal(), cmd.listAll(), cmd.isRecursive(), caller);
+        ListResponse<SnapshotResponse> response = new ListResponse<>();
+        ResponseView respView = ResponseView.Restricted;
+        if (cmd instanceof ListSnapshotsCmdByAdmin) {
+            respView = ResponseView.Full;
+        }
+        List<SnapshotResponse> templateResponses = ViewResponseHelper.createSnapshotResponse(respView, cmd.isShowUnique(), result.first().toArray(new SnapshotJoinVO[result.first().size()]));
+        response.setResponses(templateResponses, result.second());
+        return response;
+    }
+
+    @Override
+    public SnapshotResponse listSnapshot(CopySnapshotCmd cmd) {
+        Account caller = CallContext.current().getCallingAccount();
+        List<Long> zoneIds = cmd.getDestinationZoneIds();
+        Pair<List<SnapshotJoinVO>, Integer> result = searchForSnapshotsWithParams(cmd.getId(), null,
+                null, null, null, null,
+                null, null, zoneIds.get(0), Snapshot.LocationType.SECONDARY.name(),
+                false, null, null, null, null, null,
+                null, null, true, false, caller);
+        ResponseView respView = ResponseView.Restricted;
+        if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN) {
+            respView = ResponseView.Full;
+        }
+        List<SnapshotResponse> templateResponses = ViewResponseHelper.createSnapshotResponse(respView, false, result.first().get(0));
+        return templateResponses.get(0);
+    }
+
+
+
+    private Pair<List<SnapshotJoinVO>, Integer> searchForSnapshotsWithParams(final Long id, List<Long> ids,
+            final Long volumeId, final String name, final String keyword, final Map<String, String> tags,
+            final String snapshotTypeStr, final String intervalTypeStr, final Long zoneId, final String locationTypeStr,
+            final boolean isShowUnique, final String accountName, Long domainId, final Long projectId, final Long storagePoolId, final Long imageStoreId,
+            final Long startIndex, final Long pageSize,final boolean listAll, boolean isRecursive, final Account caller) {
+        ids = getIdsListFromCmd(id, ids);
+        Snapshot.LocationType locationType = null;
+        if (locationTypeStr != null) {
+            try {
+                locationType = Snapshot.LocationType.valueOf(locationTypeStr.trim().toUpperCase());
+            } catch (IllegalArgumentException e) {
+                throw new InvalidParameterValueException(String.format("Invalid %s specified, %s", ApiConstants.LOCATION_TYPE, locationTypeStr));
+            }
+        }
+
+        Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", SortKeyAscending.value(), startIndex, pageSize);
+
+        List<Long> permittedAccountIds = new ArrayList<>();
+        Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, isRecursive, null);
+        accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccountIds, domainIdRecursiveListProject, listAll, false);
+        ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
+        domainId = domainIdRecursiveListProject.first();
+        isRecursive = domainIdRecursiveListProject.second();
+        // Verify parameters
+        if (volumeId != null) {
+            VolumeVO volume = volumeDao.findById(volumeId);
+            if (volume != null) {
+                accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
+            }
+        }
+
+        SearchBuilder<SnapshotJoinVO> sb = snapshotJoinDao.createSearchBuilder();
+        if (isShowUnique) {
+            sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct snapshotId
+        } else {
+            sb.select(null, Func.DISTINCT, sb.entity().getSnapshotStorePair()); // select distinct (snapshotId, store_role, store_id) key
+        }
+        accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccountIds, listProjectResourcesCriteria);
+        sb.and("statusNEQ", sb.entity().getStatus(), SearchCriteria.Op.NEQ); //exclude those Destroyed snapshot, not showing on UI
+        sb.and("volumeId", sb.entity().getVolumeId(), SearchCriteria.Op.EQ);
+        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
+        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
+        sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
+        sb.and("snapshotTypeEQ", sb.entity().getSnapshotType(), SearchCriteria.Op.IN);
+        sb.and("snapshotTypeNEQ", sb.entity().getSnapshotType(), SearchCriteria.Op.NIN);
+        sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ);
+        sb.and("locationType", sb.entity().getStoreRole(), SearchCriteria.Op.EQ);
+        sb.and("imageStoreId", sb.entity().getStoreId(), SearchCriteria.Op.EQ);
+
+        if (tags != null && !tags.isEmpty()) {
+            SearchBuilder<ResourceTagVO> tagSearch = resourceTagDao.createSearchBuilder();
+            for (int count = 0; count < tags.size(); count++) {
+                tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
+                tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
+                tagSearch.cp();
+            }
+            tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
+            sb.groupBy(sb.entity().getId());
+            sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
+        }
+
+        if (storagePoolId != null) {
+            SearchBuilder<SnapshotDataStoreVO> storagePoolSb = snapshotDataStoreDao.createSearchBuilder();
+            storagePoolSb.and("poolId", storagePoolSb.entity().getDataStoreId(), SearchCriteria.Op.EQ);
+            storagePoolSb.and("role", storagePoolSb.entity().getRole(), SearchCriteria.Op.EQ);
+            sb.join("storagePoolSb", storagePoolSb, sb.entity().getId(), storagePoolSb.entity().getSnapshotId(), JoinBuilder.JoinType.INNER);
+        }
+
+        SearchCriteria<SnapshotJoinVO> sc = sb.create();
+        accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccountIds, listProjectResourcesCriteria);
+
+        sc.setParameters("statusNEQ", Snapshot.State.Destroyed);
+
+        if (imageStoreId != null) {
+            sc.setParameters("imageStoreId", imageStoreId);
+            locationType = Snapshot.LocationType.SECONDARY;
+        }
+
+        if (storagePoolId != null) {
+            sc.setJoinParameters("storagePoolSb", "poolId", storagePoolId);
+            sc.setJoinParameters("storagePoolSb", "role", DataStoreRole.Image);
+        }
+
+        if (volumeId != null) {
+            sc.setParameters("volumeId", volumeId);
+        }
+
+        if (tags != null && !tags.isEmpty()) {
+            int count = 0;
+            sc.setJoinParameters("tagSearch", "resourceType", ResourceObjectType.Snapshot.toString());
+            for (String key : tags.keySet()) {
+                sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key);
+                sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key));
+                count++;
+            }
+        }
+
+        if (zoneId != null) {
+            sc.setParameters("dataCenterId", zoneId);
+        }
+
+        setIdsListToSearchCriteria(sc, ids);
+
+        if (name != null) {
+            sc.setParameters("name", name);
+        }
+
+        if (id != null) {
+            sc.setParameters("id", id);
+        }
+
+        if (locationType != null) {
+            sc.setParameters("locationType", Snapshot.LocationType.PRIMARY.equals(locationType) ? locationType.name() : DataStoreRole.Image.name());
+        }
+
+        if (keyword != null) {
+            SearchCriteria<SnapshotJoinVO> ssc = snapshotJoinDao.createSearchCriteria();
+            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+        }
+
+        if (snapshotTypeStr != null) {
+            Snapshot.Type snapshotType = SnapshotVO.getSnapshotType(snapshotTypeStr);
+            if (snapshotType == null) {
+                throw new InvalidParameterValueException("Unsupported snapshot type " + snapshotTypeStr);
+            }
+            if (snapshotType == Snapshot.Type.RECURRING) {
+                sc.setParameters("snapshotTypeEQ", Snapshot.Type.HOURLY.ordinal(), Snapshot.Type.DAILY.ordinal(), Snapshot.Type.WEEKLY.ordinal(), Snapshot.Type.MONTHLY.ordinal());
+            } else {
+                sc.setParameters("snapshotTypeEQ", snapshotType.ordinal());
+            }
+        } else if (intervalTypeStr != null && volumeId != null) {
+            Snapshot.Type type = SnapshotVO.getSnapshotType(intervalTypeStr);
+            if (type == null) {
+                throw new InvalidParameterValueException("Unsupported snapshot interval type " + intervalTypeStr);
+            }
+            sc.setParameters("snapshotTypeEQ", type.ordinal());
+        } else {
+            // Show only MANUAL and RECURRING snapshot types
+            sc.setParameters("snapshotTypeNEQ", Snapshot.Type.TEMPLATE.ordinal(), Snapshot.Type.GROUP.ordinal());
+        }
+
+        Pair<List<SnapshotJoinVO>, Integer> snapshotDataPair;
+        if (isShowUnique) {
+            snapshotDataPair = snapshotJoinDao.searchAndDistinctCount(sc, searchFilter, new String[]{"snapshot_view.id"});
+        } else {
+            snapshotDataPair = snapshotJoinDao.searchAndDistinctCount(sc, searchFilter, new String[]{"snapshot_view.snapshot_store_pair"});
+        }
+
+        Integer count = snapshotDataPair.second();
+        if (count == 0) {
+            // empty result
+            return snapshotDataPair;
+        }
+        List<SnapshotJoinVO> snapshotData = snapshotDataPair.first();
+        List<SnapshotJoinVO> snapshots;
+        if (isShowUnique) {
+            snapshots = snapshotJoinDao.findByDistinctIds(zoneId, snapshotData.stream().map(SnapshotJoinVO::getId).toArray(Long[]::new));
+        } else {
+            snapshots = snapshotJoinDao.searchBySnapshotStorePair(snapshotData.stream().map(SnapshotJoinVO::getSnapshotStorePair).toArray(String[]::new));
+        }
+
+        return new Pair<>(snapshots, count);
+    }
+
+    public ListResponse<ObjectStoreResponse> searchForObjectStores(ListObjectStoragePoolsCmd cmd) {
+        Pair<List<ObjectStoreVO>, Integer> result = searchForObjectStoresInternal(cmd);
+        ListResponse<ObjectStoreResponse> response = new ListResponse<ObjectStoreResponse>();
+
+        List<ObjectStoreResponse> poolResponses = ViewResponseHelper.createObjectStoreResponse(result.first().toArray(new ObjectStoreVO[result.first().size()]));
+        response.setResponses(poolResponses, result.second());
+        return response;
+    }
+
+    private Pair<List<ObjectStoreVO>, Integer> searchForObjectStoresInternal(ListObjectStoragePoolsCmd cmd) {
+
+        Object id = cmd.getId();
+        Object name = cmd.getStoreName();
+        String provider = cmd.getProvider();
+        Object keyword = cmd.getKeyword();
+        Long startIndex = cmd.getStartIndex();
+        Long pageSize = cmd.getPageSizeVal();
+
+        Filter searchFilter = new Filter(ObjectStoreVO.class, "id", Boolean.TRUE, startIndex, pageSize);
+
+        SearchBuilder<ObjectStoreVO> sb = objectStoreDao.createSearchBuilder();
+        sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct
+        // ids
+        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
+        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
+        sb.and("provider", sb.entity().getProviderName(), SearchCriteria.Op.EQ);
+
+        SearchCriteria<ObjectStoreVO> sc = sb.create();
+
+        if (keyword != null) {
+            SearchCriteria<ObjectStoreVO> ssc = objectStoreDao.createSearchCriteria();
+            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+            ssc.addOr("providerName", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+        }
+
+        if (id != null) {
+            sc.setParameters("id", id);
+        }
+
+        if (name != null) {
+            sc.setParameters("name", name);
+        }
+
+        if (provider != null) {
+            sc.setParameters("provider", provider);
+        }
+
+        // search Store details by ids
+        Pair<List<ObjectStoreVO>, Integer> uniqueStorePair = objectStoreDao.searchAndCount(sc, searchFilter);
+        Integer count = uniqueStorePair.second();
+        if (count.intValue() == 0) {
+            // empty result
+            return uniqueStorePair;
+        }
+        List<ObjectStoreVO> uniqueStores = uniqueStorePair.first();
+        Long[] osIds = new Long[uniqueStores.size()];
+        int i = 0;
+        for (ObjectStoreVO v : uniqueStores) {
+            osIds[i++] = v.getId();
+        }
+        List<ObjectStoreVO> objectStores = objectStoreDao.searchByIds(osIds);
+        return new Pair<>(objectStores, count);
+    }
+
+
+    @Override
+    public ListResponse<BucketResponse> searchForBuckets(ListBucketsCmd listBucketsCmd) {
+        List<BucketVO> buckets = searchForBucketsInternal(listBucketsCmd);
+        List<BucketResponse> bucketResponses = new ArrayList<>();
+        for (BucketVO bucket : buckets) {
+            bucketResponses.add(responseGenerator.createBucketResponse(bucket));
+        }
+        ListResponse<BucketResponse> response = new ListResponse<>();
+        response.setResponses(bucketResponses, bucketResponses.size());
+        return response;
+    }
+
+    private List<BucketVO> searchForBucketsInternal(ListBucketsCmd cmd) {
+
+        Long id = cmd.getId();
+        String name = cmd.getBucketName();
+        Long storeId = cmd.getObjectStorageId();
+        String keyword = cmd.getKeyword();
+        Long startIndex = cmd.getStartIndex();
+        Long pageSize = cmd.getPageSizeVal();
+        Account caller = CallContext.current().getCallingAccount();
+        List<Long> permittedAccounts = new ArrayList<Long>();
+
+        // Verify parameters
+        if (id != null) {
+            BucketVO bucket = bucketDao.findById(id);
+            if (bucket != null) {
+                accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, bucket);
+            }
+        }
+
+        List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
+
+        Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(),
+                cmd.isRecursive(), null);
+        accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
+        Long domainId = domainIdRecursiveListProject.first();
+        Boolean isRecursive = domainIdRecursiveListProject.second();
+        ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
+
+        Filter searchFilter = new Filter(BucketVO.class, "id", Boolean.TRUE, startIndex, pageSize);
+
+        SearchBuilder<BucketVO> sb = bucketDao.createSearchBuilder();
+        accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+        sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct
+        // ids
+        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
+        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
+
+        SearchCriteria<BucketVO> sc = sb.create();
+        accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
+
+        if (keyword != null) {
+            SearchCriteria<BucketVO> ssc = bucketDao.createSearchCriteria();
+            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+            ssc.addOr("state", SearchCriteria.Op.LIKE, "%" + keyword + "%");
+            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
+        }
+
+        if (id != null) {
+            sc.setParameters("id", id);
+        }
+
+        if (name != null) {
+            sc.setParameters("name", name);
+        }
+
+        setIdsListToSearchCriteria(sc, ids);
+
+        // search Volume details by ids
+        Pair<List<BucketVO>, Integer> uniqueBktPair = bucketDao.searchAndCount(sc, searchFilter);
+        Integer count = uniqueBktPair.second();
+        if (count.intValue() == 0) {
+            // empty result
+            return uniqueBktPair.first();
+        }
+        List<BucketVO> uniqueBkts = uniqueBktPair.first();
+        Long[] bktIds = new Long[uniqueBkts.size()];
+        int i = 0;
+        for (BucketVO b : uniqueBkts) {
+            bktIds[i++] = b.getId();
+        }
+
+        return bucketDao.searchByIds(bktIds);
+    }
+
+    @Override
     public String getConfigComponentName() {
         return QueryService.class.getSimpleName();
     }
diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
index 4803142..623ba43 100644
--- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
@@ -21,6 +21,7 @@
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -43,12 +44,14 @@
 import org.apache.cloudstack.api.response.HostTagResponse;
 import org.apache.cloudstack.api.response.ImageStoreResponse;
 import org.apache.cloudstack.api.response.InstanceGroupResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
 import org.apache.cloudstack.api.response.ProjectAccountResponse;
 import org.apache.cloudstack.api.response.ProjectInvitationResponse;
 import org.apache.cloudstack.api.response.ProjectResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 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.StoragePoolResponse;
 import org.apache.cloudstack.api.response.StorageTagResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
@@ -57,6 +60,7 @@
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
 import org.apache.log4j.Logger;
 
 import com.cloud.api.ApiDBUtils;
@@ -78,6 +82,7 @@
 import com.cloud.api.query.vo.ResourceTagJoinVO;
 import com.cloud.api.query.vo.SecurityGroupJoinVO;
 import com.cloud.api.query.vo.ServiceOfferingJoinVO;
+import com.cloud.api.query.vo.SnapshotJoinVO;
 import com.cloud.api.query.vo.StoragePoolJoinVO;
 import com.cloud.api.query.vo.TemplateJoinVO;
 import com.cloud.api.query.vo.UserAccountJoinVO;
@@ -536,11 +541,7 @@
     }
 
     public static List<AccountResponse> createAccountResponse(ResponseView view, EnumSet<DomainDetails> details, AccountJoinVO... accounts) {
-        List<AccountResponse> respList = new ArrayList<AccountResponse>();
-        for (AccountJoinVO vt : accounts){
-            respList.add(ApiDBUtils.newAccountResponse(view, details, vt));
-        }
-        return respList;
+        return ApiDBUtils.newAccountResponses(view, details, accounts);
     }
 
     public static List<AsyncJobResponse> createAsyncJobResponse(AsyncJobJoinVO... jobs) {
@@ -592,6 +593,23 @@
         return new ArrayList<TemplateResponse>(vrDataList.values());
     }
 
+    public static List<SnapshotResponse> createSnapshotResponse(ResponseView view, boolean isShowUnique, SnapshotJoinVO... snapshots) {
+        LinkedHashMap<String, SnapshotResponse> vrDataList = new LinkedHashMap<>();
+        for (SnapshotJoinVO vr : snapshots) {
+            SnapshotResponse vrData = vrDataList.get(vr.getSnapshotStorePair());
+            if (vrData == null) {
+                // first time encountering this snapshot
+                vrData = ApiDBUtils.newSnapshotResponse(view, isShowUnique, vr);
+            }
+            else{
+                // update tags
+                vrData = ApiDBUtils.fillSnapshotDetails(vrData, vr);
+            }
+            vrDataList.put(vr.getSnapshotStorePair(), vrData);
+        }
+        return new ArrayList<SnapshotResponse>(vrDataList.values());
+    }
+
     public static List<TemplateResponse> createTemplateUpdateResponse(ResponseView view, TemplateJoinVO... templates) {
         LinkedHashMap<Long, TemplateResponse> vrDataList = new LinkedHashMap<>();
         for (TemplateJoinVO vr : templates) {
@@ -614,7 +632,7 @@
             TemplateResponse vrData = vrDataList.get(vr.getTempZonePair());
             if (vrData == null) {
                 // first time encountering this volume
-                vrData = ApiDBUtils.newIsoResponse(vr);
+                vrData = ApiDBUtils.newIsoResponse(vr, view);
             } else {
                 // update tags
                 vrData = ApiDBUtils.fillTemplateDetails(EnumSet.of(DomainDetails.all), view, vrData, vr);
@@ -640,4 +658,20 @@
         return new ArrayList<AffinityGroupResponse>(vrDataList.values());
     }
 
+    public static List<ObjectStoreResponse> createObjectStoreResponse(ObjectStoreVO[] stores) {
+        Hashtable<Long, ObjectStoreResponse> storeList = new Hashtable<Long, ObjectStoreResponse>();
+        // Initialise the storeList with the input data
+        for (ObjectStoreVO store : stores) {
+            ObjectStoreResponse storeData = storeList.get(store.getId());
+            if (storeData == null) {
+                // first time encountering this store
+                storeData = ApiDBUtils.newObjectStoreResponse(store);
+            } else {
+                // update tags
+                storeData = ApiDBUtils.fillObjectStoreDetails(storeData, store);
+            }
+            storeList.put(store.getId(), storeData);
+        }
+        return new ArrayList<>(storeList.values());
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDao.java
index 42842f5..16bda5b 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDao.java
@@ -17,6 +17,7 @@
 package com.cloud.api.query.dao;
 
 import java.util.EnumSet;
+import java.util.List;
 
 import org.apache.cloudstack.api.ApiConstants.DomainDetails;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
@@ -35,4 +36,5 @@
 
     void setResourceLimits(AccountJoinVO account, boolean accountIsAdmin, ResourceLimitAndCountResponse response);
 
+    List<AccountJoinVO> searchByIds(Long... ids);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
index 790758c..2daa411 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
@@ -16,11 +16,13 @@
 // under the License.
 package com.cloud.api.query.dao;
 
+import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
 import javax.inject.Inject;
 
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -46,12 +48,19 @@
 public class AccountJoinDaoImpl extends GenericDaoBase<AccountJoinVO, Long> implements AccountJoinDao {
     public static final Logger s_logger = Logger.getLogger(AccountJoinDaoImpl.class);
 
+    @Inject
+    private ConfigurationDao configDao;
     private final SearchBuilder<AccountJoinVO> acctIdSearch;
+    private final SearchBuilder<AccountJoinVO> domainSearch;
     @Inject
     AccountManager _acctMgr;
 
     protected AccountJoinDaoImpl() {
 
+        domainSearch = createSearchBuilder();
+        domainSearch.and("idIN", domainSearch.entity().getId(), SearchCriteria.Op.IN);
+        domainSearch.done();
+
         acctIdSearch = createSearchBuilder();
         acctIdSearch.and("id", acctIdSearch.entity().getId(), SearchCriteria.Op.EQ);
         acctIdSearch.done();
@@ -233,6 +242,50 @@
     }
 
     @Override
+    public List<AccountJoinVO> searchByIds(Long... accountIds) {
+        // set detail batch query size
+        int DETAILS_BATCH_SIZE = 2000;
+        String batchCfg = configDao.getValue("detail.batch.query.size");
+        if (batchCfg != null) {
+            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
+        }
+
+        List<AccountJoinVO> uvList = new ArrayList<>();
+        // query details by batches
+        int curr_index = 0;
+        if (accountIds.length > DETAILS_BATCH_SIZE) {
+            while ((curr_index + DETAILS_BATCH_SIZE) <= accountIds.length) {
+                Long[] ids = new Long[DETAILS_BATCH_SIZE];
+                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
+                    ids[k] = accountIds[j];
+                }
+                SearchCriteria<AccountJoinVO> sc = domainSearch.create();
+                sc.setParameters("idIN", ids);
+                List<AccountJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
+                if (accounts != null) {
+                    uvList.addAll(accounts);
+                }
+                curr_index += DETAILS_BATCH_SIZE;
+            }
+        }
+        if (curr_index < accountIds.length) {
+            int batch_size = (accountIds.length - curr_index);
+            // set the ids value
+            Long[] ids = new Long[batch_size];
+            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
+                ids[k] = accountIds[j];
+            }
+            SearchCriteria<AccountJoinVO> sc = domainSearch.create();
+            sc.setParameters("idIN", ids);
+            List<AccountJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
+            if (accounts != null) {
+                uvList.addAll(accounts);
+            }
+        }
+        return uvList;
+    }
+
+    @Override
     public AccountJoinVO newAccountView(Account acct) {
         SearchCriteria<AccountJoinVO> sc = acctIdSearch.create();
         sc.setParameters("id", acct.getId());
diff --git a/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java
index bd11015..32cd1c2 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java
@@ -53,6 +53,11 @@
     public AsyncJobResponse newAsyncJobResponse(final AsyncJobJoinVO job) {
         final AsyncJobResponse jobResponse = new AsyncJobResponse();
         jobResponse.setAccountId(job.getAccountUuid());
+        jobResponse.setAccount(job.getAccountName());
+        jobResponse.setDomainId(job.getDomainUuid());
+        StringBuilder domainPath = new StringBuilder("ROOT");
+        (domainPath.append(job.getDomainPath())).deleteCharAt(domainPath.length() - 1);
+        jobResponse.setDomainPath(domainPath.toString());
         jobResponse.setUserId(job.getUserUuid());
         jobResponse.setCmd(job.getCmd());
         jobResponse.setCreated(job.getCreated());
@@ -60,6 +65,7 @@
         jobResponse.setJobId(job.getUuid());
         jobResponse.setJobStatus(job.getStatus());
         jobResponse.setJobProcStatus(job.getProcessStatus());
+        jobResponse.setMsid(job.getExecutingMsid());
 
         if (job.getInstanceType() != null && job.getInstanceId() != null) {
             jobResponse.setJobInstanceType(job.getInstanceType().toString());
diff --git a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDao.java
index 3d612c6..3b38a56 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDao.java
@@ -33,4 +33,6 @@
     DiskOfferingResponse newDiskOfferingResponse(DiskOfferingJoinVO dof);
 
     DiskOfferingJoinVO newDiskOfferingView(DiskOffering dof);
+
+    List<DiskOfferingJoinVO> searchByIds(Long... idArray);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
index 9592986..14fc56c 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.api.query.dao;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -26,6 +27,7 @@
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.response.DiskOfferingResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -51,9 +53,12 @@
     @Inject
     private AnnotationDao annotationDao;
     @Inject
+    private ConfigurationDao configDao;
+    @Inject
     private AccountManager accountManager;
 
     private final SearchBuilder<DiskOfferingJoinVO> dofIdSearch;
+    private SearchBuilder<DiskOfferingJoinVO> diskOfferingSearch;
     private final Attribute _typeAttr;
 
     protected DiskOfferingJoinDaoImpl() {
@@ -62,6 +67,11 @@
         dofIdSearch.and("id", dofIdSearch.entity().getId(), SearchCriteria.Op.EQ);
         dofIdSearch.done();
 
+        diskOfferingSearch = createSearchBuilder();
+        diskOfferingSearch.and("idIN", diskOfferingSearch.entity().getId(), SearchCriteria.Op.IN);
+        diskOfferingSearch.done();
+
+
         _typeAttr = _allAttributes.get("type");
 
         _count = "select count(distinct id) from disk_offering_view WHERE ";
@@ -95,6 +105,7 @@
         DiskOfferingResponse diskOfferingResponse = new DiskOfferingResponse();
         diskOfferingResponse.setId(offering.getUuid());
         diskOfferingResponse.setName(offering.getName());
+        diskOfferingResponse.setState(offering.getState().toString());
         diskOfferingResponse.setDisplayText(offering.getDisplayText());
         diskOfferingResponse.setProvisioningType(offering.getProvisioningType().toString());
         diskOfferingResponse.setCreated(offering.getCreated());
@@ -154,4 +165,48 @@
         assert offerings != null && offerings.size() == 1 : "No disk offering found for offering id " + offering.getId();
         return offerings.get(0);
     }
+
+    @Override
+    public List<DiskOfferingJoinVO> searchByIds(Long... offeringIds) {
+        // set detail batch query size
+        int DETAILS_BATCH_SIZE = 2000;
+        String batchCfg = configDao.getValue("detail.batch.query.size");
+        if (batchCfg != null) {
+            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
+        }
+
+        List<DiskOfferingJoinVO> uvList = new ArrayList<>();
+        // query details by batches
+        int curr_index = 0;
+        if (offeringIds.length > DETAILS_BATCH_SIZE) {
+            while ((curr_index + DETAILS_BATCH_SIZE) <= offeringIds.length) {
+                Long[] ids = new Long[DETAILS_BATCH_SIZE];
+                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
+                    ids[k] = offeringIds[j];
+                }
+                SearchCriteria<DiskOfferingJoinVO> sc = diskOfferingSearch.create();
+                sc.setParameters("idIN", ids);
+                List<DiskOfferingJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
+                if (accounts != null) {
+                    uvList.addAll(accounts);
+                }
+                curr_index += DETAILS_BATCH_SIZE;
+            }
+        }
+        if (curr_index < offeringIds.length) {
+            int batch_size = (offeringIds.length - curr_index);
+            // set the ids value
+            Long[] ids = new Long[batch_size];
+            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
+                ids[k] = offeringIds[j];
+            }
+            SearchCriteria<DiskOfferingJoinVO> sc = diskOfferingSearch.create();
+            sc.setParameters("idIN", ids);
+            List<DiskOfferingJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
+            if (accounts != null) {
+                uvList.addAll(accounts);
+            }
+        }
+        return uvList;
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDao.java
index 94efc28..c7960fd 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDao.java
@@ -17,6 +17,7 @@
 package com.cloud.api.query.dao;
 
 import java.util.EnumSet;
+import java.util.List;
 
 import org.apache.cloudstack.api.ApiConstants.DomainDetails;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
@@ -35,4 +36,5 @@
 
     void setResourceLimits(DomainJoinVO domain, boolean isRootDomain, ResourceLimitAndCountResponse response);
 
+    List<DomainJoinVO> searchByIds(Long... domainIds);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
index 56f5417..24200fa 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.api.query.dao;
 
+import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
@@ -29,6 +30,7 @@
 import org.apache.cloudstack.api.response.DomainResponse;
 import org.apache.cloudstack.api.response.ResourceLimitAndCountResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -47,10 +49,13 @@
     public static final Logger s_logger = Logger.getLogger(DomainJoinDaoImpl.class);
 
     private SearchBuilder<DomainJoinVO> domainIdSearch;
+    private SearchBuilder<DomainJoinVO> domainSearch;
 
     @Inject
     private AnnotationDao annotationDao;
     @Inject
+    private ConfigurationDao configDao;
+    @Inject
     private AccountManager accountManager;
 
     protected DomainJoinDaoImpl() {
@@ -59,6 +64,10 @@
         domainIdSearch.and("id", domainIdSearch.entity().getId(), SearchCriteria.Op.EQ);
         domainIdSearch.done();
 
+        domainSearch = createSearchBuilder();
+        domainSearch.and("idIN", domainSearch.entity().getId(), SearchCriteria.Op.IN);
+        domainSearch.done();
+
         this._count = "select count(distinct id) from domain_view WHERE ";
     }
 
@@ -208,6 +217,50 @@
     }
 
     @Override
+    public List<DomainJoinVO> searchByIds(Long... domainIds) {
+        // set detail batch query size
+        int DETAILS_BATCH_SIZE = 2000;
+        String batchCfg = configDao.getValue("detail.batch.query.size");
+        if (batchCfg != null) {
+            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
+        }
+
+        List<DomainJoinVO> uvList = new ArrayList<>();
+        // query details by batches
+        int curr_index = 0;
+        if (domainIds.length > DETAILS_BATCH_SIZE) {
+            while ((curr_index + DETAILS_BATCH_SIZE) <= domainIds.length) {
+                Long[] ids = new Long[DETAILS_BATCH_SIZE];
+                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
+                    ids[k] = domainIds[j];
+                }
+                SearchCriteria<DomainJoinVO> sc = domainSearch.create();
+                sc.setParameters("idIN", ids);
+                List<DomainJoinVO> domains = searchIncludingRemoved(sc, null, null, false);
+                if (domains != null) {
+                    uvList.addAll(domains);
+                }
+                curr_index += DETAILS_BATCH_SIZE;
+            }
+        }
+        if (curr_index < domainIds.length) {
+            int batch_size = (domainIds.length - curr_index);
+            // set the ids value
+            Long[] ids = new Long[batch_size];
+            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
+                ids[k] = domainIds[j];
+            }
+            SearchCriteria<DomainJoinVO> sc = domainSearch.create();
+            sc.setParameters("idIN", ids);
+            List<DomainJoinVO> domains = searchIncludingRemoved(sc, null, null, false);
+            if (domains != null) {
+                uvList.addAll(domains);
+            }
+        }
+        return uvList;
+    }
+
+    @Override
     public DomainJoinVO newDomainView(Domain domain) {
         SearchCriteria<DomainJoinVO> sc = domainIdSearch.create();
         sc.setParameters("id", domain.getId());
diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
index 83a8962..e3011bc 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java
@@ -126,7 +126,7 @@
         }
 
         if (router.getHypervisorType() != null) {
-            routerResponse.setHypervisor(router.getHypervisorType().toString());
+            routerResponse.setHypervisor(router.getHypervisorType().getHypervisorDisplayName());
         }
         routerResponse.setHasAnnotation(annotationDao.hasAnnotations(router.getUuid(), AnnotationService.EntityType.VR.name(),
                 _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
diff --git a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
index 7b52007..da81f42 100644
--- a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
@@ -125,7 +125,10 @@
         hostResponse.setCpuNumber(host.getCpus());
         hostResponse.setZoneId(host.getZoneUuid());
         hostResponse.setDisconnectedOn(host.getDisconnectedOn());
-        hostResponse.setHypervisor(host.getHypervisorType());
+        if (host.getHypervisorType() != null) {
+            String hypervisorType = host.getHypervisorType().getHypervisorDisplayName();
+            hostResponse.setHypervisor(hypervisorType);
+        }
         hostResponse.setHostType(host.getType());
         hostResponse.setLastPinged(new Date(host.getLastPinged()));
         Long mshostId = host.getManagementServerId();
@@ -202,6 +205,7 @@
 
                 String hostTags = host.getTag();
                 hostResponse.setHostTags(hostTags);
+                hostResponse.setIsTagARule(host.getIsTagARule());
                 hostResponse.setHaHost(containsHostHATag(hostTags));
 
                 hostResponse.setHypervisorVersion(host.getHypervisorVersion());
@@ -239,7 +243,8 @@
                     hostResponse.setUefiCapabilty(new Boolean(false));
                 }
             }
-            if (details.contains(HostDetails.all) && host.getHypervisorType() == Hypervisor.HypervisorType.KVM) {
+            if (details.contains(HostDetails.all) && (host.getHypervisorType() == Hypervisor.HypervisorType.KVM ||
+                    host.getHypervisorType() == Hypervisor.HypervisorType.Custom)) {
                 //only kvm has the requirement to return host details
                 try {
                     hostResponse.setDetails(hostDetails);
@@ -303,7 +308,7 @@
         hostResponse.setCpuNumber(host.getCpus());
         hostResponse.setZoneId(host.getZoneUuid());
         hostResponse.setDisconnectedOn(host.getDisconnectedOn());
-        hostResponse.setHypervisor(host.getHypervisorType());
+        hostResponse.setHypervisor(host.getHypervisorType().getHypervisorDisplayName());
         hostResponse.setHostType(host.getType());
         hostResponse.setLastPinged(new Date(host.getLastPinged()));
         hostResponse.setManagementServerId(host.getManagementServerId());
diff --git a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java
index 767b9ac..f5c58a2 100644
--- a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java
@@ -18,6 +18,7 @@
 package com.cloud.api.query.dao;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.cloudstack.api.response.NetworkOfferingResponse;
 
@@ -52,4 +53,6 @@
     NetworkOfferingResponse newNetworkOfferingResponse(NetworkOffering nof);
 
     NetworkOfferingJoinVO newNetworkOfferingView(NetworkOffering nof);
+
+    Map<Long, List<String>> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java
index 474409a..d50f161 100644
--- a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java
@@ -17,8 +17,15 @@
 
 package com.cloud.api.query.dao;
 
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import com.cloud.utils.db.TransactionLegacy;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cloudstack.api.response.NetworkOfferingResponse;
 import org.apache.log4j.Logger;
@@ -44,6 +51,17 @@
         _count = "select count(distinct id) from network_offering_view WHERE ";
     }
 
+    private static final String LIST_DOMAINS_OF_NETWORK_OFFERINGS_USED_BY_DOMAIN_PATH = "SELECT nov.domain_id, \n" +
+            "            GROUP_CONCAT('Network:', net.uuid) \n" +
+            "            FROM   cloud.network_offering_view AS nov\n" +
+            "            INNER  JOIN cloud.networks AS net ON (net.network_offering_id  = nov.id) \n" +
+            "            INNER  JOIN cloud.domain AS domain ON (domain.id = net.domain_id) \n" +
+            "            INNER  JOIN cloud.domain AS domain_so ON (domain_so.id = nov.domain_id) \n" +
+            "            WHERE  domain.path LIKE ? \n" +
+            "            AND    domain_so.path NOT LIKE ? \n" +
+            "            AND    net.removed IS NULL \n" +
+            "            GROUP  BY nov.id";
+
     @Override
     public List<NetworkOfferingJoinVO> findByDomainId(long domainId, Boolean includeAllDomainOffering) {
         SearchBuilder<NetworkOfferingJoinVO> sb = createSearchBuilder();
@@ -120,4 +138,39 @@
         assert offerings != null && offerings.size() == 1 : "No network offering found for offering id " + offering.getId();
         return offerings.get(0);
     }
+
+
+
+    @Override
+    public Map<Long, List<String>> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath) {
+        s_logger.debug(String.format("Retrieving the domains of the network offerings used by domain with path [%s].", domainPath));
+
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_NETWORK_OFFERINGS_USED_BY_DOMAIN_PATH)) {
+            Map<Long, List<String>> domainsOfNetworkOfferingsUsedByDomainPath = new HashMap<>();
+
+            String domainSearch = domainPath.concat("%");
+            pstmt.setString(1, domainSearch);
+            pstmt.setString(2, domainSearch);
+
+            try (ResultSet rs = pstmt.executeQuery()) {
+                while (rs.next()) {
+                    Long domainId = rs.getLong(1);
+                    List<String> networkUuids = Arrays.asList(rs.getString(2).split(","));
+
+                    domainsOfNetworkOfferingsUsedByDomainPath.put(domainId, networkUuids);
+                }
+            }
+
+            return domainsOfNetworkOfferingsUsedByDomainPath;
+        } catch (SQLException e) {
+            s_logger.error(String.format("Failed to retrieve the domains of the network offerings used by domain with path [%s] due to [%s]. Returning an empty "
+                    + "list of domains.", domainPath, e.getMessage()));
+
+            s_logger.debug(String.format("Failed to retrieve the domains of the network offerings used by domain with path [%s]. Returning an empty " +
+                    "list of domains.", domainPath), e);
+
+            return new HashMap<>();
+        }
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java
index 94cc943..d28b9fc 100644
--- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java
@@ -17,6 +17,7 @@
 package com.cloud.api.query.dao;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
 
@@ -31,4 +32,7 @@
     ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offering);
 
     ServiceOfferingJoinVO newServiceOfferingView(ServiceOffering offering);
+
+    Map<Long, List<String>> listDomainsOfServiceOfferingsUsedByDomainPath(String domainPath);
+    List<ServiceOfferingJoinVO> searchByIds(Long... id);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
index 1cad19e..1c7c273 100644
--- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
@@ -16,18 +16,26 @@
 // under the License.
 package com.cloud.api.query.dao;
 
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
 import com.cloud.dc.VsphereStoragePolicyVO;
 import com.cloud.dc.dao.VsphereStoragePolicyDao;
 import com.cloud.user.AccountManager;
+import com.cloud.utils.db.TransactionLegacy;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import com.cloud.storage.DiskOfferingVO;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -50,10 +58,14 @@
     @Inject
     private AnnotationDao annotationDao;
     @Inject
+    private ConfigurationDao configDao;
+    @Inject
     private AccountManager accountManager;
 
     private SearchBuilder<ServiceOfferingJoinVO> sofIdSearch;
 
+    private SearchBuilder<ServiceOfferingJoinVO> srvOfferingSearch;
+
     /**
      * Constant used to convert GB into Bytes (or the other way around).
      * GB   *  MB  *  KB  = Bytes //
@@ -61,12 +73,28 @@
      */
     private static final long GB_TO_BYTES = 1073741824;
 
+
+    private static final String LIST_DOMAINS_OF_SERVICE_OFFERINGS_USED_BY_DOMAIN_PATH = "SELECT sov.domain_id, \n" +
+            "            GROUP_CONCAT('Offering:', vm.uuid) \n" +
+            "            FROM   cloud.service_offering_view AS sov\n" +
+            "            INNER  JOIN cloud.vm_instance AS vm ON (vm.service_offering_id  = sov.id) \n" +
+            "            INNER  JOIN cloud.domain AS domain ON (domain.id = vm.domain_id) \n" +
+            "            INNER  JOIN cloud.domain AS domain_so ON (domain_so.id = sov.domain_id) \n" +
+            "            WHERE  domain.path LIKE ? \n" +
+            "            AND    domain_so.path NOT LIKE ? \n " +
+            "            AND    vm.removed IS NULL \n" +
+            "            GROUP  BY sov.id";
+
     protected ServiceOfferingJoinDaoImpl() {
 
         sofIdSearch = createSearchBuilder();
         sofIdSearch.and("id", sofIdSearch.entity().getId(), SearchCriteria.Op.EQ);
         sofIdSearch.done();
 
+        srvOfferingSearch = createSearchBuilder();
+        srvOfferingSearch.and("idIN", srvOfferingSearch.entity().getId(), SearchCriteria.Op.IN);
+        srvOfferingSearch.done();
+
         this._count = "select count(distinct service_offering_view.id) from service_offering_view WHERE ";
     }
 
@@ -87,6 +115,7 @@
         ServiceOfferingResponse offeringResponse = new ServiceOfferingResponse();
         offeringResponse.setId(offering.getUuid());
         offeringResponse.setName(offering.getName());
+        offeringResponse.setState(offering.getState().toString());
         offeringResponse.setIsSystemOffering(offering.isSystemUse());
         offeringResponse.setDefaultUse(offering.isDefaultUse());
         offeringResponse.setSystemVmType(offering.getSystemVmType());
@@ -165,4 +194,81 @@
         assert offerings != null && offerings.size() == 1 : "No service offering found for offering id " + offering.getId();
         return offerings.get(0);
     }
+
+    @Override
+    public Map<Long, List<String>> listDomainsOfServiceOfferingsUsedByDomainPath(String domainPath) {
+        s_logger.debug(String.format("Retrieving the domains of the service offerings used by domain with path [%s].", domainPath));
+
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_SERVICE_OFFERINGS_USED_BY_DOMAIN_PATH)) {
+            Map<Long, List<String>> domainsOfServiceOfferingsUsedByDomainPath = new HashMap<>();
+
+            String domainSearch = domainPath.concat("%");
+            pstmt.setString(1, domainSearch);
+            pstmt.setString(2, domainSearch);
+
+            try (ResultSet rs = pstmt.executeQuery()) {
+                while (rs.next()) {
+                    Long domainId = rs.getLong(1);
+                    List<String> vmUuids = Arrays.asList(rs.getString(2).split(","));
+
+                    domainsOfServiceOfferingsUsedByDomainPath.put(domainId, vmUuids);
+                }
+            }
+
+            return domainsOfServiceOfferingsUsedByDomainPath;
+        } catch (SQLException e) {
+            s_logger.error(String.format("Failed to retrieve the domains of the service offerings used by domain with path [%s] due to [%s]. Returning an empty "
+                    + "list of domains.", domainPath, e.getMessage()));
+
+            s_logger.debug(String.format("Failed to retrieve the domains of the service offerings used by domain with path [%s]. Returning an empty "
+                    + "list of domains.", domainPath), e);
+
+            return new HashMap<>();
+        }
+    }
+
+    @Override
+    public List<ServiceOfferingJoinVO> searchByIds(Long... offeringIds) {
+        // set detail batch query size
+        int DETAILS_BATCH_SIZE = 2000;
+        String batchCfg = configDao.getValue("detail.batch.query.size");
+        if (batchCfg != null) {
+            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
+        }
+
+        List<ServiceOfferingJoinVO> uvList = new ArrayList<>();
+        // query details by batches
+        int curr_index = 0;
+        if (offeringIds.length > DETAILS_BATCH_SIZE) {
+            while ((curr_index + DETAILS_BATCH_SIZE) <= offeringIds.length) {
+                Long[] ids = new Long[DETAILS_BATCH_SIZE];
+                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
+                    ids[k] = offeringIds[j];
+                }
+                SearchCriteria<ServiceOfferingJoinVO> sc = srvOfferingSearch.create();
+                sc.setParameters("idIN", ids);
+                List<ServiceOfferingJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
+                if (accounts != null) {
+                    uvList.addAll(accounts);
+                }
+                curr_index += DETAILS_BATCH_SIZE;
+            }
+        }
+        if (curr_index < offeringIds.length) {
+            int batch_size = (offeringIds.length - curr_index);
+            // set the ids value
+            Long[] ids = new Long[batch_size];
+            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
+                ids[k] = offeringIds[j];
+            }
+            SearchCriteria<ServiceOfferingJoinVO> sc = srvOfferingSearch.create();
+            sc.setParameters("idIN", ids);
+            List<ServiceOfferingJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
+            if (accounts != null) {
+                uvList.addAll(accounts);
+            }
+        }
+        return uvList;
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java
new file mode 100644
index 0000000..4e916e6
--- /dev/null
+++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.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.api.query.dao;
+
+import java.util.List;
+
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.response.SnapshotResponse;
+
+import com.cloud.api.query.vo.SnapshotJoinVO;
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.GenericDao;
+import com.cloud.utils.db.SearchCriteria;
+
+public interface SnapshotJoinDao extends GenericDao<SnapshotJoinVO, Long> {
+
+    SnapshotResponse newSnapshotResponse(ResponseObject.ResponseView view, boolean isShowUnique, SnapshotJoinVO snapshotJoinVO);
+
+    SnapshotResponse setSnapshotResponse(SnapshotResponse snapshotResponse, SnapshotJoinVO snapshot);
+
+    Pair<List<SnapshotJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<SnapshotJoinVO> sc, final Filter filter);
+
+    List<SnapshotJoinVO> searchBySnapshotStorePair(String... pairs);
+    List<SnapshotJoinVO> findByDistinctIds(Long zoneId, Long... ids);
+}
diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java
new file mode 100644
index 0000000..a913dd7
--- /dev/null
+++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java
@@ -0,0 +1,248 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES 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.api.query.dao;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.response.SnapshotResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.query.QueryService;
+import org.apache.log4j.Logger;
+
+import com.cloud.api.ApiResponseHelper;
+import com.cloud.api.query.vo.SnapshotJoinVO;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import com.cloud.user.Account;
+import com.cloud.user.AccountService;
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation<SnapshotJoinVO, SnapshotResponse> implements SnapshotJoinDao {
+
+    public static final Logger s_logger = Logger.getLogger(SnapshotJoinDaoImpl.class);
+
+    @Inject
+    private AccountService accountService;
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private ConfigurationDao configDao;
+    @Inject
+    SnapshotDataFactory snapshotDataFactory;
+
+    private final SearchBuilder<SnapshotJoinVO> snapshotStorePairSearch;
+
+    private final SearchBuilder<SnapshotJoinVO> snapshotIdsSearch;
+
+    SnapshotJoinDaoImpl() {
+        snapshotStorePairSearch = createSearchBuilder();
+        snapshotStorePairSearch.and("snapshotStoreState", snapshotStorePairSearch.entity().getStoreState(), SearchCriteria.Op.IN);
+        snapshotStorePairSearch.and("snapshotStoreIdIN", snapshotStorePairSearch.entity().getSnapshotStorePair(), SearchCriteria.Op.IN);
+        snapshotStorePairSearch.done();
+
+        snapshotIdsSearch = createSearchBuilder();
+        snapshotIdsSearch.and("zoneId", snapshotIdsSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
+        snapshotIdsSearch.and("idsIN", snapshotIdsSearch.entity().getId(), SearchCriteria.Op.IN);
+        snapshotIdsSearch.groupBy(snapshotIdsSearch.entity().getId());
+        snapshotIdsSearch.done();
+    }
+
+    private void setSnapshotInfoDetailsInResponse(SnapshotJoinVO snapshot, SnapshotResponse snapshotResponse, boolean isShowUnique) {
+        if (!isShowUnique) {
+            return;
+        }
+        if (snapshot.getDataCenterId() == null) {
+            return;
+        }
+        SnapshotInfo snapshotInfo = null;
+        snapshotInfo = snapshotDataFactory.getSnapshotWithRoleAndZone(snapshot.getId(), snapshot.getStoreRole(), snapshot.getDataCenterId());
+        if (snapshotInfo == null) {
+            s_logger.debug("Unable to find info for image store snapshot with uuid " + snapshot.getUuid());
+            snapshotResponse.setRevertable(false);
+        } else {
+            snapshotResponse.setRevertable(snapshotInfo.isRevertable());
+            snapshotResponse.setPhysicalSize(snapshotInfo.getPhysicalSize());
+        }
+    }
+
+    private String getSnapshotStatus(SnapshotJoinVO snapshot) {
+        String status = snapshot.getStatus().toString();
+        if (snapshot.getDownloadState() == null) {
+            return status;
+        }
+        if (snapshot.getDownloadState() != VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
+            status = "Processing";
+            if (snapshot.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
+                status = snapshot.getDownloadPercent() + "% Downloaded";
+            } else if (snapshot.getErrorString() == null) {
+                status = snapshot.getStoreState().toString();
+            } else {
+                status = snapshot.getErrorString();
+            }
+        }
+        return status;
+    }
+
+    @Override
+    public SnapshotResponse newSnapshotResponse(ResponseObject.ResponseView view, boolean isShowUnique, SnapshotJoinVO snapshot) {
+        final Account caller = CallContext.current().getCallingAccount();
+        SnapshotResponse snapshotResponse = new SnapshotResponse();
+        snapshotResponse.setId(snapshot.getUuid());
+        // populate owner.
+        ApiResponseHelper.populateOwner(snapshotResponse, snapshot);
+        if (snapshot.getVolumeId() != null) {
+            snapshotResponse.setVolumeId(snapshot.getVolumeUuid());
+            snapshotResponse.setVolumeName(snapshot.getVolumeName());
+            snapshotResponse.setVolumeType(snapshot.getVolumeType().name());
+            snapshotResponse.setVirtualSize(snapshot.getVolumeSize());
+        }
+        snapshotResponse.setZoneId(snapshot.getDataCenterUuid());
+        snapshotResponse.setZoneName(snapshot.getDataCenterName());
+        snapshotResponse.setCreated(snapshot.getCreated());
+        snapshotResponse.setName(snapshot.getName());
+        String intervalType = null;
+        if (snapshot.getSnapshotType() >= 0 && snapshot.getSnapshotType() < Snapshot.Type.values().length) {
+            intervalType = Snapshot.Type.values()[snapshot.getSnapshotType()].name();
+        }
+        snapshotResponse.setIntervalType(intervalType);
+        snapshotResponse.setState(snapshot.getStatus());
+        snapshotResponse.setLocationType(snapshot.getLocationType() != null ? snapshot.getLocationType().name() : null);
+        if (!isShowUnique) {
+            snapshotResponse.setDatastoreState(snapshot.getStoreState() != null ? snapshot.getStoreState().name() : null);
+            if (view.equals(ResponseObject.ResponseView.Full)) {
+                snapshotResponse.setDatastoreId(snapshot.getStoreUuid());
+                snapshotResponse.setDatastoreName(snapshot.getStoreName());
+                snapshotResponse.setDatastoreType(snapshot.getStoreRole() != null ? snapshot.getStoreRole().name() : null);
+            }
+            // If the user is an 'Admin' or 'the owner of template' or template belongs to a project, add the template download status
+            if (view == ResponseObject.ResponseView.Full ||
+                    snapshot.getAccountId() == caller.getId() ||
+                    snapshot.getAccountType() == Account.Type.PROJECT) {
+                String status = getSnapshotStatus(snapshot);
+                if (status != null) {
+                    snapshotResponse.setStatus(status);
+                }
+            }
+            Map<String, String> downloadDetails = new HashMap<>();
+            downloadDetails.put("downloadPercent", Integer.toString(snapshot.getDownloadPercent()));
+            downloadDetails.put("downloadState", (snapshot.getDownloadState() != null ? snapshot.getDownloadState().toString() : ""));
+            snapshotResponse.setDownloadDetails(downloadDetails);
+        }
+        setSnapshotInfoDetailsInResponse(snapshot, snapshotResponse, isShowUnique);
+        setSnapshotResponse(snapshotResponse, snapshot);
+
+        snapshotResponse.setObjectName("snapshot");
+        return snapshotResponse;
+    }
+
+    @Override
+    public SnapshotResponse setSnapshotResponse(SnapshotResponse snapshotResponse, SnapshotJoinVO snapshot) {
+        // update tag information
+        long tag_id = snapshot.getTagId();
+        if (tag_id > 0) {
+            addTagInformation(snapshot, snapshotResponse);
+        }
+
+        if (snapshotResponse.hasAnnotation() == null) {
+            snapshotResponse.setHasAnnotation(annotationDao.hasAnnotations(snapshot.getUuid(), AnnotationService.EntityType.SNAPSHOT.name(),
+                    accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+        }
+        return snapshotResponse;
+    }
+
+    @Override
+    public Pair<List<SnapshotJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<SnapshotJoinVO> sc, final Filter filter) {
+        List<SnapshotJoinVO> objects = searchIncludingRemoved(sc, filter, null, false);
+        Integer count = getDistinctCount(sc);
+        return new Pair<>(objects, count);
+    }
+
+    @Override
+    public List<SnapshotJoinVO> searchBySnapshotStorePair(String... pairs) {
+        // set detail batch query size
+        int DETAILS_BATCH_SIZE = 2000;
+        String batchCfg = configDao.getValue("detail.batch.query.size");
+        if (batchCfg != null) {
+            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
+        }
+        // query details by batches
+        Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", QueryService.SortKeyAscending.value(), null, null);
+        List<SnapshotJoinVO> uvList = new ArrayList<>();
+        // query details by batches
+        int curr_index = 0;
+        if (pairs.length > DETAILS_BATCH_SIZE) {
+            while ((curr_index + DETAILS_BATCH_SIZE) <= pairs.length) {
+                String[] labels = new String[DETAILS_BATCH_SIZE];
+                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
+                    labels[k] = pairs[j];
+                }
+                SearchCriteria<SnapshotJoinVO> sc = snapshotStorePairSearch.create();
+                sc.setParameters("snapshotStoreIdIN", labels);
+                List<SnapshotJoinVO> snaps = searchIncludingRemoved(sc, searchFilter, null, false);
+                if (snaps != null) {
+                    uvList.addAll(snaps);
+                }
+                curr_index += DETAILS_BATCH_SIZE;
+            }
+        }
+        if (curr_index < pairs.length) {
+            int batch_size = (pairs.length - curr_index);
+            String[] labels = new String[batch_size];
+            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
+                labels[k] = pairs[j];
+            }
+            SearchCriteria<SnapshotJoinVO> sc = snapshotStorePairSearch.create();
+            sc.setParameters("snapshotStoreIdIN", labels);
+            List<SnapshotJoinVO> vms = searchIncludingRemoved(sc, searchFilter, null, false);
+            if (vms != null) {
+                uvList.addAll(vms);
+            }
+        }
+        return uvList;
+    }
+
+    @Override
+    public List<SnapshotJoinVO> findByDistinctIds(Long zoneId, Long... ids) {
+        if (ids == null || ids.length == 0) {
+            return new ArrayList<>();
+        }
+
+        Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", QueryService.SortKeyAscending.value(), null, null);
+
+        SearchCriteria<SnapshotJoinVO> sc = snapshotIdsSearch.create();
+        if (zoneId != null) {
+            sc.setParameters("zoneId", zoneId);
+        }
+        sc.setParameters("idsIN", ids);
+        return searchIncludingRemoved(sc, searchFilter, null, false);
+    }
+}
diff --git a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java
index 8765921..26ee3f0 100644
--- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java
@@ -19,14 +19,12 @@
 import java.util.List;
 
 import com.cloud.storage.ScopeType;
-import com.cloud.storage.StoragePoolStatus;
-import com.cloud.utils.Pair;
-import com.cloud.utils.db.Filter;
 import org.apache.cloudstack.api.response.StoragePoolResponse;
 
 import com.cloud.api.query.vo.StoragePoolJoinVO;
 import com.cloud.storage.StoragePool;
 import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 
 public interface StoragePoolJoinDao extends GenericDao<StoragePoolJoinVO, Long> {
 
@@ -42,6 +40,6 @@
 
     List<StoragePoolJoinVO> searchByIds(Long... spIds);
 
-    Pair<List<StoragePoolJoinVO>, Integer> searchAndCount(Long storagePoolId, String storagePoolName, Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType, StoragePoolStatus status, String keyword, Filter searchFilter);
+    List<StoragePoolVO> findStoragePoolByScopeAndRuleTags(Long datacenterId, Long podId, Long clusterId, ScopeType scopeType, List<String> tags);
 
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
index 527cc94..f3b832d 100644
--- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
@@ -23,12 +23,10 @@
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.Storage;
 import com.cloud.storage.StoragePool;
-import com.cloud.storage.StoragePoolStatus;
 import com.cloud.storage.StorageStats;
+import com.cloud.storage.VolumeApiServiceImpl;
 import com.cloud.user.AccountManager;
-import com.cloud.utils.Pair;
 import com.cloud.utils.StringUtils;
-import com.cloud.utils.db.Filter;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
@@ -44,6 +42,7 @@
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -71,10 +70,13 @@
     @Inject
     private StoragePoolDetailsDao storagePoolDetailsDao;
 
+
     private final SearchBuilder<StoragePoolJoinVO> spSearch;
 
     private final SearchBuilder<StoragePoolJoinVO> spIdSearch;
 
+    private final SearchBuilder<StoragePoolJoinVO> findByDatacenterAndScopeSb;
+
     protected StoragePoolJoinDaoImpl() {
 
         spSearch = createSearchBuilder();
@@ -85,6 +87,15 @@
         spIdSearch.and("id", spIdSearch.entity().getId(), SearchCriteria.Op.EQ);
         spIdSearch.done();
 
+        findByDatacenterAndScopeSb = createSearchBuilder();
+        findByDatacenterAndScopeSb.and("zoneId", findByDatacenterAndScopeSb.entity().getZoneId(), SearchCriteria.Op.EQ);
+        findByDatacenterAndScopeSb.and("clusterId", findByDatacenterAndScopeSb.entity().getClusterId(), SearchCriteria.Op.EQ);
+        findByDatacenterAndScopeSb.and("podId", findByDatacenterAndScopeSb.entity().getPodId(), SearchCriteria.Op.EQ);
+        findByDatacenterAndScopeSb.and("scope", findByDatacenterAndScopeSb.entity().getScope(), SearchCriteria.Op.EQ);
+        findByDatacenterAndScopeSb.and("status", findByDatacenterAndScopeSb.entity().getStatus(), SearchCriteria.Op.EQ);
+        findByDatacenterAndScopeSb.and("is_tag_a_rule", findByDatacenterAndScopeSb.entity().getIsTagARule(), SearchCriteria.Op.EQ);
+        findByDatacenterAndScopeSb.done();
+
         _count = "select count(distinct id) from storage_pool_view WHERE ";
     }
 
@@ -110,7 +121,7 @@
             poolResponse.setScope(pool.getScope().toString());
         }
         if (pool.getHypervisor() != null) {
-            poolResponse.setHypervisor(pool.getHypervisor().toString());
+            poolResponse.setHypervisor(pool.getHypervisor().getHypervisorDisplayName());
         }
 
         StoragePoolDetailVO poolType = storagePoolDetailsDao.findDetail(pool.getId(), "pool_type");
@@ -149,6 +160,7 @@
         poolResponse.setClusterName(pool.getClusterName());
         poolResponse.setProvider(pool.getStorageProviderName());
         poolResponse.setTags(pool.getTag());
+        poolResponse.setIsTagARule(pool.getIsTagARule());
         poolResponse.setOverProvisionFactor(Double.toString(CapacityManager.StorageOverprovisioningFactor.valueIn(pool.getId())));
 
         // set async job
@@ -201,7 +213,7 @@
         poolResponse.setCreated(pool.getCreated());
         poolResponse.setScope(pool.getScope().toString());
         if (pool.getHypervisor() != null) {
-            poolResponse.setHypervisor(pool.getHypervisor().toString());
+            poolResponse.setHypervisor(pool.getHypervisor().getHypervisorDisplayName());
         }
 
         long allocatedSize = pool.getUsedCapacity();
@@ -221,6 +233,7 @@
         poolResponse.setClusterName(pool.getClusterName());
         poolResponse.setProvider(pool.getStorageProviderName());
         poolResponse.setTags(pool.getTag());
+        poolResponse.setIsTagARule(pool.getIsTagARule());
 
         // set async job
         poolResponse.setJobId(pool.getJobUuid());
@@ -296,74 +309,45 @@
     }
 
     @Override
-    public Pair<List<StoragePoolJoinVO>, Integer> searchAndCount(Long storagePoolId, String storagePoolName, Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType, StoragePoolStatus status, String keyword, Filter searchFilter) {
-        SearchCriteria<StoragePoolJoinVO> sc = createStoragePoolSearchCriteria(storagePoolId, storagePoolName, zoneId, path, podId, clusterId, address, scopeType, status, keyword);
-        return searchAndCount(sc, searchFilter);
-    }
-
-    private SearchCriteria<StoragePoolJoinVO> createStoragePoolSearchCriteria(Long storagePoolId, String storagePoolName, Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType, StoragePoolStatus status, String keyword) {
-        SearchBuilder<StoragePoolJoinVO> sb = createSearchBuilder();
-        sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getId()); // select distinct
-        // ids
-        sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
-        sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
-        sb.and("path", sb.entity().getPath(), SearchCriteria.Op.EQ);
-        sb.and("dataCenterId", sb.entity().getZoneId(), SearchCriteria.Op.EQ);
-        sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ);
-        sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ);
-        sb.and("hostAddress", sb.entity().getHostAddress(), SearchCriteria.Op.EQ);
-        sb.and("scope", sb.entity().getScope(), SearchCriteria.Op.EQ);
-        sb.and("status", sb.entity().getStatus(), SearchCriteria.Op.EQ);
-        sb.and("parent", sb.entity().getParent(), SearchCriteria.Op.EQ);
-
-        SearchCriteria<StoragePoolJoinVO> sc = sb.create();
-
-        if (keyword != null) {
-            SearchCriteria<StoragePoolJoinVO> ssc = createSearchCriteria();
-            ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-            ssc.addOr("poolType", SearchCriteria.Op.LIKE, "%" + keyword + "%");
-
-            sc.addAnd("name", SearchCriteria.Op.SC, ssc);
-        }
-
-        if (storagePoolId != null) {
-            sc.setParameters("id", storagePoolId);
-        }
-
-        if (storagePoolName != null) {
-            sc.setParameters("name", storagePoolName);
-        }
-
-        if (path != null) {
-            sc.setParameters("path", path);
-        }
-        if (zoneId != null) {
-            sc.setParameters("dataCenterId", zoneId);
-        }
-        if (podId != null) {
-            SearchCriteria<StoragePoolJoinVO> ssc = createSearchCriteria();
-            ssc.addOr("podId", SearchCriteria.Op.EQ, podId);
-            ssc.addOr("podId", SearchCriteria.Op.NULL);
-
-            sc.addAnd("podId", SearchCriteria.Op.SC, ssc);
-        }
-        if (address != null) {
-            sc.setParameters("hostAddress", address);
+    public List<StoragePoolVO> findStoragePoolByScopeAndRuleTags(Long datacenterId, Long podId, Long clusterId, ScopeType scopeType, List<String> tags) {
+        SearchCriteria<StoragePoolJoinVO> sc =  findByDatacenterAndScopeSb.create();
+        if (datacenterId != null) {
+            sc.setParameters("zoneId", datacenterId);
         }
         if (clusterId != null) {
-            SearchCriteria<StoragePoolJoinVO> ssc = createSearchCriteria();
-            ssc.addOr("clusterId", SearchCriteria.Op.EQ, clusterId);
-            ssc.addOr("clusterId", SearchCriteria.Op.NULL);
+            sc.setParameters("clusterId", clusterId);
+        }
+        if (podId != null) {
+            sc.setParameters("podId", podId);
+        }
 
-            sc.addAnd("clusterId", SearchCriteria.Op.SC, ssc);
+        sc.setParameters("scope", scopeType);
+        sc.setParameters("status", "Up");
+        sc.setParameters("is_tag_a_rule", true);
+        List<StoragePoolJoinVO> storagePools = search(sc, null, false, false);
+
+        List<StoragePoolVO> filteredPools = new ArrayList<>();
+
+        StringBuilder injectableTagsBuilder = new StringBuilder();
+        for (String tag : tags) {
+            injectableTagsBuilder.append(tag).append(",");
         }
-        if (scopeType != null) {
-            sc.setParameters("scope", scopeType.toString());
+        if (!tags.isEmpty()) {
+            injectableTagsBuilder.deleteCharAt(injectableTagsBuilder.length() - 1);
         }
-        if (status != null) {
-            sc.setParameters("status", status.toString());
+        String injectableTag = injectableTagsBuilder.toString();
+
+        for (StoragePoolJoinVO storagePoolJoinVO : storagePools) {
+            if (TagAsRuleHelper.interpretTagAsRule(storagePoolJoinVO.getTag(), injectableTag, VolumeApiServiceImpl.storageTagRuleExecutionTimeout.value())) {
+                StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePoolJoinVO.getId());
+                if (storagePoolVO != null) {
+                    filteredPools.add(storagePoolVO);
+                } else {
+                    s_logger.warn(String.format("Unable to find Storage Pool [%s] in the DB.", storagePoolJoinVO.getUuid()));
+                }
+            }
         }
-        sc.setParameters("parent", 0);
-        return sc;
+        return filteredPools;
     }
+
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDao.java
index 58cb886..a7b82e4 100644
--- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDao.java
@@ -34,7 +34,7 @@
 
     TemplateResponse newTemplateResponse(EnumSet<ApiConstants.DomainDetails> detailsView, ResponseView view, TemplateJoinVO tmpl);
 
-    TemplateResponse newIsoResponse(TemplateJoinVO tmpl);
+    TemplateResponse newIsoResponse(TemplateJoinVO tmpl, ResponseView view);
 
     TemplateResponse newUpdateResponse(TemplateJoinVO tmpl);
 
@@ -48,6 +48,8 @@
 
     List<TemplateJoinVO> listActiveTemplates(long storeId);
 
+    List<TemplateJoinVO> listPublicTemplates();
+
     Pair<List<TemplateJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<TemplateJoinVO> sc, final Filter filter);
 
     List<TemplateJoinVO> findByDistinctIds(Long... ids);
diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
index 8195592..5a0c199 100644
--- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
@@ -17,23 +17,36 @@
 package com.cloud.api.query.dao;
 
 import java.util.ArrayList;
+import java.util.Collections;
 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.stream.Collectors;
 
 import javax.inject.Inject;
 
 import com.cloud.deployasis.DeployAsIsConstants;
 import com.cloud.deployasis.TemplateDeployAsIsDetailVO;
 import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.storage.dao.VnfTemplateDetailsDao;
+import com.cloud.storage.dao.VnfTemplateNicDao;
 import com.cloud.user.dao.UserDataDao;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.response.VnfNicResponse;
+import org.apache.cloudstack.api.response.VnfTemplateResponse;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.utils.security.DigestHelper;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -86,6 +99,10 @@
     @Inject
     private ImageStoreDao dataStoreDao;
     @Inject
+    private PrimaryDataStoreDao primaryDataStoreDao;
+    @Inject
+    private VMTemplatePoolDao templatePoolDao;
+    @Inject
     private VMTemplateDetailsDao _templateDetailsDao;
     @Inject
     private TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao;
@@ -93,6 +110,10 @@
     private AnnotationDao annotationDao;
     @Inject
     private UserDataDao userDataDao;
+    @Inject
+    VnfTemplateDetailsDao vnfTemplateDetailsDao;
+    @Inject
+    VnfTemplateNicDao vnfTemplateNicDao;
 
     private final SearchBuilder<TemplateJoinVO> tmpltIdPairSearch;
 
@@ -104,6 +125,8 @@
 
     private final SearchBuilder<TemplateJoinVO> activeTmpltSearch;
 
+    private final SearchBuilder<TemplateJoinVO> publicTmpltSearch;
+
     protected TemplateJoinDaoImpl() {
 
         tmpltIdPairSearch = createSearchBuilder();
@@ -137,6 +160,10 @@
         activeTmpltSearch.cp();
         activeTmpltSearch.done();
 
+        publicTmpltSearch = createSearchBuilder();
+        publicTmpltSearch.and("public", publicTmpltSearch.entity().isPublicTemplate(), SearchCriteria.Op.EQ);
+        publicTmpltSearch.done();
+
         // select distinct pair (template_id, zone_id)
         _count = "select count(distinct temp_zone_pair) from template_view WHERE ";
     }
@@ -171,20 +198,50 @@
         List<ImageStoreVO> storesInZone = dataStoreDao.listStoresByZoneId(template.getDataCenterId());
         Long[] storeIds = storesInZone.stream().map(ImageStoreVO::getId).toArray(Long[]::new);
         List<TemplateDataStoreVO> templatesInStore = _templateStoreDao.listByTemplateNotBypassed(template.getId(), storeIds);
+
+        List<Long> dataStoreIdList = templatesInStore.stream().map(TemplateDataStoreVO::getDataStoreId).collect(Collectors.toList());
+        Map<Long, ImageStoreVO> imageStoreMap = dataStoreDao.listByIds(dataStoreIdList).stream().collect(Collectors.toMap(ImageStoreVO::getId, imageStore -> imageStore));
+
         List<Map<String, String>> downloadProgressDetails = new ArrayList<>();
         HashMap<String, String> downloadDetailInImageStores = null;
         for (TemplateDataStoreVO templateInStore : templatesInStore) {
             downloadDetailInImageStores = new HashMap<>();
-            ImageStoreVO datastore = dataStoreDao.findById(templateInStore.getDataStoreId());
-            if (datastore != null) {
-                downloadDetailInImageStores.put("datastore", datastore.getName());
+            ImageStoreVO imageStore = imageStoreMap.get(templateInStore.getDataStoreId());
+            if (imageStore != null) {
+                downloadDetailInImageStores.put("datastore", imageStore.getName());
+                if (view.equals(ResponseView.Full)) {
+                    downloadDetailInImageStores.put("datastoreId", imageStore.getUuid());
+                    downloadDetailInImageStores.put("datastoreRole", imageStore.getRole().name());
+                }
                 downloadDetailInImageStores.put("downloadPercent", Integer.toString(templateInStore.getDownloadPercent()));
                 downloadDetailInImageStores.put("downloadState", (templateInStore.getDownloadState() != null ? templateInStore.getDownloadState().toString() : ""));
                 downloadProgressDetails.add(downloadDetailInImageStores);
             }
         }
 
-        TemplateResponse templateResponse = new TemplateResponse();
+        List<StoragePoolVO> poolsInZone = primaryDataStoreDao.listByDataCenterId(template.getDataCenterId());
+        List<Long> poolIds = poolsInZone.stream().map(StoragePoolVO::getId).collect(Collectors.toList());
+        List<VMTemplateStoragePoolVO> templatesInPool = templatePoolDao.listByTemplateId(template.getId(), poolIds);
+
+        dataStoreIdList = templatesInStore.stream().map(TemplateDataStoreVO::getDataStoreId).collect(Collectors.toList());
+        Map<Long, StoragePoolVO> storagePoolMap = primaryDataStoreDao.listByIds(dataStoreIdList).stream().collect(Collectors.toMap(StoragePoolVO::getId, store -> store));
+
+        for (VMTemplateStoragePoolVO templateInPool : templatesInPool) {
+            downloadDetailInImageStores = new HashMap<>();
+            StoragePoolVO storagePool = storagePoolMap.get(templateInPool.getDataStoreId());
+            if (storagePool != null) {
+                downloadDetailInImageStores.put("datastore", storagePool.getName());
+                if (view.equals(ResponseView.Full)) {
+                    downloadDetailInImageStores.put("datastoreId", storagePool.getUuid());
+                    downloadDetailInImageStores.put("datastoreRole", DataStoreRole.Primary.name());
+                }
+                downloadDetailInImageStores.put("downloadPercent", Integer.toString(templateInPool.getDownloadPercent()));
+                downloadDetailInImageStores.put("downloadState", (templateInPool.getDownloadState() != null ? templateInPool.getDownloadState().toString() : ""));
+                downloadProgressDetails.add(downloadDetailInImageStores);
+            }
+        }
+
+        TemplateResponse templateResponse = initTemplateResponse(template);
         templateResponse.setDownloadProgress(downloadProgressDetails);
         templateResponse.setId(template.getUuid());
         templateResponse.setName(template.getName());
@@ -208,7 +265,7 @@
             templateResponse.setTemplateType(template.getTemplateType().toString());
         }
 
-        templateResponse.setHypervisor(template.getHypervisorType().toString());
+        templateResponse.setHypervisor(template.getHypervisorType().getHypervisorDisplayName());
 
         templateResponse.setOsTypeId(template.getGuestOSUuid());
         templateResponse.setOsTypeName(template.getGuestOSName());
@@ -299,10 +356,29 @@
             templateResponse.setUserDataParams(template.getUserDataParams());
             templateResponse.setUserDataPolicy(template.getUserDataPolicy());
         }
+
         templateResponse.setObjectName("template");
         return templateResponse;
     }
 
+    private TemplateResponse initTemplateResponse(TemplateJoinVO template) {
+        TemplateResponse templateResponse = new TemplateResponse();
+        if (Storage.TemplateType.VNF.equals(template.getTemplateType())) {
+            VnfTemplateResponse vnfTemplateResponse = new VnfTemplateResponse();
+            List<VnfTemplateNicVO> nics = vnfTemplateNicDao.listByTemplateId(template.getId());
+            for (VnfTemplateNicVO nic : nics) {
+                vnfTemplateResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.isRequired(), nic.isManagement(), nic.getDescription()));
+            }
+            List<VnfTemplateDetailVO> details = vnfTemplateDetailsDao.listDetails(template.getId());
+            Collections.sort(details, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName()));
+            for (VnfTemplateDetailVO detail : details) {
+                vnfTemplateResponse.addVnfDetail(detail.getName(), detail.getValue());
+            }
+            templateResponse = vnfTemplateResponse;
+        }
+        return templateResponse;
+    }
+
     private void setDeployAsIsDetails(TemplateJoinVO template, TemplateResponse templateResponse) {
         if (template.isDeployAsIs()) {
             List<TemplateDeployAsIsDetailVO> deployAsIsDetails = templateDeployAsIsDetailsDao.listDetails(template.getId());
@@ -320,7 +396,7 @@
     // compared to listTemplates and listIsos.
     @Override
     public TemplateResponse newUpdateResponse(TemplateJoinVO result) {
-        TemplateResponse response = new TemplateResponse();
+        TemplateResponse response = initTemplateResponse(result);
         response.setId(result.getUuid());
         response.setName(result.getName());
         response.setDisplayText(result.getDisplayText());
@@ -330,7 +406,7 @@
         response.setOsTypeId(result.getGuestOSUuid());
         response.setOsTypeName(result.getGuestOSName());
         response.setBootable(result.isBootable());
-        response.setHypervisor(result.getHypervisorType().toString());
+        response.setHypervisor(result.getHypervisorType().getHypervisorDisplayName());
         response.setDynamicallyScalable(result.isDynamicallyScalable());
 
         // populate owner.
@@ -392,7 +468,7 @@
     }
 
     @Override
-    public TemplateResponse newIsoResponse(TemplateJoinVO iso) {
+    public TemplateResponse newIsoResponse(TemplateJoinVO iso, ResponseView view) {
 
         TemplateResponse isoResponse = new TemplateResponse();
         isoResponse.setId(iso.getUuid());
@@ -455,6 +531,43 @@
                 isoResponse.setStatus("Successfully Installed");
             }
             isoResponse.setUrl(iso.getUrl());
+            List<TemplateDataStoreVO> isosInStore = _templateStoreDao.listByTemplateNotBypassed(iso.getId());
+            List<Map<String, String>> downloadProgressDetails = new ArrayList<>();
+            HashMap<String, String> downloadDetailInImageStores = null;
+            for (TemplateDataStoreVO isoInStore : isosInStore) {
+                downloadDetailInImageStores = new HashMap<>();
+                ImageStoreVO imageStore = dataStoreDao.findById(isoInStore.getDataStoreId());
+                if (imageStore != null) {
+                    downloadDetailInImageStores.put("datastore", imageStore.getName());
+                    if (view.equals(ResponseView.Full)) {
+                        downloadDetailInImageStores.put("datastoreId", imageStore.getUuid());
+                        downloadDetailInImageStores.put("datastoreRole", imageStore.getRole().name());
+                    }
+                    downloadDetailInImageStores.put("downloadPercent", Integer.toString(isoInStore.getDownloadPercent()));
+                    downloadDetailInImageStores.put("downloadState", (isoInStore.getDownloadState() != null ? isoInStore.getDownloadState().toString() : ""));
+                    downloadProgressDetails.add(downloadDetailInImageStores);
+                }
+            }
+
+            List<StoragePoolVO> poolsInZone = primaryDataStoreDao.listByDataCenterId(iso.getDataCenterId());
+            List<Long> poolIds = poolsInZone.stream().map(StoragePoolVO::getId).collect(Collectors.toList());
+            List<VMTemplateStoragePoolVO> isosInPool = templatePoolDao.listByTemplateId(iso.getId(), poolIds);
+
+            for (VMTemplateStoragePoolVO isoInPool : isosInPool) {
+                downloadDetailInImageStores = new HashMap<>();
+                StoragePoolVO storagePool = primaryDataStoreDao.findById(isoInPool.getDataStoreId());
+                if (storagePool != null) {
+                    downloadDetailInImageStores.put("datastore", storagePool.getName());
+                    if (view.equals(ResponseView.Full)) {
+                        downloadDetailInImageStores.put("datastoreId", storagePool.getUuid());
+                        downloadDetailInImageStores.put("datastoreRole", DataStoreRole.Primary.name());
+                    }
+                    downloadDetailInImageStores.put("downloadPercent", Integer.toString(isoInPool.getDownloadPercent()));
+                    downloadDetailInImageStores.put("downloadState", (isoInPool.getDownloadState() != null ? isoInPool.getDownloadState().toString() : ""));
+                    downloadProgressDetails.add(downloadDetailInImageStores);
+                }
+            }
+            isoResponse.setDownloadProgress(downloadProgressDetails);
         }
 
         if (iso.getDataCenterId() > 0) {
@@ -573,6 +686,13 @@
     }
 
     @Override
+    public List<TemplateJoinVO> listPublicTemplates() {
+        SearchCriteria<TemplateJoinVO> sc = publicTmpltSearch.create();
+        sc.setParameters("public", Boolean.TRUE);
+        return listBy(sc);
+    }
+
+    @Override
     public Pair<List<TemplateJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<TemplateJoinVO> sc, final Filter filter) {
         List<TemplateJoinVO> objects = searchIncludingRemoved(sc, filter, null, false);
         Integer count = getCountIncludingRemoved(sc);
diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java
index 652f51b..6356add 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java
@@ -19,6 +19,7 @@
 import java.util.List;
 import java.util.Set;
 
+import com.cloud.vm.VirtualMachine;
 import org.apache.cloudstack.api.ApiConstants.VMDetails;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.response.UserVmResponse;
@@ -37,6 +38,8 @@
 
     List<UserVmJoinVO> newUserVmView(UserVm... userVms);
 
+    List<UserVmJoinVO> newUserVmView(VirtualMachine... vms);
+
     List<UserVmJoinVO> searchByIds(Long... ids);
 
     List<UserVmJoinVO> listActiveByIsoId(Long isoId);
diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index 7f376b1..e5cc9ee 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -18,6 +18,7 @@
 
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
@@ -27,7 +28,6 @@
 
 import javax.inject.Inject;
 
-import com.cloud.storage.Volume;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
@@ -39,6 +39,7 @@
 import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.api.response.VnfNicResponse;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.query.QueryService;
@@ -52,11 +53,18 @@
 import com.cloud.api.query.vo.UserVmJoinVO;
 import com.cloud.gpu.GPU;
 import com.cloud.host.ControlState;
+import com.cloud.network.IpAddress;
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.service.ServiceOfferingDetailsVO;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.GuestOS;
+import com.cloud.storage.Storage.TemplateType;
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.storage.Volume;
+import com.cloud.storage.dao.VnfTemplateDetailsDao;
+import com.cloud.storage.dao.VnfTemplateNicDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.User;
@@ -70,6 +78,7 @@
 import com.cloud.utils.net.Dhcp;
 import com.cloud.vm.UserVmDetailVO;
 import com.cloud.vm.UserVmManager;
+import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachine.State;
 import com.cloud.vm.VmStats;
 import com.cloud.vm.dao.NicExtraDhcpOptionDao;
@@ -96,6 +105,10 @@
     private VpcDao vpcDao;
     @Inject
     UserStatisticsDao userStatsDao;
+    @Inject
+    VnfTemplateDetailsDao vnfTemplateDetailsDao;
+    @Inject
+    VnfTemplateNicDao vnfTemplateNicDao;
 
     private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
     private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
@@ -130,7 +143,7 @@
         UserVmResponse userVmResponse = new UserVmResponse();
 
         if (userVm.getHypervisorType() != null) {
-            userVmResponse.setHypervisor(userVm.getHypervisorType().toString());
+            userVmResponse.setHypervisor(userVm.getHypervisorType().getHypervisorDisplayName());
         }
         userVmResponse.setId(userVm.getUuid());
         userVmResponse.setName(userVm.getName());
@@ -184,6 +197,7 @@
             userVmResponse.setTemplateName(userVm.getTemplateName());
             userVmResponse.setTemplateDisplayText(userVm.getTemplateDisplayText());
             userVmResponse.setPasswordEnabled(userVm.isPasswordEnabled());
+            userVmResponse.setTemplateType(userVm.getTemplateType().toString());
         }
         if (details.contains(VMDetails.all) || details.contains(VMDetails.iso)) {
             userVmResponse.setIsoId(userVm.getIsoUuid());
@@ -321,6 +335,12 @@
                     }
                     nicResponse.setSecondaryIps(ipList);
                 }
+                IpAddress publicIp = ApiDBUtils.findIpByAssociatedVmIdAndNetworkId(userVm.getId(), userVm.getNetworkId());
+                if (publicIp != null) {
+                    nicResponse.setPublicIpId(publicIp.getUuid());
+                    nicResponse.setPublicIp(publicIp.getAddress().toString());
+                }
+
                 nicResponse.setObjectName("nic");
 
                 List<NicExtraDhcpOptionResponse> nicExtraDhcpOptionResponses = _nicExtraDhcpOptionDao.listByNicId(nic_id).stream()
@@ -420,9 +440,25 @@
 
         addVmRxTxDataToResponse(userVm, userVmResponse);
 
+        if (TemplateType.VNF.equals(userVm.getTemplateType()) && (details.contains(VMDetails.all) || details.contains(VMDetails.vnfnics))) {
+            addVnfInfoToserVmResponse(userVm, userVmResponse);
+        }
+
         return userVmResponse;
     }
 
+    private void addVnfInfoToserVmResponse(UserVmJoinVO userVm, UserVmResponse userVmResponse) {
+        List<VnfTemplateNicVO> vnfNics = vnfTemplateNicDao.listByTemplateId(userVm.getTemplateId());
+        for (VnfTemplateNicVO nic : vnfNics) {
+            userVmResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.isRequired(), nic.isManagement(), nic.getDescription()));
+        }
+        List<VnfTemplateDetailVO> vnfDetails = vnfTemplateDetailsDao.listDetails(userVm.getTemplateId());
+        Collections.sort(vnfDetails, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName()));
+        for (VnfTemplateDetailVO detail : vnfDetails) {
+            userVmResponse.addVnfDetail(detail.getName(), detail.getValue());
+        }
+    }
+
     private void addVmRxTxDataToResponse(UserVmJoinVO userVm, UserVmResponse userVmResponse) {
         Long bytesReceived = 0L;
         Long bytesSent = 0L;
@@ -520,6 +556,11 @@
                 }
                 nicResponse.setSecondaryIps(ipList);
             }
+            IpAddress publicIp = ApiDBUtils.findIpByAssociatedVmIdAndNetworkId(uvo.getId(), uvo.getNetworkId());
+            if (publicIp != null) {
+                nicResponse.setPublicIpId(publicIp.getUuid());
+                nicResponse.setPublicIp(publicIp.getAddress().toString());
+            }
 
             /* 18: extra dhcp options */
             nicResponse.setObjectName("nic");
@@ -626,4 +667,18 @@
         return uvms;
     }
 
+    @Override
+    public List<UserVmJoinVO> newUserVmView(VirtualMachine... vms) {
+
+        Hashtable<Long,VirtualMachine> userVmDataHash = new Hashtable<>();
+        for (VirtualMachine vm : vms) {
+            if (!userVmDataHash.containsKey(vm.getId())) {
+                userVmDataHash.put(vm.getId(), vm);
+            }
+        }
+
+        Set<Long> vmIdSet = userVmDataHash.keySet();
+        return searchByIds(vmIdSet.toArray(new Long[vmIdSet.size()]));
+    }
+
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
index 46a1b20..8fcad6e 100644
--- a/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
@@ -21,6 +21,7 @@
 
 import javax.inject.Inject;
 
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.offering.DiskOffering;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
@@ -147,8 +148,10 @@
             volResponse.setSize(volume.getVolumeStoreSize());
             volResponse.setCreated(volume.getCreatedOnStore());
 
-            if (view == ResponseView.Full)
-                volResponse.setHypervisor(ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), volume.getFormat()).toString());
+            if (view == ResponseView.Full) {
+                Hypervisor.HypervisorType hypervisorTypeFromFormat = ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), volume.getFormat());
+                volResponse.setHypervisor(hypervisorTypeFromFormat.getHypervisorDisplayName());
+            }
             if (volume.getDownloadState() != Status.DOWNLOADED) {
                 String volumeStatus = "Processing";
                 if (volume.getDownloadState() == Status.DOWNLOAD_IN_PROGRESS) {
@@ -209,9 +212,10 @@
         if (view == ResponseView.Full) {
             if (volume.getState() != Volume.State.UploadOp) {
                 if (volume.getHypervisorType() != null) {
-                    volResponse.setHypervisor(volume.getHypervisorType().toString());
+                    volResponse.setHypervisor(volume.getHypervisorType().getHypervisorDisplayName());
                 } else {
-                    volResponse.setHypervisor(ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), volume.getFormat()).toString());
+                    Hypervisor.HypervisorType hypervisorTypeFromFormat = ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), volume.getFormat());
+                    volResponse.setHypervisor(hypervisorTypeFromFormat.getHypervisorDisplayName());
                 }
             }
             Long poolId = volume.getPoolId();
diff --git a/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java
index a4db864..cee5526 100644
--- a/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java
@@ -75,6 +75,9 @@
     @Column(name = "job_cmd")
     private String cmd;
 
+    @Column(name = "job_executing_msid")
+    private Long executingMsid;
+
     @Column(name = "job_status")
     private int status;
 
@@ -214,6 +217,10 @@
         return null;
     }
 
+    public Long getExecutingMsid() {
+        return executingMsid;
+    }
+
     @Override
     public String getProjectUuid() {
         // TODO Auto-generated method stub
diff --git a/server/src/main/java/com/cloud/api/query/vo/HostJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/HostJoinVO.java
index bb3b693..78a4542 100644
--- a/server/src/main/java/com/cloud/api/query/vo/HostJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/HostJoinVO.java
@@ -172,6 +172,9 @@
     @Column(name = "tag")
     private String tag;
 
+    @Column(name = "is_tag_a_rule")
+    private Boolean isTagARule;
+
     @Column(name = "memory_used_capacity")
     private long memUsedCapacity;
 
@@ -388,6 +391,10 @@
         return tag;
     }
 
+    public Boolean getIsTagARule() {
+        return isTagARule;
+    }
+
     public String getAnnotation() {
         return annotation;
     }
diff --git a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
index e0fe3d2..01811c8 100644
--- a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
@@ -20,9 +20,12 @@
 
 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 com.cloud.offering.ServiceOffering.State;
 import org.apache.cloudstack.api.Identity;
 import org.apache.cloudstack.api.InternalIdentity;
 
@@ -43,6 +46,10 @@
     @Column(name = "name")
     private String name;
 
+    @Column(name = "state")
+    @Enumerated(value = EnumType.STRING)
+    private State state;
+
     @Column(name = "display_text")
     private String displayText;
 
@@ -231,6 +238,10 @@
         return name;
     }
 
+    public State getState() {
+        return state;
+    }
+
     public String getDisplayText() {
         return displayText;
     }
diff --git a/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java
new file mode 100644
index 0000000..9ec74da
--- /dev/null
+++ b/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java
@@ -0,0 +1,352 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES 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.api.query.vo;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Table;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
+
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import com.cloud.storage.Volume;
+import com.cloud.user.Account;
+import com.cloud.utils.db.GenericDao;
+
+@Entity
+@Table(name = "snapshot_view")
+public class SnapshotJoinVO extends BaseViewWithTagInformationVO implements ControlledViewEntity {
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "status")
+    @Enumerated(value = EnumType.STRING)
+    private Snapshot.State status;
+
+    @Column(name = "disk_offering_id")
+    Long diskOfferingId;
+
+    @Column(name = "snapshot_type")
+    short snapshotType;
+
+    @Column(name = "type_description")
+    String typeDescription;
+
+    @Column(name = "size")
+    long size;
+
+    @Column(name = GenericDao.CREATED_COLUMN)
+    Date created;
+
+    @Column(name = GenericDao.REMOVED_COLUMN)
+    Date removed;
+
+    @Column(name = "location_type")
+    @Enumerated(value = EnumType.STRING)
+    private Snapshot.LocationType locationType;
+
+    @Column(name = "hypervisor_type")
+    @Enumerated(value = EnumType.STRING)
+    Hypervisor.HypervisorType hypervisorType;
+
+    @Column(name = "account_id")
+    private long accountId;
+
+    @Column(name = "account_uuid")
+    private String accountUuid;
+
+    @Column(name = "account_name")
+    private String accountName = null;
+
+    @Column(name = "account_type")
+    @Enumerated(value = EnumType.ORDINAL)
+    private Account.Type accountType;
+
+    @Column(name = "domain_id")
+    private long domainId;
+
+    @Column(name = "domain_uuid")
+    private String domainUuid;
+
+    @Column(name = "domain_name")
+    private String domainName = null;
+
+    @Column(name = "domain_path")
+    private String domainPath = null;
+
+    @Column(name = "project_id")
+    private Long projectId;
+
+    @Column(name = "project_uuid")
+    private String projectUuid;
+
+    @Column(name = "project_name")
+    private String projectName;
+
+    @Column(name = "data_center_id")
+    private Long dataCenterId;
+
+    @Column(name = "data_center_uuid")
+    private String dataCenterUuid;
+
+    @Column(name = "data_center_name")
+    private String dataCenterName;
+
+    @Column(name = "volume_id")
+    private Long volumeId;
+
+    @Column(name = "volume_uuid")
+    private String volumeUuid;
+
+    @Column(name = "volume_name")
+    private String volumeName;
+
+    @Column(name = "volume_type")
+    @Enumerated(EnumType.STRING)
+    Volume.Type volumeType = Volume.Type.UNKNOWN;
+
+    @Column(name = "volume_size")
+    Long volumeSize;
+
+    @Column(name = "store_id")
+    private Long storeId;
+
+    @Column(name = "store_uuid")
+    private String storeUuid;
+
+    @Column(name = "store_name")
+    private String storeName;
+
+    @Column(name = "store_role")
+    @Enumerated(EnumType.STRING)
+    private DataStoreRole storeRole;
+
+    @Column(name = "store_state")
+    @Enumerated(EnumType.STRING)
+    private ObjectInDataStoreStateMachine.State storeState;
+
+    @Column(name = "download_state")
+    @Enumerated(EnumType.STRING)
+    private VMTemplateStorageResourceAssoc.Status downloadState;
+
+    @Column(name = "download_pct")
+    private int downloadPercent;
+
+    @Column(name = "error_str")
+    private String errorString;
+
+    @Column(name = "store_size")
+    private long storeSize;
+
+    @Column(name = "created_on_store")
+    private Date createdOnStore = null;
+
+    @Column(name = "snapshot_store_pair")
+    private String snapshotStorePair;
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public Snapshot.State getStatus() {
+        return status;
+    }
+
+    public Long getDiskOfferingId() {
+        return diskOfferingId;
+    }
+
+    public short getSnapshotType() {
+        return snapshotType;
+    }
+
+    public String getTypeDescription() {
+        return typeDescription;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public Snapshot.LocationType getLocationType() {
+        return locationType;
+    }
+
+    public Hypervisor.HypervisorType getHypervisorType() {
+        return hypervisorType;
+    }
+
+    @Override
+    public long getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public String getAccountUuid() {
+        return accountUuid;
+    }
+
+    @Override
+    public String getAccountName() {
+        return accountName;
+    }
+
+    @Override
+    public Account.Type getAccountType() {
+        return accountType;
+    }
+
+    @Override
+    public long getDomainId() {
+        return domainId;
+    }
+
+    @Override
+    public String getDomainUuid() {
+        return domainUuid;
+    }
+
+    @Override
+    public String getDomainName() {
+        return domainName;
+    }
+
+    @Override
+    public String getDomainPath() {
+        return domainPath;
+    }
+
+    public long getProjectId() {
+        return projectId;
+    }
+
+    @Override
+    public String getProjectUuid() {
+        return projectUuid;
+    }
+
+    @Override
+    public String getProjectName() {
+        return projectName;
+    }
+
+    public Long getDataCenterId() {
+        return dataCenterId;
+    }
+
+    public String getDataCenterUuid() {
+        return dataCenterUuid;
+    }
+
+    public String getDataCenterName() {
+        return dataCenterName;
+    }
+
+    public Long getVolumeId() {
+        return volumeId;
+    }
+
+    public String getVolumeUuid() {
+        return volumeUuid;
+    }
+
+    public String getVolumeName() {
+        return volumeName;
+    }
+
+    public Volume.Type getVolumeType() {
+        return volumeType;
+    }
+
+    public Long getVolumeSize() {
+        return volumeSize;
+    }
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public String getStoreUuid() {
+        return storeUuid;
+    }
+
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public DataStoreRole getStoreRole() {
+        return storeRole;
+    }
+
+    public ObjectInDataStoreStateMachine.State getStoreState() {
+        return storeState;
+    }
+
+    public VMTemplateStorageResourceAssoc.Status getDownloadState() {
+        return downloadState;
+    }
+
+    public int getDownloadPercent() {
+        return downloadPercent;
+    }
+
+    public String getErrorString() {
+        return errorString;
+    }
+
+    public long getStoreSize() {
+        return storeSize;
+    }
+
+    public Date getCreatedOnStore() {
+        return createdOnStore;
+    }
+
+    public String getSnapshotStorePair() {
+        return snapshotStorePair;
+    }
+
+    @Override
+    public Class<?> getEntityType() {
+        return Snapshot.class;
+    }
+}
diff --git a/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
index 1831aaa..5eb04d2 100644
--- a/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
@@ -110,6 +110,9 @@
     @Column(name = "tag")
     private String tag;
 
+    @Column(name = "is_tag_a_rule")
+    private boolean isTagARule;
+
     @Column(name = "disk_used_capacity")
     private long usedCapacity;
 
@@ -243,6 +246,10 @@
         return tag;
     }
 
+    public boolean getIsTagARule() {
+        return isTagARule;
+    }
+
     public long getUsedCapacity() {
         return usedCapacity;
     }
diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
index 4fa4581..a465e89 100644
--- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
@@ -34,6 +34,7 @@
 import com.cloud.network.Network.GuestType;
 import com.cloud.network.Networks.TrafficType;
 import com.cloud.resource.ResourceState;
+import com.cloud.storage.Storage.TemplateType;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.Volume;
 import com.cloud.user.Account;
@@ -191,6 +192,9 @@
     @Column(name = "template_name")
     private String templateName;
 
+    @Column(name = "template_type")
+    private TemplateType templateType;
+
     @Column(name = "template_display_text", length = 4096)
     private String templateDisplayText;
 
@@ -632,6 +636,10 @@
         return templateName;
     }
 
+    public TemplateType getTemplateType() {
+        return templateType;
+    }
+
     public String getTemplateDisplayText() {
         return templateDisplayText;
     }
diff --git a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java
index 9c5f3e0..6926f67 100644
--- a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java
+++ b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java
@@ -978,16 +978,12 @@
         allocateVmCapacity(vm, fromLastHost);
       }
 
-      if (newState == State.Stopped) {
-        if (vm.getType() == VirtualMachine.Type.User) {
-
+      if (newState == State.Stopped && event != Event.RestoringFailed && event != Event.RestoringSuccess && vm.getType() == VirtualMachine.Type.User) {
           UserVmVO userVM = _userVMDao.findById(vm.getId());
           _userVMDao.loadDetails(userVM);
           // free the message sent flag if it exists
           userVM.setDetail(VmDetailConstants.MESSAGE_RESERVED_CAPACITY_FREED_FLAG, "false");
           _userVMDao.saveDetails(userVM);
-
-        }
       }
 
       return true;
diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java
index aeefdb5..2d67704 100644
--- a/server/src/main/java/com/cloud/configuration/Config.java
+++ b/server/src/main/java/com/cloud/configuration/Config.java
@@ -844,7 +844,7 @@
             "The interval (in milliseconds) when vm stats are retrieved from agents.",
             null),
     VmDiskStatsInterval("Advanced", ManagementServer.class, Integer.class, "vm.disk.stats.interval", "0", "Interval (in seconds) to report vm disk statistics.", null),
-    VolumeStatsInterval("Advanced", ManagementServer.class, Integer.class, "volume.stats.interval", "60000", "Interval (in miliseconds) to report volume statistics.", null),
+    VolumeStatsInterval("Advanced", ManagementServer.class, Integer.class, "volume.stats.interval", "60000", "Interval (in milliseconds) to report volume statistics.", null),
     VmTransitionWaitInterval(
             "Advanced",
             ManagementServer.class,
@@ -1621,7 +1621,7 @@
             String.class,
             "implicit.host.tags",
             "GPU",
-            "Tag hosts at the time of host disovery based on the host properties/capabilities",
+            "Tag hosts at the time of host discovery based on the host properties/capabilities",
             null),
     VpcCleanupInterval(
             "Advanced",
@@ -1729,7 +1729,7 @@
             String.class,
             "baremetal.ipmi.fail.retry",
             "5",
-            "ipmi interface will be temporary out of order after power opertions(e.g. cycle, on), it leads following commands fail immediately. The value specifies retry times before accounting it as real failure",
+            "ipmi interface will be temporary out of order after power operations(e.g. cycle, on), it leads following commands fail immediately. The value specifies retry times before accounting it as real failure",
             null),
 
     ApiLimitEnabled("Advanced", ManagementServer.class, Boolean.class, "api.throttling.enabled", "false", "Enable/disable Api rate limit", null),
diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 080bb83..9baf4df 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -46,6 +46,12 @@
 import javax.naming.ConfigurationException;
 
 
+import com.cloud.hypervisor.HypervisorGuru;
+import com.cloud.utils.crypt.DBEncryptionUtil;
+import com.cloud.host.HostTagVO;
+import com.cloud.storage.StoragePoolTagVO;
+import com.cloud.storage.VolumeApiServiceImpl;
+import com.googlecode.ipv6.IPv6Address;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupService;
@@ -87,6 +93,7 @@
 import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd;
 import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
 import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd;
+import org.apache.cloudstack.cluster.ClusterDrsService;
 import org.apache.cloudstack.config.ApiServiceConfiguration;
 import org.apache.cloudstack.config.Configuration;
 import org.apache.cloudstack.context.CallContext;
@@ -125,6 +132,7 @@
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
 import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
@@ -291,11 +299,12 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Sets;
-import com.googlecode.ipv6.IPv6Address;
 import com.googlecode.ipv6.IPv6Network;
 
 public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable {
     public static final Logger s_logger = Logger.getLogger(ConfigurationManagerImpl.class);
+    public static final String PERACCOUNT = "peraccount";
+    public static final String PERZONE = "perzone";
 
     @Inject
     EntityManager _entityMgr;
@@ -459,7 +468,6 @@
     protected Set<String> configValuesForValidation;
     private Set<String> weightBasedParametersForValidation;
     private Set<String> overprovisioningFactorsForValidation;
-    public static final String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length";
 
     public static final ConfigKey<Boolean> SystemVMUseLocalStorage = new ConfigKey<Boolean>(Boolean.class, "system.vm.use.local.storage", "Advanced", "false",
             "Indicates whether to use local storage pools or shared storage pools for system VMs.", false, ConfigKey.Scope.Zone, null);
@@ -490,8 +498,6 @@
     public static ConfigKey<Integer> VM_SERVICE_OFFERING_MAX_RAM_SIZE = new ConfigKey<Integer>("Advanced", Integer.class, "vm.serviceoffering.ram.size.max", "0", "Maximum RAM size in "
       + "MB for vm service offering. If 0 - no limitation", true);
 
-    public static final ConfigKey<Integer> VM_USERDATA_MAX_LENGTH = new ConfigKey<Integer>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768",
-            "Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true);
     public static final ConfigKey<Boolean> MIGRATE_VM_ACROSS_CLUSTERS = new ConfigKey<Boolean>(Boolean.class, "migrate.vm.across.clusters", "Advanced", "false",
             "Indicates whether the VM can be migrated to different cluster if no host is found in same cluster",true, ConfigKey.Scope.Zone, null);
 
@@ -571,6 +577,8 @@
         weightBasedParametersForValidation.add(Config.AgentLoadThreshold.key());
         weightBasedParametersForValidation.add(Config.VmUserDispersionWeight.key());
         weightBasedParametersForValidation.add(CapacityManager.SecondaryStorageCapacityThreshold.key());
+        weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceThreshold.key());
+        weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceSkipThreshold.key());
 
     }
 
@@ -645,12 +653,12 @@
             if (localCidrs != null && localCidrs.length > 0) {
                 s_logger.warn("Management network CIDR is not configured originally. Set it default to " + localCidrs[0]);
 
-                _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE, 0, new Long(0), "Management network CIDR is not configured originally. Set it default to "
+                _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE, 0, new Long(0), "Management network CIDR is not configured originally. Set it default to "
                         + localCidrs[0], "");
                 _configDao.update(Config.ManagementNetwork.key(), Config.ManagementNetwork.getCategory(), localCidrs[0]);
             } else {
                 s_logger.warn("Management network CIDR is not properly configured and we are not able to find a default setting");
-                _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE, 0, new Long(0),
+                _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE, 0, new Long(0),
                         "Management network CIDR is not properly configured and we are not able to find a default setting", "");
             }
         }
@@ -665,7 +673,7 @@
 
     @Override
     @DB
-    public String updateConfiguration(final long userId, final String name, final String category, final String value, final String scope, final Long resourceId) {
+    public String updateConfiguration(final long userId, final String name, final String category, String value, final String scope, final Long resourceId) {
         final String validationMsg = validateConfigurationValue(name, value, scope);
 
         if (validationMsg != null) {
@@ -678,6 +686,11 @@
         // if scope is mentioned as global or not mentioned then it is normal
         // global parameter updation
         if (scope != null && !scope.isEmpty() && !ConfigKey.Scope.Global.toString().equalsIgnoreCase(scope)) {
+            boolean valueEncrypted = shouldEncryptValue(category);
+            if (valueEncrypted) {
+                value = DBEncryptionUtil.encrypt(value);
+            }
+
             switch (ConfigKey.Scope.valueOf(scope)) {
             case Zone:
                 final DataCenterVO zone = _zoneDao.findById(resourceId);
@@ -768,13 +781,15 @@
             default:
                 throw new InvalidParameterValueException("Scope provided is invalid");
             }
-            return value;
+
+            return valueEncrypted ? DBEncryptionUtil.decrypt(value) : value;
         }
 
         // Execute all updates in a single transaction
         final TransactionLegacy txn = TransactionLegacy.currentTxn();
         txn.start();
 
+        String previousValue = _configDao.getValue(name);
         if (!_configDao.update(name, category, value)) {
             s_logger.error("Failed to update configuration option, name: " + name + ", value:" + value);
             throw new CloudRuntimeException("Failed to update configuration value. Please contact Cloud Support.");
@@ -855,6 +870,8 @@
             } catch (final Throwable e) {
                 throw new CloudRuntimeException("Failed to clean up download URLs in template_store_ref or volume_store_ref due to exception ", e);
             }
+        } else if (HypervisorGuru.HypervisorCustomDisplayName.key().equals(name)) {
+            updateCustomDisplayNameOnHypervisorsList(previousValue, value);
         }
 
         txn.commit();
@@ -862,6 +879,24 @@
         return _configDao.getValue(name);
     }
 
+    private boolean shouldEncryptValue(String category) {
+        return StringUtils.equalsAny(category, "Hidden", "Secure");
+    }
+
+    /**
+     * Updates the 'hypervisor.list' value to match the new custom hypervisor name set as newValue if the previous value was set
+     */
+    private void updateCustomDisplayNameOnHypervisorsList(String previousValue, String newValue) {
+        String hypervisorListConfigName = Config.HypervisorList.key();
+        String hypervisors = _configDao.getValue(hypervisorListConfigName);
+        if (Arrays.asList(hypervisors.split(",")).contains(previousValue)) {
+            hypervisors = hypervisors.replace(previousValue, newValue);
+            s_logger.info(String.format("Updating the hypervisor list configuration '%s' " +
+                    "to match the new custom hypervisor display name", hypervisorListConfigName));
+            _configDao.update(hypervisorListConfigName, hypervisors);
+        }
+    }
+
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, eventDescription = "updating configuration")
     public Configuration updateConfiguration(final UpdateCfgCmd cmd) throws InvalidParameterValueException {
@@ -874,10 +909,11 @@
         final Long imageStoreId = cmd.getImageStoreId();
         Long accountId = cmd.getAccountId();
         Long domainId = cmd.getDomainId();
-        CallContext.current().setEventDetails(" Name: " + name + " New Value: " + (name.toLowerCase().contains("password") ? "*****" : value == null ? "" : value));
         // check if config value exists
         final ConfigurationVO config = _configDao.findByName(name);
-        String catergory = null;
+        String category = null;
+        String eventValue = encryptEventValueIfConfigIsEncrypted(config, value);
+        CallContext.current().setEventDetails(String.format(" Name: %s New Value: %s", name, eventValue));
 
         final Account caller = CallContext.current().getCallingAccount();
         if (_accountMgr.isDomainAdmin(caller.getId())) {
@@ -896,9 +932,9 @@
                 s_logger.warn("Probably the component manager where configuration variable " + name + " is defined needs to implement Configurable interface");
                 throw new InvalidParameterValueException("Config parameter with name " + name + " doesn't exist");
             }
-            catergory = _configDepot.get(name).category();
+            category = _configDepot.get(name).category();
         } else {
-            catergory = config.getCategory();
+            category = config.getCategory();
         }
 
         validateIpAddressRelatedConfigValues(name, value);
@@ -955,7 +991,7 @@
             value = (id == null) ? null : "";
         }
 
-        final String updatedValue = updateConfiguration(userId, name, catergory, value, scope, id);
+        final String updatedValue = updateConfiguration(userId, name, category, value, scope, id);
         if (value == null && updatedValue == null || updatedValue.equalsIgnoreCase(value)) {
             return _configDao.findByName(name);
         } else {
@@ -963,6 +999,13 @@
         }
     }
 
+    private String encryptEventValueIfConfigIsEncrypted(ConfigurationVO config, String value) {
+        if (config != null && config.isEncrypted()) {
+           return  "*****";
+        }
+        return Objects.requireNonNullElse(value, "");
+    }
+
     private ParamCountPair getParamCount(Map<String, Long> scopeMap) {
         Long id = null;
         int paramCount = 0;
@@ -3413,6 +3456,7 @@
         final List<Long> zoneIds = cmd.getZoneIds();
         String storageTags = cmd.getStorageTags();
         String hostTags = cmd.getHostTags();
+        ServiceOffering.State state = cmd.getState();
 
         if (userId == null) {
             userId = Long.valueOf(User.UID_SYSTEM);
@@ -3497,7 +3541,7 @@
             throw new InvalidParameterValueException(String.format("Unable to update service offering: %s by id user: %s because it is not root-admin or domain-admin", offeringHandle.getUuid(), user.getUuid()));
         }
 
-        final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null;
+        final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null || state != null;
         final boolean detailsUpdateNeeded = !filteredDomainIds.equals(existingDomainIds) || !filteredZoneIds.equals(existingZoneIds);
         if (!updateNeeded && !detailsUpdateNeeded) {
             return _serviceOfferingDao.findById(id);
@@ -3517,8 +3561,17 @@
             offering.setSortKey(sortKey);
         }
 
+        if (state != null) {
+            offering.setState(state);
+        }
+
         DiskOfferingVO diskOffering = _diskOfferingDao.findById(offeringHandle.getDiskOfferingId());
         updateOfferingTagsIfIsNotNull(storageTags, diskOffering);
+
+        if (diskOffering.isComputeOnly() && state != null) {
+            diskOffering.setState(state == ServiceOffering.State.Active ? DiskOffering.State.Active : DiskOffering.State.Inactive);
+        }
+
         _diskOfferingDao.update(diskOffering.getId(), diskOffering);
 
         updateServiceOfferingHostTagsIfNotNull(hostTags, offering);
@@ -3873,6 +3926,7 @@
         Long iopsWriteRateMax = cmd.getIopsWriteRateMax();
         Long iopsWriteRateMaxLength = cmd.getIopsWriteRateMaxLength();
         String cacheMode = cmd.getCacheMode();
+        DiskOffering.State state = cmd.getState();
 
         // Check if diskOffering exists
         final DiskOffering diskOfferingHandle = _entityMgr.findById(DiskOffering.class, diskOfferingId);
@@ -3886,22 +3940,9 @@
         List<Long> existingZoneIds = diskOfferingDetailsDao.findZoneIds(diskOfferingId);
         Collections.sort(existingZoneIds);
 
-        // check if valid domain
-        if (CollectionUtils.isNotEmpty(domainIds)) {
-            for (final Long domainId: domainIds) {
-                if (_domainDao.findById(domainId) == null) {
-                    throw new InvalidParameterValueException("Please specify a valid domain id");
-                }
-            }
-        }
+        validateDomain(domainIds);
 
-        // check if valid zone
-        if (CollectionUtils.isNotEmpty(zoneIds)) {
-            for (Long zoneId : zoneIds) {
-                if (_zoneDao.findById(zoneId) == null)
-                    throw new InvalidParameterValueException("Please specify a valid zone id");
-            }
-        }
+        validateZone(zoneIds);
 
         Long userId = CallContext.current().getCallingUserId();
         if (userId == null) {
@@ -3924,41 +3965,22 @@
         Collections.sort(filteredZoneIds);
 
         if (account.getType() == Account.Type.DOMAIN_ADMIN) {
-            if (!filteredZoneIds.equals(existingZoneIds)) { // Domain-admins cannot update zone(s) for offerings
-                throw new InvalidParameterValueException(String.format("Unable to update zone(s) for disk offering: %s by admin: %s as it is domain-admin", diskOfferingHandle.getUuid(), user.getUuid()));
-            }
-            if (existingDomainIds.isEmpty()) {
-                throw new InvalidParameterValueException(String.format("Unable to update public disk offering: %s by user: %s because it is domain-admin", diskOfferingHandle.getUuid(), user.getUuid()));
-            } else {
-                if (filteredDomainIds.isEmpty()) {
-                    throw new InvalidParameterValueException(String.format("Unable to update disk offering: %s to a public offering by user: %s because it is domain-admin", diskOfferingHandle.getUuid(), user.getUuid()));
-                }
-            }
+            checkDomainAdminUpdateOfferingRestrictions(diskOfferingHandle, user, filteredZoneIds, existingZoneIds, existingDomainIds, filteredDomainIds);
+
             if (StringUtils.isNotBlank(tags) && !ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS.valueIn(account.getAccountId())) {
                 throw new InvalidParameterValueException(String.format("User [%s] is unable to update disk offering tags.", user.getUuid()));
             }
 
-            List<Long> nonChildDomains = new ArrayList<>();
-            for (Long domainId : existingDomainIds) {
-                if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) {
-                    if (name != null || displayText != null || sortKey != null) { // Domain-admins cannot update name, display text, sort key for offerings with domain which are not child domains for domain-admin
-                        throw new InvalidParameterValueException(String.format("Unable to update disk offering: %s as it has linked domain(s) which are not child domain for domain-admin: %s", diskOfferingHandle.getUuid(), user.getUuid()));
-                    }
-                    nonChildDomains.add(domainId);
-                }
-            }
-            for (Long domainId : filteredDomainIds) {
-                if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) {
-                    Domain domain = _entityMgr.findById(Domain.class, domainId);
-                    throw new InvalidParameterValueException(String.format("Unable to update disk offering: %s by domain-admin: %s with domain: %3$s which is not a child domain", diskOfferingHandle.getUuid(), user.getUuid(), domain.getUuid()));
-                }
-            }
+            List<Long> nonChildDomains = getAccountNonChildDomains(diskOfferingHandle, account, user, cmd, existingDomainIds);
+
+            checkIfDomainIsChildDomain(diskOfferingHandle, account, user, filteredDomainIds);
+
             filteredDomainIds.addAll(nonChildDomains); // Final list must include domains which were not child domain for domain-admin but specified for this offering prior to update
         } else if (account.getType() != Account.Type.ADMIN) {
             throw new InvalidParameterValueException(String.format("Unable to update disk offering: %s by id user: %s because it is not root-admin or domain-admin", diskOfferingHandle.getUuid(), user.getUuid()));
         }
 
-        boolean updateNeeded = shouldUpdateDiskOffering(name, displayText, sortKey, displayDiskOffering, tags, cacheMode) ||
+        boolean updateNeeded = shouldUpdateDiskOffering(name, displayText, sortKey, displayDiskOffering, tags, cacheMode, state) ||
                 shouldUpdateIopsRateParameters(iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength) ||
                 shouldUpdateBytesRateParameters(bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength);
 
@@ -3968,22 +3990,7 @@
         }
 
         final DiskOfferingVO diskOffering = _diskOfferingDao.createForUpdate(diskOfferingId);
-
-        if (name != null) {
-            diskOffering.setName(name);
-        }
-
-        if (displayText != null) {
-            diskOffering.setDisplayText(displayText);
-        }
-
-        if (sortKey != null) {
-            diskOffering.setSortKey(sortKey);
-        }
-
-        if (displayDiskOffering != null) {
-            diskOffering.setDisplayOffering(displayDiskOffering);
-        }
+        updateDiskOfferingIfCmdAttributeNotNull(diskOffering, cmd);
 
         updateOfferingTagsIfIsNotNull(tags, diskOffering);
 
@@ -4001,31 +4008,16 @@
             diskOffering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase()));
         }
 
+        if (state != null) {
+            diskOffering.setState(state);
+        }
+
         if (updateNeeded && !_diskOfferingDao.update(diskOfferingId, diskOffering)) {
             return null;
         }
         List<DiskOfferingDetailVO> detailsVO = new ArrayList<>();
         if(detailsUpdateNeeded) {
-            SearchBuilder<DiskOfferingDetailVO> sb = diskOfferingDetailsDao.createSearchBuilder();
-            sb.and("offeringId", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
-            sb.and("detailName", sb.entity().getName(), SearchCriteria.Op.EQ);
-            sb.done();
-            SearchCriteria<DiskOfferingDetailVO> sc = sb.create();
-            sc.setParameters("offeringId", String.valueOf(diskOfferingId));
-            if(!filteredDomainIds.equals(existingDomainIds)) {
-                sc.setParameters("detailName", ApiConstants.DOMAIN_ID);
-                diskOfferingDetailsDao.remove(sc);
-                for (Long domainId : filteredDomainIds) {
-                    detailsVO.add(new DiskOfferingDetailVO(diskOfferingId, ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
-                }
-            }
-            if(!filteredZoneIds.equals(existingZoneIds)) {
-                sc.setParameters("detailName", ApiConstants.ZONE_ID);
-                diskOfferingDetailsDao.remove(sc);
-                for (Long zoneId : filteredZoneIds) {
-                    detailsVO.add(new DiskOfferingDetailVO(diskOfferingId, ApiConstants.ZONE_ID, String.valueOf(zoneId), false));
-                }
-            }
+            updateDiskOfferingDetails(detailsVO, diskOfferingId, filteredDomainIds, existingDomainIds, filteredZoneIds, existingZoneIds);
         }
         if (!detailsVO.isEmpty()) {
             for (DiskOfferingDetailVO detailVO : detailsVO) {
@@ -4036,6 +4028,128 @@
         return _diskOfferingDao.findById(diskOfferingId);
     }
 
+    protected void validateDomain(List<Long> domainIds) {
+        if (CollectionUtils.isEmpty(domainIds)) {
+            return;
+        }
+
+        for (final Long domainId: domainIds) {
+            if (_domainDao.findById(domainId) == null) {
+                throw new InvalidParameterValueException("Please specify a valid domain id.");
+            }
+        }
+    }
+
+    protected void validateZone(List<Long> zoneIds) {
+        if (CollectionUtils.isEmpty(zoneIds)) {
+            return;
+        }
+
+        for (Long zoneId : zoneIds) {
+            if (_zoneDao.findById(zoneId) == null) {
+                throw new InvalidParameterValueException("Please specify a valid zone id.");
+            }
+        }
+    }
+
+    protected void updateDiskOfferingIfCmdAttributeNotNull(DiskOfferingVO diskOffering, UpdateDiskOfferingCmd cmd) {
+        if (cmd.getDiskOfferingName() != null) {
+            diskOffering.setName(cmd.getDiskOfferingName());
+        }
+
+        if (cmd.getDisplayText() != null) {
+            diskOffering.setDisplayText(cmd.getDisplayText());
+        }
+
+        if (cmd.getSortKey() != null) {
+            diskOffering.setSortKey(cmd.getSortKey());
+        }
+
+        if (cmd.getDisplayOffering() != null) {
+            diskOffering.setDisplayOffering(cmd.getDisplayOffering());
+        }
+    }
+
+    protected void updateDiskOfferingDetails(List<DiskOfferingDetailVO> detailsVO, Long diskOfferingId, List<Long> filteredDomainIds,
+                                           List<Long> existingDomainIds, List<Long> filteredZoneIds, List<Long> existingZoneIds) {
+        SearchBuilder<DiskOfferingDetailVO> sb = diskOfferingDetailsDao.createSearchBuilder();
+        sb.and("offeringId", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
+        sb.and("detailName", sb.entity().getName(), SearchCriteria.Op.EQ);
+        sb.done();
+        SearchCriteria<DiskOfferingDetailVO> sc = sb.create();
+        sc.setParameters("offeringId", String.valueOf(diskOfferingId));
+
+        updateDiskOfferingDetailsDomainIds(detailsVO, sc, diskOfferingId, filteredDomainIds, existingDomainIds);
+        updateDiskOfferingDetailsZoneIds(detailsVO, sc, diskOfferingId, filteredZoneIds, existingZoneIds);
+    }
+
+    protected void updateDiskOfferingDetailsDomainIds(List<DiskOfferingDetailVO> detailsVO, SearchCriteria<DiskOfferingDetailVO> sc, Long diskOfferingId, List<Long> filteredDomainIds, List<Long> existingDomainIds) {
+        if (filteredDomainIds.equals(existingDomainIds)) {
+            return;
+        }
+
+        sc.setParameters("detailName", ApiConstants.DOMAIN_ID);
+        diskOfferingDetailsDao.remove(sc);
+        for (Long domainId : filteredDomainIds) {
+            detailsVO.add(new DiskOfferingDetailVO(diskOfferingId, ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
+        }
+    }
+
+    protected void updateDiskOfferingDetailsZoneIds(List<DiskOfferingDetailVO> detailsVO, SearchCriteria<DiskOfferingDetailVO> sc, Long diskOfferingId, List<Long> filteredZoneIds, List<Long> existingZoneIds) {
+        if (filteredZoneIds.equals(existingZoneIds)) {
+            return;
+        }
+
+        sc.setParameters("detailName", ApiConstants.ZONE_ID);
+        diskOfferingDetailsDao.remove(sc);
+        for (Long zoneId : filteredZoneIds) {
+            detailsVO.add(new DiskOfferingDetailVO(diskOfferingId, ApiConstants.ZONE_ID, String.valueOf(zoneId), false));
+        }
+    }
+
+    protected void checkDomainAdminUpdateOfferingRestrictions(DiskOffering diskOffering, User user, List<Long> filteredZoneIds, List<Long> existingZoneIds,
+                                                            List<Long> existingDomainIds, List<Long> filteredDomainIds) {
+        if (!filteredZoneIds.equals(existingZoneIds)) {
+            throw new InvalidParameterValueException(String.format("Unable to update zone(s) for disk offering [%s] by admin [%s] as it is domain-admin.", diskOffering.getUuid(), user.getUuid()));
+        }
+        if (existingDomainIds.isEmpty()) {
+            throw new InvalidParameterValueException(String.format("Unable to update public disk offering [%s] by user [%s] because it is domain-admin.", diskOffering.getUuid(), user.getUuid()));
+        }
+        if (filteredDomainIds.isEmpty()) {
+            throw new InvalidParameterValueException(String.format("Unable to update disk offering [%s] to a public offering by user [%s] because it is domain-admin.", diskOffering.getUuid(), user.getUuid()));
+        }
+    }
+
+    protected List<Long> getAccountNonChildDomains(DiskOffering diskOffering, Account account, User user,
+                                                 UpdateDiskOfferingCmd cmd, List<Long> existingDomainIds) {
+        List<Long> nonChildDomains = new ArrayList<>();
+        String name = cmd.getDiskOfferingName();
+        String displayText = cmd.getDisplayText();
+        Integer sortKey = cmd.getSortKey();
+        for (Long domainId : existingDomainIds) {
+            if (_domainDao.isChildDomain(account.getDomainId(), domainId)) {
+                continue;
+            }
+
+            if (ObjectUtils.anyNotNull(name, displayText, sortKey)) {
+                throw new InvalidParameterValueException(String.format("Unable to update disk offering [%s] as it has linked domain(s) which are not child domain for domain-admin [%s].", diskOffering.getUuid(), user.getUuid()));
+            }
+            nonChildDomains.add(domainId);
+        }
+        return nonChildDomains;
+    }
+
+    protected void checkIfDomainIsChildDomain(DiskOffering diskOffering, Account account, User user, List<Long> filteredDomainIds) {
+        for (Long domainId : filteredDomainIds) {
+            if (_domainDao.isChildDomain(account.getDomainId(), domainId)) {
+                continue;
+            }
+
+            Domain domain = _entityMgr.findById(Domain.class, domainId);
+            throw new InvalidParameterValueException(String.format("Unable to update disk offering [%s] by domain-admin [%s] with domain [%3$s] which is not a child domain.", diskOffering.getUuid(), user.getUuid(), domain.getUuid()));
+        }
+    }
+
     /**
      * Check the tags parameters to the disk/service offering
      * <ul>
@@ -4052,17 +4166,23 @@
             if (CollectionUtils.isNotEmpty(pools)) {
                 List<String> listOfTags = Arrays.asList(tags.split(","));
                 for (StoragePoolVO storagePoolVO : pools) {
-                    List<String> tagsOnPool = storagePoolTagDao.getStoragePoolTags(storagePoolVO.getId());
-                    if (CollectionUtils.isEmpty(tagsOnPool) || !tagsOnPool.containsAll(listOfTags)) {
-                        DiskOfferingVO offeringToRetrieveInfo = _diskOfferingDao.findById(diskOffering.getId());
-                        List<VolumeVO> volumes = _volumeDao.findByDiskOfferingId(diskOffering.getId());
-                        String listOfVolumesNamesAndUuid = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(volumes, "name", "uuid");
-                        String diskOfferingInfo = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offeringToRetrieveInfo, "name", "uuid");
-                        String poolInfo = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(storagePoolVO, "name", "uuid");
-                        throw new InvalidParameterValueException(String.format("There are active volumes using the disk offering %s, and the pool %s doesn't have the new tags. " +
-                                "The following volumes are using the mentioned disk offering %s. Please first add the new tags to the mentioned storage pools before adding them" +
-                                " to the disk offering.", diskOfferingInfo, poolInfo, listOfVolumesNamesAndUuid));
+                    List<StoragePoolTagVO> tagsOnPool = storagePoolTagDao.findStoragePoolTags(storagePoolVO.getId());
+                    List<String> tagsAsString = tagsOnPool.stream().map(StoragePoolTagVO::getTag).collect(Collectors.toList());
+
+                    if ((CollectionUtils.isNotEmpty(tagsAsString) && tagsAsString.containsAll(listOfTags)) ||
+                        (tagsOnPool.size() == 1 && tagsOnPool.get(0).isTagARule() &&
+                        TagAsRuleHelper.interpretTagAsRule(tagsOnPool.get(0).getTag(), tags, VolumeApiServiceImpl.storageTagRuleExecutionTimeout.value()))) {
+                        continue;
                     }
+
+                    DiskOfferingVO offeringToRetrieveInfo = _diskOfferingDao.findById(diskOffering.getId());
+                    List<VolumeVO> volumes = _volumeDao.findByDiskOfferingId(diskOffering.getId());
+                    String listOfVolumesNamesAndUuid = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(volumes, "name", "uuid");
+                    String diskOfferingInfo = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offeringToRetrieveInfo, "name", "uuid");
+                    String poolInfo = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(storagePoolVO, "name", "uuid");
+                    throw new InvalidParameterValueException(String.format("There are active volumes using the disk offering %s, and the pool %s doesn't have the new tags. " +
+                            "The following volumes are using the mentioned disk offering %s. Please first add the new tags to the mentioned storage pools before adding them" +
+                            " to the disk offering.", diskOfferingInfo, poolInfo, listOfVolumesNamesAndUuid));
                 }
             }
             diskOffering.setTags(tags);
@@ -4089,10 +4209,17 @@
             if (CollectionUtils.isNotEmpty(hosts)) {
                 List<String> listOfHostTags = Arrays.asList(hostTags.split(","));
                 for (HostVO host : hosts) {
-                    List<String> tagsOnHost = hostTagDao.getHostTags(host.getId());
-                    if (CollectionUtils.isEmpty(tagsOnHost) || !tagsOnHost.containsAll(listOfHostTags)) {
-                        throw new InvalidParameterValueException(String.format("There are active VMs using offering [%s], and the hosts [%s] don't have the new tags", offering.getId(), hosts));
+                    List<HostTagVO> tagsOnHost = hostTagDao.getHostTags(host.getId());
+                    List<String> tagsAsString = tagsOnHost.stream().map(HostTagVO::getTag).collect(Collectors.toList());
+
+                    if ((CollectionUtils.isNotEmpty(tagsAsString) && tagsAsString.containsAll(listOfHostTags)) ||
+                        (tagsOnHost.size() == 1 && tagsOnHost.get(0).getIsTagARule() &&
+                        TagAsRuleHelper.interpretTagAsRule(tagsOnHost.get(0).getTag(), hostTags, HostTagsDao.hostTagRuleExecutionTimeout.value()))) {
+                        continue;
                     }
+
+                    throw new InvalidParameterValueException(String.format("There are active VMs using offering [%s], and the hosts [%s] don't have the new tags",
+                        offering.getId(), hosts));
                 }
             }
             offering.setHostTag(hostTags);
@@ -4105,8 +4232,8 @@
      * Check if it needs to update any parameter when updateDiskoffering is called
      * Verify if name or displayText are not blank, tags is not null, sortkey and displayDiskOffering is not null
      */
-    protected boolean shouldUpdateDiskOffering(String name, String displayText, Integer sortKey, Boolean displayDiskOffering, String tags, String cacheMode) {
-        return !StringUtils.isAllBlank(name, displayText, cacheMode) || tags != null || sortKey != null || displayDiskOffering != null;
+    protected boolean shouldUpdateDiskOffering(String name, String displayText, Integer sortKey, Boolean displayDiskOffering, String tags, String cacheMode, DiskOffering.State state) {
+        return !StringUtils.isAllBlank(name, displayText, cacheMode) || tags != null || sortKey != null || displayDiskOffering != null || state != null;
     }
 
     protected boolean shouldUpdateBytesRateParameters(Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength, Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength) {
@@ -6246,34 +6373,32 @@
     }
 
     void validateSourceNatServiceCapablities(final Map<Capability, String> sourceNatServiceCapabilityMap) {
-        if (sourceNatServiceCapabilityMap != null && !sourceNatServiceCapabilityMap.isEmpty()) {
-            if (sourceNatServiceCapabilityMap.keySet().size() > 2) {
-                throw new InvalidParameterValueException("Only " + Capability.SupportedSourceNatTypes.getName() + " and " + Capability.RedundantRouter
-                        + " capabilities can be sepcified for source nat service");
-            }
+        if (MapUtils.isNotEmpty(sourceNatServiceCapabilityMap) && (sourceNatServiceCapabilityMap.size() > 2 || ! sourceNatCapabilitiesContainValidValues(sourceNatServiceCapabilityMap))) {
+            throw new InvalidParameterValueException("Only " + Capability.SupportedSourceNatTypes.getName()
+                    + ", " + Capability.RedundantRouter
+                    + " capabilities can be specified for source nat service");
+        }
+    }
 
-            for (final Map.Entry<Capability ,String> srcNatPair : sourceNatServiceCapabilityMap.entrySet()) {
-                final Capability capability = srcNatPair.getKey();
-                final String value = srcNatPair.getValue();
-                if (capability == Capability.SupportedSourceNatTypes) {
-                    final boolean perAccount = value.contains("peraccount");
-                    final boolean perZone = value.contains("perzone");
-                    if (perAccount && perZone || !perAccount && !perZone) {
-                        throw new InvalidParameterValueException("Either peraccount or perzone source NAT type can be specified for "
-                                + Capability.SupportedSourceNatTypes.getName());
-                    }
-                } else if (capability == Capability.RedundantRouter) {
-                    final boolean enabled = value.contains("true");
-                    final boolean disabled = value.contains("false");
-                    if (!enabled && !disabled) {
-                        throw new InvalidParameterValueException("Unknown specified value for " + Capability.RedundantRouter.getName());
-                    }
-                } else {
-                    throw new InvalidParameterValueException("Only " + Capability.SupportedSourceNatTypes.getName() + " and " + Capability.RedundantRouter
-                            + " capabilities can be sepcified for source nat service");
+    boolean sourceNatCapabilitiesContainValidValues(Map<Capability, String> sourceNatServiceCapabilityMap) {
+        for (final Entry<Capability ,String> srcNatPair : sourceNatServiceCapabilityMap.entrySet()) {
+            final Capability capability = srcNatPair.getKey();
+            final String value = srcNatPair.getValue();
+            if (Capability.SupportedSourceNatTypes.equals(capability)) {
+                List<String> snatTypes = Arrays.asList(PERACCOUNT, PERZONE);
+                if (! snatTypes.contains(value) || ( value.contains(PERACCOUNT) && value.contains(PERZONE))) {
+                    throw new InvalidParameterValueException("Either peraccount or perzone source NAT type can be specified for "
+                            + Capability.SupportedSourceNatTypes.getName());
                 }
+            } else if (Capability.RedundantRouter.equals(capability)) {
+                if (! Arrays.asList("true", "false").contains(value.toLowerCase())) {
+                    throw new InvalidParameterValueException("Unknown specified value for " + capability.getName());
+                }
+            } else {
+                return false;
             }
         }
+        return true;
     }
 
     void validateStaticNatServiceCapablities(final Map<Capability, String> staticNatServiceCapabilityMap) {
@@ -6450,17 +6575,8 @@
 
             final Map<Capability, String> sourceNatServiceCapabilityMap = serviceCapabilityMap.get(Service.SourceNat);
             if (sourceNatServiceCapabilityMap != null && !sourceNatServiceCapabilityMap.isEmpty()) {
-                final String sourceNatType = sourceNatServiceCapabilityMap.get(Capability.SupportedSourceNatTypes);
-                if (sourceNatType != null) {
-                    _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.SupportedSourceNatTypes, sourceNatType);
-                    sharedSourceNat = sourceNatType.contains("perzone");
-                }
-
-                final String param = sourceNatServiceCapabilityMap.get(Capability.RedundantRouter);
-                if (param != null) {
-                    _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.RedundantRouter, param);
-                    redundantRouter = param.contains("true");
-                }
+                sharedSourceNat = isSharedSourceNat(serviceProviderMap, sourceNatServiceCapabilityMap);
+                redundantRouter = isRedundantRouter(serviceProviderMap, sourceNatServiceCapabilityMap);
             }
 
             final Map<Capability, String> staticNatServiceCapabilityMap = serviceCapabilityMap.get(Service.StaticNat);
@@ -6610,6 +6726,26 @@
         });
     }
 
+    boolean isRedundantRouter(Map<Service, Set<Provider>> serviceProviderMap, Map<Capability, String> sourceNatServiceCapabilityMap) {
+        boolean redundantRouter = false;
+        String param = sourceNatServiceCapabilityMap.get(Capability.RedundantRouter);
+        if (param != null) {
+            _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.RedundantRouter, param);
+            redundantRouter = param.contains("true");
+        }
+        return redundantRouter;
+    }
+
+    boolean isSharedSourceNat(Map<Service, Set<Provider>> serviceProviderMap, Map<Capability, String> sourceNatServiceCapabilityMap) {
+        boolean sharedSourceNat = false;
+        String param = sourceNatServiceCapabilityMap.get(Capability.SupportedSourceNatTypes);
+        if (param != null) {
+            _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.SupportedSourceNatTypes, param);
+            sharedSourceNat = param.contains(PERZONE);
+        }
+        return sharedSourceNat;
+    }
+
     protected void validateNtwkOffDetails(final Map<Detail, String> details, final Map<Service, Set<Provider>> serviceProviderMap) {
         for (final Detail detail : details.keySet()) {
 
@@ -7075,7 +7211,11 @@
                     }
                 }
 
-                offering.setTags(tags);
+                if (StringUtils.isBlank(tags)) {
+                    offering.setTags(null);
+                } else {
+                    offering.setTags(tags);
+                }
             }
 
             // Verify availability
@@ -7382,7 +7522,7 @@
             networkRate = offering.getRateMbps();
         } else {
             // for domain router service offering, get network rate from
-            if (offering.getSystemVmType() != null && offering.getSystemVmType().equalsIgnoreCase(VirtualMachine.Type.DomainRouter.toString())) {
+            if (offering.getVmType() != null && offering.getVmType().equalsIgnoreCase(VirtualMachine.Type.DomainRouter.toString())) {
                 networkRate = NetworkOrchestrationService.NetworkThrottlingRate.valueIn(dataCenterId);
             } else {
                 networkRate = Integer.parseInt(_configDao.getValue(Config.VmNetworkThrottlingRate.key()));
diff --git a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java
index 1a67ff4..c6ed450 100644
--- a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java
+++ b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java
@@ -17,6 +17,7 @@
 package com.cloud.dc.dao;
 
 import java.util.List;
+import java.util.Map;
 
 import com.cloud.dc.DedicatedResourceVO;
 import com.cloud.utils.Pair;
@@ -58,4 +59,6 @@
     List<Long> findHostsByCluster(Long clusterId);
 
     List<Long> findHostsByZone(Long zoneId);
+
+    Map<Long, List<String>> listDomainsOfDedicatedResourcesUsedByDomainPath(String domainPath);
 }
diff --git a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java
index c10ef2d..31f2e36 100644
--- a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java
+++ b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java
@@ -16,7 +16,12 @@
 // under the License.
 package com.cloud.dc.dao;
 
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -26,6 +31,7 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.utils.db.Filter;
 import org.springframework.stereotype.Component;
+import org.apache.log4j.Logger;
 
 import com.cloud.dc.DedicatedResourceVO;
 import com.cloud.utils.Pair;
@@ -45,6 +51,8 @@
 @DB
 public class DedicatedResourceDaoImpl extends GenericDaoBase<DedicatedResourceVO, Long> implements DedicatedResourceDao {
 
+    public static Logger LOGGER = Logger.getLogger(DedicatedResourceDaoImpl.class.getName());
+
     @Inject
     protected HostDao hostDao;
 
@@ -79,6 +87,17 @@
     protected SearchBuilder<DedicatedResourceVO> ListHostsByCluster;
     protected SearchBuilder<DedicatedResourceVO> ListHostsByZone;
 
+    private static final String LIST_DOMAINS_OF_DEDICATED_RESOURCES_USED_BY_DOMAIN_PATH = "SELECT dr.domain_id, \n" +
+            "            GROUP_CONCAT('VM:', vm.uuid) \n" +
+            "            FROM   cloud.dedicated_resources AS dr\n" +
+            "            INNER  JOIN cloud.vm_instance AS vm ON (vm.domain_id  = dr.domain_id)\n" +
+            "            INNER  JOIN cloud.domain AS domain ON (domain.id = vm.domain_id) \n" +
+            "            INNER  JOIN cloud.domain AS domain_dr ON (domain.id = dr.domain_id) \n" +
+            "            WHERE  domain.path LIKE ? \n" +
+            "            AND    domain_dr.path NOT LIKE ? \n " +
+            "            AND    vm.removed IS NULL \n" +
+            "            GROUP  BY dr.id";
+
     protected DedicatedResourceDaoImpl() {
         PodSearch = createSearchBuilder();
         PodSearch.and("podId", PodSearch.entity().getPodId(), SearchCriteria.Op.EQ);
@@ -428,4 +447,38 @@
         }
         return hosts;
     }
+
+
+    @Override
+    public Map<Long, List<String>> listDomainsOfDedicatedResourcesUsedByDomainPath(String domainPath) {
+        LOGGER.debug(String.format("Retrieving the domains of the dedicated resources used by domain with path [%s].", domainPath));
+
+        TransactionLegacy txn = TransactionLegacy.currentTxn();
+        try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_DEDICATED_RESOURCES_USED_BY_DOMAIN_PATH)) {
+            Map<Long, List<String>> domainsOfDedicatedResourcesUsedByDomainPath = new HashMap<>();
+
+            String domainSearch = domainPath.concat("%");
+            pstmt.setString(1, domainSearch);
+            pstmt.setString(2, domainSearch);
+
+            try (ResultSet rs = pstmt.executeQuery()) {
+                while (rs.next()) {
+                    Long domainId = rs.getLong(1);
+                    List<String> vmUuids = Arrays.asList(rs.getString(2).split(","));
+
+                    domainsOfDedicatedResourcesUsedByDomainPath.put(domainId, vmUuids);
+                }
+            }
+
+            return domainsOfDedicatedResourcesUsedByDomainPath;
+        } catch (SQLException e) {
+            LOGGER.error(String.format("Failed to retrieve the domains of the dedicated resources used by domain with path [%s] due to [%s]. Returning an empty "
+                    + "list of domains.", domainPath, e.getMessage()));
+
+            LOGGER.debug(String.format("Failed to retrieve the domains of the dedicated resources used by domain with path [%s]. Returning an empty "
+                    + "list of domains.", domainPath), e);
+
+            return new HashMap<>();
+        }
+    }
 }
diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
index 0ef462b..cb22e81 100644
--- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
+++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
@@ -16,6 +16,8 @@
 // under the License.
 package com.cloud.deploy;
 
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -23,28 +25,17 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
 import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
-import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.user.AccountVO;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.exception.StorageUnavailableException;
-import com.cloud.utils.db.Filter;
-import com.cloud.utils.fsm.StateMachine2;
-
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.Configurable;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
 import org.apache.cloudstack.affinity.AffinityGroupProcessor;
 import org.apache.cloudstack.affinity.AffinityGroupService;
 import org.apache.cloudstack.affinity.AffinityGroupVMMapVO;
@@ -57,6 +48,8 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
+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.messagebus.MessageBus;
 import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
@@ -64,6 +57,9 @@
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.Listener;
@@ -95,6 +91,7 @@
 import com.cloud.exception.AffinityConflictException;
 import com.cloud.exception.ConnectionException;
 import com.cloud.exception.InsufficientServerCapacityException;
+import com.cloud.exception.StorageUnavailableException;
 import com.cloud.gpu.GPU;
 import com.cloud.host.DetailVO;
 import com.cloud.host.Host;
@@ -115,26 +112,32 @@
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StoragePoolHostVO;
+import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.GuestOSCategoryDao;
 import com.cloud.storage.dao.GuestOSDao;
 import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.DateUtil;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.Manager;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.db.DB;
+import com.cloud.utils.db.Filter;
 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 com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.StateListener;
+import com.cloud.utils.fsm.StateMachine2;
 import com.cloud.vm.DiskProfile;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
@@ -144,8 +147,6 @@
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
 
-import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
-
 public class DeploymentPlanningManagerImpl extends ManagerBase implements DeploymentPlanningManager, Manager, Listener,
 StateListener<State, VirtualMachine.Event, VirtualMachine>, Configurable {
 
@@ -266,6 +267,35 @@
         _affinityProcessors = affinityProcessors;
     }
 
+    protected void avoidOtherClustersForDeploymentIfMigrationDisabled(VirtualMachine vm, Host lastHost, ExcludeList avoids) {
+        if (lastHost == null || lastHost.getClusterId() == null ||
+                ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS.valueIn(vm.getDataCenterId())) {
+            return;
+        }
+        List<VolumeVO> volumes = _volsDao.findUsableVolumesForInstance(vm.getId());
+        if (CollectionUtils.isEmpty(volumes)) {
+            return;
+        }
+        boolean storageMigrationNeededDuringClusterMigration = false;
+        for (Volume volume : volumes) {
+            StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId());
+            if (List.of(ScopeType.HOST, ScopeType.CLUSTER).contains(pool.getScope())) {
+                storageMigrationNeededDuringClusterMigration = true;
+                break;
+            }
+        }
+        if (!storageMigrationNeededDuringClusterMigration) {
+            return;
+        }
+        final Long lastHostClusterId = lastHost.getClusterId();
+        s_logger.warn(String.format("VM last host ID: %d belongs to zone ID: %s for which config - %s is false and storage migration would be needed for inter-cluster migration, therefore, adding all other clusters except ID: %d from this zone to avoid list",
+                lastHost.getId(), vm.getDataCenterId(), ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS.key(), lastHostClusterId));
+        List<Long> clusterIds = _clusterDao.listAllClusters(lastHost.getDataCenterId());
+        Set<Long> existingAvoidedClusters = avoids.getClustersToAvoid();
+        clusterIds = clusterIds.stream().filter(x -> !Objects.equals(x, lastHostClusterId) && (existingAvoidedClusters == null || !existingAvoidedClusters.contains(x))).collect(Collectors.toList());
+        avoids.addClusterList(clusterIds);
+    }
+
     @Override
     public DeployDestination planDeployment(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoids, DeploymentPlanner planner)
             throws InsufficientServerCapacityException, AffinityConflictException {
@@ -408,6 +438,8 @@
             planner = getDeploymentPlannerByName(plannerName);
         }
 
+        Host lastHost = null;
+
         String considerLastHostStr = (String)vmProfile.getParameter(VirtualMachineProfile.Param.ConsiderLastHost);
         boolean considerLastHost = vm.getLastHostId() != null && haVmTag == null &&
                 (considerLastHostStr == null || Boolean.TRUE.toString().equalsIgnoreCase(considerLastHostStr));
@@ -415,6 +447,7 @@
             s_logger.debug("This VM has last host_id specified, trying to choose the same host: " + vm.getLastHostId());
 
             HostVO host = _hostDao.findById(vm.getLastHostId());
+            lastHost = host;
             _hostDao.loadHostTags(host);
             _hostDao.loadDetails(host);
             ServiceOfferingDetailsVO offeringDetails = null;
@@ -519,6 +552,8 @@
             s_logger.debug("Cannot choose the last host to deploy this VM ");
         }
 
+        avoidOtherClustersForDeploymentIfMigrationDisabled(vm, lastHost, avoids);
+
         DeployDestination dest = null;
         List<Long> clusterList = null;
 
@@ -1326,7 +1361,7 @@
 
             if (vmRequiresSharedStorage) {
                 // check shared pools
-                List<StoragePoolVO> allPoolsInCluster = _storagePoolDao.findPoolsByTags(clusterVO.getDataCenterId(), clusterVO.getPodId(), clusterVO.getId(), null);
+                List<StoragePoolVO> allPoolsInCluster = _storagePoolDao.findPoolsByTags(clusterVO.getDataCenterId(), clusterVO.getPodId(), clusterVO.getId(), null, false, 0);
                 for (StoragePoolVO pool : allPoolsInCluster) {
                     if (!allocatorAvoidOutput.shouldAvoid(pool)) {
                         // there's some pool in the cluster that is not yet in avoid set
@@ -1339,7 +1374,7 @@
             if (vmRequiresLocalStorege) {
                 // check local pools
                 List<StoragePoolVO> allLocalPoolsInCluster =
-                        _storagePoolDao.findLocalStoragePoolsByTags(clusterVO.getDataCenterId(), clusterVO.getPodId(), clusterVO.getId(), null);
+                        _storagePoolDao.findLocalStoragePoolsByTags(clusterVO.getDataCenterId(), clusterVO.getPodId(), clusterVO.getId(), null, false);
                 for (StoragePoolVO pool : allLocalPoolsInCluster) {
                     if (!allocatorAvoidOutput.shouldAvoid(pool)) {
                         // there's some pool in the cluster that is not yet
diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java
index 4e83851..c2969ec 100644
--- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java
+++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java
@@ -27,6 +27,7 @@
 import javax.naming.ConfigurationException;
 
 import com.cloud.capacity.CapacityVO;
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
@@ -521,6 +522,12 @@
     private void removeClustersWithoutMatchingTag(List<Long> clusterListForVmAllocation, String hostTagOnOffering) {
 
         List<Long> matchingClusters = hostDao.listClustersByHostTag(hostTagOnOffering);
+        matchingClusters.addAll(hostDao.findClustersThatMatchHostTagRule(hostTagOnOffering));
+
+        if (matchingClusters.isEmpty()) {
+            s_logger.error(String.format("No suitable host found for the following compute offering tags [%s].", hostTagOnOffering));
+            throw new CloudRuntimeException("No suitable host found.");
+        }
 
         clusterListForVmAllocation.retainAll(matchingClusters);
 
diff --git a/server/src/main/java/com/cloud/event/ActionEventUtils.java b/server/src/main/java/com/cloud/event/ActionEventUtils.java
index dacccb5..36461d2 100644
--- a/server/src/main/java/com/cloud/event/ActionEventUtils.java
+++ b/server/src/main/java/com/cloud/event/ActionEventUtils.java
@@ -236,7 +236,7 @@
         try {
             s_eventBus.publish(event);
         } catch (EventBusException e) {
-            s_logger.warn("Failed to publish action event on the the event bus.");
+            s_logger.warn("Failed to publish action event on the event bus.");
         }
     }
 
@@ -318,7 +318,6 @@
             return details;
         }
         HashMap<String, Pair<ApiCommandResourceType, String>> typeParentMethodMap = new HashMap<>();
-        typeParentMethodMap.put(ApiCommandResourceType.Snapshot.toString(), new Pair<>(ApiCommandResourceType.Volume, "getVolumeId"));
         typeParentMethodMap.put(ApiCommandResourceType.VmSnapshot.toString(), new Pair<>(ApiCommandResourceType.VirtualMachine, "getVmId"));
         if (!typeParentMethodMap.containsKey(details.third())) {
             return details;
diff --git a/server/src/main/java/com/cloud/event/AlertGenerator.java b/server/src/main/java/com/cloud/event/AlertGenerator.java
index 49a0ae9..9e12486 100644
--- a/server/src/main/java/com/cloud/event/AlertGenerator.java
+++ b/server/src/main/java/com/cloud/event/AlertGenerator.java
@@ -109,7 +109,7 @@
         try {
             s_eventBus.publish(event);
         } catch (EventBusException e) {
-            s_logger.warn("Failed to publish alert on the the event bus.");
+            s_logger.warn("Failed to publish alert on the event bus.");
         }
     }
 }
diff --git a/server/src/main/java/com/cloud/event/dao/EventJoinDaoImpl.java b/server/src/main/java/com/cloud/event/dao/EventJoinDaoImpl.java
index c73f575..24c699a 100644
--- a/server/src/main/java/com/cloud/event/dao/EventJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/event/dao/EventJoinDaoImpl.java
@@ -110,6 +110,9 @@
         responseEvent.setParentId(event.getStartUuid());
         responseEvent.setState(event.getState());
         responseEvent.setUsername(event.getUserName());
+        if (event.getArchived()) {
+            responseEvent.setArchived(true);
+        }
         Long resourceId = event.getResourceId();
         responseEvent.setResourceType(event.getResourceType());
         ApiCommandResourceType resourceType = ApiCommandResourceType.fromString(event.getResourceType());
diff --git a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java
index 1b9b512..f22bcde 100644
--- a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java
+++ b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java
@@ -29,6 +29,10 @@
 import javax.naming.ConfigurationException;
 
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+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.PrimaryDataStoreDriver;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -67,8 +71,11 @@
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.StorageManager;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.dao.GuestOSCategoryDao;
 import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.secondary.SecondaryStorageVmManager;
 import com.cloud.user.AccountManager;
 import com.cloud.utils.component.ManagerBase;
@@ -133,6 +140,10 @@
     private ConsoleProxyManager consoleProxyManager;
     @Inject
     private SecondaryStorageVmManager secondaryStorageVmManager;
+    @Inject
+    VolumeDao volumeDao;
+    @Inject
+    DataStoreProviderManager dataStoreProviderMgr;
 
     long _serverId;
 
@@ -314,6 +325,7 @@
     }
 
     protected void wakeupWorkers() {
+        s_logger.debug("Wakeup workers HA");
         for (WorkerThread worker : _workers) {
             worker.wakup();
         }
@@ -332,6 +344,7 @@
 
     @Override
     public void scheduleRestart(VMInstanceVO vm, boolean investigate) {
+        s_logger.debug("HA schedule restart");
         Long hostId = vm.getHostId();
         if (hostId == null) {
             try {
@@ -425,6 +438,7 @@
     }
 
     protected Long restart(final HaWorkVO work) {
+        s_logger.debug("RESTART with HAWORK");
         List<HaWorkVO> items = _haDao.listFutureHaWorkForVm(work.getInstanceId(), work.getId());
         if (items.size() > 0) {
             StringBuilder str = new StringBuilder("Cancelling this work item because newer ones have been scheduled.  Work Ids = [");
@@ -599,6 +613,20 @@
             }
 
             try{
+                if (HypervisorType.KVM == host.getHypervisorType()) {
+                    List<VolumeVO> volumes = volumeDao.findByInstance(vmId);
+                    for (VolumeVO volumeVO : volumes) {
+                        //detach the volumes from all clusters before starting the VM on another host.
+                        if (volumeVO.getPoolType() == StoragePoolType.StorPool) {
+                            DataStoreProvider storeProvider = dataStoreProviderMgr.getDataStoreProvider(volumeVO.getPoolType().name());
+                            DataStoreDriver storeDriver = storeProvider.getDataStoreDriver();
+                            if (storeDriver instanceof PrimaryDataStoreDriver) {
+                                PrimaryDataStoreDriver primaryStoreDriver = (PrimaryDataStoreDriver)storeDriver;
+                                primaryStoreDriver.detachVolumeFromAllStorageNodes(volumeVO);
+                            }
+                        }
+                    }
+                }
                 // First try starting the vm with its original planner, if it doesn't succeed send HAPlanner as its an emergency.
                 _itMgr.advanceStart(vm.getUuid(), params, null);
             }catch (InsufficientCapacityException e){
@@ -1064,6 +1092,6 @@
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey[] {TimeBetweenCleanup, MigrationMaxRetries, TimeToSleep, TimeBetweenFailures,
             StopRetryInterval, RestartRetryInterval, MigrateRetryInterval, InvestigateRetryInterval,
-            HAWorkers, ForceHA};
+            HAWorkers, ForceHA, KvmHAFenceHostIfHeartbeatFailsOnStorage};
     }
 }
diff --git a/server/src/main/java/com/cloud/ha/KVMFencer.java b/server/src/main/java/com/cloud/ha/KVMFencer.java
index e102bc2..ea10570 100644
--- a/server/src/main/java/com/cloud/ha/KVMFencer.java
+++ b/server/src/main/java/com/cloud/ha/KVMFencer.java
@@ -82,6 +82,7 @@
 
         List<HostVO> hosts = _resourceMgr.listAllHostsInCluster(host.getClusterId());
         FenceCommand fence = new FenceCommand(vm, host);
+        fence.setReportCheckFailureIfOneStorageIsDown(HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value());
 
         int i = 0;
         for (HostVO h : hosts) {
diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
index 4803e27..74d9130 100644
--- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
+++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
@@ -28,6 +28,7 @@
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -369,7 +370,21 @@
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor, VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor };
+        return new ConfigKey<?>[] {VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor,
+                VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor,
+                HypervisorCustomDisplayName
+        };
     }
 
+    @Override
+    public UnmanagedInstanceTO cloneHypervisorVMOutOfBand(String hostIp, String vmName, Map<String, String> params) {
+        s_logger.error("Unsupported operation: cannot clone external VM");
+        return null;
+    }
+
+    @Override
+    public boolean removeClonedHypervisorVMOutOfBand(String hostIp, String vmName, Map<String, String> params) {
+        s_logger.error("Unsupported operation: cannot remove external VM");
+        return false;
+    }
 }
diff --git a/server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java b/server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java
new file mode 100644
index 0000000..e5ef783
--- /dev/null
+++ b/server/src/main/java/com/cloud/hypervisor/discoverer/CustomServerDiscoverer.java
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.hypervisor.discoverer;
+
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.kvm.discoverer.LibvirtServerDiscoverer;
+
+public class CustomServerDiscoverer extends LibvirtServerDiscoverer {
+    @Override
+    public Hypervisor.HypervisorType getHypervisorType() {
+        return Hypervisor.HypervisorType.Custom;
+    }
+
+    @Override
+    protected String getPatchPath() {
+        return "scripts/vm/hypervisor/kvm/";
+    }
+
+    @Override
+    public void processHostAdded(long hostId) {
+        // Not using super class implementation here.
+    }
+}
diff --git a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
index 440961d..e9f0d5f 100644
--- a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
+++ b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
@@ -110,7 +110,7 @@
     @Override
     public void processHostAdded(long hostId) {
         HostVO host = hostDao.findById(hostId);
-        if (host != null) {
+        if (host != null && getHypervisorType().equals(host.getHypervisorType())) {
             directDownloadManager.syncCertificatesToHost(hostId, host.getDataCenterId());
         }
     }
diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
index 9882ed5..b590893 100644
--- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
@@ -18,11 +18,13 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
@@ -31,6 +33,8 @@
 
 import javax.inject.Inject;
 
+import com.cloud.network.dao.PublicIpQuarantineDao;
+import com.cloud.network.vo.PublicIpQuarantineVO;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.annotation.AnnotationService;
@@ -308,16 +312,22 @@
     @Inject
     MessageBus messageBus;
 
+    @Inject
+    PublicIpQuarantineDao publicIpQuarantineDao;
+
     SearchBuilder<IPAddressVO> AssignIpAddressSearch;
     SearchBuilder<IPAddressVO> AssignIpAddressFromPodVlanSearch;
     private static final Object allocatedLock = new Object();
 
     static Boolean rulesContinueOnErrFlag = true;
 
-    private static final ConfigKey<Boolean> SystemVmPublicIpReservationModeStrictness = new ConfigKey<Boolean>("Advanced",
+    public static final ConfigKey<Boolean> SystemVmPublicIpReservationModeStrictness = new ConfigKey<Boolean>("Advanced",
             Boolean.class, "system.vm.public.ip.reservation.mode.strictness", "false",
             "If enabled, the use of System VMs public IP reservation is strict, preferred if not.", true, ConfigKey.Scope.Global);
 
+    public static final ConfigKey<Integer> PUBLIC_IP_ADDRESS_QUARANTINE_DURATION = new ConfigKey<>("Network", Integer.class, "public.ip.address.quarantine.duration",
+            "0", "The duration (in minutes) for the public IP address to be quarantined when it is disassociated.", true, ConfigKey.Scope.Domain);
+
     private Random rand = new Random(System.currentTimeMillis());
 
     private List<Long> getIpv6SupportingVlanRangeIds(long dcId) throws InsufficientAddressCapacityException {
@@ -523,8 +533,7 @@
         return true;
     }
 
-    private IpAddress allocateIP(Account ipOwner, boolean isSystem, long zoneId) throws ResourceAllocationException, InsufficientAddressCapacityException,
-            ConcurrentOperationException {
+    private IpAddress allocateIP(Account ipOwner, boolean isSystem, long zoneId) throws InsufficientAddressCapacityException, ConcurrentOperationException {
         Account caller = CallContext.current().getCallingAccount();
         long callerUserId = CallContext.current().getCallingUserId();
         // check permissions
@@ -698,6 +707,9 @@
     public boolean disassociatePublicIpAddress(long addrId, long userId, Account caller) {
 
         boolean success = true;
+        IPAddressVO ipToBeDisassociated = _ipAddressDao.findById(addrId);
+
+        PublicIpQuarantine publicIpQuarantine = null;
         // Cleanup all ip address resources - PF/LB/Static nat rules
         if (!cleanupIpResources(addrId, userId, caller)) {
             success = false;
@@ -723,10 +735,9 @@
             } catch (ResourceUnavailableException e) {
                 throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e);
             }
-        } else {
-            if (ip.getState() == IpAddress.State.Releasing) {
-                _ipAddressDao.unassignIpAddress(ip.getId());
-            }
+        } else if (ip.getState() == State.Releasing) {
+            publicIpQuarantine = addPublicIpAddressToQuarantine(ipToBeDisassociated, caller.getDomainId());
+            _ipAddressDao.unassignIpAddress(ip.getId());
         }
 
         annotationDao.removeByEntityType(AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(), ip.getUuid());
@@ -736,6 +747,8 @@
                 releasePortableIpAddress(addrId);
             }
             s_logger.debug("Released a public ip id=" + addrId);
+        } else if (publicIpQuarantine != null) {
+            removePublicIpAddressFromQuarantine(publicIpQuarantine.getId(), "Public IP address removed from quarantine as there was an error while disassociating it.");
         }
 
         return success;
@@ -972,6 +985,13 @@
 
         if (lockOneRow) {
             assert (addrs.size() == 1) : "Return size is incorrect: " + addrs.size();
+            IpAddress ipAddress = addrs.get(0);
+            boolean ipCanBeAllocated = canPublicIpAddressBeAllocated(ipAddress, owner);
+
+            if (!ipCanBeAllocated) {
+                throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP address [%s] as it is in quarantine.", ipAddress.getAddress()),
+                        DataCenter.class, dcId);
+            }
         }
 
         if (assign && !fetchFromDedicatedRange && VlanType.VirtualNetwork.equals(vlanUse)) {
@@ -1126,6 +1146,7 @@
             } else if (addr.getState() == IpAddress.State.Releasing) {
                 // Cleanup all the resources for ip address if there are any, and only then un-assign ip in the system
                 if (cleanupIpResources(addr.getId(), Account.ACCOUNT_ID_SYSTEM, _accountMgr.getSystemAccount())) {
+                    addPublicIpAddressToQuarantine(addr, network.getDomainId());
                     _ipAddressDao.unassignIpAddress(addr.getId());
                     messageBus.publish(_name, MESSAGE_RELEASE_IPADDR_EVENT, PublishScope.LOCAL, addr);
                 } else {
@@ -1258,8 +1279,7 @@
     @DB
     @Override
     public IpAddress allocateIp(final Account ipOwner, final boolean isSystem, Account caller, long callerUserId, final DataCenter zone, final Boolean displayIp, final String ipaddress)
-            throws ConcurrentOperationException,
-            ResourceAllocationException, InsufficientAddressCapacityException {
+            throws ConcurrentOperationException, InsufficientAddressCapacityException, CloudRuntimeException {
 
         final VlanType vlanType = VlanType.VirtualNetwork;
         final boolean assign = false;
@@ -1478,15 +1498,7 @@
             throw new InvalidParameterValueException("Ip address can be associated to the network with trafficType " + TrafficType.Guest);
         }
 
-        // Check that network belongs to IP owner - skip this check
-        //     - if zone is basic zone as there is just one guest network,
-        //     - if shared network in Advanced zone
-        //     - and it belongs to the system
-        if (network.getAccountId() != owner.getId()) {
-            if (zone.getNetworkType() != NetworkType.Basic && !(zone.getNetworkType() == NetworkType.Advanced && network.getGuestType() == Network.GuestType.Shared)) {
-                throw new InvalidParameterValueException("The owner of the network is not the same as owner of the IP");
-            }
-        }
+        validateNetworkAndIpOwnership(owner, ipToAssoc, network, zone);
 
         if (zone.getNetworkType() == NetworkType.Advanced) {
             // In Advance zone allow to do IP assoc only for Isolated networks with source nat service enabled
@@ -1550,6 +1562,21 @@
     }
 
     /**
+     * Check that network belongs to IP owner - skip this check
+     *  - if the IP belongs to the same VPC as the network
+     *  - if zone is basic zone as there is just one guest network,
+     *  - if shared network in Advanced zone
+     *  - and it belongs to the system
+     */
+    private static void validateNetworkAndIpOwnership(Account owner, IPAddressVO ipToAssoc, Network network, DataCenter zone) {
+        if (network.getAccountId() != owner.getId()) {
+            if (!network.getVpcId().equals(ipToAssoc.getVpcId()) && zone.getNetworkType() == NetworkType.Advanced && network.getGuestType() != GuestType.Shared) {
+                throw new InvalidParameterValueException("The owner of the network is not the same as owner of the IP");
+            }
+        }
+    }
+
+    /**
      * Prevents associating an IP address to an allocated (unimplemented network) network, throws an Exception otherwise
      * @param owner Used to check if the user belongs to the Network
      * @param ipToAssoc IP address to be associated to a Network, can only be associated to an implemented network for Source NAT
@@ -1631,15 +1658,7 @@
 
         DataCenter zone = _entityMgr.findById(DataCenter.class, network.getDataCenterId());
 
-        // Check that network belongs to IP owner - skip this check
-        //     - if zone is basic zone as there is just one guest network,
-        //     - if shared network in Advanced zone
-        //     - and it belongs to the system
-        if (network.getAccountId() != owner.getId()) {
-            if (zone.getNetworkType() != NetworkType.Basic && !(zone.getNetworkType() == NetworkType.Advanced && network.getGuestType() == Network.GuestType.Shared)) {
-                throw new InvalidParameterValueException("The owner of the network is not the same as owner of the IP");
-            }
-        }
+        validateNetworkAndIpOwnership(owner, ipToAssoc, network, zone);
 
         // Check if IP has any services (rules) associated in the network
         List<PublicIpAddress> ipList = new ArrayList<PublicIpAddress>();
@@ -2348,7 +2367,8 @@
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {UseSystemPublicIps, RulesContinueOnError, SystemVmPublicIpReservationModeStrictness, VrouterRedundantTiersPlacement, AllowUserListAvailableIpsOnSharedNetwork};
+        return new ConfigKey<?>[] {UseSystemPublicIps, RulesContinueOnError, SystemVmPublicIpReservationModeStrictness, VrouterRedundantTiersPlacement, AllowUserListAvailableIpsOnSharedNetwork,
+                PUBLIC_IP_ADDRESS_QUARANTINE_DURATION};
     }
 
     /**
@@ -2381,4 +2401,111 @@
     public static ConfigKey<Boolean> getSystemvmpublicipreservationmodestrictness() {
         return SystemVmPublicIpReservationModeStrictness;
     }
+
+    @Override
+    public boolean canPublicIpAddressBeAllocated(IpAddress ip, Account newOwner) {
+        PublicIpQuarantineVO publicIpQuarantineVO = publicIpQuarantineDao.findByPublicIpAddressId(ip.getId());
+
+        if (publicIpQuarantineVO == null) {
+            s_logger.debug(String.format("Public IP address [%s] is not in quarantine; therefore, it is allowed to be allocated.", ip));
+            return true;
+        }
+
+        if (!isPublicIpAddressStillInQuarantine(publicIpQuarantineVO, new Date())) {
+            s_logger.debug(String.format("Public IP address [%s] is no longer in quarantine; therefore, it is allowed to be allocated.", ip));
+            return true;
+        }
+
+        Account previousOwner = _accountMgr.getAccount(publicIpQuarantineVO.getPreviousOwnerId());
+
+        if (Objects.equals(previousOwner.getUuid(), newOwner.getUuid())) {
+            s_logger.debug(String.format("Public IP address [%s] is in quarantine; however, the Public IP previous owner [%s] is the same as the new owner [%s]; therefore the IP" +
+                    " can be allocated. The public IP address will be removed from quarantine.", ip, previousOwner, newOwner));
+            removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(), "IP was removed from quarantine because it has been allocated by the previous owner");
+            return true;
+        }
+
+        s_logger.error(String.format("Public IP address [%s] is in quarantine and the previous owner [%s] is different than the new owner [%s]; therefore, the IP cannot be " +
+                "allocated.", ip, previousOwner, newOwner));
+        return false;
+    }
+
+    public boolean isPublicIpAddressStillInQuarantine(PublicIpQuarantineVO publicIpQuarantineVO, Date currentDate) {
+        Date quarantineEndDate = publicIpQuarantineVO.getEndDate();
+        Date removedDate = publicIpQuarantineVO.getRemoved();
+        boolean hasQuarantineEndedEarly = removedDate != null;
+
+        return hasQuarantineEndedEarly && currentDate.before(removedDate) ||
+                !hasQuarantineEndedEarly && currentDate.before(quarantineEndDate);
+    }
+
+    @Override
+    public PublicIpQuarantine addPublicIpAddressToQuarantine(IpAddress publicIpAddress, Long domainId) {
+        Integer quarantineDuration = PUBLIC_IP_ADDRESS_QUARANTINE_DURATION.valueInDomain(domainId);
+        if (quarantineDuration <= 0) {
+            s_logger.debug(String.format("Not adding IP [%s] to quarantine because configuration [%s] has value equal or less to 0.", publicIpAddress.getAddress(),
+                    PUBLIC_IP_ADDRESS_QUARANTINE_DURATION.key()));
+            return null;
+        }
+
+        long ipId = publicIpAddress.getId();
+        long accountId = publicIpAddress.getAccountId();
+
+        if (accountId == Account.ACCOUNT_ID_SYSTEM) {
+            s_logger.debug(String.format("Not adding IP [%s] to quarantine because it belongs to the system account.", publicIpAddress.getAddress()));
+            return null;
+        }
+
+        Date currentDate = new Date();
+        Calendar quarantineEndDate = Calendar.getInstance();
+        quarantineEndDate.setTime(currentDate);
+        quarantineEndDate.add(Calendar.MINUTE, quarantineDuration);
+
+        PublicIpQuarantineVO publicIpQuarantine = new PublicIpQuarantineVO(ipId, accountId, currentDate, quarantineEndDate.getTime());
+        s_logger.debug(String.format("Adding public IP Address [%s] to quarantine for the duration of [%s] minute(s).", publicIpAddress.getAddress(), quarantineDuration));
+        return publicIpQuarantineDao.persist(publicIpQuarantine);
+    }
+
+    @Override
+    public void removePublicIpAddressFromQuarantine(Long quarantineProcessId, String removalReason) {
+        PublicIpQuarantineVO publicIpQuarantineVO = publicIpQuarantineDao.findById(quarantineProcessId);
+        Ip ipAddress = _ipAddressDao.findById(publicIpQuarantineVO.getPublicIpAddressId()).getAddress();
+        Date removedDate = new Date();
+        Long removerAccountId = CallContext.current().getCallingAccountId();
+
+        publicIpQuarantineVO.setRemoved(removedDate);
+        publicIpQuarantineVO.setRemovalReason(removalReason);
+        publicIpQuarantineVO.setRemoverAccountId(removerAccountId);
+
+        s_logger.debug(String.format("Removing public IP Address [%s] from quarantine by updating the removed date to [%s].", ipAddress, removedDate));
+        publicIpQuarantineDao.persist(publicIpQuarantineVO);
+    }
+
+    @Override
+    public PublicIpQuarantine updatePublicIpAddressInQuarantine(Long quarantineProcessId, Date newEndDate) {
+        PublicIpQuarantineVO publicIpQuarantineVO = publicIpQuarantineDao.findById(quarantineProcessId);
+        Ip ipAddress = _ipAddressDao.findById(publicIpQuarantineVO.getPublicIpAddressId()).getAddress();
+        Date currentEndDate = publicIpQuarantineVO.getEndDate();
+
+        publicIpQuarantineVO.setEndDate(newEndDate);
+
+        s_logger.debug(String.format("Updating the end date for the quarantine of the public IP Address [%s] from [%s] to [%s].", ipAddress, currentEndDate, newEndDate));
+        publicIpQuarantineDao.persist(publicIpQuarantineVO);
+        return publicIpQuarantineVO;
+    }
+
+    @Override
+    public void updateSourceNatIpAddress(IPAddressVO requestedIp, List<IPAddressVO> userIps) throws Exception{
+        Transaction.execute((TransactionCallbackWithException<IpAddress, Exception>) status -> {
+            // update all other IPs to not be sourcenat, should be at most one
+            for(IPAddressVO oldIpAddress :userIps) {
+                oldIpAddress.setSourceNat(false);
+                _ipAddressDao.update(oldIpAddress.getId(), oldIpAddress);
+            }
+            requestedIp.setSourceNat(true);
+            _ipAddressDao.update(requestedIp.getId(),requestedIp);
+            return requestedIp;
+        });
+    }
+
 }
diff --git a/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java b/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java
index 0d68329..52096f9 100644
--- a/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java
@@ -135,7 +135,7 @@
     /**
      * Allocates a public IPv6 address for the guest NIC. It will throw exceptions in the following cases:
      * <ul>
-     *    <li>the the requested IPv6 address is already in use in the network;</li>
+     *    <li>the requested IPv6 address is already in use in the network;</li>
      *    <li>IPv6 address is equals to the Gateway;</li>
      *    <li>the network offering is empty;</li>
      *    <li>the IPv6 address is not in the network.</li>
@@ -151,7 +151,7 @@
     /**
      * Performs some checks on the given IPv6 address. It will throw exceptions in the following cases:
      * <ul>
-     *    <li>the the requested IPv6 address is already in use in the network;</li>
+     *    <li>the requested IPv6 address is already in use in the network;</li>
      *    <li>IPv6 address is equals to the Gateway;</li>
      *    <li>the network offering is empty;</li>
      *    <li>the IPv6 address is not in the network.</li>
diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java
index e1f4fe7..0cfd6e6 100644
--- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java
@@ -297,6 +297,7 @@
         Vpc copyOfVpc;
         long copyOfVpcId;
         try {
+
             copyOfVpc = _vpcService.createVpc(vpc.getZoneId(), vpcOfferingId, vpc.getAccountId(), vpc.getName(),
                     vpc.getDisplayText(), vpc.getCidr(), vpc.getNetworkDomain(), vpc.getIp4Dns1(), vpc.getIp4Dns2(),
                     vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu());
diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java
index 696e93d..8600020 100644
--- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java
+++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java
@@ -44,6 +44,7 @@
 import org.apache.cloudstack.network.NetworkPermissionVO;
 import org.apache.cloudstack.network.dao.NetworkPermissionDao;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.api.ApiDBUtils;
@@ -122,7 +123,6 @@
 import com.cloud.user.User;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.Pair;
-import com.cloud.utils.StringUtils;
 import com.cloud.utils.component.AdapterBase;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.db.DB;
@@ -2377,7 +2377,7 @@
     @Override
     public void checkIp6Parameters(String startIPv6, String endIPv6, String ip6Gateway, String ip6Cidr) throws InvalidParameterValueException {
 
-        if (org.apache.commons.lang3.StringUtils.isAnyBlank(ip6Gateway, ip6Cidr)) {
+        if (StringUtils.isAnyBlank(ip6Gateway, ip6Cidr)) {
             throw new InvalidParameterValueException("ip6Gateway and ip6Cidr should be defined for an IPv6 network work properly");
         }
 
@@ -2392,7 +2392,7 @@
             throw new InvalidParameterValueException("ip6Gateway is not in ip6cidr indicated network!");
         }
 
-        if (org.apache.commons.lang3.StringUtils.isNotBlank(startIPv6)) {
+        if (StringUtils.isNotBlank(startIPv6)) {
             if (!NetUtils.isValidIp6(startIPv6)) {
                 throw new InvalidParameterValueException("Invalid format for the startIPv6 parameter");
             }
@@ -2401,7 +2401,7 @@
             }
         }
 
-        if (org.apache.commons.lang3.StringUtils.isNotBlank(endIPv6)) {
+        if (StringUtils.isNotBlank(endIPv6)) {
             if (!NetUtils.isValidIp6(endIPv6)) {
                 throw new InvalidParameterValueException("Invalid format for the endIPv6 parameter");
             }
@@ -2602,15 +2602,15 @@
         if (userData != null) {
             vmData.add(new String[]{USERDATA_DIR, USERDATA_FILE, userData});
         }
-        vmData.add(new String[]{METATDATA_DIR, SERVICE_OFFERING_FILE, StringUtils.unicodeEscape(serviceOffering)});
-        vmData.add(new String[]{METATDATA_DIR, AVAILABILITY_ZONE_FILE, StringUtils.unicodeEscape(zoneName)});
-        vmData.add(new String[]{METATDATA_DIR, LOCAL_HOSTNAME_FILE, StringUtils.unicodeEscape(vmHostName)});
+        vmData.add(new String[]{METATDATA_DIR, SERVICE_OFFERING_FILE, com.cloud.utils.StringUtils.unicodeEscape(serviceOffering)});
+        vmData.add(new String[]{METATDATA_DIR, AVAILABILITY_ZONE_FILE, com.cloud.utils.StringUtils.unicodeEscape(zoneName)});
+        vmData.add(new String[]{METATDATA_DIR, LOCAL_HOSTNAME_FILE, com.cloud.utils.StringUtils.unicodeEscape(vmHostName)});
         vmData.add(new String[]{METATDATA_DIR, LOCAL_IPV4_FILE, guestIpAddress});
 
         addUserDataDetailsToCommand(vmData, userDataDetails);
 
         String publicIpAddress = guestIpAddress;
-        String publicHostName = StringUtils.unicodeEscape(vmHostName);
+        String publicHostName = com.cloud.utils.StringUtils.unicodeEscape(vmHostName);
 
         if (dcVo.getNetworkType() != DataCenter.NetworkType.Basic) {
             if (publicIp != null) {
@@ -2655,7 +2655,7 @@
                     throw new CloudRuntimeException("Unable to get MD5 MessageDigest", e);
                 }
                 md5.reset();
-                md5.update(password.getBytes(StringUtils.getPreferredCharset()));
+                md5.update(password.getBytes(com.cloud.utils.StringUtils.getPreferredCharset()));
                 byte[] digest = md5.digest();
                 BigInteger bigInt = new BigInteger(1, digest);
                 String hashtext = bigInt.toString(16);
@@ -2674,6 +2674,11 @@
             vmData.add(new String[]{METATDATA_DIR, CLOUD_DOMAIN_ID_FILE, domain.getUuid()});
         }
 
+        String customCloudName = VirtualMachineManager.MetadataCustomCloudName.valueIn(datacenterId);
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(customCloudName)) {
+            vmData.add(new String[]{METATDATA_DIR, CLOUD_NAME_FILE, customCloudName});
+        }
+
         return vmData;
     }
 
@@ -2683,8 +2688,8 @@
             String[] keyValuePairs = userDataDetails.split(",");
             for(String pair : keyValuePairs)
             {
-                final Pair<String, String> keyValue = StringUtils.getKeyValuePairWithSeparator(pair, "=");
-                vmData.add(new String[]{METATDATA_DIR, keyValue.first(), StringUtils.unicodeEscape(keyValue.second())});
+                final Pair<String, String> keyValue = com.cloud.utils.StringUtils.getKeyValuePairWithSeparator(pair, "=");
+                vmData.add(new String[]{METATDATA_DIR, keyValue.first(), com.cloud.utils.StringUtils.unicodeEscape(keyValue.second())});
             }
         }
     }
@@ -2707,7 +2712,7 @@
 
     @Override
     public Pair<String, String> getNetworkIp4Dns(final Network network, final DataCenter zone) {
-        if (org.apache.commons.lang3.StringUtils.isNotBlank(network.getDns1())) {
+        if (StringUtils.isNotBlank(network.getDns1())) {
             return new Pair<>(network.getDns1(), network.getDns2());
         }
         return new Pair<>(zone.getDns1(), zone.getDns2());
@@ -2715,7 +2720,7 @@
 
     @Override
     public Pair<String, String> getNetworkIp6Dns(final Network network, final DataCenter zone) {
-        if (org.apache.commons.lang3.StringUtils.isNotBlank(network.getIp6Dns1())) {
+        if (StringUtils.isNotBlank(network.getIp6Dns1())) {
             return new Pair<>(network.getIp6Dns1(), network.getIp6Dns2());
         }
         return new Pair<>(zone.getIp6Dns1(), zone.getIp6Dns2());
@@ -2723,26 +2728,26 @@
 
     @Override
     public void verifyIp4DnsPair(String ip4Dns1, String ip4Dns2) {
-        if (org.apache.commons.lang3.StringUtils.isEmpty(ip4Dns1) && org.apache.commons.lang3.StringUtils.isNotEmpty(ip4Dns2)) {
+        if (StringUtils.isEmpty(ip4Dns1) && StringUtils.isNotEmpty(ip4Dns2)) {
             throw new InvalidParameterValueException("Second IPv4 DNS can be specified only with the first IPv4 DNS");
         }
-        if (org.apache.commons.lang3.StringUtils.isNotEmpty(ip4Dns1) && !NetUtils.isValidIp4(ip4Dns1)) {
+        if (StringUtils.isNotEmpty(ip4Dns1) && !NetUtils.isValidIp4(ip4Dns1)) {
             throw new InvalidParameterValueException("Invalid IPv4 for DNS1");
         }
-        if (org.apache.commons.lang3.StringUtils.isNotEmpty(ip4Dns2) && !NetUtils.isValidIp4(ip4Dns2)) {
+        if (StringUtils.isNotEmpty(ip4Dns2) && !NetUtils.isValidIp4(ip4Dns2)) {
             throw new InvalidParameterValueException("Invalid IPv4 for DNS2");
         }
     }
 
     @Override
     public void verifyIp6DnsPair(String ip6Dns1, String ip6Dns2) {
-        if (org.apache.commons.lang3.StringUtils.isEmpty(ip6Dns1) && org.apache.commons.lang3.StringUtils.isNotEmpty(ip6Dns2)) {
+        if (StringUtils.isEmpty(ip6Dns1) && StringUtils.isNotEmpty(ip6Dns2)) {
             throw new InvalidParameterValueException("Second IPv6 DNS can be specified only with the first IPv6 DNS");
         }
-        if (org.apache.commons.lang3.StringUtils.isNotEmpty(ip6Dns1) && !NetUtils.isValidIp6(ip6Dns1)) {
+        if (StringUtils.isNotEmpty(ip6Dns1) && !NetUtils.isValidIp6(ip6Dns1)) {
             throw new InvalidParameterValueException("Invalid IPv6 for IPv6 DNS1");
         }
-        if (org.apache.commons.lang3.StringUtils.isNotEmpty(ip6Dns2) && !NetUtils.isValidIp6(ip6Dns2)) {
+        if (StringUtils.isNotEmpty(ip6Dns2) && !NetUtils.isValidIp6(ip6Dns2)) {
             throw new InvalidParameterValueException("Invalid IPv6 for IPv6 DNS2");
         }
     }
diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
index c46ab8c..ca1967a 100644
--- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
+++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
@@ -41,6 +41,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.dao.PublicIpQuarantineDao;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.service.dao.ServiceOfferingDao;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
@@ -55,6 +56,8 @@
 import org.apache.cloudstack.api.command.admin.network.ListNetworksCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.network.UpdateNetworkCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd;
+import org.apache.cloudstack.api.command.user.address.RemoveQuarantinedIpCmd;
+import org.apache.cloudstack.api.command.user.address.UpdateQuarantinedIpCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkPermissionsCmd;
 import org.apache.cloudstack.api.command.user.network.ListNetworkPermissionsCmd;
@@ -80,6 +83,8 @@
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
@@ -176,11 +181,13 @@
 import com.cloud.network.vpc.NetworkACL;
 import com.cloud.network.vpc.PrivateIpVO;
 import com.cloud.network.vpc.Vpc;
+import com.cloud.network.vpc.VpcGatewayVO;
 import com.cloud.network.vpc.VpcManager;
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.NetworkACLDao;
 import com.cloud.network.vpc.dao.PrivateIpDao;
 import com.cloud.network.vpc.dao.VpcDao;
+import com.cloud.network.vpc.dao.VpcGatewayDao;
 import com.cloud.network.vpc.dao.VpcOfferingDao;
 import com.cloud.offering.NetworkOffering;
 import com.cloud.offerings.NetworkOfferingVO;
@@ -264,9 +271,13 @@
     private static final long MIN_GRE_KEY = 0L;
     private static final long MAX_GRE_KEY = 4294967295L; // 2^32 -1
     private static final long MIN_VXLAN_VNI = 0L;
+    /**
+     // MAX_VXLAN_VNI should be 16777215L (2^24-1), but Linux vxlan interface doesn't accept VNI:2^24-1 now.
+     // It seems a bug.
+     // Is this still valid (per 2023?)
+     */
     private static final long MAX_VXLAN_VNI = 16777214L; // 2^24 -2
-    // MAX_VXLAN_VNI should be 16777215L (2^24-1), but Linux vxlan interface doesn't accept VNI:2^24-1 now.
-    // It seems a bug.
+    private static final String NETWORK_OFFERING_ID = "networkOfferingId";
 
     @Inject
     DataCenterDao _dcDao = null;
@@ -382,6 +393,8 @@
     @Inject
     Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao;
     @Inject
+    VpcGatewayDao vpcGatewayDao;
+    @Inject
     AlertManager alertManager;
     @Inject
     DomainRouterDao routerDao;
@@ -391,6 +404,8 @@
     CommandSetupHelper commandSetupHelper;
     @Inject
     ServiceOfferingDao serviceOfferingDao;
+    @Inject
+    PublicIpQuarantineDao publicIpQuarantineDao;
 
     @Autowired
     @Qualifier("networkHelper")
@@ -828,7 +843,7 @@
     }
 
     /**
-     * It allocates a secondary IP alias on the NIC. It can be either an Ipv4 or an Ipv6 or even both, according to the the given IpAddresses object.
+     * It allocates a secondary IP alias on the NIC. It can be either an Ipv4 or an Ipv6 or even both, according to the given IpAddresses object.
      */
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_NIC_SECONDARY_IP_ASSIGN, eventDescription = "assigning secondary ip to nic", create = true)
@@ -1289,15 +1304,20 @@
         }
     }
 
-    private void validateRouterIps(String routerIp, String routerIpv6, String startIp, String endIp, String gateway,
-                                   String netmask, String startIpv6, String endIpv6, String ip6Cidr) {
+    void validateSharedNetworkRouterIPs(String gateway, String startIP, String endIP, String netmask, String routerIPv4, String routerIPv6, String startIPv6, String endIPv6, String ip6Cidr, NetworkOffering ntwkOff) {
+        if (ntwkOff.getGuestType() == GuestType.Shared) {
+            validateSharedNetworkRouterIPv4(routerIPv4, startIP, endIP, gateway, netmask);
+            validateSharedNetworkRouterIPv6(routerIPv6, startIPv6, endIPv6, ip6Cidr);
+
+        }
+    }
+
+    private void validateSharedNetworkRouterIPv4(String routerIp, String startIp, String endIp, String gateway, String netmask) {
         if (StringUtils.isNotBlank(routerIp)) {
             if (startIp != null && endIp == null) {
                 endIp = startIp;
             }
-            if (!NetUtils.isValidIp4(routerIp)) {
-                throw new CloudRuntimeException("Router IPv4 IP provided is of incorrect format");
-            }
+            isIPv4AddressValid(routerIp);
             if (StringUtils.isNoneBlank(startIp, endIp)) {
                 if (!NetUtils.isIpInRange(routerIp, startIp, endIp)) {
                     throw new CloudRuntimeException("Router IPv4 IP provided is not within the specified range: " + startIp + " - " + endIp);
@@ -1309,26 +1329,39 @@
                 }
             }
         }
-        if (StringUtils.isNotBlank(routerIpv6)) {
-            if (startIpv6 != null && endIpv6 == null) {
-                endIpv6 = startIpv6;
+    }
+
+    private void validateSharedNetworkRouterIPv6(String routerIPv6, String startIPv6, String endIPv6, String cidrIPv6) {
+        if (StringUtils.isNotBlank(routerIPv6)) {
+            if (startIPv6 != null && endIPv6 == null) {
+                endIPv6 = startIPv6;
             }
-            if (!NetUtils.isValidIp6(routerIpv6)) {
-                throw new CloudRuntimeException("Router IPv6 address provided is of incorrect format");
-            }
-            if (StringUtils.isNoneBlank(startIpv6, endIpv6)) {
-                String ipv6Range = startIpv6 + "-" + endIpv6;
-                if (!NetUtils.isIp6InRange(routerIpv6, ipv6Range)) {
-                    throw new CloudRuntimeException("Router IPv6 address provided is not within the specified range: " + startIpv6 + " - " + endIpv6);
+            isIPv6AddressValid(routerIPv6);
+            if (StringUtils.isNoneBlank(startIPv6, endIPv6)) {
+                String ipv6Range = startIPv6 + "-" + endIPv6;
+                if (!NetUtils.isIp6InRange(routerIPv6, ipv6Range)) {
+                    throw new CloudRuntimeException("Router IPv6 address provided is not within the specified range: " + startIPv6 + " - " + endIPv6);
                 }
             } else {
-                if (!NetUtils.isIp6InNetwork(routerIpv6, ip6Cidr)) {
+                if (!NetUtils.isIp6InNetwork(routerIPv6, cidrIPv6)) {
                     throw new CloudRuntimeException("Router IPv6 address provided is not with the network range");
                 }
             }
         }
     }
 
+    private void isIPv4AddressValid(String routerIp) {
+        if (!NetUtils.isValidIp4(routerIp)) {
+            throw new CloudRuntimeException("Router IPv4 IP provided is of incorrect format");
+        }
+    }
+
+    private void isIPv6AddressValid(String routerIPv6) {
+        if (!NetUtils.isValidIp6(routerIPv6)) {
+            throw new CloudRuntimeException("Router IPv6 address provided is of incorrect format");
+        }
+    }
+
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_NETWORK_CREATE, eventDescription = "creating network")
@@ -1339,34 +1372,26 @@
         String endIP = cmd.getEndIp();
         String netmask = cmd.getNetmask();
         String networkDomain = cmd.getNetworkDomain();
-        String vlanId = null;
-        boolean bypassVlanOverlapCheck = false;
-        boolean hideIpAddressUsage = false;
-        String routerIp = null;
-        String routerIpv6 = null;
-        if (cmd instanceof CreateNetworkCmdByAdmin) {
-            vlanId = ((CreateNetworkCmdByAdmin)cmd).getVlan();
-            bypassVlanOverlapCheck = ((CreateNetworkCmdByAdmin)cmd).getBypassVlanOverlapCheck();
-            hideIpAddressUsage = ((CreateNetworkCmdByAdmin)cmd).getHideIpAddressUsage();
-            routerIp = ((CreateNetworkCmdByAdmin)cmd).getRouterIp();
-            routerIpv6 = ((CreateNetworkCmdByAdmin)cmd).getRouterIpv6();
-        }
+
+        boolean adminCalledUs = cmd instanceof CreateNetworkCmdByAdmin;
+        String vlanId = adminCalledUs ? ((CreateNetworkCmdByAdmin)cmd).getVlan() : null;
+        boolean bypassVlanOverlapCheck = adminCalledUs && ((CreateNetworkCmdByAdmin)cmd).getBypassVlanOverlapCheck();
+        boolean hideIpAddressUsage = adminCalledUs && ((CreateNetworkCmdByAdmin)cmd).getHideIpAddressUsage();
+        String routerIPv4 = adminCalledUs ? ((CreateNetworkCmdByAdmin)cmd).getRouterIp() : null;
+        String routerIPv6 = adminCalledUs ? ((CreateNetworkCmdByAdmin)cmd).getRouterIpv6() : null;
 
         String name = cmd.getNetworkName();
         String displayText = cmd.getDisplayText();
         Account caller = CallContext.current().getCallingAccount();
         Long physicalNetworkId = cmd.getPhysicalNetworkId();
-        Long zoneId = cmd.getZoneId();
-        String aclTypeStr = cmd.getAclType();
         Long domainId = cmd.getDomainId();
-        boolean isDomainSpecific = false;
         Boolean subdomainAccess = cmd.getSubdomainAccess();
         Long vpcId = cmd.getVpcId();
         String startIPv6 = cmd.getStartIpv6();
         String endIPv6 = cmd.getEndIpv6();
         String ip6Gateway = cmd.getIp6Gateway();
         String ip6Cidr = cmd.getIp6Cidr();
-        Boolean displayNetwork = cmd.getDisplayNetwork();
+        boolean displayNetwork = ! Boolean.FALSE.equals(cmd.getDisplayNetwork());
         Long aclId = cmd.getAclId();
         String isolatedPvlan = cmd.getIsolatedPvlan();
         String externalId = cmd.getExternalId();
@@ -1379,127 +1404,31 @@
         String ip6Dns1 = cmd.getIp6Dns1();
         String ip6Dns2 = cmd.getIp6Dns2();
 
-        // Validate network offering
-        NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
-        if (ntwkOff == null || ntwkOff.isSystemOnly()) {
-            InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find network offering by specified id");
-            if (ntwkOff != null) {
-                ex.addProxyObject(ntwkOff.getUuid(), "networkOfferingId");
-            }
-            throw ex;
-        }
+        // Validate network offering id
+        NetworkOffering ntwkOff = getAndValidateNetworkOffering(networkOfferingId);
 
-        Account owner = null;
-        if ((cmd.getAccountName() != null && domainId != null) || cmd.getProjectId() != null) {
-            owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), domainId, cmd.getProjectId());
-        } else {
-            s_logger.info(String.format("Assigning the network to caller:%s because either projectId or accountname and domainId are not provided", caller.getAccountName()));
-            owner = caller;
-        }
+        Account owner = getOwningAccount(cmd, caller);
 
-        // validate physical network and zone
-        // Check if physical network exists
-        PhysicalNetwork pNtwk = null;
-        if (physicalNetworkId != null) {
-            pNtwk = _physicalNetworkDao.findById(physicalNetworkId);
-            if (pNtwk == null) {
-                throw new InvalidParameterValueException("Unable to find a physical network having the specified physical network id");
-            }
-        }
+        PhysicalNetwork pNtwk = getAndValidatePhysicalNetwork(physicalNetworkId);
 
-        if (zoneId == null) {
-            zoneId = pNtwk.getDataCenterId();
-        }
-
-        if (displayNetwork == null) {
-            displayNetwork = true;
-        }
-
-        DataCenter zone = _dcDao.findById(zoneId);
-        if (zone == null) {
-            throw new InvalidParameterValueException("Specified zone id was not found");
-        }
+        DataCenter zone = getAndValidateZone(cmd, pNtwk);
 
         _accountMgr.checkAccess(owner, ntwkOff, zone);
 
-        if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
-            // See DataCenterVO.java
-            PermissionDeniedException ex = new PermissionDeniedException("Cannot perform this operation since specified Zone is currently disabled");
-            ex.addProxyObject(zone.getUuid(), "zoneId");
-            throw ex;
-        }
+        validateZoneAvailability(caller, zone);
 
-        // Only domain and account ACL types are supported in Acton.
-        ACLType aclType = null;
-        if (aclTypeStr != null) {
-            if (aclTypeStr.equalsIgnoreCase(ACLType.Account.toString())) {
-                aclType = ACLType.Account;
-            } else if (aclTypeStr.equalsIgnoreCase(ACLType.Domain.toString())) {
-                aclType = ACLType.Domain;
-            } else {
-                throw new InvalidParameterValueException("Incorrect aclType specified. Check the API documentation for supported types");
-            }
-            // In 3.0 all Shared networks should have aclType == Domain, all Isolated networks aclType==Account
-            if (ntwkOff.getGuestType() == GuestType.Isolated) {
-                if (aclType != ACLType.Account) {
-                    throw new InvalidParameterValueException("AclType should be " + ACLType.Account + " for network of type " + Network.GuestType.Isolated);
-                }
-            } else if (ntwkOff.getGuestType() == GuestType.Shared) {
-                if (!(aclType == ACLType.Domain || aclType == ACLType.Account)) {
-                    throw new InvalidParameterValueException("AclType should be " + ACLType.Domain + " or " + ACLType.Account + " for network of type " + Network.GuestType.Shared);
-                }
-            }
-        } else {
-            if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2) {
-                aclType = ACLType.Account;
-            } else if (ntwkOff.getGuestType() == GuestType.Shared) {
-                if (_accountMgr.isRootAdmin(caller.getId())) {
-                    aclType = ACLType.Domain;
-                } else if (_accountMgr.isNormalUser(caller.getId())) {
-                    aclType = ACLType.Account;
-                } else {
-                    throw new InvalidParameterValueException("AclType must be specified for shared network created by domain admin");
-                }
-            }
-        }
+        ACLType aclType = getAclType(caller, cmd.getAclType(), ntwkOff);
 
-        if (ntwkOff.getGuestType() != GuestType.Shared && (!StringUtils.isAllBlank(routerIp, routerIpv6))) {
+        if (ntwkOff.getGuestType() != GuestType.Shared && (!StringUtils.isAllBlank(routerIPv4, routerIPv6))) {
             throw new InvalidParameterValueException("Router IP can be specified only for Shared networks");
         }
 
         if (ntwkOff.getGuestType() == GuestType.Shared && !_networkModel.isProviderForNetworkOffering(Provider.VirtualRouter, networkOfferingId)
-                && (!StringUtils.isAllBlank(routerIp, routerIpv6))) {
+                && (!StringUtils.isAllBlank(routerIPv4, routerIPv6))) {
             throw new InvalidParameterValueException("Virtual Router is not a supported provider for the Shared network, hence router ip should not be provided");
         }
 
-        // Check if the network is domain specific
-        if (aclType == ACLType.Domain) {
-            // only Admin can create domain with aclType=Domain
-            if (!_accountMgr.isAdmin(caller.getId())) {
-                throw new PermissionDeniedException("Only admin can create networks with aclType=Domain");
-            }
-
-            // only shared networks can be Domain specific
-            if (ntwkOff.getGuestType() != GuestType.Shared) {
-                throw new InvalidParameterValueException("Only " + GuestType.Shared + " networks can have aclType=" + ACLType.Domain);
-            }
-
-            if (domainId != null) {
-                if (ntwkOff.getTrafficType() != TrafficType.Guest || ntwkOff.getGuestType() != Network.GuestType.Shared) {
-                    throw new InvalidParameterValueException("Domain level networks are supported just for traffic type " + TrafficType.Guest + " and guest type " + Network.GuestType.Shared);
-                }
-
-                DomainVO domain = _domainDao.findById(domainId);
-                if (domain == null) {
-                    throw new InvalidParameterValueException("Unable to find domain by specified id");
-                }
-                _accountMgr.checkAccess(caller, domain);
-            }
-            isDomainSpecific = true;
-
-        } else if (subdomainAccess != null) {
-            throw new InvalidParameterValueException("Parameter subDomainAccess can be specified only with aclType=Domain");
-        }
+        boolean isDomainSpecific = isDomainSpecificNetworkRequested(caller, domainId, subdomainAccess, ntwkOff, aclType);
 
         if (aclType == ACLType.Domain) {
             owner = _accountDao.findById(Account.ACCOUNT_ID_SYSTEM);
@@ -1601,7 +1530,8 @@
             }
         }
 
-        validateRouterIps(routerIp, routerIpv6, startIP, endIP, gateway, netmask, startIPv6, endIPv6, ip6Cidr);
+        validateSharedNetworkRouterIPs(gateway, startIP, endIP, netmask, routerIPv4, routerIPv6, startIPv6, endIPv6, ip6Cidr, ntwkOff);
+
         Pair<String, String> ip6GatewayCidr = null;
         if (zone.getNetworkType() == NetworkType.Advanced && ntwkOff.getGuestType() == GuestType.Isolated) {
             ipv6 = _networkOfferingDao.isIpv6Supported(ntwkOff.getId());
@@ -1673,7 +1603,7 @@
         if (cidr != null && providersConfiguredForExternalNetworking(ntwkProviders)) {
             if (ntwkOff.getGuestType() == GuestType.Shared && (zone.getNetworkType() == NetworkType.Advanced) && isSharedNetworkOfferingWithServices(networkOfferingId)) {
                 // validate if CIDR specified overlaps with any of the CIDR's allocated for isolated networks and shared networks in the zone
-                checkSharedNetworkCidrOverlap(zoneId, pNtwk.getId(), cidr);
+                checkSharedNetworkCidrOverlap(zone.getId(), pNtwk.getId(), cidr);
             } else {
                 // if the guest network is for the VPC, if any External Provider are supported in VPC
                 // cidr will not be null as it is generated from the super cidr of vpc.
@@ -1698,10 +1628,10 @@
 
         // Can add vlan range only to the network which allows it
         if (createVlan && !ntwkOff.isSpecifyIpRanges()) {
-            throwInvalidIdException("Network offering with specified id doesn't support adding multiple ip ranges", ntwkOff.getUuid(), "networkOfferingId");
+            throwInvalidIdException("Network offering with specified id doesn't support adding multiple ip ranges", ntwkOff.getUuid(), NETWORK_OFFERING_ID);
         }
 
-        Pair<Integer, Integer> interfaceMTUs = validateMtuConfig(publicMtu, privateMtu, zoneId);
+        Pair<Integer, Integer> interfaceMTUs = validateMtuConfig(publicMtu, privateMtu, zone.getId());
         mtuCheckForVpcNetwork(vpcId, interfaceMTUs, publicMtu, privateMtu);
 
         Network associatedNetwork = null;
@@ -1720,9 +1650,12 @@
 
         checkNetworkDns(ipv6, ntwkOff, vpcId, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2);
 
-        Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zoneId,
+        Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zone.getId(),
                 domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan,
-                externalId, routerIp, routerIpv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs);
+                externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs);
+
+        // retrieve, acquire and associate the correct ip adresses
+        checkAndSetRouterSourceNatIp(owner, cmd, network);
 
         if (hideIpAddressUsage) {
             _networkDetailsDao.persist(new NetworkDetailVO(network.getId(), Network.hideIpAddressUsage, String.valueOf(hideIpAddressUsage), false));
@@ -1739,6 +1672,213 @@
         return network;
     }
 
+    void checkAndSetRouterSourceNatIp(Account owner, CreateNetworkCmd cmd, Network network) throws InsufficientAddressCapacityException, ResourceAllocationException {
+        String sourceNatIp = cmd.getSourceNatIP();
+        if (sourceNatIp == null) {
+            s_logger.debug(String.format("no source nat ip given for create network %s command, using something arbitrary.", cmd.getNetworkName()));
+            return; // nothing to try
+        }
+        IpAddress ip = allocateIP(owner, cmd.getZoneId(), network.getId(), null, sourceNatIp);
+        try {
+            associateIPToNetwork(ip.getId(), network.getId());
+        } catch (ResourceUnavailableException e) {
+            String msg = String.format("can´t use %s as sourcenat IP address for network %s/%s as it is un available", sourceNatIp, network.getName(), network.getUuid());
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg,e);
+        }
+    }
+
+    /**
+     * @param cmd
+     * @param network
+     * @return whether the sourceNat is changed, and consequently restart is needed
+     * @throws InsufficientAddressCapacityException
+     * @throws ResourceAllocationException
+     */
+    private boolean checkAndUpdateRouterSourceNatIp(UpdateNetworkCmd cmd, Network network) {
+        IPAddressVO requestedIp = checkSourceNatIpAddressForUpdate(cmd, network);
+        if (requestedIp == null) return false; // ip not associated with this network
+
+        List<IPAddressVO> userIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), true);
+        if (! userIps.isEmpty()) {
+            try {
+                _ipAddrMgr.updateSourceNatIpAddress(requestedIp, userIps);
+            } catch (Exception e) { // pokemon execption from transaction
+                String msg = String.format("Update of source NAT ip to %s for network \"%s\"/%s failed due to %s",
+                        requestedIp.getAddress().addr(), network.getName(), network.getUuid(), e.getLocalizedMessage());
+                s_logger.error(msg);
+                throw new CloudRuntimeException(msg, e);
+            }
+        }
+        return true;
+    }
+
+    @Nullable
+    private IPAddressVO checkSourceNatIpAddressForUpdate(UpdateNetworkCmd cmd, Network network) {
+        String sourceNatIp = cmd.getSourceNatIP();
+        if (sourceNatIp == null) {
+            s_logger.trace(String.format("no source NAT ip given to update network %s with.", cmd.getNetworkName()));
+            return null;
+        } else {
+            s_logger.info(String.format("updating network %s to have source NAT ip %s", cmd.getNetworkName(), sourceNatIp));
+        }
+        // check if the address is already aqcuired for this network
+        IPAddressVO requestedIp = _ipAddressDao.findByIp(sourceNatIp);
+        if (requestedIp == null || requestedIp.getAssociatedWithNetworkId() == null || ! requestedIp.getAssociatedWithNetworkId().equals(network.getId())) {
+            s_logger.warn(String.format("Source NAT IP %s is not associated with network %s/%s. It cannot be used as source NAT IP.",
+                    sourceNatIp, network.getName(), network.getUuid()));
+            return null;
+        }
+        // check if it is the current source NAT address
+        if (requestedIp.isSourceNat()) {
+            s_logger.info(String.format("IP address %s is allready the source Nat address. Not updating!", sourceNatIp));
+            return null;
+        }
+        return requestedIp;
+    }
+
+    @Nullable
+    private ACLType getAclType(Account caller, String aclTypeStr, NetworkOffering ntwkOff) {
+        // Only domain and account ACL types are supported in Acton.
+        ACLType aclType = null;
+        if (aclTypeStr != null) {
+            aclType = getAclType(aclTypeStr, ntwkOff);
+        } else {
+            aclType = getAclType(caller, ntwkOff, aclType);
+        }
+        return aclType;
+    }
+
+    @NotNull
+    private static ACLType getAclType(String aclTypeStr, NetworkOffering ntwkOff) {
+        ACLType aclType;
+        if (aclTypeStr.equalsIgnoreCase(ACLType.Account.toString())) {
+            aclType = ACLType.Account;
+        } else if (aclTypeStr.equalsIgnoreCase(ACLType.Domain.toString())) {
+            aclType = ACLType.Domain;
+        } else {
+            throw new InvalidParameterValueException("Incorrect aclType specified. Check the API documentation for supported types");
+        }
+        // In 3.0 all Shared networks should have aclType == Domain, all Isolated networks aclType==Account
+        if (ntwkOff.getGuestType() == GuestType.Isolated && aclType != ACLType.Account) {
+            throw new InvalidParameterValueException("AclType should be " + ACLType.Account + " for network of type " + GuestType.Isolated);
+        }
+        return aclType;
+    }
+
+    private ACLType getAclType(Account caller, NetworkOffering ntwkOff, ACLType aclType) {
+        if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2) {
+            aclType = ACLType.Account;
+        } else if (ntwkOff.getGuestType() == GuestType.Shared) {
+            if (_accountMgr.isRootAdmin(caller.getId())) {
+                aclType = ACLType.Domain;
+            } else if (_accountMgr.isNormalUser(caller.getId())) {
+                aclType = ACLType.Account;
+            } else {
+                throw new InvalidParameterValueException("AclType must be specified for shared network created by domain admin");
+            }
+        }
+        return aclType;
+    }
+
+    private void validateZoneAvailability(Account caller, DataCenter zone) {
+        if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
+            // See DataCenterVO.java
+            PermissionDeniedException ex = new PermissionDeniedException("Cannot perform this operation since specified Zone is currently disabled");
+            ex.addProxyObject(zone.getUuid(), "zoneId");
+            throw ex;
+        }
+    }
+
+    private boolean isDomainSpecificNetworkRequested(Account caller, Long domainId, Boolean subdomainAccess, NetworkOffering ntwkOff, ACLType aclType) {
+        boolean isDomainSpecific = false;
+        // Check if the network is domain specific
+        if (aclType == ACLType.Domain) {
+            // only Admin can create domain with aclType=Domain
+            if (!_accountMgr.isAdmin(caller.getId())) {
+                throw new PermissionDeniedException("Only admin can create networks with aclType=Domain");
+            }
+
+            // only shared networks can be Domain specific
+            if (ntwkOff.getGuestType() != GuestType.Shared) {
+                throw new InvalidParameterValueException("Only " + GuestType.Shared + " networks can have aclType=" + ACLType.Domain);
+            }
+
+            if (domainId != null) {
+                if (ntwkOff.getTrafficType() != TrafficType.Guest || ntwkOff.getGuestType() != GuestType.Shared) {
+                    throw new InvalidParameterValueException("Domain level networks are supported just for traffic type " + TrafficType.Guest + " and guest type " + GuestType.Shared);
+                }
+
+                DomainVO domain = _domainDao.findById(domainId);
+                if (domain == null) {
+                    throw new InvalidParameterValueException("Unable to find domain by specified id");
+                }
+                _accountMgr.checkAccess(caller, domain);
+            }
+            isDomainSpecific = true;
+
+        } else if (subdomainAccess != null) {
+            throw new InvalidParameterValueException("Parameter subDomainAccess can be specified only with aclType=Domain");
+        }
+        return isDomainSpecific;
+    }
+
+    @NotNull
+    private DataCenter getAndValidateZone(CreateNetworkCmd cmd, PhysicalNetwork pNtwk) {
+        Long zoneId = (cmd.getZoneId() == null) ? pNtwk.getDataCenterId() : cmd.getZoneId();
+        DataCenter zone = _dcDao.findById(zoneId);
+        if (zone == null) {
+            throw new InvalidParameterValueException("Specified zone id was not found");
+        }
+        return zone;
+    }
+
+    /**
+     // validate physical network and zone
+     // Check if physical network exists
+     *
+     * @param physicalNetworkId the id of the required physical network
+     * @return the data object for the physical network
+     */
+    @NotNull
+    private PhysicalNetwork getAndValidatePhysicalNetwork(Long physicalNetworkId) {
+        PhysicalNetwork pNtwk = null;
+        if (physicalNetworkId != null) {
+            pNtwk = getPhysicalNetwork(physicalNetworkId);
+            if (pNtwk == null) {
+                throw new InvalidParameterValueException("Unable to find a physical network having the specified physical network id");
+            }
+        } else {
+            throw new CloudRuntimeException("cannot create Guestnetwork without physical network.");
+        }
+        return pNtwk;
+    }
+
+    private Account getOwningAccount(CreateNetworkCmd cmd, Account caller) {
+        Account owner = null;
+        Long domainId = cmd.getDomainId();
+        if ((cmd.getAccountName() != null && domainId != null) || cmd.getProjectId() != null) {
+            owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), domainId, cmd.getProjectId());
+        } else {
+            s_logger.info(String.format("Assigning the network to caller:%s because either projectId or accountname and domainId are not provided", caller.getAccountName()));
+            owner = caller;
+        }
+        return owner;
+    }
+
+    @NotNull
+    private NetworkOffering getAndValidateNetworkOffering(Long networkOfferingId) {
+        NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
+        if (ntwkOff == null || ntwkOff.isSystemOnly()) {
+            InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find network offering by specified id");
+            if (ntwkOff != null) {
+                ex.addProxyObject(ntwkOff.getUuid(), NETWORK_OFFERING_ID);
+            }
+            throw ex;
+        }
+        return ntwkOff;
+    }
+
     protected void mtuCheckForVpcNetwork(Long vpcId, Pair<Integer, Integer> interfaceMTUs, Integer publicMtu, Integer privateMtu) {
         if (vpcId != null && publicMtu != null) {
             VpcVO vpc = _vpcDao.findById(vpcId);
@@ -1808,24 +1948,17 @@
         if (domainId != null && associatedNetwork.getDomainId() != domainId) {
             throw new InvalidParameterValueException("The new network and associated network MUST be in same domain");
         }
-        if (cidr != null && associatedNetwork.getCidr() != null && NetUtils.isNetworksOverlap(cidr, associatedNetwork.getCidr())) {
-            throw new InvalidParameterValueException("The cidr overlaps with associated network: " + associatedNetwork.getName());
-        }
-        List<NetworkDetailVO> associatedNetworks = _networkDetailsDao.findDetails(Network.AssociatedNetworkId, String.valueOf(associatedNetworkId), null);
-        for (NetworkDetailVO networkDetailVO : associatedNetworks) {
-            NetworkVO associatedNetwork2 = _networksDao.findById(networkDetailVO.getResourceId());
-            if (associatedNetwork2 != null) {
-                List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(associatedNetwork2.getId());
-                if (vlans.isEmpty()) {
-                    continue;
-                }
-                String startIP2 = vlans.get(0).getIpRange().split("-")[0];
-                String endIP2 = vlans.get(0).getIpRange().split("-")[1];
-                if (StringUtils.isNoneBlank(startIp, startIP2) && NetUtils.ipRangesOverlap(startIp, endIp, startIP2, endIP2)) {
-                    throw new InvalidParameterValueException("The startIp/endIp overlaps with network: " + associatedNetwork2.getName());
-                }
+        if (cidr != null && associatedNetwork.getCidr() != null) {
+            String[] guestVmCidrPair = associatedNetwork.getCidr().split("\\/");
+            String[] cidrIpRange = NetUtils.getIpRangeFromCidr(guestVmCidrPair[0], Long.valueOf(guestVmCidrPair[1]));
+            if (StringUtils.isNoneBlank(startIp, endIp) && NetUtils.ipRangesOverlap(startIp, endIp, cidrIpRange[0], cidrIpRange[1])) {
+                throw new InvalidParameterValueException(String.format("The IP range (%s-%s) overlaps with cidr of associated network: %s (%s)",
+                        startIp, endIp, associatedNetwork.getName(), associatedNetwork.getCidr()));
             }
         }
+        // Check IP range overlap on shared networks and vpc private gateways associated to the same network
+        checkIpRangeOverlapWithAssociatedNetworks(associatedNetworkId, startIp, endIp);
+
         associatedNetwork = implementedNetworkInCreation(caller, zone, associatedNetwork);
         if (associatedNetwork == null || (associatedNetwork.getState() != Network.State.Implemented && associatedNetwork.getState() != Network.State.Setup)) {
             throw new InvalidParameterValueException("Unable to implement associated network " + associatedNetwork);
@@ -1853,7 +1986,7 @@
         }
     }
 
-    private void validateNetworkOfferingForNonRootAdminUser(NetworkOfferingVO ntwkOff) {
+    private void validateNetworkOfferingForNonRootAdminUser(NetworkOffering ntwkOff) {
         if (ntwkOff.getTrafficType() != TrafficType.Guest) {
             throw new InvalidParameterValueException("This user can only create a Guest network");
         }
@@ -1915,7 +2048,7 @@
     private Network commitNetwork(final Long networkOfferingId, final String gateway, final String startIP, final String endIP, final String netmask, final String networkDomain, final String vlanIdFinal,
                                   final Boolean bypassVlanOverlapCheck, final String name, final String displayText, final Account caller, final Long physicalNetworkId, final Long zoneId, final Long domainId,
                                   final boolean isDomainSpecific, final Boolean subdomainAccessFinal, final Long vpcId, final String startIPv6, final String endIPv6, final String ip6Gateway, final String ip6Cidr,
-                                  final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOfferingVO ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal,
+                                  final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOffering ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal,
                                   final String cidr, final boolean createVlan, final String externalId, String routerIp, String routerIpv6,
                                   final Network associatedNetwork, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, Pair<Integer, Integer> vrIfaceMTUs) throws InsufficientCapacityException, ResourceAllocationException {
         try {
@@ -1967,12 +2100,9 @@
                                 throw new InvalidParameterValueException("Unable to find specified NetworkACL");
                             }
 
-                            if (aclId != NetworkACL.DEFAULT_DENY && aclId != NetworkACL.DEFAULT_ALLOW) {
-                                // ACL is not default DENY/ALLOW
-                                // ACL should be associated with a VPC
-                                if (!vpcId.equals(acl.getVpcId())) {
-                                    throw new InvalidParameterValueException("ACL: " + aclId + " do not belong to the VPC");
-                                }
+                            Long aclVpcId = acl.getVpcId();
+                            if (!isDefaultAcl(aclId) && isAclAttachedToVpc(aclVpcId, vpcId)) {
+                                throw new InvalidParameterValueException(String.format("ACL [%s] does not belong to the VPC [%s].", aclId, aclVpcId));
                             }
                         }
                         network = _vpcMgr.createVpcGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType,
@@ -2364,7 +2494,7 @@
         }
 
         if (networkOfferingId != null) {
-            sc.addAnd("networkOfferingId", SearchCriteria.Op.EQ, networkOfferingId);
+            sc.addAnd(NETWORK_OFFERING_ID, SearchCriteria.Op.EQ, networkOfferingId);
         }
 
         if (associatedNetworkId != null) {
@@ -2665,6 +2795,11 @@
         return _ipAddressDao.findById(ipAddressId);
     }
 
+    @Override
+    public IpAddress getIp(String ipAddress) {
+        return _ipAddressDao.findByIp(ipAddress);
+    }
+
     protected boolean providersConfiguredForExternalNetworking(Collection<String> providers) {
         for (String providerStr : providers) {
             Provider provider = Network.Provider.getProvider(providerStr);
@@ -2787,6 +2922,8 @@
         _accountMgr.checkAccess(callerAccount, AccessType.OperateEntry, true, network);
         _accountMgr.checkAccess(_accountMgr.getActiveAccountById(network.getAccountId()), offering, _dcDao.findById(network.getDataCenterId()));
 
+        restartNetwork |= checkAndUpdateRouterSourceNatIp(cmd, network);
+
         if (cmd instanceof UpdateNetworkCmdByAdmin) {
             final Boolean hideIpAddressUsage = ((UpdateNetworkCmdByAdmin) cmd).getHideIpAddressUsage();
             if (hideIpAddressUsage != null) {
@@ -2835,13 +2972,13 @@
         NetworkOfferingVO networkOffering = _networkOfferingDao.findById(networkOfferingId);
         if (networkOfferingId != null) {
             if (networkOffering == null || networkOffering.isSystemOnly()) {
-                throwInvalidIdException("Unable to find network offering with specified id", networkOfferingId.toString(), "networkOfferingId");
+                throwInvalidIdException("Unable to find network offering with specified id", networkOfferingId.toString(), NETWORK_OFFERING_ID);
             }
 
             // network offering should be in Enabled state
             if (networkOffering.getState() != NetworkOffering.State.Enabled) {
                 throwInvalidIdException("Network offering with specified id is not in " + NetworkOffering.State.Enabled + " state, can't upgrade to it", networkOffering.getUuid(),
-                        "networkOfferingId");
+                        NETWORK_OFFERING_ID);
             }
             //can't update from vpc to non-vpc network offering
             boolean forVpcNew = _configMgr.isOfferingForVpc(networkOffering);
@@ -2884,10 +3021,8 @@
             }
         }
 
-        if (checkAndUpdateNetworkDns(network, networkOfferingChanged ? networkOffering : oldNtwkOff, ip4Dns1, ip4Dns2,
-                ip6Dns1, ip6Dns2)) {
-            restartNetwork = true;
-        }
+        restartNetwork |= checkAndUpdateNetworkDns(network, networkOfferingChanged ? networkOffering : oldNtwkOff, ip4Dns1, ip4Dns2,
+                ip6Dns1, ip6Dns2);
 
         final Map<String, String> newSvcProviders = networkOfferingChanged
                 ? _networkMgr.finalizeServicesAndProvidersForNetwork(_entityMgr.findById(NetworkOffering.class, networkOfferingId), network.getPhysicalNetworkId())
@@ -2934,8 +3069,9 @@
             if (networkOfferingChanged) {
                 throw new InvalidParameterValueException("Cannot specify this network offering change and guestVmCidr at same time. Specify only one.");
             }
-            if (!(network.getState() == Network.State.Implemented)) {
-                throw new InvalidParameterValueException("The network must be in " + Network.State.Implemented + " state. IP Reservation cannot be applied in " + network.getState() + " state");
+            if (network.getState() != Network.State.Implemented && network.getState() != Network.State.Allocated) {
+                throw new InvalidParameterValueException(String.format("The network must be in %s or %s state. IP Reservation cannot be applied in %s state",
+                        Network.State.Implemented, Network.State.Allocated, network.getState()));
             }
             if (!NetUtils.isValidIp4Cidr(guestVmCidr)) {
                 throw new InvalidParameterValueException("Invalid format of Guest VM CIDR.");
@@ -2994,6 +3130,9 @@
                 }
             }
 
+            // Check IP range overlap on shared networks and vpc private gateways associated to this network
+            checkIpRangeOverlapWithAssociatedNetworks(networkId, cidrIpRange[0], cidrIpRange[1]);
+
             // When reservation is applied for the first time, network_cidr will be null
             // Populate it with the actual network cidr
             if (network.getNetworkCidr() == null) {
@@ -3084,7 +3223,7 @@
             if (servicesNotInNewOffering != null && !servicesNotInNewOffering.isEmpty()) {
                 _networkMgr.cleanupConfigForServicesInNetwork(servicesNotInNewOffering, network);
             }
-        } catch (Throwable e) {
+        } catch (Exception e) { // old pokemon catch that used to catch throwable
             s_logger.debug("failed to cleanup config related to unused services error:" + e.getMessage());
         }
 
@@ -3638,7 +3777,7 @@
         if (newNtwkOff == null || newNtwkOff.isSystemOnly()) {
             InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find network offering.");
             if (newNtwkOff != null) {
-                ex.addProxyObject(String.valueOf(newNtwkOff.getId()), "networkOfferingId");
+                ex.addProxyObject(String.valueOf(newNtwkOff.getId()), NETWORK_OFFERING_ID);
             }
             throw ex;
         }
@@ -4072,7 +4211,7 @@
         for (String vlanRange : listOfRanges) {
             String[] VnetRange = vlanRange.split("-");
 
-            // Init with [min,max] of VLAN. Actually 0x000 and 0xFFF are reserved by IEEE, shoudn't be used.
+            // Init with [min,max] of VLAN. Actually 0x000 and 0xFFF are reserved by IEEE, shouldn't be used.
             long minVnet = MIN_VLAN_ID;
             long maxVnet = MAX_VLAN_ID;
 
@@ -4147,9 +4286,9 @@
         }
 
         final String virtualMachineDomainRouterType = VirtualMachine.Type.DomainRouter.toString();
-        if (!virtualMachineDomainRouterType.equalsIgnoreCase(serviceOffering.getSystemVmType())) {
+        if (!virtualMachineDomainRouterType.equalsIgnoreCase(serviceOffering.getVmType())) {
             throw new InvalidParameterValueException(String.format("The specified service offering [%s] is of type [%s]. Virtual routers can only be created with service offering "
-                    + "of type [%s].", serviceOffering, serviceOffering.getSystemVmType(), virtualMachineDomainRouterType.toLowerCase()));
+                    + "of type [%s].", serviceOffering, serviceOffering.getVmType(), virtualMachineDomainRouterType.toLowerCase()));
         }
     }
 
@@ -4980,7 +5119,7 @@
             List<SecondaryStorageVmVO> ssvms = _stnwMgr.getSSVMWithNoStorageNetwork(network.getDataCenterId());
             if (!ssvms.isEmpty()) {
                 StringBuilder sb = new StringBuilder("Cannot add " + trafficType
-                        + " traffic type as there are below secondary storage vm still running. Please stop them all and add Storage traffic type again, then destory them all to allow CloudStack recreate them with storage network(If you have added storage network ip range)");
+                        + " traffic type as there are below secondary storage vm still running. Please stop them all and add Storage traffic type again, then destroy them all to allow CloudStack recreate them with storage network(If you have added storage network ip range)");
                 sb.append("SSVMs:");
                 for (SecondaryStorageVmVO ssvm : ssvms) {
                     sb.append(ssvm.getInstanceName()).append(":").append(ssvm.getState());
@@ -5774,6 +5913,30 @@
         return accountIds;
     }
 
+    private void checkIpRangeOverlapWithAssociatedNetworks(Long associatedNetworkId, String startIp, String endIp) {
+        List<NetworkDetailVO> associatedNetworks = _networkDetailsDao.findDetails(Network.AssociatedNetworkId, String.valueOf(associatedNetworkId), null);
+        for (NetworkDetailVO networkDetailVO : associatedNetworks) {
+            NetworkVO associatedNetwork2 = _networksDao.findById(networkDetailVO.getResourceId());
+            if (associatedNetwork2 != null) {
+                List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(associatedNetwork2.getId());
+                if (vlans.isEmpty()) {
+                    VpcGatewayVO vpcGateway = vpcGatewayDao.getVpcGatewayByNetworkId(associatedNetwork2.getId());
+                    if (vpcGateway != null && NetUtils.ipRangesOverlap(startIp, endIp, vpcGateway.getIp4Address(), vpcGateway.getIp4Address())) {
+                        throw new InvalidParameterValueException(String.format("The startIp/endIp (%s - %s) overlaps with vpc private gateway %s (%s): ",
+                                startIp, endIp, associatedNetwork2.getName(), vpcGateway.getIp4Address()));
+                    }
+                    continue;
+                }
+                String startIP2 = vlans.get(0).getIpRange().split("-")[0];
+                String endIP2 = vlans.get(0).getIpRange().split("-")[1];
+                if (StringUtils.isNoneBlank(startIp, startIP2) && NetUtils.ipRangesOverlap(startIp, endIp, startIP2, endIP2)) {
+                    throw new InvalidParameterValueException(String.format("The startIp/endIp (%s - %s) overlaps with network %s (%s - %s)",
+                            startIp, endIp, associatedNetwork2.getName(), startIP2, endIP2));
+                }
+            }
+        }
+    }
+
     @Override
     public String getConfigComponentName() {
         return NetworkService.class.getSimpleName();
@@ -5783,4 +5946,83 @@
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey<?>[] {AllowDuplicateNetworkName, AllowEmptyStartEndIpAddress, VRPrivateInterfaceMtu, VRPublicInterfaceMtu, AllowUsersToSpecifyVRMtu};
     }
+
+    public boolean isDefaultAcl(Long aclId) {
+        return aclId == NetworkACL.DEFAULT_DENY || aclId == NetworkACL.DEFAULT_ALLOW;
+    }
+
+    public boolean isAclAttachedToVpc(Long aclVpcId, Long vpcId) {
+        return aclVpcId != 0 && !vpcId.equals(aclVpcId);
+    }
+
+    @Override
+    public PublicIpQuarantine updatePublicIpAddressInQuarantine(UpdateQuarantinedIpCmd cmd) throws CloudRuntimeException {
+        Long ipId = cmd.getId();
+        String ipAddress = cmd.getIpAddress();
+        Date newEndDate = cmd.getEndDate();
+
+        if (new Date().after(newEndDate)) {
+            throw new InvalidParameterValueException(String.format("The given end date [%s] is invalid as it is before the current date.", newEndDate));
+        }
+
+        PublicIpQuarantine publicIpQuarantine = retrievePublicIpQuarantine(ipId, ipAddress);
+        checkCallerForPublicIpQuarantineAccess(publicIpQuarantine);
+
+        String publicIpQuarantineAddress = _ipAddressDao.findById(publicIpQuarantine.getPublicIpAddressId()).getAddress().toString();
+        Date currentEndDate = publicIpQuarantine.getEndDate();
+
+        if (new Date().after(currentEndDate)) {
+            throw new CloudRuntimeException(String.format("The quarantine for the public IP address [%s] is no longer active; thus, it cannot be updated.", publicIpQuarantineAddress));
+        }
+
+        return _ipAddrMgr.updatePublicIpAddressInQuarantine(publicIpQuarantine.getId(), newEndDate);
+    }
+
+    @Override
+    public void removePublicIpAddressFromQuarantine(RemoveQuarantinedIpCmd cmd) throws CloudRuntimeException {
+        Long ipId = cmd.getId();
+        String ipAddress = cmd.getIpAddress();
+        PublicIpQuarantine publicIpQuarantine = retrievePublicIpQuarantine(ipId, ipAddress);
+
+        String removalReason = cmd.getRemovalReason();
+        if (StringUtils.isBlank(removalReason)) {
+            s_logger.error("The removalReason parameter cannot be blank.");
+            ipAddress = ObjectUtils.defaultIfNull(ipAddress, _ipAddressDao.findById(publicIpQuarantine.getPublicIpAddressId()).getAddress().toString());
+            throw new CloudRuntimeException(String.format("The given reason for removing the public IP address [%s] from quarantine is blank.", ipAddress));
+        }
+
+        checkCallerForPublicIpQuarantineAccess(publicIpQuarantine);
+
+        _ipAddrMgr.removePublicIpAddressFromQuarantine(publicIpQuarantine.getId(), removalReason);
+    }
+
+    /**
+     * Retrieves the active quarantine for the given public IP address. It can find by the ID of the quarantine or the address of the public IP.
+     * @throws CloudRuntimeException if it does not find an active quarantine for the given public IP.
+     */
+    protected PublicIpQuarantine retrievePublicIpQuarantine(Long ipId, String ipAddress) throws CloudRuntimeException {
+        PublicIpQuarantine publicIpQuarantine;
+        if (ipId != null) {
+            s_logger.debug("The ID of the IP in quarantine was informed; therefore, the `ipAddress` parameter will be ignored.");
+            publicIpQuarantine = publicIpQuarantineDao.findById(ipId);
+        } else if (ipAddress != null) {
+            s_logger.debug("The address of the IP in quarantine was informed, it will be used to fetch its metadata.");
+            publicIpQuarantine = publicIpQuarantineDao.findByIpAddress(ipAddress);
+        } else {
+            throw new CloudRuntimeException("Either the ID or the address of the IP in quarantine must be informed.");
+        }
+
+        if (publicIpQuarantine == null) {
+            throw new CloudRuntimeException("There is no active quarantine for the specified IP address.");
+        }
+
+        return  publicIpQuarantine;
+    }
+
+    protected void checkCallerForPublicIpQuarantineAccess(PublicIpQuarantine publicIpQuarantine) {
+        Account callingAccount = CallContext.current().getCallingAccount();
+        DomainVO domainOfThePreviousOwner = _domainDao.findById(_accountDao.findById(publicIpQuarantine.getPreviousOwnerId()).getDomainId());
+
+        _accountMgr.checkAccess(callingAccount, domainOfThePreviousOwner);
+    }
 }
diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManager.java b/server/src/main/java/com/cloud/network/as/AutoScaleManager.java
index 1d829b1..cf6aab6 100644
--- a/server/src/main/java/com/cloud/network/as/AutoScaleManager.java
+++ b/server/src/main/java/com/cloud/network/as/AutoScaleManager.java
@@ -55,4 +55,8 @@
     void checkIfVmActionAllowed(Long vmId);
 
     void removeVmFromVmGroup(Long vmId);
+
+    String getNextVmHostName(AutoScaleVmGroupVO asGroup);
+
+    void checkAutoScaleVmGroupName(String groupName);
 }
diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
index de8a3ff..c10ff89 100644
--- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
@@ -1938,7 +1938,8 @@
         }
     }
 
-    private String getNextVmHostName(AutoScaleVmGroupVO asGroup) {
+    @Override
+    public String getNextVmHostName(AutoScaleVmGroupVO asGroup) {
         String vmHostNameSuffix = "-" + asGroup.getNextVmSeq() + "-" +
                 RandomStringUtils.random(VM_HOSTNAME_RANDOM_SUFFIX_LENGTH, 0, 0, true, false, (char[])null, new SecureRandom()).toLowerCase();
         // Truncate vm group name because max length of vm name is 63
@@ -1946,7 +1947,8 @@
         return VM_HOSTNAME_PREFIX + asGroup.getName().substring(0, subStringLength) + vmHostNameSuffix;
     }
 
-    private void checkAutoScaleVmGroupName(String groupName) {
+    @Override
+    public void checkAutoScaleVmGroupName(String groupName) {
         String errorMessage = "";
         if (groupName == null || groupName.length() > 255 || groupName.length() < 1) {
             errorMessage = "AutoScale Vm Group name must be between 1 and 255 characters long";
@@ -1964,7 +1966,7 @@
     private boolean startNewVM(long vmId) {
         try {
             CallContext.current().setEventDetails("Vm Id: " + vmId);
-            userVmMgr.startVirtualMachine(vmId, null, null, null);
+            userVmMgr.startVirtualMachine(vmId, null, new HashMap<>(), null);
         } catch (final ResourceUnavailableException ex) {
             s_logger.warn("Exception: ", ex);
             throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
diff --git a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java
index 839ab9a..52f5273 100644
--- a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java
+++ b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java
@@ -219,7 +219,7 @@
             return false;
         }
 
-        final Map<VirtualMachineProfile.Param, Object> params = new HashMap<VirtualMachineProfile.Param, Object>(1);
+        final Map<VirtualMachineProfile.Param, Object> params = new HashMap<>(1);
         params.put(VirtualMachineProfile.Param.ReProgramGuestNetworks, true);
 
         if (network.isRollingRestart()) {
@@ -264,27 +264,13 @@
             return false;
         }
 
-        final NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
-        if (offering.isSystemOnly()) {
-            return false;
-        }
         if (!_networkMdl.isProviderEnabledInPhysicalNetwork(_networkMdl.getPhysicalNetworkId(network), getProvider().getName())) {
             return false;
         }
 
-        final RouterDeploymentDefinition routerDeploymentDefinition =
-                routerDeploymentDefinitionBuilder.create()
-                .setGuestNetwork(network)
-                .setDeployDestination(dest)
-                .setAccountOwner(_accountMgr.getAccount(network.getAccountId()))
-                .setParams(vm.getParameters())
-                .build();
+        final NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
+        implement(network, offering, dest, context);
 
-        final List<DomainRouterVO> routers = routerDeploymentDefinition.deployVirtualRouter();
-
-        if (routers == null || routers.size() == 0) {
-            throw new ResourceUnavailableException("Can't find at least one running router!", DataCenter.class, network.getDataCenterId());
-        }
         return true;
     }
 
diff --git a/server/src/main/java/com/cloud/network/guru/ControlNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/ControlNetworkGuru.java
index ce62c7b..ce59b50 100644
--- a/server/src/main/java/com/cloud/network/guru/ControlNetworkGuru.java
+++ b/server/src/main/java/com/cloud/network/guru/ControlNetworkGuru.java
@@ -21,6 +21,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.router.VirtualNetworkApplianceManager;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.log4j.Logger;
 
@@ -166,18 +167,24 @@
         assert nic.getTrafficType() == TrafficType.Control;
         HypervisorType hType = vm.getHypervisorType();
         if ( ( (hType == HypervisorType.VMware) || (hType == HypervisorType.Hyperv) )&& isRouterVm(vm)) {
+            if (!VirtualNetworkApplianceManager.RemoveControlIpOnStop.valueIn(vm.getVirtualMachine().getDataCenterId())) {
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug(String.format("not releasing %s from %s with reservationId %s, as systemvm.release.control.ip.on.stop is set to false for the data center.", nic, vm, reservationId));
+                }
+                return true;
+            }
             long dcId = vm.getVirtualMachine().getDataCenterId();
             DataCenterVO dcVo = _dcDao.findById(dcId);
             if (dcVo.getNetworkType() != NetworkType.Basic) {
                 super.release(nic, vm, reservationId);
                 if (s_logger.isDebugEnabled()) {
-                    s_logger.debug("Released nic: " + nic);
+                    s_logger.debug(String.format("Released nic: %s for vm %s", nic, vm));
                 }
                 return true;
             } else {
                 nic.deallocate();
                 if (s_logger.isDebugEnabled()) {
-                    s_logger.debug("Released nic: " + nic);
+                    s_logger.debug(String.format("Released nic: %s for vm %s", nic, vm));
                 }
                 return true;
             }
@@ -187,7 +194,7 @@
 
         nic.deallocate();
         if (s_logger.isDebugEnabled()) {
-            s_logger.debug("Released nic: " + nic);
+            s_logger.debug(String.format("Released nic: %s for vm %s", nic, vm));
         }
 
         return true;
diff --git a/server/src/main/java/com/cloud/network/guru/DirectPodBasedNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/DirectPodBasedNetworkGuru.java
index f0ddc12..7186812 100644
--- a/server/src/main/java/com/cloud/network/guru/DirectPodBasedNetworkGuru.java
+++ b/server/src/main/java/com/cloud/network/guru/DirectPodBasedNetworkGuru.java
@@ -222,7 +222,7 @@
                  * Linux, FreeBSD and Windows all calculate the same IPv6 address when configured properly.
                  *
                  * Using Router Advertisements the routers in the network should announce the IPv6 CIDR which is configured
-                 * in in the vlan table in the database.
+                 * in the vlan table in the database.
                  *
                  * This way the NIC will be populated with a IPv6 address on which the Instance is reachable.
                  */
diff --git a/server/src/main/java/com/cloud/network/guru/PodBasedNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/PodBasedNetworkGuru.java
index 9f9771e..44d349a 100644
--- a/server/src/main/java/com/cloud/network/guru/PodBasedNetworkGuru.java
+++ b/server/src/main/java/com/cloud/network/guru/PodBasedNetworkGuru.java
@@ -159,7 +159,7 @@
         nic.deallocate();
 
         if (s_logger.isDebugEnabled()) {
-            s_logger.debug("Released nic: " + nic);
+            s_logger.debug(String.format("Released nic: %s for vm %s", nic, vm));
         }
 
         return true;
diff --git a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java
index 3c51e12..8cb8972 100644
--- a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java
@@ -49,6 +49,7 @@
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO;
 import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
@@ -2322,36 +2323,40 @@
         Boolean applied = cmd.isApplied();
 
         if (applied == null) {
+            s_logger.info(String.format("The [%s] parameter was not passed. Using the default value [%s].", ApiConstants.APPLIED, Boolean.TRUE));
             applied = Boolean.TRUE;
         }
 
         LoadBalancerVO loadBalancer = _lbDao.findById(loadBalancerId);
         if (loadBalancer == null) {
-            return null;
-        }
-
-        _accountMgr.checkAccess(caller, null, true, loadBalancer);
-
-        List<UserVmVO> loadBalancerInstances = new ArrayList<UserVmVO>();
-        List<String> serviceStates = new ArrayList<String>();
-        List<LoadBalancerVMMapVO> vmLoadBalancerMappings = null;
-        vmLoadBalancerMappings = _lb2VmMapDao.listByLoadBalancerId(loadBalancerId);
-        if(vmLoadBalancerMappings == null) {
-            String msg = "no VM Loadbalancer Mapping found";
+            String msg = String.format("Unable to find the load balancer with ID [%s].", cmd.getId());
             s_logger.error(msg);
             throw new CloudRuntimeException(msg);
         }
-        Map<Long, String> vmServiceState = new HashMap<Long, String>(vmLoadBalancerMappings.size());
-        List<Long> appliedInstanceIdList = new ArrayList<Long>();
 
-        if ((vmLoadBalancerMappings != null) && !vmLoadBalancerMappings.isEmpty()) {
-            for (LoadBalancerVMMapVO vmLoadBalancerMapping : vmLoadBalancerMappings) {
-                appliedInstanceIdList.add(vmLoadBalancerMapping.getInstanceId());
-                vmServiceState.put(vmLoadBalancerMapping.getInstanceId(), vmLoadBalancerMapping.getState());
-            }
+        String loadBalancerAsString = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(loadBalancer, "uuid", "name");
+
+        _accountMgr.checkAccess(caller, null, true, loadBalancer);
+
+        List<UserVmVO> loadBalancerInstances = new ArrayList<>();
+        List<String> serviceStates = new ArrayList<>();
+        List<LoadBalancerVMMapVO> vmLoadBalancerMappings = _lb2VmMapDao.listByLoadBalancerId(loadBalancerId);
+
+        if (vmLoadBalancerMappings == null) {
+            String msg = String.format("Unable to find map of VMs related to load balancer [%s].", loadBalancerAsString);
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
         }
 
-        List<UserVmVO> userVms = _vmDao.listVirtualNetworkInstancesByAcctAndNetwork(loadBalancer.getAccountId(), loadBalancer.getNetworkId());
+        Map<Long, String> vmServiceState = new HashMap<>(vmLoadBalancerMappings.size());
+        List<Long> appliedInstanceIdList = new ArrayList<>();
+
+        for (LoadBalancerVMMapVO vmLoadBalancerMapping : vmLoadBalancerMappings) {
+            appliedInstanceIdList.add(vmLoadBalancerMapping.getInstanceId());
+            vmServiceState.put(vmLoadBalancerMapping.getInstanceId(), vmLoadBalancerMapping.getState());
+        }
+
+        List<UserVmVO> userVms = _vmDao.listByIds(appliedInstanceIdList);
 
         for (UserVmVO userVm : userVms) {
             // if the VM is destroyed, being expunged, in an error state, or in
@@ -2364,13 +2369,25 @@
                 continue;
             }
 
+            String userVmAsString = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(userVm, "uuid", "name");
+
             boolean isApplied = appliedInstanceIdList.contains(userVm.getId());
-            if ((isApplied && applied) || (!isApplied && !applied)) {
-                loadBalancerInstances.add(userVm);
-                serviceStates.add(vmServiceState.get(userVm.getId()));
+            String isAppliedMsg = isApplied ? "is applied" : "is not applied";
+            s_logger.debug(String.format("The user VM [%s] %s to a rule of the load balancer [%s].", userVmAsString, isAppliedMsg, loadBalancerAsString));
+
+            if (isApplied != applied) {
+                s_logger.debug(String.format("Skipping adding service state from the user VM [%s] to the service state list. This happens because the VM %s to the load "
+                        + "balancer rule and the [%s] parameter was passed as [%s].", userVmAsString, isAppliedMsg, ApiConstants.APPLIED, applied));
+                continue;
             }
+
+            loadBalancerInstances.add(userVm);
+            String serviceState = vmServiceState.get(userVm.getId());
+            s_logger.debug(String.format("Adding the service state [%s] from the user VM [%s] to the service state list.", serviceState, userVmAsString));
+            serviceStates.add(serviceState);
         }
-        return new Pair<List<? extends UserVm>, List<String>>(loadBalancerInstances, serviceStates);
+
+        return new Pair<>(loadBalancerInstances, serviceStates);
     }
 
     @Override
diff --git a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java
index 6bd0199..a7ed647 100644
--- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java
+++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java
@@ -231,6 +231,11 @@
                 vmDataCommand.addVmData(NetworkModel.METATDATA_DIR, NetworkModel.CLOUD_DOMAIN_ID_FILE, domain.getUuid());
             }
 
+            String customCloudName = VirtualMachineManager.MetadataCustomCloudName.valueIn(vm.getDataCenterId());
+            if (org.apache.commons.lang3.StringUtils.isNotBlank(customCloudName)) {
+                vmDataCommand.addVmData(NetworkModel.METATDATA_DIR, NetworkModel.CLOUD_NAME_FILE, customCloudName);
+            }
+
             cmds.addCommand("vmdata", vmDataCommand);
         }
     }
diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java
index f0fa50b..38286b5 100644
--- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java
+++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java
@@ -547,6 +547,7 @@
 
                 router.setDynamicallyScalable(template.isDynamicallyScalable());
                 router.setRole(Role.VIRTUAL_ROUTER);
+                router.setLimitCpuUse(routerOffering.getLimitCpuUse());
                 router = _routerDao.persist(router);
 
                 reallocateRouterNetworks(routerDeploymentDefinition, router, template, null);
diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java
index d569d32..f77081a 100644
--- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java
+++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java
@@ -36,82 +36,83 @@
  */
 public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkApplianceService {
 
-    static final String RouterTemplateXenCK = "router.template.xenserver";
-    static final String RouterTemplateKvmCK = "router.template.kvm";
-    static final String RouterTemplateVmwareCK = "router.template.vmware";
-    static final String RouterTemplateHyperVCK = "router.template.hyperv";
-    static final String RouterTemplateLxcCK = "router.template.lxc";
-    static final String RouterTemplateOvm3CK = "router.template.ovm3";
-    static final String SetServiceMonitorCK = "network.router.EnableServiceMonitoring";
-    static final String RouterAlertsCheckIntervalCK = "router.alerts.check.interval";
-    static final String VirtualRouterServiceOfferingCK = "router.service.offering";
+    String RouterTemplateXenCK = "router.template.xenserver";
+    String RouterTemplateKvmCK = "router.template.kvm";
+    String RouterTemplateVmwareCK = "router.template.vmware";
+    String RouterTemplateHyperVCK = "router.template.hyperv";
+    String RouterTemplateLxcCK = "router.template.lxc";
+    String RouterTemplateOvm3CK = "router.template.ovm3";
+    String SetServiceMonitorCK = "network.router.EnableServiceMonitoring";
+    String RouterAlertsCheckIntervalCK = "router.alerts.check.interval";
+    String VirtualRouterServiceOfferingCK = "router.service.offering";
 
-    static final String RouterHealthChecksConfigRefreshIntervalCK = "router.health.checks.config.refresh.interval";
-    static final String RouterHealthChecksResultFetchIntervalCK = "router.health.checks.results.fetch.interval";
-    static final String RouterHealthChecksFailuresToRecreateVrCK = "router.health.checks.failures.to.recreate.vr";
+    String RouterHealthChecksConfigRefreshIntervalCK = "router.health.checks.config.refresh.interval";
+    String RouterHealthChecksResultFetchIntervalCK = "router.health.checks.results.fetch.interval";
+    String RouterHealthChecksFailuresToRecreateVrCK = "router.health.checks.failures.to.recreate.vr";
+    String RemoveControlIpOnStopCK = "systemvm.release.control.ip.on.stop";
 
-    static final ConfigKey<String> RouterTemplateXen = new ConfigKey<String>(String.class, RouterTemplateXenCK, "Advanced", "SystemVM Template (XenServer)",
+    ConfigKey<String> RouterTemplateXen = new ConfigKey<>(String.class, RouterTemplateXenCK, "Advanced", "SystemVM Template (XenServer)",
             "Name of the default router template on Xenserver.", true, ConfigKey.Scope.Zone, null);
-    static final ConfigKey<String> RouterTemplateKvm = new ConfigKey<String>(String.class, RouterTemplateKvmCK, "Advanced", "SystemVM Template (KVM)",
+    ConfigKey<String> RouterTemplateKvm = new ConfigKey<>(String.class, RouterTemplateKvmCK, "Advanced", "SystemVM Template (KVM)",
             "Name of the default router template on KVM.", true, ConfigKey.Scope.Zone, null);
-    static final ConfigKey<String> RouterTemplateVmware = new ConfigKey<String>(String.class, RouterTemplateVmwareCK, "Advanced", "SystemVM Template (vSphere)",
+    ConfigKey<String> RouterTemplateVmware = new ConfigKey<>(String.class, RouterTemplateVmwareCK, "Advanced", "SystemVM Template (vSphere)",
             "Name of the default router template on Vmware.", true, ConfigKey.Scope.Zone, null);
-    static final ConfigKey<String> RouterTemplateHyperV = new ConfigKey<String>(String.class, RouterTemplateHyperVCK, "Advanced", "SystemVM Template (HyperV)",
+    ConfigKey<String> RouterTemplateHyperV = new ConfigKey<>(String.class, RouterTemplateHyperVCK, "Advanced", "SystemVM Template (HyperV)",
             "Name of the default router template on Hyperv.", true, ConfigKey.Scope.Zone, null);
-    static final ConfigKey<String> RouterTemplateLxc = new ConfigKey<String>(String.class, RouterTemplateLxcCK, "Advanced", "SystemVM Template (LXC)",
+    ConfigKey<String> RouterTemplateLxc = new ConfigKey<>(String.class, RouterTemplateLxcCK, "Advanced", "SystemVM Template (LXC)",
             "Name of the default router template on LXC.", true, ConfigKey.Scope.Zone, null);
-    static final ConfigKey<String> RouterTemplateOvm3 = new ConfigKey<String>(String.class, RouterTemplateOvm3CK, "Advanced", "SystemVM Template (Ovm3)",
+    ConfigKey<String> RouterTemplateOvm3 = new ConfigKey<>(String.class, RouterTemplateOvm3CK, "Advanced", "SystemVM Template (Ovm3)",
             "Name of the default router template on Ovm3.", true, ConfigKey.Scope.Zone, null);
 
-    static final ConfigKey<Boolean> SetServiceMonitor = new ConfigKey<Boolean>(Boolean.class, SetServiceMonitorCK, "Advanced", "true",
+    ConfigKey<Boolean> SetServiceMonitor = new ConfigKey<>(Boolean.class, SetServiceMonitorCK, "Advanced", "true",
             "service monitoring in router enable/disable option, default true", true, ConfigKey.Scope.Zone, null);
 
-    static final ConfigKey<Integer> RouterAlertsCheckInterval = new ConfigKey<Integer>(Integer.class, RouterAlertsCheckIntervalCK, "Advanced", "1800",
+    ConfigKey<Integer> RouterAlertsCheckInterval = new ConfigKey<>(Integer.class, RouterAlertsCheckIntervalCK, "Advanced", "1800",
             "Interval (in seconds) to check for alerts in Virtual Router.", false, ConfigKey.Scope.Global, null);
-    static final ConfigKey<Boolean> RouterVersionCheckEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "router.version.check", "true",
+    ConfigKey<Boolean> RouterVersionCheckEnabled = new ConfigKey<>("Advanced", Boolean.class, "router.version.check", "true",
             "If true, router minimum required version is checked before sending command", false);
-    static final ConfigKey<Boolean> UseExternalDnsServers = new ConfigKey<Boolean>(Boolean.class, "use.external.dns", "Advanced", "false",
+    ConfigKey<Boolean> UseExternalDnsServers = new ConfigKey<>(Boolean.class, "use.external.dns", "Advanced", "false",
             "Bypass internal dns, use external dns1 and dns2", true, ConfigKey.Scope.Zone, null);
-    static final ConfigKey<Boolean> ExposeDnsAndBootpServer = new ConfigKey<Boolean>(Boolean.class, "expose.dns.externally", "Advanced", "false",
+    ConfigKey<Boolean> ExposeDnsAndBootpServer = new ConfigKey<>(Boolean.class, "expose.dns.externally", "Advanced", "false",
             "open dns, dhcp and bootp on the public interface", true, ConfigKey.Scope.Zone, null);
 
-    static final ConfigKey<String> VirtualRouterServiceOffering = new ConfigKey<String>(String.class, VirtualRouterServiceOfferingCK, "Advanced", "",
+    ConfigKey<String> VirtualRouterServiceOffering = new ConfigKey<>(String.class, VirtualRouterServiceOfferingCK, "Advanced", "",
             "Uuid of the service offering used by virtual routers; if NULL - system offering will be used", true, ConfigKey.Scope.Account, null);
 
     // Health checks
-    static final ConfigKey<Boolean> RouterHealthChecksEnabled = new ConfigKey<Boolean>(Boolean.class, "router.health.checks.enabled", "Advanced", "true",
+    ConfigKey<Boolean> RouterHealthChecksEnabled = new ConfigKey<>(Boolean.class, "router.health.checks.enabled", "Advanced", "true",
             "If true, router health checks are allowed to be executed and read. If false, all scheduled checks and API calls for on demand checks are disabled.",
             true, ConfigKey.Scope.Global, null);
-    static final ConfigKey<Integer> RouterHealthChecksBasicInterval = new ConfigKey<Integer>(Integer.class, "router.health.checks.basic.interval", "Advanced", "3",
+    ConfigKey<Integer> RouterHealthChecksBasicInterval = new ConfigKey<>(Integer.class, "router.health.checks.basic.interval", "Advanced", "3",
             "Interval in minutes at which basic router health checks are performed. If set to 0, no tests are scheduled.",
             true, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key());
-    static final ConfigKey<Integer> RouterHealthChecksAdvancedInterval = new ConfigKey<Integer>(Integer.class, "router.health.checks.advanced.interval", "Advanced", "10",
+    ConfigKey<Integer> RouterHealthChecksAdvancedInterval = new ConfigKey<>(Integer.class, "router.health.checks.advanced.interval", "Advanced", "10",
             "Interval in minutes at which advanced router health checks are performed. If set to 0, no tests are scheduled.",
             true, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key());
-    static final ConfigKey<Integer> RouterHealthChecksConfigRefreshInterval = new ConfigKey<Integer>(Integer.class, RouterHealthChecksConfigRefreshIntervalCK, "Advanced", "10",
+    ConfigKey<Integer> RouterHealthChecksConfigRefreshInterval = new ConfigKey<>(Integer.class, RouterHealthChecksConfigRefreshIntervalCK, "Advanced", "10",
             "Interval in minutes at which router health checks config - such as scheduling intervals, excluded checks, etc is updated on virtual routers by the management server. This value should" +
                     " be sufficiently high (like 2x) from the router.health.checks.basic.interval and router.health.checks.advanced.interval so that there is time between new results generation and results generation for passed data.",
             false, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key());
-    static final ConfigKey<Integer> RouterHealthChecksResultFetchInterval = new ConfigKey<Integer>(Integer.class, RouterHealthChecksResultFetchIntervalCK, "Advanced", "10",
+    ConfigKey<Integer> RouterHealthChecksResultFetchInterval = new ConfigKey<>(Integer.class, RouterHealthChecksResultFetchIntervalCK, "Advanced", "10",
             "Interval in minutes at which router health checks results are fetched by management server. On each result fetch, management server evaluates need to recreate VR as per configuration of " + RouterHealthChecksFailuresToRecreateVrCK +
                     "This value should be sufficiently high (like 2x) from the router.health.checks.basic.interval and router.health.checks.advanced.interval so that there is time between new results generation and fetch.",
             false, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key());
-    static final ConfigKey<String> RouterHealthChecksFailuresToRecreateVr = new ConfigKey<String>(String.class, RouterHealthChecksFailuresToRecreateVrCK, "Advanced", "",
+    ConfigKey<String> RouterHealthChecksFailuresToRecreateVr = new ConfigKey<>(String.class, RouterHealthChecksFailuresToRecreateVrCK, "Advanced", "",
             "Health checks failures defined by this config are the checks that should cause router recreation. If empty the recreate is not attempted for any health check failure. Possible values are comma separated script names " +
                     "from systemvm’s /root/health_scripts/ (namely - cpu_usage_check.py, dhcp_check.py, disk_space_check.py, dns_check.py, gateways_check.py, haproxy_check.py, iptables_check.py, memory_usage_check.py, router_version_check.py), connectivity.test, filesystem.writable.test " +
                     " or services (namely - loadbalancing.service, webserver.service, dhcp.service) ",
             true, ConfigKey.Scope.Zone, null, null, RouterHealthChecksEnabled.key(), null, null, ConfigKey.Kind.CSV, null);
-    static final ConfigKey<String> RouterHealthChecksToExclude = new ConfigKey<String>(String.class, "router.health.checks.to.exclude", "Advanced", "",
+    ConfigKey<String> RouterHealthChecksToExclude = new ConfigKey<>(String.class, "router.health.checks.to.exclude", "Advanced", "",
             "Health checks that should be excluded when executing scheduled checks on the router. This can be a comma separated list of script names placed in the '/root/health_checks/' folder. Currently the following scripts are " +
                     "placed in default systemvm template -  cpu_usage_check.py, disk_space_check.py, gateways_check.py, iptables_check.py, router_version_check.py, dhcp_check.py, dns_check.py, haproxy_check.py, memory_usage_check.py.",
             true, ConfigKey.Scope.Zone, null, null, RouterHealthChecksEnabled.key(), null, null, ConfigKey.Kind.CSV, null);
-    static final ConfigKey<Double> RouterHealthChecksFreeDiskSpaceThreshold = new ConfigKey<Double>(Double.class, "router.health.checks.free.disk.space.threshold",
+    ConfigKey<Double> RouterHealthChecksFreeDiskSpaceThreshold = new ConfigKey<>(Double.class, "router.health.checks.free.disk.space.threshold",
             "Advanced", "100", "Free disk space threshold (in MB) on VR below which the check is considered a failure.",
             true, ConfigKey.Scope.Zone, null, RouterHealthChecksEnabled.key());
-    static final ConfigKey<Double> RouterHealthChecksMaxCpuUsageThreshold = new ConfigKey<Double>(Double.class, "router.health.checks.max.cpu.usage.threshold",
+    ConfigKey<Double> RouterHealthChecksMaxCpuUsageThreshold = new ConfigKey<>(Double.class, "router.health.checks.max.cpu.usage.threshold",
             "Advanced", "100", " Max CPU Usage threshold as % above which check is considered a failure.",
             true, ConfigKey.Scope.Zone, null, RouterHealthChecksEnabled.key());
-    static final ConfigKey<Double> RouterHealthChecksMaxMemoryUsageThreshold = new ConfigKey<Double>(Double.class, "router.health.checks.max.memory.usage.threshold",
+    ConfigKey<Double> RouterHealthChecksMaxMemoryUsageThreshold = new ConfigKey<>(Double.class, "router.health.checks.max.memory.usage.threshold",
             "Advanced", "100", "Max Memory Usage threshold as % above which check is considered a failure.",
             true, ConfigKey.Scope.Zone, null, RouterHealthChecksEnabled.key());
     ConfigKey<String> RouterLogrotateFrequency = new ConfigKey<>(String.class, "router.logrotate.frequency", "Advanced", "*:00:00",
@@ -121,23 +122,15 @@
                     "reach the minimum size.",
             true, ConfigKey.Scope.Zone, null);
 
-    public static final int DEFAULT_ROUTER_VM_RAMSIZE = 256;            // 256M
-    public static final int DEFAULT_ROUTER_CPU_MHZ = 500;                // 500 MHz
-    public static final boolean USE_POD_VLAN = false;
-    public static final int DEFAULT_PRIORITY = 100;
-    public static final int DEFAULT_DELTA = 2;
+    ConfigKey<Boolean> RemoveControlIpOnStop = new ConfigKey<>(Boolean.class, RemoveControlIpOnStopCK, "Advanced", "true",
+            "on stopping routers and system VMs the IP will be released to preserve IPv4 space.", true, ConfigKey.Scope.Zone, null);
 
-    /**
-    /*
-     * Send ssh public/private key pair to specified host
-     * @param hostId
-     * @param pubKey
-     * @param prvKey
-     *
-     * NOT USED IN THE VIRTUAL NET APPLIANCE
-     *
-     */
-    //boolean sendSshKeysToHost(Long hostId, String pubKey, String prvKey):
+    int DEFAULT_ROUTER_VM_RAMSIZE = 256;            // 256M
+    int DEFAULT_ROUTER_CPU_MHZ = 500;                // 500 MHz
+    boolean USE_POD_VLAN = false;
+    int DEFAULT_PRIORITY = 100;
+    int DEFAULT_DELTA = 2;
+
 
     boolean startRemoteAccessVpn(Network network, RemoteAccessVpn vpn, List<? extends VirtualRouter> routers) throws ResourceUnavailableException;
 
diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
index 3093c56..19d8fc7 100644
--- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
@@ -57,7 +57,6 @@
 import org.apache.cloudstack.config.ApiServiceConfiguration;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-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;
@@ -66,7 +65,6 @@
 import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO;
 import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
-import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinitionBuilder;
 import org.apache.cloudstack.network.topology.NetworkTopology;
 import org.apache.cloudstack.network.topology.NetworkTopologyContext;
 import org.apache.cloudstack.utils.CloudStackVersion;
@@ -116,13 +114,11 @@
 import com.cloud.cluster.ManagementServerHostVO;
 import com.cloud.cluster.dao.ManagementServerHostDao;
 import com.cloud.configuration.Config;
-import com.cloud.configuration.ConfigurationManager;
 import com.cloud.configuration.ZoneConfig;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenter.NetworkType;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.HostPodVO;
-import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.dc.dao.HostPodDao;
 import com.cloud.dc.dao.VlanDao;
@@ -144,7 +140,6 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.network.IpAddress;
-import com.cloud.network.IpAddressManager;
 import com.cloud.network.MonitoringService;
 import com.cloud.network.Network;
 import com.cloud.network.Network.GuestType;
@@ -178,17 +173,13 @@
 import com.cloud.network.dao.NetworkVO;
 import com.cloud.network.dao.OpRouterMonitorServiceDao;
 import com.cloud.network.dao.OpRouterMonitorServiceVO;
-import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
 import com.cloud.network.dao.RemoteAccessVpnDao;
 import com.cloud.network.dao.RouterHealthCheckResultDao;
 import com.cloud.network.dao.RouterHealthCheckResultVO;
 import com.cloud.network.dao.Site2SiteCustomerGatewayDao;
 import com.cloud.network.dao.Site2SiteVpnConnectionDao;
 import com.cloud.network.dao.Site2SiteVpnConnectionVO;
-import com.cloud.network.dao.Site2SiteVpnGatewayDao;
-import com.cloud.network.dao.UserIpv6AddressDao;
 import com.cloud.network.dao.VirtualRouterProviderDao;
-import com.cloud.network.dao.VpnUserDao;
 import com.cloud.network.lb.LoadBalancingRule;
 import com.cloud.network.lb.LoadBalancingRule.LbDestination;
 import com.cloud.network.lb.LoadBalancingRule.LbHealthCheckPolicy;
@@ -217,16 +208,11 @@
 import com.cloud.offering.ServiceOffering;
 import com.cloud.offerings.NetworkOfferingVO;
 import com.cloud.offerings.dao.NetworkOfferingDao;
-import com.cloud.resource.ResourceManager;
 import com.cloud.serializer.GsonHelper;
-import com.cloud.server.ConfigurationServer;
 import com.cloud.server.ManagementServer;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.Storage.ProvisioningType;
-import com.cloud.storage.dao.GuestOSDao;
-import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.storage.dao.VolumeDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.User;
@@ -273,9 +259,7 @@
 import com.cloud.vm.dao.NicDao;
 import com.cloud.vm.dao.NicIpAliasDao;
 import com.cloud.vm.dao.NicIpAliasVO;
-import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.UserVmDetailsDao;
-import com.cloud.vm.dao.VMInstanceDao;
 import com.google.gson.JsonSyntaxException;
 import com.google.gson.reflect.TypeToken;
 
@@ -302,7 +286,6 @@
     @Inject private LoadBalancerDao _loadBalancerDao;
     @Inject private LoadBalancerVMMapDao _loadBalancerVMMapDao;
     @Inject protected IPAddressDao _ipAddressDao;
-    @Inject private VMTemplateDao _templateDao;
     @Inject protected DomainRouterDao _routerDao;
     @Inject private UserDao _userDao;
     @Inject protected UserStatisticsDao _userStatsDao;
@@ -313,17 +296,11 @@
     @Inject protected AgentManager _agentMgr;
     @Inject private AlertManager _alertMgr;
     @Inject private AccountManager _accountMgr;
-    @Inject private ConfigurationManager _configMgr;
-    @Inject private ConfigurationServer _configServer;
     @Inject protected ServiceOfferingDao _serviceOfferingDao;
-    @Inject private UserVmDao _userVmDao;
-    @Inject private VMInstanceDao _vmDao;
     @Inject private NetworkOfferingDao _networkOfferingDao;
-    @Inject private GuestOSDao _guestOSDao;
     @Inject protected NetworkOrchestrationService _networkMgr;
     @Inject protected NetworkModel _networkModel;
     @Inject protected VirtualMachineManager _itMgr;
-    @Inject private VpnUserDao _vpnUsersDao;
     @Inject private RulesManager _rulesMgr;
     @Inject protected NetworkDao _networkDao;
     @Inject private LoadBalancingRulesManager _lbMgr;
@@ -331,21 +308,13 @@
     @Inject protected RemoteAccessVpnDao _vpnDao;
     @Inject protected NicDao _nicDao;
     @Inject private NicIpAliasDao _nicIpAliasDao;
-    @Inject private VolumeDao _volumeDao;
     @Inject private UserVmDetailsDao _vmDetailsDao;
-    @Inject private ClusterDao _clusterDao;
-    @Inject private ResourceManager _resourceMgr;
-    @Inject private PhysicalNetworkServiceProviderDao _physicalProviderDao;
     @Inject protected VirtualRouterProviderDao _vrProviderDao;
     @Inject private ManagementServerHostDao _msHostDao;
     @Inject private Site2SiteCustomerGatewayDao _s2sCustomerGatewayDao;
-    @Inject private Site2SiteVpnGatewayDao _s2sVpnGatewayDao;
     @Inject private Site2SiteVpnConnectionDao _s2sVpnConnectionDao;
     @Inject private Site2SiteVpnManager _s2sVpnMgr;
-    @Inject private UserIpv6AddressDao _ipv6Dao;
     @Inject private NetworkService _networkSvc;
-    @Inject private IpAddressManager _ipAddrMgr;
-    @Inject private ConfigDepot _configDepot;
     @Inject protected MonitoringServiceDao _monitorServiceDao;
     @Inject private AsyncJobManager _asyncMgr;
     @Inject protected VpcDao _vpcDao;
@@ -372,7 +341,6 @@
     @Inject protected RouterControlHelper _routerControlHelper;
 
     @Inject protected CommandSetupHelper _commandSetupHelper;
-    @Inject protected RouterDeploymentDefinitionBuilder _routerDeploymentManagerBuilder;
     @Inject private ManagementServer mgr;
 
     private int _routerRamSize;
@@ -2532,7 +2500,7 @@
                         boolean revoke = false;
                         if (ip.getState() == IpAddress.State.Releasing ) {
                             // for ips got struck in releasing state we need to delete the rule not add.
-                            s_logger.debug("Rule revoke set to true for the ip " + ip.getAddress() +" becasue it is in releasing state");
+                            s_logger.debug("Rule revoke set to true for the ip " + ip.getAddress() +" because it is in releasing state");
                             revoke = true;
                         }
                         final StaticNatImpl staticNat = new StaticNatImpl(ip.getAccountId(), ip.getDomainId(), guestNetworkId, ip.getId(), ip.getVmIp(), revoke);
@@ -2808,28 +2776,43 @@
             final VirtualMachine vm = profile.getVirtualMachine();
             final DomainRouterVO domR = _routerDao.findById(vm.getId());
             processStopOrRebootAnswer(domR, answer);
-            final List<? extends Nic> routerNics = _nicDao.listByVmId(profile.getId());
-            for (final Nic nic : routerNics) {
-                final Network network = _networkModel.getNetwork(nic.getNetworkId());
-                final DataCenterVO dcVO = _dcDao.findById(network.getDataCenterId());
-
-                if (network.getTrafficType() == TrafficType.Guest && nic.getBroadcastUri() != null && nic.getBroadcastUri().getScheme().equals("pvlan")) {
-                    final NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic");
-
-                    final NetworkTopology networkTopology = _networkTopologyContext.retrieveNetworkTopology(dcVO);
-                    try {
-                        networkTopology.setupDhcpForPvlan(false, domR, domR.getHostId(), nicProfile);
-                    } catch (final ResourceUnavailableException e) {
-                        s_logger.debug("ERROR in finalizeStop: ", e);
-                    }
-                }
+            if (Boolean.TRUE.equals(RemoveControlIpOnStop.valueIn(profile.getVirtualMachine().getDataCenterId()))) {
+                removeNics(vm, domR);
             }
-
         }
     }
 
     @Override
     public void finalizeExpunge(final VirtualMachine vm) {
+        if (Boolean.FALSE.equals(RemoveControlIpOnStop.valueIn(vm.getDataCenterId()))) {
+            final DomainRouterVO domR = _routerDao.findById(vm.getId());
+            s_logger.info(String.format("removing nics for VR [%s]", vm));
+            removeNics(vm, domR);
+        }
+    }
+
+    private void removeNics(VirtualMachine vm, DomainRouterVO domR) {
+        final List<? extends Nic> routerNics = _nicDao.listByVmId(vm.getId());
+        final DataCenterVO dcVO = _dcDao.findById(vm.getDataCenterId());
+
+        for (final Nic nic : routerNics) {
+            final Network network = _networkModel.getNetwork(nic.getNetworkId());
+
+            removeDhcpRulesForPvLan(domR, nic, network, dcVO);
+        }
+    }
+
+    private void removeDhcpRulesForPvLan(DomainRouterVO domR, Nic nic, Network network, DataCenterVO dcVO) {
+        if (network.getTrafficType() == TrafficType.Guest && nic.getBroadcastUri() != null && nic.getBroadcastUri().getScheme().equals("pvlan")) {
+            final NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic");
+
+            final NetworkTopology networkTopology = _networkTopologyContext.retrieveNetworkTopology(dcVO);
+            try {
+                networkTopology.setupDhcpForPvlan(false, domR, domR.getHostId(), nicProfile);
+            } catch (final ResourceUnavailableException e) {
+                s_logger.debug("ERROR in finalizeStop: ", e);
+            }
+        }
     }
 
     @Override
@@ -3351,7 +3334,8 @@
                 RouterHealthChecksMaxCpuUsageThreshold,
                 RouterHealthChecksMaxMemoryUsageThreshold,
                 ExposeDnsAndBootpServer,
-                RouterLogrotateFrequency
+                RouterLogrotateFrequency,
+                RemoveControlIpOnStop
         };
     }
 
diff --git a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java
index 0feb240..624fbfb 100644
--- a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java
@@ -670,7 +670,7 @@
                 " as it's already assigned to antoher vm");
         }
 
-        //check wether the vm ip is alreday associated with any public ip address
+        //check whether the vm ip is already associated with any public ip address
         IPAddressVO oldIP = _ipAddressDao.findByAssociatedVmIdAndVmIp(vmId, vmIp);
 
         if (oldIP != null) {
@@ -1134,7 +1134,7 @@
         // revoke all port forwarding rules
         success = success && applyPortForwardingRules(ipId,  _ipAddrMgr.RulesContinueOnError.value(), caller);
 
-        // revoke all all static nat rules
+        // revoke all static nat rules
         success = success && applyStaticNatRulesForIp(ipId,  _ipAddrMgr.RulesContinueOnError.value(), caller, true);
 
         // revoke static nat for the ip address
@@ -1190,7 +1190,7 @@
         success = success && applyPortForwardingRulesForNetwork(networkId, true, caller);
         success = success && applyPortForwardingRulesForNetwork(networkId,  _ipAddrMgr.RulesContinueOnError.value(), caller);
 
-        // revoke all all static nat rules for the network
+        // revoke all static nat rules for the network
         success = success && applyStaticNatRulesForNetwork(networkId, true, caller);
         success = success && applyStaticNatRulesForNetwork(networkId,  _ipAddrMgr.RulesContinueOnError.value(), caller);
 
diff --git a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
index 551a6ed..5d4b473 100644
--- a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java
@@ -473,7 +473,7 @@
     protected List<Long> getAffectedVmsForVmStop(VMInstanceVO vm) {
         List<Long> affectedVms = new ArrayList<Long>();
         List<SecurityGroupVMMapVO> groupsForVm = _securityGroupVMMapDao.listByInstanceId(vm.getId());
-        // For each group, find the security rules rules that allow the group
+        // For each group, find the security rules that allow the group
         for (SecurityGroupVMMapVO mapVO : groupsForVm) {// FIXME: use custom sql in the dao
             //Add usage events for security group remove
             UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SECURITY_GROUP_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), mapVO.getSecurityGroupId(), vm
diff --git a/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java b/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java
index ed37eb5..8139ac1 100644
--- a/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java
+++ b/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java
@@ -24,6 +24,7 @@
 
 import javax.inject.Inject;
 
+import com.cloud.exception.PermissionDeniedException;
 import org.apache.cloudstack.api.ApiErrorCode;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
@@ -103,12 +104,15 @@
 
     @Override
     public NetworkACL createNetworkACL(final String name, final String description, final long vpcId, final Boolean forDisplay) {
-        final Account caller = CallContext.current().getCallingAccount();
-        final Vpc vpc = _entityMgr.findById(Vpc.class, vpcId);
-        if (vpc == null) {
-            throw new InvalidParameterValueException("Unable to find VPC");
+        if (vpcId != 0) {
+            final Account caller = CallContext.current().getCallingAccount();
+            final Vpc vpc = _vpcDao.findById(vpcId);
+            if (vpc == null) {
+                throw new InvalidParameterValueException(String.format("Unable to find VPC with ID [%s].", vpcId));
+            }
+            _accountMgr.checkAccess(caller, null, true, vpc);
+
         }
-        _accountMgr.checkAccess(caller, null, true, vpc);
         return _networkAclMgr.createNetworkACL(name, description, vpcId, forDisplay);
     }
 
@@ -212,22 +216,17 @@
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_DELETE, eventDescription = "Deleting Network ACL List", async = true)
     public boolean deleteNetworkACL(final long id) {
-        final Account caller = CallContext.current().getCallingAccount();
         final NetworkACL acl = _networkACLDao.findById(id);
+        Account account = CallContext.current().getCallingAccount();
         if (acl == null) {
             throw new InvalidParameterValueException("Unable to find specified ACL");
         }
 
-        //Do not allow deletion of default ACLs
-        if (acl.getId() == NetworkACL.DEFAULT_ALLOW || acl.getId() == NetworkACL.DEFAULT_DENY) {
+        if (isDefaultAcl(acl.getId())) {
             throw new InvalidParameterValueException("Default ACL cannot be removed");
         }
 
-        final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
-        if (vpc == null) {
-            throw new InvalidParameterValueException("Unable to find specified VPC associated with the ACL");
-        }
-        _accountMgr.checkAccess(caller, null, true, vpc);
+        validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACLs.");
         return _networkAclMgr.deleteNetworkACL(acl);
     }
 
@@ -253,7 +252,7 @@
             throw new InvalidParameterValueException("Unable to find specified vpc id");
         }
 
-        if (aclId != NetworkACL.DEFAULT_DENY && aclId != NetworkACL.DEFAULT_ALLOW) {
+        if (!isDefaultAcl(aclId)) {
             final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
             if (vpc == null) {
                 throw new InvalidParameterValueException("Unable to find Vpc associated with the NetworkACL");
@@ -293,15 +292,9 @@
             throw new InvalidParameterValueException("Network ACL can be created just for networks of type " + Networks.TrafficType.Guest);
         }
 
-        if (aclId != NetworkACL.DEFAULT_DENY && aclId != NetworkACL.DEFAULT_ALLOW) {
-            //ACL is not default DENY/ALLOW
-            // ACL should be associated with a VPC
-            final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
-            if (vpc == null) {
-                throw new InvalidParameterValueException("Unable to find Vpc associated with the NetworkACL");
-            }
+        if (!isDefaultAcl(aclId) && !isGlobalAcl(acl.getVpcId())) {
+            validateAclAssociatedToVpc(acl.getVpcId(), caller, acl.getUuid());
 
-            _accountMgr.checkAccess(caller, null, true, vpc);
             if (!network.getVpcId().equals(acl.getVpcId())) {
                 throw new InvalidParameterValueException("Network: " + networkId + " and ACL: " + aclId + " do not belong to the same VPC");
             }
@@ -340,6 +333,11 @@
         NetworkACL acl = _networkAclMgr.getNetworkACL(aclId);
 
         validateNetworkAcl(acl);
+        Account caller = CallContext.current().getCallingAccount();
+
+        if (isGlobalAcl(acl.getVpcId()) && !Account.Type.ADMIN.equals(caller.getType())) {
+            throw new PermissionDeniedException("Only Root Admins can create rules for a global ACL.");
+        }
         validateAclRuleNumber(createNetworkACLCmd, acl);
 
         NetworkACLItem.Action ruleAction = validateAndCreateNetworkAclRuleAction(action);
@@ -409,7 +407,8 @@
      * <ul>
      *  <li>If the parameter is null, we return an  {@link InvalidParameterValueException};
      *  <li>Default ACLs {@link NetworkACL#DEFAULT_ALLOW} and {@link NetworkACL#DEFAULT_DENY} cannot be modified. Therefore, if any of them is provided we throw a {@link InvalidParameterValueException};
-     *  <li>If the network does not have a VPC, we will throw an {@link InvalidParameterValueException}.
+     *  <li>If no VPC is given, then it is a global ACL and there is no need to check any VPC ID. However, if a VPC is given and it does not exist, throws an
+     *  {@link InvalidParameterValueException}.
      * </ul>
      *
      * After all validations, we check if the user has access to the given network ACL using {@link AccountManager#checkAccess(Account, org.apache.cloudstack.acl.SecurityChecker.AccessType, boolean, org.apache.cloudstack.acl.ControlledEntity...)}.
@@ -419,16 +418,14 @@
             throw new InvalidParameterValueException("Unable to find specified ACL.");
         }
 
-        if (acl.getId() == NetworkACL.DEFAULT_DENY || acl.getId() == NetworkACL.DEFAULT_ALLOW) {
+        if (isDefaultAcl(acl.getId())) {
             throw new InvalidParameterValueException("Default ACL cannot be modified");
         }
 
-        Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
-        if (vpc == null) {
-            throw new InvalidParameterValueException(String.format("Unable to find Vpc associated with the NetworkACL [%s]", acl.getUuid()));
+        Long aclVpcId = acl.getVpcId();
+        if (!isGlobalAcl(aclVpcId)) {
+            validateAclAssociatedToVpc(aclVpcId, CallContext.current().getCallingAccount(), acl.getUuid());
         }
-        Account caller = CallContext.current().getCallingAccount();
-        _accountMgr.checkAccess(caller, null, true, vpc);
     }
 
     /**
@@ -792,17 +789,12 @@
         final NetworkACLItemVO aclItem = _networkACLItemDao.findById(ruleId);
         if (aclItem != null) {
             final NetworkACL acl = _networkAclMgr.getNetworkACL(aclItem.getAclId());
+            final Account account = CallContext.current().getCallingAccount();
 
-            final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
-
-            if (aclItem.getAclId() == NetworkACL.DEFAULT_ALLOW || aclItem.getAclId() == NetworkACL.DEFAULT_DENY) {
+            if (isDefaultAcl(aclItem.getAclId())) {
                 throw new InvalidParameterValueException("ACL Items in default ACL cannot be deleted");
             }
-
-            final Account caller = CallContext.current().getCallingAccount();
-
-            _accountMgr.checkAccess(caller, null, true, vpc);
-
+            validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACL rules.");
         }
         return _networkAclMgr.revokeNetworkACLItem(ruleId);
     }
@@ -826,6 +818,9 @@
         NetworkACL acl = _networkAclMgr.getNetworkACL(networkACLItemVo.getAclId());
         validateNetworkAcl(acl);
 
+        Account account = CallContext.current().getCallingAccount();
+        validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admins can update global ACLs.");
+
         transferDataToNetworkAclRulePojo(updateNetworkACLItemCmd, networkACLItemVo, acl);
         validateNetworkACLItem(networkACLItemVo);
         return _networkAclMgr.updateNetworkACLItem(networkACLItemVo);
@@ -912,14 +907,13 @@
     }
 
     @Override
-    @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_UPDATE, eventDescription = "updating network acl", async = true)
+    @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_UPDATE, eventDescription = "Updating Network ACL", async = true)
     public NetworkACL updateNetworkACL(UpdateNetworkACLListCmd updateNetworkACLListCmd) {
         Long id = updateNetworkACLListCmd.getId();
         NetworkACLVO acl = _networkACLDao.findById(id);
-        Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
+        Account account = CallContext.current().getCallingAccount();
 
-        Account caller = CallContext.current().getCallingAccount();
-        _accountMgr.checkAccess(caller, null, true, vpc);
+        validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Must be Root Admin to update a global ACL.");
 
         String name = updateNetworkACLListCmd.getName();
         if (StringUtils.isNotBlank(name)) {
@@ -1149,14 +1143,59 @@
         long aclId = ruleBeingMoved.getAclId();
 
         if ((nextRule != null && nextRule.getAclId() != aclId) || (previousRule != null && previousRule.getAclId() != aclId)) {
-            throw new InvalidParameterValueException("Cannot use ACL rules from differenting ACLs. Rule being moved.");
+            throw new InvalidParameterValueException("Cannot use ACL rules from differentiating ACLs. Rule being moved.");
         }
         NetworkACLVO acl = _networkACLDao.findById(aclId);
-        Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
-        if (vpc == null) {
-            throw new InvalidParameterValueException("Re-ordering rules for a default ACL is prohibited");
+        Account account = CallContext.current().getCallingAccount();
+
+        if (isDefaultAcl(aclId)) {
+            throw new InvalidParameterValueException("Default ACL rules cannot be moved.");
         }
-        Account caller = CallContext.current().getCallingAccount();
-        _accountMgr.checkAccess(caller, null, true, vpc);
+
+        validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account,"Must be Root Admin to move global ACL rules.");
+    }
+
+    /**
+     * Checks if the given ACL is a global ACL. If it is, then checks if the account is Root Admin, and throws an exception according to {@code exceptionMessage} param if it
+     * does not have permission.
+     */
+    protected void checkGlobalAclPermission(Long aclVpcId, Account account, String exceptionMessage) {
+        if (isGlobalAcl(aclVpcId) && !Account.Type.ADMIN.equals(account.getType())) {
+            throw new PermissionDeniedException(exceptionMessage);
+        }
+    }
+
+    protected void validateAclAssociatedToVpc(Long aclVpcId, Account account, String aclUuid) {
+        final Vpc vpc = _vpcDao.findById(aclVpcId);
+        if (vpc == null) {
+            throw new InvalidParameterValueException(String.format("Unable to find specified VPC [%s] associated with the ACL [%s].", aclVpcId, aclUuid));
+        }
+        _accountMgr.checkAccess(account, null, true, vpc);
+    }
+
+    /**
+     * It performs two different verifications depending on if the ACL is global or not.
+     * <ul>
+     *     <li> If the given ACL is a Global ACL, i.e. has VPC ID = 0, then checks if the account is Root Admin, and throws an exception if it isn't.
+     *     <li> If the given ACL is associated to a VPC, i.e. VPC ID != 0, then calls {@link #validateAclAssociatedToVpc(Long, Account, String)} and checks if the given {@code
+     *     aclVpcId} is from a valid VPC.
+     * </ul>
+     */
+    protected void validateGlobalAclPermissionAndAclAssociatedToVpc(NetworkACL acl, Account account, String exception){
+        if (isGlobalAcl(acl.getVpcId())) {
+            s_logger.info(String.format("Checking if account [%s] has permission to manipulate global ACL [%s].", account, acl));
+            checkGlobalAclPermission(acl.getVpcId(), account, exception);
+        } else {
+            s_logger.info(String.format("Validating ACL [%s] associated to VPC [%s] with account [%s].", acl, acl.getVpcId(), account));
+            validateAclAssociatedToVpc(acl.getVpcId(), account, acl.getUuid());
+        }
+    }
+
+    protected boolean isGlobalAcl(Long aclVpcId) {
+        return aclVpcId != null && aclVpcId == 0;
+    }
+
+    protected boolean isDefaultAcl(long aclId) {
+        return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY;
     }
 }
diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
index f568266..341a3b8 100644
--- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.network.vpc;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -53,7 +54,9 @@
 import org.apache.cloudstack.api.command.user.vpc.ListPrivateGatewaysCmd;
 import org.apache.cloudstack.api.command.user.vpc.ListStaticRoutesCmd;
 import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd;
+import org.apache.cloudstack.api.command.user.vpc.ListVPCsCmd;
 import org.apache.cloudstack.api.command.user.vpc.RestartVPCCmd;
+import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -62,6 +65,7 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.log4j.Logger;
+import org.jetbrains.annotations.Nullable;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
@@ -1017,7 +1021,7 @@
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", create = true)
     public Vpc createVpc(final long zoneId, final long vpcOffId, final long vpcOwnerId, final String vpcName, final String displayText, final String cidr, String networkDomain,
-            final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Boolean displayVpc, Integer publicMtu) throws ResourceAllocationException {
+                         final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Boolean displayVpc, Integer publicMtu) throws ResourceAllocationException {
         final Account caller = CallContext.current().getCallingAccount();
         final Account owner = _accountMgr.getAccount(vpcOwnerId);
 
@@ -1091,6 +1095,7 @@
         final VpcVO vpc = new VpcVO(zoneId, vpcName, displayText, owner.getId(), owner.getDomainId(), vpcOffId, cidr, networkDomain, useDistributedRouter, isRegionLevelVpcOff,
                 vpcOff.isRedundantRouter(), ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2);
             vpc.setPublicMtu(publicMtu);
+            vpc.setDisplay(Boolean.TRUE.equals(displayVpc));
 
         return createVpc(displayVpc, vpc);
     }
@@ -1098,9 +1103,28 @@
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", create = true)
     public Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException {
-        return createVpc(cmd.getZoneId(), cmd.getVpcOffering(), cmd.getEntityOwnerId(), cmd.getVpcName(), cmd.getDisplayText(),
+        Vpc vpc = createVpc(cmd.getZoneId(), cmd.getVpcOffering(), cmd.getEntityOwnerId(), cmd.getVpcName(), cmd.getDisplayText(),
             cmd.getCidr(), cmd.getNetworkDomain(), cmd.getIp4Dns1(), cmd.getIp4Dns2(), cmd.getIp6Dns1(),
             cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu());
+
+        String sourceNatIP = cmd.getSourceNatIP();
+        if (sourceNatIP != null) {
+            s_logger.info(String.format("Trying to allocate the specified IP [%s] as the source NAT of VPC [%s].", sourceNatIP, vpc));
+            allocateSourceNatIp(vpc, sourceNatIP);
+        }
+        return vpc;
+    }
+
+    private void allocateSourceNatIp(Vpc vpc, String sourceNatIP) {
+        Account account = _accountMgr.getAccount(vpc.getAccountId());
+        DataCenter zone = _dcDao.findById(vpc.getZoneId());
+        // reserve this ip and then
+        try {
+            IpAddress ip = _ipAddrMgr.allocateIp(account, false, CallContext.current().getCallingAccount(), CallContext.current().getCallingUserId(), zone, null, sourceNatIP);
+            this.associateIPToVpc(ip.getId(), vpc.getId());
+        } catch (ResourceAllocationException | ResourceUnavailableException | InsufficientAddressCapacityException e){
+            throw new CloudRuntimeException("new source NAT address cannot be acquired", e);
+        }
     }
 
     @DB
@@ -1126,10 +1150,6 @@
         return Transaction.execute(new TransactionCallback<VpcVO>() {
             @Override
             public VpcVO doInTransaction(final TransactionStatus status) {
-                if (displayVpc != null) {
-                    vpc.setDisplay(displayVpc);
-                }
-
                 final VpcVO persistedVpc = vpcDao.persist(vpc, finalizeServicesAndProvidersForVpc(vpc.getZoneId(), vpc.getVpcOfferingId()));
                 _resourceLimitMgr.incrementResourceCount(vpc.getAccountId(), ResourceType.vpc);
                 s_logger.debug("Created VPC " + persistedVpc);
@@ -1243,8 +1263,13 @@
     }
 
     @Override
+    public Vpc updateVpc(UpdateVPCCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException {
+        return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP());
+    }
+
+    @Override
     @ActionEvent(eventType = EventTypes.EVENT_VPC_UPDATE, eventDescription = "updating vpc")
-    public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, final Boolean displayVpc, Integer mtu) {
+    public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, final Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException {
         CallContext.current().setEventDetails(" Id: " + vpcId);
         final Account caller = CallContext.current().getCallingAccount();
 
@@ -1279,14 +1304,80 @@
             updateMtuOfVpcNetwork(vpcToUpdate, vpc, mtu);
         }
 
-        if (vpcDao.update(vpcId, vpc)) {
+        boolean restartRequired = checkAndUpdateRouterSourceNatIp(vpcToUpdate, sourceNatIp);
+
+        if (vpcDao.update(vpcId, vpc) || restartRequired) { // Note that the update may fail because nothing has changed, other than the sourcenat ip
             s_logger.debug("Updated VPC id=" + vpcId);
+            if (restartRequired) {
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug(String.format("restarting vpc %s/%s, due to changing sourcenat in Update VPC call", vpc.getName(), vpc.getUuid()));
+                }
+                final User callingUser = _accountMgr.getActiveUser(CallContext.current().getCallingUserId());
+                restartVpc(vpcId, true, false, false, callingUser);
+            } else {
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug("no restart needed.");
+                }
+            }
             return vpcDao.findById(vpcId);
         } else {
+            s_logger.error(String.format("failed to update vpc %s/%s",vpc.getName(), vpc.getUuid()));
             return null;
         }
     }
 
+    private boolean checkAndUpdateRouterSourceNatIp(Vpc vpc, String sourceNatIp) {
+        IPAddressVO requestedIp = validateSourceNatip(vpc, sourceNatIp);
+        if (requestedIp == null) return false; // ip not associated with this network
+
+        List<IPAddressVO> userIps = _ipAddressDao.listByAssociatedVpc(vpc.getId(), true);
+        if (! userIps.isEmpty()) {
+            try {
+                _ipAddrMgr.updateSourceNatIpAddress(requestedIp, userIps);
+            } catch (Exception e) { // pokemon exception from transaction
+                String msg = String.format("Update of source NAT ip to %s for network \"%s\"/%s failed due to %s",
+                        requestedIp.getAddress().addr(), vpc.getName(), vpc.getUuid(), e.getLocalizedMessage());
+                s_logger.error(msg);
+                throw new CloudRuntimeException(msg, e);
+            }
+        }
+        return true;
+    }
+
+    @Nullable
+    protected IPAddressVO validateSourceNatip(Vpc vpc, String sourceNatIp) {
+        if (sourceNatIp == null) {
+            s_logger.trace(String.format("no source NAT ip given to update vpc %s with.", vpc.getName()));
+            return null;
+        } else {
+            s_logger.info(String.format("updating VPC %s to have source NAT ip %s", vpc.getName(), sourceNatIp));
+        }
+        IPAddressVO requestedIp = getIpAddressVO(vpc, sourceNatIp);
+        if (requestedIp == null) return null;
+        // check if it is the current source NAT address
+        if (requestedIp.isSourceNat()) {
+            s_logger.info(String.format("IP address %s is already the source Nat address. Not updating!", sourceNatIp));
+            return null;
+        }
+        if (_firewallDao.countRulesByIpId(requestedIp.getId()) > 0) {
+            s_logger.info(String.format("IP address %s has firewall/portforwarding rules. Not updating!", sourceNatIp));
+            return null;
+        }
+        return requestedIp;
+    }
+
+    @Nullable
+    private IPAddressVO getIpAddressVO(Vpc vpc, String sourceNatIp) {
+        // check if the address is already aqcuired for this network
+        IPAddressVO requestedIp = _ipAddressDao.findByIp(sourceNatIp);
+        if (requestedIp == null || requestedIp.getVpcId() == null || ! requestedIp.getVpcId().equals(vpc.getId())) {
+            s_logger.warn(String.format("Source NAT IP %s is not associated with network %s/%s. It cannot be used as source NAT IP.",
+                    sourceNatIp, vpc.getName(), vpc.getUuid()));
+            return null;
+        }
+        return requestedIp;
+    }
+
     protected Integer validateMtu(VpcVO vpcToUpdate, Integer mtu) {
         Long zoneId = vpcToUpdate.getZoneId();
         if (mtu == null || NetworkService.AllowUsersToSpecifyVRMtu.valueIn(zoneId)) {
@@ -1374,6 +1465,13 @@
     }
 
     @Override
+    public Pair<List<? extends Vpc>, Integer> listVpcs(ListVPCsCmd cmd) {
+        return listVpcs(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getSupportedServices(), cmd.getCidr(), cmd.getVpcOffId(),
+                cmd.getState(), cmd.getAccountName(), cmd.getDomainId(), cmd.getKeyword(), cmd.getStartIndex(), cmd.getPageSizeVal(),
+                cmd.getZoneId(), cmd.isRecursive(), cmd.listAll(), cmd.getRestartRequired(), cmd.getTags(), cmd.getProjectId(),
+                cmd.getDisplay());
+    }
+    @Override
     public Pair<List<? extends Vpc>, Integer> listVpcs(final Long id, final String vpcName, final String displayText, final List<String> supportedServicesStr, final String cidr,
                                                        final Long vpcOffId, final String state, final String accountName, Long domainId, final String keyword, final Long startIndex, final Long pageSizeVal,
                                                        final Long zoneId, Boolean isRecursive, final Boolean listAll, final Boolean restartRequired, final Map<String, String> tags, final Long projectId,
@@ -1476,7 +1574,7 @@
         final boolean listBySupportedServices = supportedServicesStr != null && !supportedServicesStr.isEmpty() && !vpcs.isEmpty();
 
         if (listBySupportedServices) {
-            final List<VpcVO> supportedVpcs = new ArrayList<VpcVO>();
+            final List<Vpc> supportedVpcs = new ArrayList<>();
             Service[] supportedServices = null;
 
             if (listBySupportedServices) {
@@ -1501,22 +1599,20 @@
 
             final List<? extends Vpc> wPagination = StringUtils.applyPagination(supportedVpcs, startIndex, pageSizeVal);
             if (wPagination != null) {
-                final Pair<List<? extends Vpc>, Integer> listWPagination = new Pair<List<? extends Vpc>, Integer>(wPagination, supportedVpcs.size());
-                return listWPagination;
+                return new Pair<>(wPagination, supportedVpcs.size());
             }
-            return new Pair<List<? extends Vpc>, Integer>(supportedVpcs, supportedVpcs.size());
+            return new Pair<>(supportedVpcs, supportedVpcs.size());
         } else {
             final List<? extends Vpc> wPagination = StringUtils.applyPagination(vpcs, startIndex, pageSizeVal);
             if (wPagination != null) {
-                final Pair<List<? extends Vpc>, Integer> listWPagination = new Pair<List<? extends Vpc>, Integer>(wPagination, vpcs.size());
-                return listWPagination;
+                return new Pair<>(wPagination, vpcs.size());
             }
-            return new Pair<List<? extends Vpc>, Integer>(vpcs, vpcs.size());
+            return new Pair<>(vpcs, vpcs.size());
         }
     }
 
     protected List<Service> getSupportedServices() {
-        final List<Service> services = new ArrayList<Service>();
+        final List<Service> services = new ArrayList<>();
         services.add(Network.Service.Dhcp);
         services.add(Network.Service.Dns);
         services.add(Network.Service.UserData);
@@ -1713,9 +1809,9 @@
          * ("No redunant router support when network belnogs to VPC"); }
          */
 
-        // 4) Conserve mode should be off
+        // 4) Conserve mode should be off in older versions
         if (guestNtwkOff.isConserveMode()) {
-            throw new InvalidParameterValueException("Only networks with conserve mode Off can belong to VPC");
+            s_logger.info("Creating a network with conserve mode in VPC");
         }
 
         // 5) If Netscaler is LB provider make sure it is in dedicated mode
@@ -1764,10 +1860,8 @@
                         }
                     }
 
-                    // 4) vpc and network should belong to the same owner
-                    if (vpc.getAccountId() != networkOwner.getId()) {
-                        throw new InvalidParameterValueException("Vpc " + vpc + " owner is different from the network owner " + networkOwner);
-                    }
+                    // 4) Vpc's account should be able to access network owner's account
+                    CheckAccountsAccess(vpc, networkOwner);
 
                     // 5) network domain should be the same as VPC's
                     if (!networkDomain.equalsIgnoreCase(vpc.getNetworkDomain())) {
@@ -1786,6 +1880,17 @@
         });
     }
 
+    private void CheckAccountsAccess(Vpc vpc, Account networkAccount) {
+        Account vpcaccount = _accountMgr.getAccount(vpc.getAccountId());
+        try {
+            _accountMgr.checkAccess(vpcaccount, null, false, networkAccount);
+        }
+        catch (PermissionDeniedException e) {
+            s_logger.error(e.getMessage());
+            throw new InvalidParameterValueException(String.format("VPC owner does not have access to account [%s].", networkAccount.getAccountName()));
+        }
+    }
+
     public List<VpcProvider> getVpcElements() {
         if (vpcElements == null) {
             vpcElements = new ArrayList<VpcProvider>();
@@ -1872,16 +1977,14 @@
 
         searchBuilder.and("vpcId", searchBuilder.entity().getVpcId(), Op.IN);
         final SearchCriteria<NetworkACLVO> searchCriteria = searchBuilder.create();
-        searchCriteria.setParameters("vpcId", vpcId, 0);
+        searchCriteria.setParameters("vpcId", vpcId);
 
         final Filter filter = new Filter(NetworkACLVO.class, "id", false, null, null);
         final Pair<List<NetworkACLVO>, Integer> aclsCountPair =  _networkAclDao.searchAndCount(searchCriteria, filter);
 
         final List<NetworkACLVO> acls = aclsCountPair.first();
         for (final NetworkACLVO networkAcl : acls) {
-            if (networkAcl.getId() != NetworkACL.DEFAULT_ALLOW && networkAcl.getId() != NetworkACL.DEFAULT_DENY) {
-                _networkAclMgr.deleteNetworkACL(networkAcl);
-            }
+            _networkAclMgr.deleteNetworkACL(networkAcl);
         }
 
         VpcVO vpc = vpcDao.findById(vpcId);
@@ -2057,6 +2160,8 @@
         final PhysicalNetwork physNetFinal = physNet;
         VpcGatewayVO gatewayVO = null;
         try {
+            validateVpcPrivateGatewayAclId(vpcId, aclId);
+
             s_logger.debug("Creating Private gateway for VPC " + vpc);
             // 1) create private network unless it is existing and
             // lswitch'd
@@ -2096,18 +2201,7 @@
                 _dcDao.update(dc.getId(), dc);
             }
 
-            long networkAclId = NetworkACL.DEFAULT_DENY;
-            if (aclId != null) {
-                final NetworkACLVO aclVO = _networkAclDao.findById(aclId);
-                if (aclVO == null) {
-                    throw new InvalidParameterValueException("Invalid network acl id passed ");
-                }
-                if (aclVO.getVpcId() != vpcId && !(aclId == NetworkACL.DEFAULT_DENY || aclId == NetworkACL.DEFAULT_ALLOW)) {
-                    throw new InvalidParameterValueException("Private gateway and network acl are not in the same vpc");
-                }
-
-                networkAclId = aclId;
-            }
+            Long networkAclId = ObjectUtils.defaultIfNull(aclId, NetworkACL.DEFAULT_DENY);
 
             { // experimental block, this is a hack
                 // set vpc id in network to null
@@ -2140,6 +2234,29 @@
         return getVpcPrivateGateway(gatewayVO.getId());
     }
 
+    /**
+     * This method checks if the ACL that is being used to create the private gateway is valid. First, the aclId is used to search for a {@link NetworkACLVO} object
+     * by calling the {@link NetworkACLDao#findById(Serializable)} method. If the object is null, an {@link InvalidParameterValueException} exception is thrown.
+     * Secondly, we check if the ACL and the private gateway are in the same VPC and an {@link InvalidParameterValueException} is thrown if they are not.
+     *
+     * @param vpcId Private gateway VPC ID.
+     * @param aclId Private gateway ACL ID.
+     * @throws InvalidParameterValueException
+     */
+    protected void validateVpcPrivateGatewayAclId(long vpcId, Long aclId) {
+        if (aclId == null) {
+            return;
+        }
+
+        final NetworkACLVO aclVO = _networkAclDao.findById(aclId);
+        if (aclVO == null) {
+            throw new InvalidParameterValueException("Invalid network acl id passed.");
+        }
+        if (aclVO.getVpcId() != vpcId && !(aclId == NetworkACL.DEFAULT_DENY || aclId == NetworkACL.DEFAULT_ALLOW)) {
+            throw new InvalidParameterValueException("Private gateway and network acl are not in the same vpc.");
+        }
+    }
+
     private void validateVpcPrivateGatewayAssociateNetworkId(NetworkOfferingVO ntwkOff, String broadcastUri, Long associatedNetworkId, Boolean bypassVlanOverlapCheck) {
         // Validate vlanId and associatedNetworkId
         if (broadcastUri == null && associatedNetworkId == null) {
@@ -2788,11 +2905,11 @@
         }
 
         // check permissions
-        _accountMgr.checkAccess(caller, null, true, owner, vpc);
+        _accountMgr.checkAccess(caller, null, false, owner, vpc);
 
         s_logger.debug("Associating ip " + ipToAssoc + " to vpc " + vpc);
 
-        final boolean isSourceNatFinal = isSrcNatIpRequired(vpc.getVpcOfferingId()) && getExistingSourceNatInVpc(owner.getId(), vpcId) == null;
+        final boolean isSourceNatFinal = isSrcNatIpRequired(vpc.getVpcOfferingId()) && getExistingSourceNatInVpc(vpc.getAccountId(), vpcId) == null;
         Transaction.execute(new TransactionCallbackNoReturn() {
             @Override
             public void doInTransactionWithoutResult(final TransactionStatus status) {
@@ -3056,4 +3173,12 @@
         }
         return filteredDomainIds;
     }
+
+    protected boolean isGlobalAcl(Long aclVpcId) {
+        return aclVpcId != null && aclVpcId == 0;
+    }
+
+    protected boolean isDefaultAcl(long aclId) {
+        return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY;
+    }
 }
diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java
index f87ab4a..19776d4 100644
--- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java
+++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java
@@ -18,9 +18,11 @@
 
 import java.io.UnsupportedEncodingException;
 import java.security.SecureRandom;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.UUID;
 import java.util.concurrent.Executors;
@@ -33,26 +35,20 @@
 import javax.mail.MessagingException;
 import javax.naming.ConfigurationException;
 
-import com.cloud.network.dao.NetworkDao;
-import com.cloud.network.dao.NetworkVO;
-import com.cloud.network.vpc.Vpc;
-import com.cloud.network.vpc.VpcManager;
-import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.VolumeVO;
-import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.storage.dao.VolumeDao;
-import com.cloud.vm.UserVmVO;
-import com.cloud.vm.dao.UserVmDao;
-import com.cloud.vm.snapshot.VMSnapshotVO;
-import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 import org.apache.cloudstack.acl.ProjectRole;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.acl.dao.ProjectRoleDao;
 import org.apache.cloudstack.context.CallContext;
+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.messagebus.MessageBus;
 import org.apache.cloudstack.framework.messagebus.PublishScope;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.utils.mailing.MailAddress;
+import org.apache.cloudstack.utils.mailing.SMTPMailProperties;
+import org.apache.cloudstack.utils.mailing.SMTPMailSender;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -72,11 +68,19 @@
 import com.cloud.exception.PermissionDeniedException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.vpc.Vpc;
+import com.cloud.network.vpc.VpcManager;
 import com.cloud.projects.Project.State;
 import com.cloud.projects.ProjectAccount.Role;
 import com.cloud.projects.dao.ProjectAccountDao;
 import com.cloud.projects.dao.ProjectDao;
 import com.cloud.projects.dao.ProjectInvitationDao;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VolumeDao;
 import com.cloud.tags.dao.ResourceTagDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
@@ -95,14 +99,10 @@
 import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
 import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.exception.CloudRuntimeException;
-import java.util.HashSet;
-import java.util.Set;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.Configurable;
-import org.apache.cloudstack.utils.mailing.MailAddress;
-import org.apache.cloudstack.utils.mailing.SMTPMailProperties;
-import org.apache.cloudstack.utils.mailing.SMTPMailSender;
-import org.apache.commons.lang3.BooleanUtils;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.snapshot.VMSnapshotVO;
+import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 
 @Component
 public class ProjectManagerImpl extends ManagerBase implements ProjectManager, Configurable {
@@ -650,7 +650,7 @@
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true)
-    public Project updateProject(final long projectId, final String displayText, final String newOwnerName) throws ResourceAllocationException {
+    public Project updateProject(final long projectId, String name, final String displayText, final String newOwnerName) throws ResourceAllocationException {
         Account caller = CallContext.current().getCallingAccount();
 
         //check that the project exists
@@ -666,10 +666,7 @@
         Transaction.execute(new TransactionCallbackWithExceptionNoReturn<ResourceAllocationException>() {
             @Override
             public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException {
-                if (displayText != null) {
-                    project.setDisplayText(displayText);
-                    _projectDao.update(projectId, project);
-                }
+                updateProjectNameAndDisplayText(project, name, displayText);
 
                 if (newOwnerName != null) {
                     //check that the new owner exists
@@ -717,7 +714,7 @@
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_PROJECT_UPDATE, eventDescription = "updating project", async = true)
-    public Project updateProject(final long projectId, final String displayText, final String newOwnerName, Long userId,
+    public Project updateProject(final long projectId, String name, final String displayText, final String newOwnerName, Long userId,
                                  Role newRole) throws ResourceAllocationException {
         Account caller = CallContext.current().getCallingAccount();
 
@@ -737,10 +734,8 @@
         Transaction.execute(new TransactionCallbackWithExceptionNoReturn<ResourceAllocationException>() {
             @Override
             public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException {
-                if (displayText != null) {
-                    project.setDisplayText(displayText);
-                    _projectDao.update(projectId, project);
-                }
+                updateProjectNameAndDisplayText(project, name, displayText);
+
                 if (newOwnerName != null) {
                     //check that the new owner exists
                     Account updatedAcc = _accountMgr.getActiveAccountByName(newOwnerName, project.getDomainId());
@@ -1443,4 +1438,17 @@
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey<?>[] {ProjectSmtpEnabledSecurityProtocols, ProjectSmtpUseStartTLS};
     }
+
+    protected void updateProjectNameAndDisplayText(final ProjectVO project, String name, String displayText) {
+        if (name == null && displayText == null){
+            return;
+        }
+        if (name != null) {
+            project.setName(name);
+        }
+        if (displayText != null) {
+            project.setDisplayText(displayText);
+        }
+        _projectDao.update(project.getId(), project);
+    }
 }
diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
index 1909063..922df25 100755
--- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
+++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
@@ -32,15 +32,20 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.alert.AlertManager;
+import com.cloud.host.HostTagVO;
 import com.cloud.exception.StorageConflictException;
 import com.cloud.exception.StorageUnavailableException;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.VolumeDao;
+import com.cloud.hypervisor.HypervisorGuru;
+import org.apache.cloudstack.alert.AlertService;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
@@ -298,6 +303,10 @@
     @Inject
     private AnnotationDao annotationDao;
     @Inject
+    private AlertManager alertManager;
+    @Inject
+    private AnnotationService annotationService;
+    @Inject
     private VolumeDao volumeDao;
 
     private final long _nodeId = ManagementServerNode.getManagementServerId();
@@ -646,7 +655,9 @@
             }
         }
 
-        return discoverHostsFull(dcId, podId, clusterId, clusterName, url, username, password, cmd.getHypervisor(), hostTags, cmd.getFullUrlParams(), false);
+        String hypervisorType = cmd.getHypervisor().equalsIgnoreCase(HypervisorGuru.HypervisorCustomDisplayName.value()) ?
+                "Custom" : cmd.getHypervisor();
+        return discoverHostsFull(dcId, podId, clusterId, clusterName, url, username, password, hypervisorType, hostTags, cmd.getFullUrlParams(), false);
     }
 
     @Override
@@ -847,7 +858,7 @@
                                     if (s_logger.isTraceEnabled()) {
                                         s_logger.trace("Adding Host Tags for KVM host, tags:  :" + hostTags);
                                     }
-                                    _hostTagsDao.persist(host.getId(), hostTags);
+                                    _hostTagsDao.persist(host.getId(), hostTags, false);
                                 }
                                 hosts.add(host);
 
@@ -1801,73 +1812,149 @@
         return hostInMaintenance;
     }
 
+    private ResourceState.Event getResourceEventFromAllocationStateString(String allocationState) {
+        final ResourceState.Event resourceEvent = ResourceState.Event.toEvent(allocationState);
+        if (resourceEvent != ResourceState.Event.Enable && resourceEvent != ResourceState.Event.Disable) {
+            throw new InvalidParameterValueException(String.format("Invalid allocation state: %s, " +
+                    "only Enable/Disable are allowed", allocationState));
+        }
+        return resourceEvent;
+    }
+
+    private void handleAutoEnableDisableKVMHost(boolean autoEnableDisableKVMSetting,
+                                                boolean isUpdateFromHostHealthCheck,
+                                                HostVO host, DetailVO hostDetail,
+                                                ResourceState.Event resourceEvent) {
+        if (autoEnableDisableKVMSetting) {
+            if (!isUpdateFromHostHealthCheck && hostDetail != null &&
+                    !Boolean.parseBoolean(hostDetail.getValue()) && resourceEvent == ResourceState.Event.Enable) {
+                hostDetail.setValue(Boolean.TRUE.toString());
+                _hostDetailsDao.update(hostDetail.getId(), hostDetail);
+            } else if (!isUpdateFromHostHealthCheck && hostDetail != null &&
+                    Boolean.parseBoolean(hostDetail.getValue()) && resourceEvent == ResourceState.Event.Disable) {
+                s_logger.info(String.format("The setting %s is enabled but the host %s is manually set into %s state," +
+                                "ignoring future auto enabling of the host based on health check results",
+                        AgentManager.EnableKVMAutoEnableDisable.key(), host.getName(), resourceEvent));
+                hostDetail.setValue(Boolean.FALSE.toString());
+                _hostDetailsDao.update(hostDetail.getId(), hostDetail);
+            } else if (hostDetail == null) {
+                String autoEnableValue = !isUpdateFromHostHealthCheck ? Boolean.FALSE.toString() : Boolean.TRUE.toString();
+                hostDetail = new DetailVO(host.getId(), ApiConstants.AUTO_ENABLE_KVM_HOST, autoEnableValue);
+                _hostDetailsDao.persist(hostDetail);
+            }
+        }
+    }
+    private boolean updateHostAllocationState(HostVO host, String allocationState,
+                                           boolean isUpdateFromHostHealthCheck) throws NoTransitionException {
+        boolean autoEnableDisableKVMSetting = AgentManager.EnableKVMAutoEnableDisable.valueIn(host.getClusterId()) &&
+                host.getHypervisorType() == HypervisorType.KVM;
+        ResourceState.Event resourceEvent = getResourceEventFromAllocationStateString(allocationState);
+        DetailVO hostDetail = _hostDetailsDao.findDetail(host.getId(), ApiConstants.AUTO_ENABLE_KVM_HOST);
+
+        if ((host.getResourceState() == ResourceState.Enabled && resourceEvent == ResourceState.Event.Enable) ||
+                (host.getResourceState() == ResourceState.Disabled && resourceEvent == ResourceState.Event.Disable)) {
+            s_logger.info(String.format("The host %s is already on the allocated state", host.getName()));
+            return false;
+        }
+
+        if (isAutoEnableAttemptForADisabledHost(autoEnableDisableKVMSetting, isUpdateFromHostHealthCheck, hostDetail, resourceEvent)) {
+            s_logger.debug(String.format("The setting '%s' is enabled and the health check succeeds on the host, " +
+                            "but the host has been manually disabled previously, ignoring auto enabling",
+                    AgentManager.EnableKVMAutoEnableDisable.key()));
+            return false;
+        }
+
+        handleAutoEnableDisableKVMHost(autoEnableDisableKVMSetting, isUpdateFromHostHealthCheck, host,
+                hostDetail, resourceEvent);
+
+        resourceStateTransitTo(host, resourceEvent, _nodeId);
+        return true;
+    }
+
+    private boolean isAutoEnableAttemptForADisabledHost(boolean autoEnableDisableKVMSetting,
+                                                        boolean isUpdateFromHostHealthCheck,
+                                                        DetailVO hostDetail, ResourceState.Event resourceEvent) {
+        return autoEnableDisableKVMSetting && isUpdateFromHostHealthCheck && hostDetail != null &&
+                !Boolean.parseBoolean(hostDetail.getValue()) && resourceEvent == ResourceState.Event.Enable;
+    }
+
+    private void updateHostName(HostVO host, String name) {
+        s_logger.debug("Updating Host name to: " + name);
+        host.setName(name);
+        _hostDao.update(host.getId(), host);
+    }
+
+    private void updateHostGuestOSCategory(Long hostId, Long guestOSCategoryId) {
+        // Verify that the guest OS Category exists
+        if (!(guestOSCategoryId > 0) || _guestOSCategoryDao.findById(guestOSCategoryId) == null) {
+            throw new InvalidParameterValueException("Please specify a valid guest OS category.");
+        }
+
+        final GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(guestOSCategoryId);
+        final DetailVO guestOSDetail = _hostDetailsDao.findDetail(hostId, "guest.os.category.id");
+
+        if (guestOSCategory != null && !GuestOSCategoryVO.CATEGORY_NONE.equalsIgnoreCase(guestOSCategory.getName())) {
+            // Create/Update an entry for guest.os.category.id
+            if (guestOSDetail != null) {
+                guestOSDetail.setValue(String.valueOf(guestOSCategory.getId()));
+                _hostDetailsDao.update(guestOSDetail.getId(), guestOSDetail);
+            } else {
+                final Map<String, String> detail = new HashMap<String, String>();
+                detail.put("guest.os.category.id", String.valueOf(guestOSCategory.getId()));
+                _hostDetailsDao.persist(hostId, detail);
+            }
+        } else {
+            // Delete any existing entry for guest.os.category.id
+            if (guestOSDetail != null) {
+                _hostDetailsDao.remove(guestOSDetail.getId());
+            }
+        }
+    }
+
+    private void updateHostTags(HostVO host, Long hostId, List<String> hostTags, Boolean isTagARule) {
+        List<VMInstanceVO> activeVMs =  _vmDao.listByHostId(hostId);
+        s_logger.warn(String.format("The following active VMs [%s] are using the host [%s]. " +
+                "Updating the host tags will not affect them.", activeVMs, host));
+
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Updating Host Tags to :" + hostTags);
+        }
+        _hostTagsDao.persist(hostId, new ArrayList<>(new HashSet<>(hostTags)), isTagARule);
+    }
+
     @Override
     public Host updateHost(final UpdateHostCmd cmd) throws NoTransitionException {
-        Long hostId = cmd.getId();
-        String name = cmd.getName();
-        Long guestOSCategoryId = cmd.getOsCategoryId();
+        return updateHost(cmd.getId(), cmd.getName(), cmd.getOsCategoryId(),
+                cmd.getAllocationState(), cmd.getUrl(), cmd.getHostTags(), cmd.getIsTagARule(), cmd.getAnnotation(), false);
+    }
 
+    private Host updateHost(Long hostId, String name, Long guestOSCategoryId, String allocationState,
+                            String url, List<String> hostTags, Boolean isTagARule, String annotation, boolean isUpdateFromHostHealthCheck) throws NoTransitionException {
         // Verify that the host exists
         final HostVO host = _hostDao.findById(hostId);
         if (host == null) {
             throw new InvalidParameterValueException("Host with id " + hostId + " doesn't exist");
         }
 
-        if (cmd.getAllocationState() != null) {
-            final ResourceState.Event resourceEvent = ResourceState.Event.toEvent(cmd.getAllocationState());
-            if (resourceEvent != ResourceState.Event.Enable && resourceEvent != ResourceState.Event.Disable) {
-                throw new CloudRuntimeException("Invalid allocation state:" + cmd.getAllocationState() + ", only Enable/Disable are allowed");
-            }
-
-            resourceStateTransitTo(host, resourceEvent, _nodeId);
+        boolean isUpdateHostAllocation = false;
+        if (StringUtils.isNotBlank(allocationState)) {
+            isUpdateHostAllocation = updateHostAllocationState(host, allocationState, isUpdateFromHostHealthCheck);
         }
 
         if (StringUtils.isNotBlank(name)) {
-            s_logger.debug("Updating Host name to: " + name);
-            host.setName(name);
-            _hostDao.update(host.getId(), host);
+            updateHostName(host, name);
         }
 
         if (guestOSCategoryId != null) {
-            // Verify that the guest OS Category exists
-            if (!(guestOSCategoryId > 0) || _guestOSCategoryDao.findById(guestOSCategoryId) == null) {
-                throw new InvalidParameterValueException("Please specify a valid guest OS category.");
-            }
-
-            final GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(guestOSCategoryId);
-            final DetailVO guestOSDetail = _hostDetailsDao.findDetail(hostId, "guest.os.category.id");
-
-            if (guestOSCategory != null && !GuestOSCategoryVO.CATEGORY_NONE.equalsIgnoreCase(guestOSCategory.getName())) {
-                // Create/Update an entry for guest.os.category.id
-                if (guestOSDetail != null) {
-                    guestOSDetail.setValue(String.valueOf(guestOSCategory.getId()));
-                    _hostDetailsDao.update(guestOSDetail.getId(), guestOSDetail);
-                } else {
-                    final Map<String, String> detail = new HashMap<String, String>();
-                    detail.put("guest.os.category.id", String.valueOf(guestOSCategory.getId()));
-                    _hostDetailsDao.persist(hostId, detail);
-                }
-            } else {
-                // Delete any existing entry for guest.os.category.id
-                if (guestOSDetail != null) {
-                    _hostDetailsDao.remove(guestOSDetail.getId());
-                }
-            }
+            updateHostGuestOSCategory(hostId, guestOSCategoryId);
         }
-        final List<String> hostTags = cmd.getHostTags();
+
         if (hostTags != null) {
-            List<VMInstanceVO> activeVMs =  _vmDao.listByHostId(hostId);
-            s_logger.warn(String.format("The following active VMs [%s] are using the host [%s]. Updating the host tags will not affect them.", activeVMs, host));
-
-            if (s_logger.isDebugEnabled()) {
-                s_logger.debug("Updating Host Tags to :" + hostTags);
-            }
-            _hostTagsDao.persist(hostId, new ArrayList(new HashSet<String>(hostTags)));
+            updateHostTags(host, hostId, hostTags, isTagARule);
         }
 
-        final String url = cmd.getUrl();
         if (url != null) {
-            _storageMgr.updateSecondaryStorage(cmd.getId(), cmd.getUrl());
+            _storageMgr.updateSecondaryStorage(hostId, url);
         }
         try {
             _storageMgr.enableHost(hostId);
@@ -1876,9 +1963,55 @@
         }
 
         final HostVO updatedHost = _hostDao.findById(hostId);
+
+        sendAlertAndAnnotationForAutoEnableDisableKVMHostFeature(host, allocationState,
+                isUpdateFromHostHealthCheck, isUpdateHostAllocation, annotation);
+
         return updatedHost;
     }
 
+    private void sendAlertAndAnnotationForAutoEnableDisableKVMHostFeature(HostVO host, String allocationState,
+                                                                          boolean isUpdateFromHostHealthCheck,
+                                                                          boolean isUpdateHostAllocation, String annotation) {
+        boolean isAutoEnableDisableKVMSettingEnabled = host.getHypervisorType() == HypervisorType.KVM &&
+                AgentManager.EnableKVMAutoEnableDisable.valueIn(host.getClusterId());
+        if (!isAutoEnableDisableKVMSettingEnabled) {
+            if (StringUtils.isNotBlank(annotation)) {
+                annotationService.addAnnotation(annotation, AnnotationService.EntityType.HOST, host.getUuid(), true);
+            }
+            return;
+        }
+
+        if (!isUpdateHostAllocation) {
+            return;
+        }
+
+        String msg = String.format("The host %s (%s) ", host.getName(), host.getUuid());
+        ResourceState.Event resourceEvent = getResourceEventFromAllocationStateString(allocationState);
+        boolean isEventEnable = resourceEvent == ResourceState.Event.Enable;
+
+        if (isUpdateFromHostHealthCheck) {
+            msg += String.format("is auto-%s after %s health check results",
+                    isEventEnable ? "enabled" : "disabled",
+                    isEventEnable ? "successful" : "failed");
+            alertManager.sendAlert(AlertService.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(),
+                    host.getPodId(), msg, msg);
+        } else {
+            msg += String.format("is %s despite the setting '%s' is enabled for the cluster %s",
+                    isEventEnable ? "enabled" : "disabled", AgentManager.EnableKVMAutoEnableDisable.key(),
+                    host.getClusterId());
+            if (StringUtils.isNotBlank(annotation)) {
+                msg += String.format(", reason: %s", annotation);
+            }
+        }
+        annotationService.addAnnotation(msg, AnnotationService.EntityType.HOST, host.getUuid(), true);
+    }
+
+    @Override
+    public Host autoUpdateHostAllocationState(Long hostId, ResourceState.Event resourceEvent) throws NoTransitionException {
+        return updateHost(hostId, null, null, resourceEvent.toString(), null, null, null, null, true);
+    }
+
     @Override
     public Cluster getCluster(final Long clusterId) {
         return _clusterDao.findById(clusterId);
@@ -2208,7 +2341,7 @@
             final List<String> implicitHostTags = ssCmd.getHostTags();
             if (!implicitHostTags.isEmpty()) {
                 if (hostTags == null) {
-                    hostTags = _hostTagsDao.getHostTags(host.getId());
+                    hostTags = _hostTagsDao.getHostTags(host.getId()).parallelStream().map(HostTagVO::getTag).collect(Collectors.toList());
                 }
                 if (hostTags != null) {
                     implicitHostTags.removeAll(hostTags);
@@ -2236,7 +2369,7 @@
         host.setManagementServerId(_nodeId);
         host.setStorageUrl(startup.getIqn());
         host.setLastPinged(System.currentTimeMillis() >> 10);
-        host.setHostTags(hostTags);
+        host.setHostTags(hostTags, false);
         host.setDetails(details);
         if (startup.getStorageIpAddressDeux() != null) {
             host.setStorageIpAddressDeux(startup.getStorageIpAddressDeux());
@@ -3220,7 +3353,7 @@
 
     @Override
     public String getHostTags(final long hostId) {
-        final List<String> hostTags = _hostTagsDao.getHostTags(hostId);
+        final List<String> hostTags = _hostTagsDao.getHostTags(hostId).parallelStream().map(HostTagVO::getTag).collect(Collectors.toList());
         if (hostTags == null) {
             return null;
         } else {
diff --git a/server/src/main/java/com/cloud/resource/RollingMaintenanceManagerImpl.java b/server/src/main/java/com/cloud/resource/RollingMaintenanceManagerImpl.java
index d881dee..25b2ad5 100644
--- a/server/src/main/java/com/cloud/resource/RollingMaintenanceManagerImpl.java
+++ b/server/src/main/java/com/cloud/resource/RollingMaintenanceManagerImpl.java
@@ -54,6 +54,7 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.OperationTimedoutException;
 import com.cloud.host.Host;
+import com.cloud.host.HostTagVO;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
 import com.cloud.host.dao.HostDao;
@@ -615,7 +616,7 @@
         if (CollectionUtils.isEmpty(vmsRunning)) {
             return new Pair<>(true, "OK");
         }
-        List<String> hostTags = hostTagsDao.getHostTags(host.getId());
+        List<HostTagVO> hostTags = hostTagsDao.getHostTags(host.getId());
 
         int successfullyCheckedVmMigrations = 0;
         for (VMInstanceVO runningVM : vmsRunning) {
@@ -668,14 +669,14 @@
     /**
      * Check hosts tags
      */
-    private boolean checkHostTags(List<String> hostTags, List<String> hostInClusterTags, String offeringTag) {
+    private boolean checkHostTags(List<HostTagVO> hostTags, List<HostTagVO> hostInClusterTags, String offeringTag) {
         if (CollectionUtils.isEmpty(hostTags) && CollectionUtils.isEmpty(hostInClusterTags)) {
             return true;
         } else if ((CollectionUtils.isNotEmpty(hostTags) && CollectionUtils.isEmpty(hostInClusterTags)) ||
                 (CollectionUtils.isEmpty(hostTags) && CollectionUtils.isNotEmpty(hostInClusterTags))) {
             return false;
         } else {
-            return hostInClusterTags.contains(offeringTag);
+            return hostInClusterTags.parallelStream().anyMatch(hostTagVO -> offeringTag.equals(hostTagVO.getTag()));
         }
     }
 
diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java
index dda2241..959a0dc 100644
--- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java
+++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java
@@ -31,6 +31,9 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.cluster.ManagementServerHostVO;
+import com.cloud.cluster.dao.ManagementServerHostDao;
+import com.cloud.utils.db.GlobalLock;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
@@ -44,6 +47,8 @@
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.user.ResourceReservation;
+import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -162,6 +167,8 @@
     private VpcDao _vpcDao;
     @Inject
     private VlanDao _vlanDao;
+    @Inject
+    private ManagementServerHostDao managementServerHostDao;
 
     protected GenericSearchBuilder<TemplateDataStoreVO, SumCount> templateSizeSearch;
     protected GenericSearchBuilder<SnapshotDataStoreVO, SumCount> snapshotSizeSearch;
@@ -701,7 +708,7 @@
                 if (isAccount) {
                     if (accountLimitStr.size() < resourceTypes.length) {
                         for (ResourceType rt : resourceTypes) {
-                            if (!accountLimitStr.contains(rt.toString()) && rt.supportsOwner(ResourceOwnerType.Account)) {
+                            if (!accountLimitStr.contains(rt.toString())) {
                                 limits.add(new ResourceLimitVO(rt, findCorrectResourceLimitForAccount(_accountMgr.getAccount(accountId), rt), accountId, ResourceOwnerType.Account));
                             }
                         }
@@ -710,7 +717,7 @@
                 } else {
                     if (domainLimitStr.size() < resourceTypes.length) {
                         for (ResourceType rt : resourceTypes) {
-                            if (!domainLimitStr.contains(rt.toString()) && rt.supportsOwner(ResourceOwnerType.Domain)) {
+                            if (!domainLimitStr.contains(rt.toString())) {
                                 limits.add(new ResourceLimitVO(rt, findCorrectResourceLimitForDomain(_domainDao.findById(domainId), rt), domainId, ResourceOwnerType.Domain));
                             }
                         }
@@ -855,16 +862,12 @@
 
         for (ResourceType type : resourceTypes) {
             if (accountId != null) {
-                if (type.supportsOwner(ResourceOwnerType.Account)) {
-                    count = recalculateAccountResourceCount(accountId, type);
-                    counts.add(new ResourceCountVO(type, count, accountId, ResourceOwnerType.Account));
-                }
+                count = recalculateAccountResourceCount(accountId, type);
+                counts.add(new ResourceCountVO(type, count, accountId, ResourceOwnerType.Account));
 
             } else {
-                if (type.supportsOwner(ResourceOwnerType.Domain)) {
-                    count = recalculateDomainResourceCount(domainId, type);
-                    counts.add(new ResourceCountVO(type, count, domainId, ResourceOwnerType.Domain));
-                }
+                count = recalculateDomainResourceCount(domainId, type);
+                counts.add(new ResourceCountVO(type, count, domainId, ResourceOwnerType.Domain));
             }
         }
 
@@ -921,25 +924,20 @@
 
                 List<DomainVO> domainChildren = _domainDao.findImmediateChildrenForParent(domainId);
                 // for each child domain update the resource count
-                if (type.supportsOwner(ResourceOwnerType.Domain)) {
 
-                    // calculate project count here
-                    if (type == ResourceType.project) {
-                        newResourceCount += _projectDao.countProjectsForDomain(domainId);
-                    }
-
-                    for (DomainVO childDomain : domainChildren) {
-                        long childDomainResourceCount = recalculateDomainResourceCount(childDomain.getId(), type);
-                        newResourceCount += childDomainResourceCount; // add the child domain count to parent domain count
-                    }
+                // calculate project count here
+                if (type == ResourceType.project) {
+                    newResourceCount += _projectDao.countProjectsForDomain(domainId);
                 }
 
-                if (type.supportsOwner(ResourceOwnerType.Account)) {
-                    List<AccountVO> accounts = _accountDao.findActiveAccountsForDomain(domainId);
-                    for (AccountVO account : accounts) {
-                        long accountResourceCount = recalculateAccountResourceCount(account.getId(), type);
-                        newResourceCount += accountResourceCount; // add account's resource count to parent domain count
-                    }
+                for (DomainVO childDomain : domainChildren) {
+                    long childDomainResourceCount = recalculateDomainResourceCount(childDomain.getId(), type);
+                    newResourceCount += childDomainResourceCount; // add the child domain count to parent domain count
+                }
+                List<AccountVO> accounts = _accountDao.findActiveAccountsForDomain(domainId);
+                for (AccountVO account : accounts) {
+                    long accountResourceCount = recalculateAccountResourceCount(account.getId(), type);
+                    newResourceCount += accountResourceCount; // add account's resource count to parent domain count
                 }
                 _resourceCountDao.setResourceCount(domainId, ResourceOwnerType.Domain, type, newResourceCount);
 
@@ -1180,25 +1178,76 @@
 
         @Override
         protected void runInContext() {
+            GlobalLock lock = GlobalLock.getInternLock("ResourceCheckTask");
+            try {
+                if (lock.lock(30)) {
+                    try {
+                        ManagementServerHostVO msHost = managementServerHostDao.findOneByLongestRuntime();
+                        if (msHost == null || (msHost.getMsid() != ManagementServerNode.getManagementServerId())) {
+                            s_logger.trace("Skipping the resource counters recalculation task on this management server");
+                            return;
+                        }
+                        runResourceCheckTaskInternal();
+                    } finally {
+                        lock.unlock();
+                    }
+                }
+            } finally {
+                lock.releaseRef();
+            }
+        }
+
+        private void runResourceCheckTaskInternal() {
             s_logger.info("Started resource counters recalculation periodic task.");
-            List<DomainVO> domains = _domainDao.findImmediateChildrenForParent(Domain.ROOT_DOMAIN);
-            List<AccountVO> accounts = _accountDao.findActiveAccountsForDomain(Domain.ROOT_DOMAIN);
+            List<DomainVO> domains;
+            List<AccountVO> accounts;
+            // try/catch task, otherwise it won't be rescheduled in case of exception
+            try {
+                domains = _domainDao.findImmediateChildrenForParent(Domain.ROOT_DOMAIN);
+            } catch (Exception e) {
+                s_logger.warn("Resource counters recalculation periodic task failed, unable to fetch immediate children for the domain " + Domain.ROOT_DOMAIN, e);
+                // initialize domains as empty list to do best effort recalculation
+                domains = new ArrayList<>();
+            }
+            // try/catch task, otherwise it won't be rescheduled in case of exception
+            try {
+                accounts = _accountDao.findActiveAccountsForDomain(Domain.ROOT_DOMAIN);
+            } catch (Exception e) {
+                s_logger.warn("Resource counters recalculation periodic task failed, unable to fetch active accounts for domain " + Domain.ROOT_DOMAIN, e);
+                // initialize accounts as empty list to do best effort recalculation
+                accounts = new ArrayList<>();
+            }
 
             for (ResourceType type : ResourceType.values()) {
-                if (type.supportsOwner(ResourceOwnerType.Domain)) {
-                    recalculateDomainResourceCount(Domain.ROOT_DOMAIN, type);
+                if (CollectionUtils.isEmpty(domains)) {
+                    recalculateDomainResourceCountInContext(Domain.ROOT_DOMAIN, type);
+                } else {
                     for (Domain domain : domains) {
                         recalculateDomainResourceCount(domain.getId(), type);
                     }
                 }
 
-                if (type.supportsOwner(ResourceOwnerType.Account)) {
-                    // run through the accounts in the root domain
-                    for (AccountVO account : accounts) {
-                        recalculateAccountResourceCount(account.getId(), type);
-                    }
+                // run through the accounts in the root domain
+                for (AccountVO account : accounts) {
+                    recalculateAccountResourceCountInContext(account.getId(), type);
                 }
             }
+            s_logger.info("Finished resource counters recalculation periodic task.");
+        }
+
+        private void recalculateDomainResourceCountInContext(long domainId, ResourceType type) {
+            try {
+                recalculateDomainResourceCount(domainId, type);
+            } catch (Exception e) {
+                s_logger.warn("Resource counters recalculation periodic task failed for the domain " + domainId + " and the resource type " + type + " .", e);
+            }
+        }
+        private void recalculateAccountResourceCountInContext(long accountId, ResourceType type) {
+            try {
+                recalculateAccountResourceCount(accountId, type);
+            } catch (Exception e) {
+                s_logger.warn("Resource counters recalculation periodic task failed for the account " + accountId + " and the resource type " + type + " .", e);
+            }
         }
     }
 }
diff --git a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
index da610ac..f7cab45 100644
--- a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
@@ -1315,22 +1315,9 @@
         List<ResourceCountVO> domainResourceCount = _resourceCountDao.listResourceCountByOwnerType(ResourceOwnerType.Domain);
         List<ResourceCountVO> accountResourceCount = _resourceCountDao.listResourceCountByOwnerType(ResourceOwnerType.Account);
 
-        final List<ResourceType> accountSupportedResourceTypes = new ArrayList<ResourceType>();
-        final List<ResourceType> domainSupportedResourceTypes = new ArrayList<ResourceType>();
+        final int expectedCount = resourceTypes.length;
 
-        for (ResourceType resourceType : resourceTypes) {
-            if (resourceType.supportsOwner(ResourceOwnerType.Account)) {
-                accountSupportedResourceTypes.add(resourceType);
-            }
-            if (resourceType.supportsOwner(ResourceOwnerType.Domain)) {
-                domainSupportedResourceTypes.add(resourceType);
-            }
-        }
-
-        final int accountExpectedCount = accountSupportedResourceTypes.size();
-        final int domainExpectedCount = domainSupportedResourceTypes.size();
-
-        if ((domainResourceCount.size() < domainExpectedCount * domains.size())) {
+        if ((domainResourceCount.size() < expectedCount * domains.size())) {
             s_logger.debug("resource_count table has records missing for some domains...going to insert them");
             for (final DomainVO domain : domains) {
                 // Lock domain
@@ -1344,8 +1331,8 @@
                             domainCountStr.add(domainCount.getType().toString());
                         }
 
-                        if (domainCountStr.size() < domainExpectedCount) {
-                            for (ResourceType resourceType : domainSupportedResourceTypes) {
+                        if (domainCountStr.size() < expectedCount) {
+                            for (ResourceType resourceType : resourceTypes) {
                                 if (!domainCountStr.contains(resourceType.toString())) {
                                     ResourceCountVO resourceCountVO = new ResourceCountVO(resourceType, 0, domain.getId(), ResourceOwnerType.Domain);
                                     s_logger.debug("Inserting resource count of type " + resourceType + " for domain id=" + domain.getId());
@@ -1359,7 +1346,7 @@
             }
         }
 
-        if ((accountResourceCount.size() < accountExpectedCount * accounts.size())) {
+        if ((accountResourceCount.size() < expectedCount * accounts.size())) {
             s_logger.debug("resource_count table has records missing for some accounts...going to insert them");
             for (final AccountVO account : accounts) {
                 // lock account
@@ -1373,8 +1360,8 @@
                             accountCountStr.add(accountCount.getType().toString());
                         }
 
-                        if (accountCountStr.size() < accountExpectedCount) {
-                            for (ResourceType resourceType : accountSupportedResourceTypes) {
+                        if (accountCountStr.size() < expectedCount) {
+                            for (ResourceType resourceType : resourceTypes) {
                                 if (!accountCountStr.contains(resourceType.toString())) {
                                     ResourceCountVO resourceCountVO = new ResourceCountVO(resourceType, 0, account.getId(), ResourceOwnerType.Account);
                                     s_logger.debug("Inserting resource count of type " + resourceType + " for account id=" + account.getId());
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 31a7874..9b635ce 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -16,12 +16,7 @@
 // under the License.
 package com.cloud.server;
 
-import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH;
-import static com.cloud.vm.UserVmManager.MAX_USER_DATA_LENGTH_BYTES;
-
-import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Field;
-import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -56,7 +51,6 @@
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
 import org.apache.cloudstack.api.command.admin.account.DeleteAccountCmd;
 import org.apache.cloudstack.api.command.admin.account.DisableAccountCmd;
@@ -92,9 +86,11 @@
 import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
 import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
 import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin;
+import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
 import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
 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.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;
@@ -189,6 +185,7 @@
 import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd;
 import org.apache.cloudstack.api.command.admin.resource.CleanVMReservationsCmd;
 import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd;
+import org.apache.cloudstack.api.command.admin.resource.ListAlertTypesCmd;
 import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd;
 import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd;
 import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd;
@@ -209,27 +206,37 @@
 import org.apache.cloudstack.api.command.admin.router.StopRouterCmd;
 import org.apache.cloudstack.api.command.admin.router.UpgradeRouterCmd;
 import org.apache.cloudstack.api.command.admin.router.UpgradeRouterTemplateCmd;
+import org.apache.cloudstack.api.command.admin.snapshot.ListSnapshotsCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.storage.AddImageStoreCmd;
 import org.apache.cloudstack.api.command.admin.storage.AddImageStoreS3CMD;
+import org.apache.cloudstack.api.command.admin.storage.AddObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd;
 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;
+import org.apache.cloudstack.api.command.admin.storage.DeleteObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.DeletePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.DeleteSecondaryStagingStoreCmd;
 import org.apache.cloudstack.api.command.admin.storage.FindStoragePoolsForMigrationCmd;
 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.ListStoragePoolsCmd;
 import org.apache.cloudstack.api.command.admin.storage.ListStorageProvidersCmd;
 import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd;
+import org.apache.cloudstack.api.command.admin.storage.MigrateResourcesToAnotherSecondaryStorageCmd;
 import org.apache.cloudstack.api.command.admin.storage.MigrateSecondaryStorageDataCmd;
 import org.apache.cloudstack.api.command.admin.storage.PreparePrimaryStorageForMaintenanceCmd;
 import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.UpdateCloudToUseObjectStoreCmd;
 import org.apache.cloudstack.api.command.admin.storage.UpdateImageStoreCmd;
+import org.apache.cloudstack.api.command.admin.storage.UpdateObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.UpdateStorageCapabilitiesCmd;
 import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.CreateSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.RemoveSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.UpdateSecondaryStorageSelectorCmd;
 import org.apache.cloudstack.api.command.admin.swift.AddSwiftCmd;
 import org.apache.cloudstack.api.command.admin.swift.ListSwiftsCmd;
 import org.apache.cloudstack.api.command.admin.systemvm.DestroySystemVmCmd;
@@ -313,6 +320,7 @@
 import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
 import org.apache.cloudstack.api.command.admin.vpc.DeletePrivateGatewayCmd;
 import org.apache.cloudstack.api.command.admin.vpc.DeleteVPCOfferingCmd;
+import org.apache.cloudstack.api.command.admin.vpc.ListPrivateGatewaysCmdByAdminCmd;
 import org.apache.cloudstack.api.command.admin.vpc.ListVPCsCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd;
@@ -330,9 +338,12 @@
 import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd;
 import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd;
 import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd;
+import org.apache.cloudstack.api.command.user.address.ListQuarantinedIpsCmd;
 import org.apache.cloudstack.api.command.user.address.ReleaseIPAddrCmd;
+import org.apache.cloudstack.api.command.user.address.RemoveQuarantinedIpCmd;
 import org.apache.cloudstack.api.command.user.address.ReserveIPAddrCmd;
 import org.apache.cloudstack.api.command.user.address.UpdateIPAddrCmd;
+import org.apache.cloudstack.api.command.user.address.UpdateQuarantinedIpCmd;
 import org.apache.cloudstack.api.command.user.affinitygroup.CreateAffinityGroupCmd;
 import org.apache.cloudstack.api.command.user.affinitygroup.DeleteAffinityGroupCmd;
 import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupTypesCmd;
@@ -357,6 +368,10 @@
 import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
 import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmProfileCmd;
 import org.apache.cloudstack.api.command.user.autoscale.UpdateConditionCmd;
+import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd;
+import org.apache.cloudstack.api.command.user.bucket.DeleteBucketCmd;
+import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd;
+import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd;
 import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd;
 import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd;
 import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd;
@@ -472,6 +487,7 @@
 import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd;
 import org.apache.cloudstack.api.command.user.securitygroup.UpdateSecurityGroupCmd;
 import org.apache.cloudstack.api.command.user.snapshot.ArchiveSnapshotCmd;
+import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
 import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
 import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnapshotCmd;
 import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
@@ -535,6 +551,7 @@
 import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
+import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.DestroyVolumeCmd;
@@ -588,6 +605,9 @@
 import org.apache.cloudstack.config.ConfigurationGroup;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
 import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
 import org.apache.cloudstack.framework.config.ConfigDepot;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -610,6 +630,7 @@
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
+import org.apache.cloudstack.userdata.UserDataManager;
 import org.apache.cloudstack.utils.CloudStackVersion;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
 import org.apache.commons.codec.binary.Base64;
@@ -619,7 +640,11 @@
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckGuestOsMappingAnswer;
+import com.cloud.agent.api.CheckGuestOsMappingCommand;
 import com.cloud.agent.api.Command;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesAnswer;
+import com.cloud.agent.api.GetHypervisorGuestOsNamesCommand;
 import com.cloud.agent.api.GetVncPortAnswer;
 import com.cloud.agent.api.GetVncPortCommand;
 import com.cloud.agent.api.PatchSystemVmAnswer;
@@ -691,10 +716,10 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.host.dao.HostDetailsDao;
 import com.cloud.host.dao.HostTagsDao;
-import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.HypervisorCapabilities;
 import com.cloud.hypervisor.HypervisorCapabilitiesVO;
+import com.cloud.hypervisor.HypervisorGuru;
 import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
 import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
 import com.cloud.info.ConsoleProxyInfo;
@@ -714,6 +739,7 @@
 import com.cloud.network.dao.NetworkDomainDao;
 import com.cloud.network.dao.NetworkDomainVO;
 import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.dao.PublicIpQuarantineDao;
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.org.Cluster;
 import com.cloud.org.Grouping.AllocationState;
@@ -821,10 +847,6 @@
     static final ConfigKey<Boolean> humanReadableSizes = new ConfigKey<Boolean>("Advanced", Boolean.class, "display.human.readable.sizes", "true", "Enables outputting human readable byte sizes to logs and usage records.", false, ConfigKey.Scope.Global);
     public static final ConfigKey<String> customCsIdentifier = new ConfigKey<String>("Advanced", String.class, "custom.cs.identifier", UUID.randomUUID().toString().split("-")[0].substring(4), "Custom identifier for the cloudstack installation", true, ConfigKey.Scope.Global);
     private static final VirtualMachine.Type []systemVmTypes = { VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.ConsoleProxy};
-
-    private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES;
-    private static final int NUM_OF_2K_BLOCKS = 512;
-    private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES;
     private static final List<HypervisorType> LIVE_MIGRATION_SUPPORTING_HYPERVISORS = List.of(HypervisorType.Hyperv, HypervisorType.KVM,
             HypervisorType.LXC, HypervisorType.Ovm, HypervisorType.Ovm3, HypervisorType.Simulator, HypervisorType.VMware, HypervisorType.XenServer);
 
@@ -949,6 +971,8 @@
     @Inject
     private PrimaryDataStoreDao _primaryDataStoreDao;
     @Inject
+    private DataStoreManager dataStoreManager;
+    @Inject
     private VolumeDataStoreDao _volumeStoreDao;
     @Inject
     private TemplateDataStoreDao _vmTemplateStoreDao;
@@ -976,6 +1000,11 @@
     protected VMTemplateDao templateDao;
     @Inject
     protected AnnotationDao annotationDao;
+    @Inject
+    UserDataManager userDataManager;
+
+    @Inject
+    private PublicIpQuarantineDao publicIpQuarantineDao;
 
     private LockControllerListener _lockControllerListener;
     private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker"));
@@ -993,7 +1022,7 @@
 
     protected List<DeploymentPlanner> _planners;
 
-    private final List<HypervisorType> supportedHypervisors = new ArrayList<Hypervisor.HypervisorType>();
+    private final List<HypervisorType> supportedHypervisors = new ArrayList<HypervisorType>();
 
     public List<DeploymentPlanner> getPlanners() {
         return _planners;
@@ -1266,7 +1295,9 @@
         }
 
         if (hypervisorType != null) {
-            sc.setParameters("hypervisorType", hypervisorType);
+            String hypervisorStr = (String) hypervisorType;
+            String hypervisorSearch = HypervisorType.getType(hypervisorStr).toString();
+            sc.setParameters("hypervisorType", hypervisorSearch);
         }
 
         if (clusterType != null) {
@@ -1328,7 +1359,7 @@
         return new Pair<List<? extends Host>, Integer>(result.first(), result.second());
     }
 
-    protected Pair<Boolean, List<HostVO>> filterUefiHostsForMigration(List<HostVO> allHosts, List<HostVO> filteredHosts, VMInstanceVO vm) {
+    protected Pair<Boolean, List<HostVO>> filterUefiHostsForMigration(List<HostVO> allHosts, List<HostVO> filteredHosts, VirtualMachine vm) {
         UserVmDetailVO userVmDetailVO = _UserVmDetailsDao.findDetail(vm.getId(), ApiConstants.BootType.UEFI.toString());
         if (userVmDetailVO != null &&
                 (ApiConstants.BootMode.LEGACY.toString().equalsIgnoreCase(userVmDetailVO.getValue()) ||
@@ -1348,9 +1379,7 @@
         return new Pair<>(true, filteredHosts);
     }
 
-    @Override
-    public Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(final Long vmId, final Long startIndex, final Long pageSize,
-            final String keyword) {
+    private void validateVmForHostMigration(VirtualMachine vm) {
         final Account caller = getCaller();
         if (!_accountMgr.isRootAdmin(caller.getId())) {
             if (s_logger.isDebugEnabled()) {
@@ -1359,10 +1388,8 @@
             throw new PermissionDeniedException("No permission to migrate VM, Only Root Admin can migrate a VM!");
         }
 
-        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
         if (vm == null) {
-            final InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find the VM with given id");
-            throw ex;
+            throw new InvalidParameterValueException("Unable to find the VM with given id");
         }
 
         if (vm.getState() != State.Running) {
@@ -1374,13 +1401,6 @@
             throw ex;
         }
 
-        if (_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) {
-            s_logger.info(" Live Migration of GPU enabled VM : " + vm.getInstanceName() + " is not supported");
-            // Return empty list.
-            return new Ternary<>(new Pair<>(new ArrayList<HostVO>(), new Integer(0)),
-                    new ArrayList<>(), new HashMap<>());
-        }
-
         if (!LIVE_MIGRATION_SUPPORTING_HYPERVISORS.contains(vm.getHypervisorType())) {
             if (s_logger.isDebugEnabled()) {
                 s_logger.debug(vm + " is not XenServer/VMware/KVM/Ovm/Hyperv/Ovm3, cannot migrate this VM.");
@@ -1391,6 +1411,41 @@
         if (VirtualMachine.Type.User.equals(vm.getType()) && HypervisorType.LXC.equals(vm.getHypervisorType())) {
             throw new InvalidParameterValueException("Unsupported Hypervisor Type for User VM migration, we support XenServer/VMware/KVM/Ovm/Hyperv/Ovm3 only");
         }
+    }
+
+    @Override
+    public Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(final Long vmId, final Long startIndex, final Long pageSize,
+                                                                                                                            final String keyword) {
+        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
+        return listHostsForMigrationOfVM(vm, startIndex, pageSize, keyword, Collections.emptyList());
+    }
+
+    protected boolean zoneWideVolumeRequiresStorageMotion(PrimaryDataStore volumeDataStore,
+               final Host sourceHost, final Host destinationHost) {
+        if (volumeDataStore.isManaged() && sourceHost.getClusterId() != destinationHost.getClusterId()) {
+            PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver)volumeDataStore.getDriver();
+            // Depends on the storage driver. For some storages simply
+            // changing volume access to host should work: grant access on destination
+            // host and revoke access on source host. For others, we still have to perform a storage migration
+            // because we need to create a new target volume and copy the contents of the
+            // source volume into it before deleting the source volume.
+            return !driver.zoneWideVolumesAvailableWithoutClusterMotion();
+        }
+        return false;
+    }
+
+    @Override
+    public Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(final VirtualMachine vm, final Long startIndex, final Long pageSize,
+            final String keyword, List<VirtualMachine> vmList) {
+
+        validateVmForHostMigration(vm);
+
+        if (_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) {
+            s_logger.info(" Live Migration of GPU enabled VM : " + vm.getInstanceName() + " is not supported");
+            // Return empty list.
+            return new Ternary<>(new Pair<>(new ArrayList<>(), 0),
+                    new ArrayList<>(), new HashMap<>());
+        }
 
         final long srcHostId = vm.getHostId();
         final Host srcHost = _hostDao.findById(srcHostId);
@@ -1447,8 +1502,8 @@
             filteredHosts = new ArrayList<>(allHosts);
 
             for (final VolumeVO volume : volumes) {
-                StoragePool storagePool = _poolDao.findById(volume.getPoolId());
-                Long volClusterId = storagePool.getClusterId();
+                PrimaryDataStore primaryDataStore = (PrimaryDataStore)dataStoreManager.getPrimaryDataStore(volume.getPoolId());
+                Long volClusterId = primaryDataStore.getClusterId();
 
                 for (Iterator<HostVO> iterator = filteredHosts.iterator(); iterator.hasNext();) {
                     final Host host = iterator.next();
@@ -1458,8 +1513,8 @@
                     }
 
                     if (volClusterId != null) {
-                        if (storagePool.isLocal() || !host.getClusterId().equals(volClusterId) || usesLocal) {
-                            if (storagePool.isManaged()) {
+                        if (primaryDataStore.isLocal() || !host.getClusterId().equals(volClusterId) || usesLocal) {
+                            if (primaryDataStore.isManaged()) {
                                 // At the time being, we do not support storage migration of a volume from managed storage unless the managed storage
                                 // is at the zone level and the source and target storage pool is the same.
                                 // If the source and target storage pool is the same and it is managed, then we still have to perform a storage migration
@@ -1477,18 +1532,8 @@
                             }
                         }
                     } else {
-                        if (storagePool.isManaged()) {
-                            if (srcHost.getClusterId() != host.getClusterId()) {
-                                if (storagePool.getPoolType() == Storage.StoragePoolType.PowerFlex) {
-                                    // No need of new volume creation for zone wide PowerFlex/ScaleIO pool
-                                    // Simply, changing volume access to host should work: grant access on dest host and revoke access on source host
-                                    continue;
-                                }
-                                // If the volume's storage pool is managed and at the zone level, then we still have to perform a storage migration
-                                // because we need to create a new target volume and copy the contents of the source volume into it before deleting
-                                // the source volume.
-                                requiresStorageMotion.put(host, true);
-                            }
+                        if (zoneWideVolumeRequiresStorageMotion(primaryDataStore, srcHost, host)) {
+                            requiresStorageMotion.put(host, true);
                         }
                     }
                 }
@@ -1529,7 +1574,7 @@
 
         if (vmGroupCount > 0) {
             for (final AffinityGroupProcessor processor : _affinityProcessors) {
-                processor.process(vmProfile, plan, excludes);
+                processor.process(vmProfile, plan, excludes, vmList);
             }
         }
 
@@ -1596,9 +1641,9 @@
     }
 
     @Override
-    public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(final Long volumeId) {
+    public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(final Long volumeId, String keyword) {
 
-        Pair<List<? extends StoragePool>, List<? extends StoragePool>> allPoolsAndSuitablePoolsPair = listStoragePoolsForMigrationOfVolumeInternal(volumeId, null, null, null, null, false, true, false);
+        Pair<List<? extends StoragePool>, List<? extends StoragePool>> allPoolsAndSuitablePoolsPair = listStoragePoolsForMigrationOfVolumeInternal(volumeId, null, null, null, null, false, true, false, keyword);
         List<? extends StoragePool> allPools = allPoolsAndSuitablePoolsPair.first();
         List<? extends StoragePool> suitablePools = allPoolsAndSuitablePoolsPair.second();
         List<StoragePool> avoidPools = new ArrayList<>();
@@ -1616,10 +1661,10 @@
 
     @Override
     public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForSystemMigrationOfVolume(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck) {
-        return listStoragePoolsForMigrationOfVolumeInternal(volumeId, newDiskOfferingId, newSize, newMinIops, newMaxIops, keepSourceStoragePool, bypassStorageTypeCheck, true);
+        return listStoragePoolsForMigrationOfVolumeInternal(volumeId, newDiskOfferingId, newSize, newMinIops, newMaxIops, keepSourceStoragePool, bypassStorageTypeCheck, true, null);
     }
 
-    public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolumeInternal(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck, boolean bypassAccountCheck) {
+    public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolumeInternal(final Long volumeId, Long newDiskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, boolean keepSourceStoragePool, boolean bypassStorageTypeCheck, boolean bypassAccountCheck, String keyword) {
         if (!bypassAccountCheck) {
             final Account caller = getCaller();
             if (!_accountMgr.isRootAdmin(caller.getId())) {
@@ -1694,7 +1739,7 @@
         Pair<Host, List<Cluster>> hostClusterPair = getVolumeVmHostClusters(srcVolumePool, vm, hypervisorType);
         Host vmHost = hostClusterPair.first();
         List<Cluster> clusters = hostClusterPair.second();
-        allPools = getAllStoragePoolCompatibleWithVolumeSourceStoragePool(srcVolumePool, hypervisorType, clusters);
+        allPools = getAllStoragePoolCompatibleWithVolumeSourceStoragePool(srcVolumePool, hypervisorType, clusters, keyword);
         ExcludeList avoid = new ExcludeList();
         if (!keepSourceStoragePool) {
             allPools.remove(srcVolumePool);
@@ -1702,7 +1747,7 @@
         }
         if (vm != null) {
             suitablePools = findAllSuitableStoragePoolsForVm(volume, diskOfferingId, newSize, newMinIops, newMaxIops, vm, vmHost, avoid,
-                    CollectionUtils.isNotEmpty(clusters) ? clusters.get(0) : null, hypervisorType, bypassStorageTypeCheck);
+                    CollectionUtils.isNotEmpty(clusters) ? clusters.get(0) : null, hypervisorType, bypassStorageTypeCheck, keyword);
         } else {
             suitablePools = findAllSuitableStoragePoolsForDetachedVolume(volume, diskOfferingId, allPools);
         }
@@ -1769,15 +1814,15 @@
      *  <li>We also all storage available filtering by data center, pod and cluster as the current storage pool used by the given volume.</li>
      * </ul>
      */
-    private List<? extends StoragePool> getAllStoragePoolCompatibleWithVolumeSourceStoragePool(StoragePool srcVolumePool, HypervisorType hypervisorType, List<Cluster> clusters) {
+    private List<? extends StoragePool> getAllStoragePoolCompatibleWithVolumeSourceStoragePool(StoragePool srcVolumePool, HypervisorType hypervisorType, List<Cluster> clusters, String keyword) {
         List<StoragePoolVO> storagePools = new ArrayList<>();
-        List<StoragePoolVO> zoneWideStoragePools = _poolDao.findZoneWideStoragePoolsByHypervisor(srcVolumePool.getDataCenterId(), hypervisorType);
+        List<StoragePoolVO> zoneWideStoragePools = _poolDao.findZoneWideStoragePoolsByHypervisor(srcVolumePool.getDataCenterId(), hypervisorType, keyword);
         if (CollectionUtils.isNotEmpty(zoneWideStoragePools)) {
             storagePools.addAll(zoneWideStoragePools);
         }
         if (CollectionUtils.isNotEmpty(clusters)) {
             List<Long> clusterIds = clusters.stream().map(Cluster::getId).collect(Collectors.toList());
-            List<StoragePoolVO> clusterAndLocalStoragePools = _poolDao.findPoolsInClusters(clusterIds);
+            List<StoragePoolVO> clusterAndLocalStoragePools = _poolDao.findPoolsInClusters(clusterIds, keyword);
             if (CollectionUtils.isNotEmpty(clusterAndLocalStoragePools)) {
                 storagePools.addAll(clusterAndLocalStoragePools);
             }
@@ -1793,7 +1838,7 @@
      *
      *  Side note: the idea behind this method is to provide power for administrators of manually overriding deployments defined by CloudStack.
      */
-    private List<StoragePool> findAllSuitableStoragePoolsForVm(final VolumeVO volume, Long diskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, VMInstanceVO vm, Host vmHost, ExcludeList avoid, Cluster srcCluster, HypervisorType hypervisorType, boolean bypassStorageTypeCheck) {
+    private List<StoragePool> findAllSuitableStoragePoolsForVm(final VolumeVO volume, Long diskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, VMInstanceVO vm, Host vmHost, ExcludeList avoid, Cluster srcCluster, HypervisorType hypervisorType, boolean bypassStorageTypeCheck, String keyword) {
         List<StoragePool> suitablePools = new ArrayList<>();
         Long clusterId = null;
         Long podId = null;
@@ -1815,7 +1860,7 @@
         }
 
         for (StoragePoolAllocator allocator : _storagePoolAllocators) {
-            List<StoragePool> pools = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL, bypassStorageTypeCheck);
+            List<StoragePool> pools = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL, bypassStorageTypeCheck, keyword);
             if (CollectionUtils.isEmpty(pools)) {
                 continue;
             }
@@ -2494,10 +2539,12 @@
             final SearchBuilder<IPAddressVO> sb2 = _publicIpAddressDao.createSearchBuilder();
             buildParameters(sb2, cmd, false);
             sb2.and("ids", sb2.entity().getId(), SearchCriteria.Op.IN);
+            sb2.and("quarantinedPublicIpsIdsNIN", sb2.entity().getId(), SearchCriteria.Op.NIN);
 
             SearchCriteria<IPAddressVO> sc2 = sb2.create();
             setParameters(sc2, cmd, vlanType, isAllocated);
             sc2.setParameters("ids", freeAddrIds.toArray());
+            _publicIpAddressDao.buildQuarantineSearchCriteria(sc2);
             addrs.addAll(_publicIpAddressDao.search(sc2, searchFilter)); // Allocated + Free
         }
         Collections.sort(addrs, Comparator.comparing(IPAddressVO::getAddress));
@@ -2677,9 +2724,15 @@
 
     @Override
     public Pair<List<? extends GuestOSHypervisor>, Integer> listGuestOSMappingByCriteria(final ListGuestOsMappingCmd cmd) {
+        final String guestOsId = "guestOsId";
         final Filter searchFilter = new Filter(GuestOSHypervisorVO.class, "hypervisorType", true, cmd.getStartIndex(), cmd.getPageSizeVal());
+        searchFilter.addOrderBy(GuestOSHypervisorVO.class, "hypervisorVersion", false);
+        searchFilter.addOrderBy(GuestOSHypervisorVO.class, guestOsId, true);
+        searchFilter.addOrderBy(GuestOSHypervisorVO.class, "created", false);
         final Long id = cmd.getId();
         final Long osTypeId = cmd.getOsTypeId();
+        final String osDisplayName = cmd.getOsDisplayName();
+        final String osNameForHypervisor = cmd.getOsNameForHypervisor();
         final String hypervisor = cmd.getHypervisor();
         final String hypervisorVersion = cmd.getHypervisorVersion();
 
@@ -2695,15 +2748,27 @@
         }
 
         if (osTypeId != null) {
-            sc.addAnd("guestOsId", SearchCriteria.Op.EQ, osTypeId);
+            sc.addAnd(guestOsId, SearchCriteria.Op.EQ, osTypeId);
+        }
+
+        if (osNameForHypervisor != null) {
+            sc.addAnd("guestOsName", SearchCriteria.Op.LIKE, "%" + osNameForHypervisor + "%");
         }
 
         if (hypervisor != null) {
-            sc.addAnd("hypervisorType", SearchCriteria.Op.EQ, hypervisor);
+            sc.addAnd("hypervisorType", SearchCriteria.Op.LIKE, "%" + hypervisor + "%");
         }
 
         if (hypervisorVersion != null) {
-            sc.addAnd("hypervisorVersion", SearchCriteria.Op.EQ, hypervisorVersion);
+            sc.addAnd("hypervisorVersion", SearchCriteria.Op.LIKE, "%" + hypervisorVersion + "%");
+        }
+
+        if (osDisplayName != null) {
+            List<GuestOSVO> guestOSVOS = _guestOSDao.listLikeDisplayName(osDisplayName);
+            if (CollectionUtils.isNotEmpty(guestOSVOS)) {
+                List<Long> guestOSids = guestOSVOS.stream().map(mo -> mo.getId()).collect(Collectors.toList());
+                sc.addAnd(guestOsId, SearchCriteria.Op.IN, guestOSids.toArray());
+            }
         }
 
         final Pair<List<GuestOSHypervisorVO>, Integer> result = _guestOSHypervisorDao.searchAndCount(sc, searchFilter);
@@ -2721,7 +2786,7 @@
         final String osNameForHypervisor = cmd.getOsNameForHypervisor();
         GuestOS guestOs = null;
 
-        if (osTypeId == null && (osStdName == null || osStdName.isEmpty())) {
+        if (osTypeId == null && StringUtils.isEmpty(osStdName)) {
             throw new InvalidParameterValueException("Please specify either a guest OS name or UUID");
         }
 
@@ -2750,9 +2815,28 @@
         final GuestOSHypervisorVO duplicate = _guestOSHypervisorDao.findByOsIdAndHypervisorAndUserDefined(guestOs.getId(), hypervisorType.toString(), hypervisorVersion, true);
 
         if (duplicate != null) {
-            throw new InvalidParameterValueException(
-                    "Mapping from hypervisor : " + hypervisorType.toString() + ", version : " + hypervisorVersion + " and guest OS : " + guestOs.getDisplayName() + " already exists!");
+            if (!cmd.isForced()) {
+                throw new InvalidParameterValueException(
+                        "Mapping from hypervisor : " + hypervisorType.toString() + ", version : " + hypervisorVersion + " and guest OS : " + guestOs.getDisplayName() + " already exists!");
+            }
+
+            if (Boolean.TRUE.equals(cmd.getOsMappingCheckEnabled())) {
+                checkGuestOSHypervisorMapping(hypervisorType, hypervisorVersion, guestOs.getDisplayName(), osNameForHypervisor);
+            }
+
+            final long guestOsId = duplicate.getId();
+            final GuestOSHypervisorVO guestOsHypervisor = _guestOSHypervisorDao.createForUpdate(guestOsId);
+            guestOsHypervisor.setGuestOsName(osNameForHypervisor);
+            if (_guestOSHypervisorDao.update(guestOsId, guestOsHypervisor)) {
+                return _guestOSHypervisorDao.findById(guestOsId);
+            }
+            return null;
         }
+
+        if (Boolean.TRUE.equals(cmd.getOsMappingCheckEnabled())) {
+            checkGuestOSHypervisorMapping(hypervisorType, hypervisorVersion, guestOs.getDisplayName(), osNameForHypervisor);
+        }
+
         final GuestOSHypervisorVO guestOsMapping = new GuestOSHypervisorVO();
         guestOsMapping.setGuestOsId(guestOs.getId());
         guestOsMapping.setGuestOsName(osNameForHypervisor);
@@ -2760,7 +2844,24 @@
         guestOsMapping.setHypervisorVersion(hypervisorVersion);
         guestOsMapping.setIsUserDefined(true);
         return _guestOSHypervisorDao.persist(guestOsMapping);
+    }
 
+    private void checkGuestOSHypervisorMapping(HypervisorType hypervisorType, String hypervisorVersion, String guestOsName, String guestOsNameForHypervisor) {
+        if (!canCheckGuestOsNameInHypervisor(hypervisorType)) {
+            throw new InvalidParameterValueException(String.format("Guest OS mapping check is not supported for hypervisor: %s, please specify a valid hypervisor : VMware, XenServer", hypervisorType.toString()));
+        }
+        final HostVO host = _hostDao.findHostByHypervisorTypeAndVersion(hypervisorType, hypervisorVersion);
+        if (host == null) {
+            throw new CloudRuntimeException(String.format("No %s hypervisor with version: %s exists, please specify available hypervisor and version", hypervisorType.toString(), hypervisorVersion));
+        }
+        CheckGuestOsMappingAnswer answer = (CheckGuestOsMappingAnswer) _agentMgr.easySend(host.getId(), new CheckGuestOsMappingCommand(guestOsName, guestOsNameForHypervisor, hypervisorVersion));
+        if (answer == null || !answer.getResult()) {
+            throw new CloudRuntimeException(String.format("Invalid hypervisor os mapping: %s for guest os: %s, hypervisor: %s and version: %s", guestOsNameForHypervisor, guestOsName, hypervisorType.toString(), hypervisorVersion));
+        }
+    }
+
+    private boolean canCheckGuestOsNameInHypervisor(HypervisorType hypervisorType) {
+        return (hypervisorType == HypervisorType.VMware || hypervisorType == HypervisorType.XenServer);
     }
 
     @Override
@@ -2770,6 +2871,25 @@
     }
 
     @Override
+    @ActionEvent(eventType = EventTypes.EVENT_GUEST_OS_HYPERVISOR_NAME_FETCH, eventDescription = "Getting guest OS names from hypervisor", async = true)
+    public List<Pair<String, String>> getHypervisorGuestOsNames(GetHypervisorGuestOsNamesCmd getHypervisorGuestOsNamesCmd) {
+        final HypervisorType hypervisorType = HypervisorType.getType(getHypervisorGuestOsNamesCmd.getHypervisor());
+        if (!canCheckGuestOsNameInHypervisor(hypervisorType)) {
+            throw new InvalidParameterValueException(String.format("Guest OS names cannot be fetched for hypervisor: %s, please specify a valid hypervisor : VMware, XenServer", hypervisorType.toString()));
+        }
+
+        final HostVO host = _hostDao.findHostByHypervisorTypeAndVersion(hypervisorType, getHypervisorGuestOsNamesCmd.getHypervisorVersion());
+        if (host == null) {
+            throw new CloudRuntimeException(String.format("No %s hypervisor with version: %s exists, please specify available hypervisor and version", hypervisorType.toString(), getHypervisorGuestOsNamesCmd.getHypervisorVersion()));
+        }
+        GetHypervisorGuestOsNamesAnswer answer = (GetHypervisorGuestOsNamesAnswer) _agentMgr.easySend(host.getId(), new GetHypervisorGuestOsNamesCommand(getHypervisorGuestOsNamesCmd.getKeyword()));
+        if (answer == null || !answer.getResult()) {
+            throw new CloudRuntimeException(String.format("Unable to get guest os names for hypervisor: %s, version: %s", hypervisorType.toString(), getHypervisorGuestOsNamesCmd.getHypervisorVersion()));
+        }
+        return answer.getHypervisorGuestOsNames();
+    }
+
+    @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_GUEST_OS_ADD, eventDescription = "Adding new guest OS type", create = true)
     public GuestOS addGuestOs(final AddGuestOsCmd cmd) {
@@ -2890,6 +3010,14 @@
             throw new InvalidParameterValueException("Unable to modify system defined Guest OS mapping");
         }
 
+        if (Boolean.TRUE.equals(cmd.getOsMappingCheckEnabled())) {
+            GuestOS guestOs = ApiDBUtils.findGuestOSById(guestOsHypervisorHandle.getGuestOsId());
+            if (guestOs == null) {
+                throw new InvalidParameterValueException("Unable to find the guest OS for the mapping");
+            }
+            checkGuestOSHypervisorMapping(HypervisorType.getType(guestOsHypervisorHandle.getHypervisorType()), guestOsHypervisorHandle.getHypervisorVersion(), guestOs.getDisplayName(), osNameForHypervisor);
+        }
+
         final GuestOSHypervisorVO guestOsHypervisor = _guestOSHypervisorDao.createForUpdate(id);
         guestOsHypervisor.setGuestOsName(osNameForHypervisor);
         if (_guestOSHypervisorDao.update(id, guestOsHypervisor)) {
@@ -3290,6 +3418,7 @@
         cmdList.add(ListDomainsCmd.class);
         cmdList.add(ListDomainsCmdByAdmin.class);
         cmdList.add(UpdateDomainCmd.class);
+        cmdList.add(MoveDomainCmd.class);
         cmdList.add(AddHostCmd.class);
         cmdList.add(AddSecondaryStorageCmd.class);
         cmdList.add(CancelMaintenanceCmd.class);
@@ -3340,6 +3469,7 @@
         cmdList.add(RemoveRegionCmd.class);
         cmdList.add(UpdateRegionCmd.class);
         cmdList.add(ListAlertsCmd.class);
+        cmdList.add(ListAlertTypesCmd.class);
         cmdList.add(ListCapacityCmd.class);
         cmdList.add(UpdatePodManagementNetworkIpRangeCmd.class);
         cmdList.add(UploadCustomCertificateCmd.class);
@@ -3466,6 +3596,7 @@
         cmdList.add(UpdateGuestOsMappingCmd.class);
         cmdList.add(RemoveGuestOsCmd.class);
         cmdList.add(RemoveGuestOsMappingCmd.class);
+        cmdList.add(GetHypervisorGuestOsNamesCmd.class);
         cmdList.add(AttachIsoCmd.class);
         cmdList.add(CopyIsoCmd.class);
         cmdList.add(DeleteIsoCmd.class);
@@ -3536,6 +3667,7 @@
         cmdList.add(UpdateSecurityGroupCmd.class);
         cmdList.add(CreateSnapshotCmd.class);
         cmdList.add(CreateSnapshotFromVMSnapshotCmd.class);
+        cmdList.add(CopySnapshotCmd.class);
         cmdList.add(DeleteSnapshotCmd.class);
         cmdList.add(ArchiveSnapshotCmd.class);
         cmdList.add(CreateSnapshotPolicyCmd.class);
@@ -3584,6 +3716,7 @@
         cmdList.add(ListVMGroupsCmd.class);
         cmdList.add(UpdateVMGroupCmd.class);
         cmdList.add(AttachVolumeCmd.class);
+        cmdList.add(CheckAndRepairVolumeCmd.class);
         cmdList.add(CreateVolumeCmd.class);
         cmdList.add(DeleteVolumeCmd.class);
         cmdList.add(UpdateVolumeCmd.class);
@@ -3701,6 +3834,9 @@
         cmdList.add(UpdateRemoteAccessVpnCmd.class);
         cmdList.add(UpdateVpnConnectionCmd.class);
         cmdList.add(UpdateVpnGatewayCmd.class);
+        cmdList.add(ListQuarantinedIpsCmd.class);
+        cmdList.add(UpdateQuarantinedIpCmd.class);
+        cmdList.add(RemoveQuarantinedIpCmd.class);
         // separated admin commands
         cmdList.add(ListAccountsCmdByAdmin.class);
         cmdList.add(ListZonesCmdByAdmin.class);
@@ -3709,6 +3845,7 @@
         cmdList.add(CopyTemplateCmdByAdmin.class);
         cmdList.add(RegisterTemplateCmdByAdmin.class);
         cmdList.add(ListTemplatePermissionsCmdByAdmin.class);
+        cmdList.add(ListSnapshotsCmdByAdmin.class);
         cmdList.add(RegisterIsoCmdByAdmin.class);
         cmdList.add(CopyIsoCmdByAdmin.class);
         cmdList.add(ListIsosCmdByAdmin.class);
@@ -3752,6 +3889,7 @@
         cmdList.add(ListVPCsCmdByAdmin.class);
         cmdList.add(UpdateVPCCmdByAdmin.class);
         cmdList.add(CreatePrivateGatewayByAdminCmd.class);
+        cmdList.add(ListPrivateGatewaysCmdByAdminCmd.class);
         cmdList.add(UpdateLBStickinessPolicyCmd.class);
         cmdList.add(UpdateLBHealthCheckPolicyCmd.class);
         cmdList.add(GetUploadParamsForTemplateCmd.class);
@@ -3771,12 +3909,18 @@
         cmdList.add(GetRouterHealthCheckResultsCmd.class);
         cmdList.add(StartRollingMaintenanceCmd.class);
         cmdList.add(MigrateSecondaryStorageDataCmd.class);
+        cmdList.add(MigrateResourcesToAnotherSecondaryStorageCmd.class);
         cmdList.add(UploadResourceIconCmd.class);
         cmdList.add(DeleteResourceIconCmd.class);
         cmdList.add(ListResourceIconCmd.class);
         cmdList.add(PatchSystemVMCmd.class);
         cmdList.add(ListGuestVlansCmd.class);
         cmdList.add(AssignVolumeCmd.class);
+        cmdList.add(ListSecondaryStorageSelectorsCmd.class);
+        cmdList.add(CreateSecondaryStorageSelectorCmd.class);
+        cmdList.add(UpdateSecondaryStorageSelectorCmd.class);
+        cmdList.add(RemoveSecondaryStorageSelectorCmd.class);
+
 
         // Out-of-band management APIs for admins
         cmdList.add(EnableOutOfBandManagementForHostCmd.class);
@@ -3796,6 +3940,16 @@
         cmdList.add(DeleteUserDataCmd.class);
         cmdList.add(ListUserDataCmd.class);
         cmdList.add(LinkUserDataToTemplateCmd.class);
+
+        //object store APIs
+        cmdList.add(AddObjectStoragePoolCmd.class);
+        cmdList.add(ListObjectStoragePoolsCmd.class);
+        cmdList.add(UpdateObjectStoragePoolCmd.class);
+        cmdList.add(DeleteObjectStoragePoolCmd.class);
+        cmdList.add(CreateBucketCmd.class);
+        cmdList.add(UpdateBucketCmd.class);
+        cmdList.add(DeleteBucketCmd.class);
+        cmdList.add(ListBucketsCmd.class);
         return cmdList;
     }
 
@@ -4245,6 +4399,7 @@
         capabilities.put("allowUserViewAllDomainAccounts", allowUserViewAllDomainAccounts);
         capabilities.put("kubernetesServiceEnabled", kubernetesServiceEnabled);
         capabilities.put("kubernetesClusterExperimentalFeaturesEnabled", kubernetesClusterExperimentalFeaturesEnabled);
+        capabilities.put("customHypervisorDisplayName", HypervisorGuru.HypervisorCustomDisplayName.value());
         capabilities.put(ApiServiceConfiguration.DefaultUIPageSize.key(), ApiServiceConfiguration.DefaultUIPageSize.value());
         capabilities.put(ApiConstants.INSTANCES_STATS_RETENTION_TIME, StatsCollector.vmStatsMaxRetentionTime.value());
         capabilities.put(ApiConstants.INSTANCES_STATS_USER_ONLY, StatsCollector.vmStatsCollectUserVMOnly.value());
@@ -4376,7 +4531,7 @@
             } else {
                 final List<ClusterVO> clustersForZone = _clusterDao.listByZoneId(zoneId);
                 for (final ClusterVO cluster : clustersForZone) {
-                    result.add(cluster.getHypervisorType().toString());
+                    result.add(cluster.getHypervisorType().getHypervisorDisplayName());
                 }
             }
 
@@ -4609,58 +4764,11 @@
         String userdata = cmd.getUserData();
         final String params = cmd.getParams();
 
-        userdata = validateUserData(userdata, cmd.getHttpMethod());
+        userdata = userDataManager.validateUserData(userdata, cmd.getHttpMethod());
 
         return createAndSaveUserData(name, userdata, params, owner);
     }
 
-    private String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod) {
-        byte[] decodedUserData = null;
-        if (userData != null) {
-
-            if (userData.contains("%")) {
-                try {
-                    userData = URLDecoder.decode(userData, "UTF-8");
-                } catch (UnsupportedEncodingException e) {
-                    throw new InvalidParameterValueException("Url decoding of userdata failed.");
-                }
-            }
-
-            if (!Base64.isBase64(userData)) {
-                throw new InvalidParameterValueException("User data is not base64 encoded");
-            }
-            // If GET, use 4K. If POST, support up to 1M.
-            if (httpmethod.equals(BaseCmd.HTTPMethod.GET)) {
-                decodedUserData = validateAndDecodeByHTTPmethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
-            } else if (httpmethod.equals(BaseCmd.HTTPMethod.POST)) {
-                decodedUserData = validateAndDecodeByHTTPmethod(userData, MAX_HTTP_POST_LENGTH, BaseCmd.HTTPMethod.POST);
-            }
-
-            if (decodedUserData == null || decodedUserData.length < 1) {
-                throw new InvalidParameterValueException("User data is too short");
-            }
-            // Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
-            return Base64.encodeBase64String(decodedUserData);
-        }
-        return null;
-    }
-
-    private byte[] validateAndDecodeByHTTPmethod(String userData, int maxHTTPlength, BaseCmd.HTTPMethod httpMethod) {
-        byte[] decodedUserData = null;
-
-        if (userData.length() >= maxHTTPlength) {
-            throw new InvalidParameterValueException(String.format("User data is too long for an http %s request", httpMethod.toString()));
-        }
-        if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) {
-            throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value());
-        }
-        decodedUserData = Base64.decodeBase64(userData.getBytes());
-        if (decodedUserData.length > maxHTTPlength) {
-            throw new InvalidParameterValueException(String.format("User data is too long for http %s request", httpMethod.toString()));
-        }
-        return decodedUserData;
-    }
-
     /**
      * @param cmd
      * @param owner
diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java
index 1982009..2467416 100644
--- a/server/src/main/java/com/cloud/server/StatsCollector.java
+++ b/server/src/main/java/com/cloud/server/StatsCollector.java
@@ -114,6 +114,9 @@
 import com.cloud.resource.ResourceManager;
 import com.cloud.resource.ResourceState;
 import com.cloud.serializer.GsonHelper;
+import com.cloud.server.StatsCollector.AbstractStatsCollector;
+import com.cloud.server.StatsCollector.AutoScaleMonitor;
+import com.cloud.server.StatsCollector.StorageCollector;
 import com.cloud.storage.ImageStoreDetailsUtil;
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.Storage;
@@ -1618,7 +1621,8 @@
                 for (StoragePoolVO pool : pools) {
                     List<VolumeVO> volumes = _volsDao.findByPoolId(pool.getId(), null);
                     for (VolumeVO volume : volumes) {
-                        if (volume.getFormat() != ImageFormat.QCOW2 && volume.getFormat() != ImageFormat.VHD && volume.getFormat() != ImageFormat.OVA && (volume.getFormat() != ImageFormat.RAW || pool.getPoolType() != Storage.StoragePoolType.PowerFlex)) {
+                        if (!List.of(ImageFormat.QCOW2, ImageFormat.VHD, ImageFormat.OVA, ImageFormat.RAW).contains(volume.getFormat()) &&
+                            !List.of(Storage.StoragePoolType.PowerFlex, Storage.StoragePoolType.FiberChannel).contains(pool.getPoolType())) {
                             LOGGER.warn("Volume stats not implemented for this format type " + volume.getFormat());
                             break;
                         }
@@ -1708,17 +1712,21 @@
                             storagePoolStats.put(pool.getId(), (StorageStats)answer);
 
                             boolean poolNeedsUpdating = false;
+                            long capacityBytes = ((StorageStats)answer).getCapacityBytes();
+                            long usedBytes = ((StorageStats)answer).getByteUsed();
                             // Seems like we have dynamically updated the pool size since the prev. size and the current do not match
-                            if (_storagePoolStats.get(poolId) != null && _storagePoolStats.get(poolId).getCapacityBytes() != ((StorageStats)answer).getCapacityBytes()) {
-                                if (((StorageStats)answer).getCapacityBytes() > 0) {
-                                    pool.setCapacityBytes(((StorageStats)answer).getCapacityBytes());
+                            if ((_storagePoolStats.get(poolId) != null && _storagePoolStats.get(poolId).getCapacityBytes() != capacityBytes)
+                                    || pool.getCapacityBytes() != capacityBytes) {
+                                if (capacityBytes > 0) {
+                                    pool.setCapacityBytes(capacityBytes);
                                     poolNeedsUpdating = true;
                                 } else {
                                     LOGGER.warn("Not setting capacity bytes, received " + ((StorageStats)answer).getCapacityBytes()  + " capacity for pool ID " + poolId);
                                 }
                             }
-                            if (pool.getUsedBytes() != ((StorageStats)answer).getByteUsed() && (pool.getStorageProviderName().equalsIgnoreCase(DataStoreProvider.DEFAULT_PRIMARY) || _storageManager.canPoolProvideStorageStats(pool))) {
-                                pool.setUsedBytes(((StorageStats) answer).getByteUsed());
+                            if (((_storagePoolStats.get(poolId) != null && _storagePoolStats.get(poolId).getByteUsed() != usedBytes)
+                                    || pool.getUsedBytes() != usedBytes) && (pool.getStorageProviderName().equalsIgnoreCase(DataStoreProvider.DEFAULT_PRIMARY) || _storageManager.canPoolProvideStorageStats(pool))) {
+                                pool.setUsedBytes(usedBytes);
                                 poolNeedsUpdating = true;
                             }
                             if (poolNeedsUpdating) {
diff --git a/server/src/main/java/com/cloud/storage/CheckAndRepairVolumePayload.java b/server/src/main/java/com/cloud/storage/CheckAndRepairVolumePayload.java
new file mode 100644
index 0000000..eabe1a4
--- /dev/null
+++ b/server/src/main/java/com/cloud/storage/CheckAndRepairVolumePayload.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.storage;
+
+public class CheckAndRepairVolumePayload {
+
+    public final String repair;
+    public String result;
+
+    public CheckAndRepairVolumePayload(String repair) {
+        this.repair = repair;
+    }
+
+    public String getRepair() {
+        return repair;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+}
diff --git a/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java b/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java
index b0bb9ea..868f785 100644
--- a/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java
+++ b/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java
@@ -16,6 +16,8 @@
 // under the License.
 package com.cloud.storage;
 
+import java.util.List;
+
 import com.cloud.user.Account;
 
 public class CreateSnapshotPayload {
@@ -25,6 +27,7 @@
     private boolean quiescevm;
     private Snapshot.LocationType locationType;
     private boolean asyncBackup;
+    private List<Long> zoneIds;
 
     public Long getSnapshotPolicyId() {
         return snapshotPolicyId;
@@ -67,4 +70,12 @@
     public boolean getAsyncBackup() {
         return this.asyncBackup;
     }
+
+    public List<Long> getZoneIds() {
+        return zoneIds;
+    }
+
+    public void setZoneIds(List<Long> zoneIds) {
+        this.zoneIds = zoneIds;
+    }
 }
diff --git a/server/src/main/java/com/cloud/storage/ImageStoreDetailsUtil.java b/server/src/main/java/com/cloud/storage/ImageStoreDetailsUtil.java
index 1a4a246..baf5ef8 100755
--- a/server/src/main/java/com/cloud/storage/ImageStoreDetailsUtil.java
+++ b/server/src/main/java/com/cloud/storage/ImageStoreDetailsUtil.java
@@ -51,7 +51,7 @@
      * Obtain NFS protocol version (if provided) for a store id, if not use default config value<br/>
      * @param storeId image store id
      * @return {@code null} if {@code secstorage.nfs.version} is not found for storeId <br/>
-     * {@code X} if {@code secstorage.nfs.version} is found found for storeId
+     * {@code X} if {@code secstorage.nfs.version} is found for storeId
      */
     public String getNfsVersion(long storeId) throws NumberFormatException {
 
@@ -68,7 +68,7 @@
      * Obtain NFS protocol version (if provided) for a store uuid.<br/>
      * @param storeUuid image store id
      * @return {@code null} if {@code secstorage.nfs.version} is not found for storeUuid <br/>
-     * {@code X} if {@code secstorage.nfs.version} is found found for storeUuid
+     * {@code X} if {@code secstorage.nfs.version} is found for storeUuid
      */
     public String getNfsVersionByUuid(String storeUuid){
         ImageStoreVO imageStore = imageStoreDao.findByUuid(storeUuid);
diff --git a/server/src/main/java/com/cloud/storage/ImageStoreServiceImpl.java b/server/src/main/java/com/cloud/storage/ImageStoreServiceImpl.java
index 43f9cd4..a92b75e 100644
--- a/server/src/main/java/com/cloud/storage/ImageStoreServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/ImageStoreServiceImpl.java
@@ -20,10 +20,13 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.utils.db.UUIDManager;
+import org.apache.cloudstack.api.command.admin.storage.MigrateResourcesToAnotherSecondaryStorageCmd;
 import org.apache.cloudstack.api.command.admin.storage.MigrateSecondaryStorageDataCmd;
 import org.apache.cloudstack.api.response.MigrationResponse;
 import org.apache.cloudstack.context.CallContext;
@@ -53,6 +56,9 @@
     @Inject
     private StorageOrchestrationService stgService;
 
+    @Inject
+    public UUIDManager uuidMgr;
+
     ConfigKey<Double> ImageStoreImbalanceThreshold = new ConfigKey<>("Advanced", Double.class,
             "image.store.imbalance.threshold",
             "0.3",
@@ -147,10 +153,67 @@
             return new MigrationResponse(message, policy.toString(), false);
         }
 
-        CallContext.current().setEventDetails("Migrating files/data objects " + "from : " + imagestores.get(0) + " to: " + imagestores.subList(1, imagestores.size()));
+        CallContext.current().setEventDetails("Migrating files/data objects from : " + imagestores.get(0) + " to: " + imagestores.subList(1, imagestores.size()));
         return  stgService.migrateData(srcImgStoreId, destDatastores, policy);
     }
 
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_IMAGE_STORE_RESOURCES_MIGRATE, eventDescription = "migrating Image store resources to another image store", async = true)
+    public MigrationResponse migrateResources(MigrateResourcesToAnotherSecondaryStorageCmd cmd) {
+        if (isMigrateJobRunning()){
+            return new MigrationResponse("A migrate job is in progress, please try again later.", null, false);
+        }
+
+        Long srcImgStoreId = cmd.getId();
+        Long destImgStoreId = cmd.getDestStoreId();
+
+        if (srcImgStoreId.equals(destImgStoreId)) {
+            throw new InvalidParameterValueException("Source and destination image stores cannot be same");
+        }
+
+        ImageStoreVO srcImageStore = imageStoreDao.findById(srcImgStoreId);
+        ImageStoreVO destImageStore = imageStoreDao.findById(destImgStoreId);
+
+        if (srcImageStore == null) {
+            throw new CloudRuntimeException("Cannot find secondary storage with id: " + srcImgStoreId);
+        }
+        if (destImageStore == null) {
+            throw new CloudRuntimeException("Cannot find secondary storage with id: " + srcImgStoreId);
+        }
+
+        if (srcImageStore.getRole() != DataStoreRole.Image) {
+            throw new CloudRuntimeException("Source Secondary storage is not of Image Role");
+        }
+
+        if (destImageStore.getRole() != DataStoreRole.Image) {
+            throw new CloudRuntimeException("Destination Secondary storage is not of Image Role");
+        }
+
+        if (!srcImageStore.getProviderName().equals(DataStoreProvider.NFS_IMAGE) || !destImageStore.getProviderName().equals(DataStoreProvider.NFS_IMAGE)) {
+            throw new InvalidParameterValueException("Migration of datastore objects is supported only for NFS based image stores");
+        }
+
+        if (destImageStore.isReadonly()) {
+            throw new InvalidParameterValueException("Destination image store is read-only. Cannot migrate resources to it");
+        }
+
+        if (srcImageStore.getDataCenterId() != null && destImageStore.getDataCenterId() != null && !srcImageStore.getDataCenterId().equals(destImageStore.getDataCenterId())) {
+            throw new InvalidParameterValueException("Source and destination stores are not in the same zone.");
+        }
+
+        List<Long> templateIdList = cmd.getTemplateIdList();
+        List<Long> snapshotIdList = cmd.getSnapshotIdList();
+        List<String> templateUuidList = templateIdList.stream().map((id) -> uuidMgr.getUuid(VMTemplateVO.class, id)).collect(Collectors.toList());
+        List<String> snapshotUuidList = snapshotIdList.stream().map((id) -> uuidMgr.getUuid(SnapshotVO.class, id)).collect(Collectors.toList());
+        CallContext.current().setEventDetails(
+                "Migrating templates (" + String.join(", ", templateUuidList) +
+                        ") and snapshots (" + String.join(", ", snapshotUuidList) +
+                        ") from : " + srcImageStore.getName() + " to: " + destImageStore.getName()
+        );
+
+        return stgService.migrateResources(srcImgStoreId, destImgStoreId, templateIdList, snapshotIdList);
+    }
+
 
     // Ensures that only one migrate job may occur at a time, in order to reduce load
     private boolean isMigrateJobRunning() {
diff --git a/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java b/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java
index 9e4c3ec..84dcd30 100644
--- a/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java
+++ b/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java
@@ -21,6 +21,7 @@
     public final Long newSize;
     public final Long newMinIops;
     public final Long newMaxIops;
+    public Long newDiskOfferingId;
     public final Integer newHypervisorSnapshotReserve;
     public final boolean shrinkOk;
     public final String instanceName;
@@ -37,5 +38,12 @@
         this.instanceName = instanceName;
         this.hosts = hosts;
         this.isManaged = isManaged;
+        this.newDiskOfferingId = null;
+    }
+
+    public ResizeVolumePayload(Long newSize, Long newMinIops, Long newMaxIops, Long newDiskOfferingId, Integer newHypervisorSnapshotReserve, boolean shrinkOk,
+            String instanceName, long[] hosts, boolean isManaged) {
+        this(newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk, instanceName, hosts, isManaged);
+        this.newDiskOfferingId = newDiskOfferingId;
     }
 }
diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
index c6379a7..3f5054b 100644
--- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
@@ -18,15 +18,18 @@
 
 import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
 
+import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URLDecoder;
 import java.net.UnknownHostException;
 import java.nio.file.Files;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -54,10 +57,15 @@
 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;
+import org.apache.cloudstack.api.command.admin.storage.DeleteObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.DeletePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.DeleteSecondaryStagingStoreCmd;
 import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
+import org.apache.cloudstack.api.command.admin.storage.UpdateObjectStoragePoolCmd;
 import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.CreateSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.RemoveSecondaryStorageSelectorCmd;
+import org.apache.cloudstack.api.command.admin.storage.heuristics.UpdateSecondaryStorageSelectorCmd;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
@@ -96,13 +104,22 @@
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.cloudstack.management.ManagementServerHost;
 import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
+import org.apache.cloudstack.secstorage.HeuristicVO;
+import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao;
+import org.apache.cloudstack.secstorage.heuristics.Heuristic;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
 import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
 import org.apache.cloudstack.storage.command.DettachCommand;
 import org.apache.cloudstack.storage.command.SyncVolumePathAnswer;
 import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadVO;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
@@ -114,8 +131,13 @@
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
 import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
+import org.apache.cloudstack.storage.object.ObjectStore;
+import org.apache.cloudstack.storage.object.ObjectStoreEntity;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.commons.lang3.EnumUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
@@ -190,6 +212,7 @@
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.Volume.Type;
+import com.cloud.storage.dao.BucketDao;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.StoragePoolHostDao;
@@ -237,6 +260,7 @@
 import com.cloud.vm.dao.VMInstanceDao;
 import com.google.common.collect.Sets;
 
+
 @Component
 public class StorageManagerImpl extends ManagerBase implements StorageManager, ClusterManagerListener, Configurable {
     private static final Logger s_logger = Logger.getLogger(StorageManagerImpl.class);
@@ -251,8 +275,6 @@
     @Inject
     protected ConfigurationManager _configMgr;
     @Inject
-    protected VolumeDao _volsDao;
-    @Inject
     private VolumeDataStoreDao _volumeDataStoreDao;
     @Inject
     protected HostDao _hostDao;
@@ -277,6 +299,8 @@
     @Inject
     protected ImageStoreDetailsDao _imageStoreDetailsDao = null;
     @Inject
+    protected ImageStoreObjectDownloadDao _imageStoreObjectDownloadDao = null;
+    @Inject
     protected SnapshotDataStoreDao _snapshotStoreDao = null;
     @Inject
     protected TemplateDataStoreDao _templateStoreDao = null;
@@ -301,7 +325,7 @@
     @Inject
     protected HypervisorGuruManager _hvGuruMgr;
     @Inject
-    protected VolumeDao _volumeDao;
+    protected VolumeDao volumeDao;
     @Inject
     ConfigurationDao _configDao;
     @Inject
@@ -348,8 +372,19 @@
     private AnnotationDao annotationDao;
 
     @Inject
+    private SecondaryStorageHeuristicDao secondaryStorageHeuristicDao;
+
+    @Inject
     protected UserVmManager userVmManager;
     @Inject
+    protected ObjectStoreDao _objectStoreDao;
+
+    @Inject
+    protected ObjectStoreDetailsDao _objectStoreDetailsDao;
+
+    @Inject
+    protected BucketDao _bucketDao;
+    @Inject
     ConfigDepot configDepot;
     @Inject
     ConfigurationDao configurationDao;
@@ -379,7 +414,7 @@
     public boolean share(VMInstanceVO vm, List<VolumeVO> vols, HostVO host, boolean cancelPreviousShare) throws StorageUnavailableException {
 
         // if pool is in maintenance and it is the ONLY pool available; reject
-        List<VolumeVO> rootVolForGivenVm = _volsDao.findByInstanceAndType(vm.getId(), Type.ROOT);
+        List<VolumeVO> rootVolForGivenVm = volumeDao.findByInstanceAndType(vm.getId(), Type.ROOT);
         if (rootVolForGivenVm != null && rootVolForGivenVm.size() > 0) {
             boolean isPoolAvailable = isPoolAvailable(rootVolForGivenVm.get(0).getPoolId());
             if (!isPoolAvailable) {
@@ -460,7 +495,7 @@
         for (StoragePoolHostVO storagePoolHostRef : storagePoolHostRefs) {
             StoragePoolVO PrimaryDataStoreVO = _storagePoolDao.findById(storagePoolHostRef.getPoolId());
             if (PrimaryDataStoreVO.getPoolType() == StoragePoolType.LVM || PrimaryDataStoreVO.getPoolType() == StoragePoolType.EXT) {
-                SearchBuilder<VolumeVO> volumeSB = _volsDao.createSearchBuilder();
+                SearchBuilder<VolumeVO> volumeSB = volumeDao.createSearchBuilder();
                 volumeSB.and("poolId", volumeSB.entity().getPoolId(), SearchCriteria.Op.EQ);
                 volumeSB.and("removed", volumeSB.entity().getRemoved(), SearchCriteria.Op.NULL);
                 volumeSB.and("state", volumeSB.entity().getState(), SearchCriteria.Op.NIN);
@@ -474,7 +509,7 @@
                 volumeSC.setParameters("state", Volume.State.Expunging, Volume.State.Destroy);
                 volumeSC.setJoinParameters("activeVmSB", "state", State.Starting, State.Running, State.Stopping, State.Migrating);
 
-                List<VolumeVO> volumes = _volsDao.search(volumeSC, null);
+                List<VolumeVO> volumes = volumeDao.search(volumeSC, null);
                 if (volumes.size() > 0) {
                     return true;
                 }
@@ -622,7 +657,7 @@
 
         StoragePoolSearch = _vmInstanceDao.createSearchBuilder();
 
-        SearchBuilder<VolumeVO> volumeSearch = _volumeDao.createSearchBuilder();
+        SearchBuilder<VolumeVO> volumeSearch = volumeDao.createSearchBuilder();
         volumeSearch.and("volumeType", volumeSearch.entity().getVolumeType(), SearchCriteria.Op.EQ);
         volumeSearch.and("poolId", volumeSearch.entity().getPoolId(), SearchCriteria.Op.EQ);
         volumeSearch.and("state", volumeSearch.entity().getState(), SearchCriteria.Op.EQ);
@@ -674,6 +709,50 @@
         return true;
     }
 
+    protected String getValidatedPareForLocalStorage(Object obj, String paramName) {
+        String result = obj == null ? null : obj.toString();
+        if (StringUtils.isEmpty(result)) {
+            throw new InvalidParameterValueException(String.format("Invalid %s provided", paramName));
+        }
+        return result;
+    }
+
+    protected DataStore createLocalStorage(Map<String, Object> poolInfos) throws ConnectionException{
+        Object existingUuid = poolInfos.get("uuid");
+        if( existingUuid == null ){
+            poolInfos.put("uuid", UUID.randomUUID().toString());
+        }
+        String hostAddress = getValidatedPareForLocalStorage(poolInfos.get("host"), "host");
+        String hostPath = getValidatedPareForLocalStorage(poolInfos.get("hostPath"), "path");
+        Host host = _hostDao.findByName(hostAddress);
+
+        if( host == null ) {
+            host = _hostDao.findByIp(hostAddress);
+
+            if( host == null ) {
+                host = _hostDao.findByPublicIp(hostAddress);
+
+                if( host == null ) {
+                    throw new InvalidParameterValueException(String.format("host %s not found",hostAddress));
+                }
+             }
+         }
+
+        long capacityBytes = poolInfos.get("capacityBytes") != null ? Long.parseLong(poolInfos.get("capacityBytes").toString()) : 0;
+
+        StoragePoolInfo pInfo = new StoragePoolInfo(poolInfos.get("uuid").toString(),
+                                                    host.getPrivateIpAddress(),
+                                                    hostPath,
+                                                    hostPath,
+                                                    StoragePoolType.Filesystem,
+                                                    capacityBytes,
+                                                    0,
+                                                    (Map<String,String>)poolInfos.get("details"),
+                                                    poolInfos.get("name").toString());
+
+        return createLocalStorage(host, pInfo);
+    }
+
     @DB
     @Override
     public DataStore createLocalStorage(Host host, StoragePoolInfo pInfo) throws ConnectionException {
@@ -719,17 +798,19 @@
             DataStoreLifeCycle lifeCycle = provider.getDataStoreLifeCycle();
             if (pool == null) {
                 Map<String, Object> params = new HashMap<String, Object>();
-                String name = createLocalStoragePoolName(host, pInfo);
+                String name = pInfo.getName() != null ? pInfo.getName() : createLocalStoragePoolName(host, pInfo);
                 params.put("zoneId", host.getDataCenterId());
                 params.put("clusterId", host.getClusterId());
                 params.put("podId", host.getPodId());
                 params.put("hypervisorType", host.getHypervisorType());
-                params.put("url", pInfo.getPoolType().toString() + "://" + pInfo.getHost() + "/" + pInfo.getHostPath());
                 params.put("name", name);
                 params.put("localStorage", true);
                 params.put("details", pInfo.getDetails());
                 params.put("uuid", pInfo.getUuid());
                 params.put("providerName", provider.getName());
+                params.put("scheme", pInfo.getPoolType().toString());
+                params.put("host", pInfo.getHost());
+                params.put("hostPath", pInfo.getHostPath());
 
                 store = lifeCycle.initialize(params);
             } else {
@@ -761,6 +842,8 @@
     @Override
     public PrimaryDataStoreInfo createPool(CreateStoragePoolCmd cmd) throws ResourceInUseException, IllegalArgumentException, UnknownHostException, ResourceUnavailableException {
         String providerName = cmd.getStorageProviderName();
+        Map<String,String> uriParams = extractUriParamsAsMap(cmd.getUrl());
+        boolean isFileScheme = "file".equals(uriParams.get("scheme"));
         DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName);
 
         if (storeProvider == null) {
@@ -775,6 +858,9 @@
         Long zoneId = cmd.getZoneId();
 
         ScopeType scopeType = ScopeType.CLUSTER;
+        if (isFileScheme) {
+            scopeType = ScopeType.HOST;
+        }
         String scope = cmd.getScope();
         if (scope != null) {
             try {
@@ -834,17 +920,25 @@
         params.put("hypervisorType", hypervisorType);
         params.put("url", cmd.getUrl());
         params.put("tags", cmd.getTags());
+        params.put("isTagARule", cmd.isTagARule());
         params.put("name", cmd.getStoragePoolName());
         params.put("details", details);
         params.put("providerName", storeProvider.getName());
         params.put("managed", cmd.isManaged());
         params.put("capacityBytes", cmd.getCapacityBytes());
         params.put("capacityIops", cmd.getCapacityIops());
+        if (MapUtils.isNotEmpty(uriParams)) {
+            params.putAll(uriParams);
+        }
 
         DataStoreLifeCycle lifeCycle = storeProvider.getDataStoreLifeCycle();
         DataStore store = null;
         try {
-            store = lifeCycle.initialize(params);
+            if (isFileScheme) {
+                store = createLocalStorage(params);
+            } else {
+                store = lifeCycle.initialize(params);
+            }
             if (scopeType == ScopeType.CLUSTER) {
                 ClusterScope clusterScope = new ClusterScope(clusterId, podId, zoneId);
                 lifeCycle.attachCluster(store, clusterScope);
@@ -869,6 +963,77 @@
         return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Primary);
     }
 
+    protected Map<String,String> extractUriParamsAsMap(String url) {
+        Map<String,String> uriParams = new HashMap<>();
+        UriUtils.UriInfo uriInfo;
+        try {
+            uriInfo = UriUtils.getUriInfo(url);
+        } catch (CloudRuntimeException cre) {
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug(String.format("URI validation for url: %s failed, returning empty uri params", url));
+            }
+            return uriParams;
+        }
+
+        String scheme = uriInfo.getScheme();
+        String storageHost = uriInfo.getStorageHost();
+        String storagePath = uriInfo.getStoragePath();
+        if (scheme == null) {
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug(String.format("Scheme for url: %s is not found, returning empty uri params", url));
+            }
+            return uriParams;
+        }
+        boolean isHostOrPathBlank = StringUtils.isAnyBlank(storagePath, storageHost);
+        if (scheme.equalsIgnoreCase("nfs")) {
+            if (isHostOrPathBlank) {
+                throw new InvalidParameterValueException("host or path is null, should be nfs://hostname/path");
+            }
+        } else if (scheme.equalsIgnoreCase("cifs")) {
+            // Don't validate against a URI encoded URI.
+            try {
+                URI cifsUri = new URI(url);
+                String warnMsg = UriUtils.getCifsUriParametersProblems(cifsUri);
+                if (warnMsg != null) {
+                    throw new InvalidParameterValueException(warnMsg);
+                }
+            } catch (URISyntaxException e) {
+                throw new InvalidParameterValueException(url + " is not a valid uri");
+            }
+        } else if (scheme.equalsIgnoreCase("sharedMountPoint")) {
+            if (storagePath == null) {
+                throw new InvalidParameterValueException("host or path is null, should be sharedmountpoint://localhost/path");
+            }
+        } else if (scheme.equalsIgnoreCase("rbd")) {
+            if (storagePath == null) {
+                throw new InvalidParameterValueException("host or path is null, should be rbd://hostname/pool");
+            }
+        } else if (scheme.equalsIgnoreCase("gluster")) {
+            if (isHostOrPathBlank) {
+                throw new InvalidParameterValueException("host or path is null, should be gluster://hostname/volume");
+            }
+        }
+
+        String hostPath = null;
+        try {
+            hostPath = URLDecoder.decode(storagePath, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            s_logger.error("[ignored] we are on a platform not supporting \"UTF-8\"!?!", e);
+        }
+        if (hostPath == null) { // if decoding fails, use getPath() anyway
+            hostPath = storagePath;
+        }
+
+        uriParams.put("scheme", scheme);
+        uriParams.put("host", storageHost);
+        uriParams.put("hostPath", hostPath);
+        uriParams.put("userInfo", uriInfo.getUserInfo());
+        if (uriInfo.getPort() > 0) {
+            uriParams.put("port", uriInfo.getPort() + "");
+        }
+        return uriParams;
+    }
+
     private Map<String, String> extractApiParamAsMap(Map ds) {
         Map<String, String> details = new HashMap<String, String>();
         if (ds != null) {
@@ -936,42 +1101,62 @@
             if (pool.getPoolType() == StoragePoolType.DatastoreCluster) {
                 List<StoragePoolVO> childStoragePools = _storagePoolDao.listChildStoragePoolsInDatastoreCluster(pool.getId());
                 for (StoragePoolVO childPool : childStoragePools) {
-                    _storagePoolTagsDao.persist(childPool.getId(), storagePoolTags);
+                    _storagePoolTagsDao.persist(childPool.getId(), storagePoolTags, cmd.isTagARule());
                 }
             }
-            _storagePoolTagsDao.persist(pool.getId(), storagePoolTags);
+            _storagePoolTagsDao.persist(pool.getId(), storagePoolTags, cmd.isTagARule());
         }
 
+        boolean changes = false;
         Long updatedCapacityBytes = null;
         Long capacityBytes = cmd.getCapacityBytes();
 
         if (capacityBytes != null) {
             if (capacityBytes != pool.getCapacityBytes()) {
                 updatedCapacityBytes = capacityBytes;
+                changes = true;
             }
         }
 
         Long updatedCapacityIops = null;
         Long capacityIops = cmd.getCapacityIops();
-
         if (capacityIops != null) {
             if (!capacityIops.equals(pool.getCapacityIops())) {
                 updatedCapacityIops = capacityIops;
+                changes = true;
             }
         }
 
-        if (updatedCapacityBytes != null || updatedCapacityIops != null) {
+        // retrieve current details and merge/overlay input to capture changes
+        Map<String, String> inputDetails = extractApiParamAsMap(cmd.getDetails());
+        Map<String, String> details = null;
+        if (inputDetails == null) {
+            details = _storagePoolDetailsDao.listDetailsKeyPairs(id);
+        } else {
+            details = _storagePoolDetailsDao.listDetailsKeyPairs(id);
+            details.putAll(inputDetails);
+            changes = true;
+        }
+
+        if (changes) {
             StoragePoolVO storagePool = _storagePoolDao.findById(id);
             DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(storagePool.getStorageProviderName());
             DataStoreLifeCycle dataStoreLifeCycle = dataStoreProvider.getDataStoreLifeCycle();
 
             if (dataStoreLifeCycle instanceof PrimaryDataStoreLifeCycle) {
-                Map<String, String> details = new HashMap<String, String>();
-
-                details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, updatedCapacityBytes != null ? String.valueOf(updatedCapacityBytes) : null);
-                details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, updatedCapacityIops != null ? String.valueOf(updatedCapacityIops) : null);
-
-                ((PrimaryDataStoreLifeCycle)dataStoreLifeCycle).updateStoragePool(storagePool, details);
+                if (updatedCapacityBytes != null) {
+                    details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, updatedCapacityBytes != null ? String.valueOf(updatedCapacityBytes) : null);
+                    _storagePoolDao.updateCapacityBytes(id, updatedCapacityBytes);
+                }
+                if (updatedCapacityIops != null) {
+                    details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, updatedCapacityIops != null ? String.valueOf(updatedCapacityIops) : null);
+                    _storagePoolDao.updateCapacityIops(id, updatedCapacityIops);
+                }
+                if (cmd.getUrl() != null) {
+                    details.put("url", cmd.getUrl());
+                }
+                _storagePoolDao.update(id, storagePool);
+                _storagePoolDao.updateDetails(id, details);
             }
         }
 
@@ -984,14 +1169,6 @@
             }
         }
 
-        if (updatedCapacityBytes != null) {
-            _storagePoolDao.updateCapacityBytes(id, capacityBytes);
-        }
-
-        if (updatedCapacityIops != null) {
-            _storagePoolDao.updateCapacityIops(id, capacityIops);
-        }
-
         return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
     }
 
@@ -1059,10 +1236,10 @@
         List<StoragePoolVO> childStoragePools = _storagePoolDao.listChildStoragePoolsInDatastoreCluster(sPool.getId());
         boolean canDelete = true;
         for (StoragePoolVO childPool : childStoragePools) {
-            Pair<Long, Long> vlms = _volsDao.getCountAndTotalByPool(childPool.getId());
+            Pair<Long, Long> vlms = volumeDao.getCountAndTotalByPool(childPool.getId());
             if (forced) {
                 if (vlms.first() > 0) {
-                    Pair<Long, Long> nonDstrdVlms = _volsDao.getNonDestroyedCountAndTotalByPool(childPool.getId());
+                    Pair<Long, Long> nonDstrdVlms = volumeDao.getNonDestroyedCountAndTotalByPool(childPool.getId());
                     if (nonDstrdVlms.first() > 0) {
                         canDelete = false;
                         break;
@@ -1079,15 +1256,15 @@
     }
 
     private boolean deleteDataStoreInternal(StoragePoolVO sPool, boolean forced) {
-        Pair<Long, Long> vlms = _volsDao.getCountAndTotalByPool(sPool.getId());
+        Pair<Long, Long> vlms = volumeDao.getCountAndTotalByPool(sPool.getId());
         if (forced) {
             if (vlms.first() > 0) {
-                Pair<Long, Long> nonDstrdVlms = _volsDao.getNonDestroyedCountAndTotalByPool(sPool.getId());
+                Pair<Long, Long> nonDstrdVlms = volumeDao.getNonDestroyedCountAndTotalByPool(sPool.getId());
                 if (nonDstrdVlms.first() > 0) {
                     throw new CloudRuntimeException("Cannot delete pool " + sPool.getName() + " as there are associated " + "non-destroyed vols for this pool");
                 }
                 // force expunge non-destroyed volumes
-                List<VolumeVO> vols = _volsDao.listVolumesToBeDestroyed();
+                List<VolumeVO> vols = volumeDao.listVolumesToBeDestroyed();
                 for (VolumeVO vol : vols) {
                     AsyncCallFuture<VolumeApiResult> future = volService.expungeVolumeAsync(volFactory.getVolume(vol.getId()));
                     try {
@@ -1315,42 +1492,69 @@
                             try {
 
                                 List<VMTemplateStoragePoolVO> unusedTemplatesInPool = _tmpltMgr.getUnusedTemplatesInPool(pool);
-                                s_logger.debug("Storage pool garbage collector found " + unusedTemplatesInPool.size() + " templates to clean up in storage pool: " + pool.getName());
+                                s_logger.debug(String.format("Storage pool garbage collector found [%s] templates to be cleaned up in storage pool [%s].", unusedTemplatesInPool.size(), pool.getName()));
                                 for (VMTemplateStoragePoolVO templatePoolVO : unusedTemplatesInPool) {
                                     if (templatePoolVO.getDownloadState() != VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
-                                        s_logger.debug("Storage pool garbage collector is skipping template with ID: " + templatePoolVO.getTemplateId() + " on pool " + templatePoolVO.getPoolId()
-                                        + " because it is not completely downloaded.");
+                                        s_logger.debug(String.format("Storage pool garbage collector is skipping template [%s] clean up on pool [%s] " +
+                                                "because it is not completely downloaded.", templatePoolVO.getTemplateId(), templatePoolVO.getPoolId()));
                                         continue;
                                     }
 
                                     if (!templatePoolVO.getMarkedForGC()) {
                                         templatePoolVO.setMarkedForGC(true);
                                         _vmTemplatePoolDao.update(templatePoolVO.getId(), templatePoolVO);
-                                        s_logger.debug("Storage pool garbage collector has marked template with ID: " + templatePoolVO.getTemplateId() + " on pool " + templatePoolVO.getPoolId()
-                                        + " for garbage collection.");
+                                        s_logger.debug(String.format("Storage pool garbage collector has marked template [%s] on pool [%s] " +
+                                                "for garbage collection.", templatePoolVO.getTemplateId(), templatePoolVO.getPoolId()));
                                         continue;
                                     }
 
                                     _tmpltMgr.evictTemplateFromStoragePool(templatePoolVO);
                                 }
                             } catch (Exception e) {
-                                s_logger.warn("Problem cleaning up primary storage pool " + pool, e);
+                                s_logger.error(String.format("Failed to clean up primary storage pool [%s] due to: [%s].", pool, e.getMessage()));
+                                s_logger.debug(String.format("Failed to clean up primary storage pool [%s].", pool), e);
                             }
                         }
                     }
 
                     //destroy snapshots in destroying state in snapshot_store_ref
                     List<SnapshotDataStoreVO> ssSnapshots = _snapshotStoreDao.listByState(ObjectInDataStoreStateMachine.State.Destroying);
-                    for (SnapshotDataStoreVO ssSnapshotVO : ssSnapshots) {
+                    for (SnapshotDataStoreVO snapshotDataStoreVO : ssSnapshots) {
+                        String snapshotUuid = null;
+                        SnapshotVO snapshot = null;
+                        final String storeRole = snapshotDataStoreVO.getRole().toString().toLowerCase();
+                        if (s_logger.isDebugEnabled()) {
+                            snapshot = _snapshotDao.findById(snapshotDataStoreVO.getSnapshotId());
+                            if (snapshot == null) {
+                                s_logger.warn(String.format("Did not find snapshot [ID: %d] for which store reference is in destroying state; therefore, it cannot be destroyed.", snapshotDataStoreVO.getSnapshotId()));
+                                continue;
+                            }
+                            snapshotUuid = snapshot.getUuid();
+                        }
+
                         try {
-                            _snapshotService.deleteSnapshot(snapshotFactory.getSnapshot(ssSnapshotVO.getSnapshotId(), DataStoreRole.Image));
+                            if (s_logger.isDebugEnabled()) {
+                                s_logger.debug(String.format("Verifying if snapshot [%s] is in destroying state in %s data store ID: %d.", snapshotUuid, storeRole, snapshotDataStoreVO.getDataStoreId()));
+                            }
+                            SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotDataStoreVO.getSnapshotId(), snapshotDataStoreVO.getDataStoreId(), snapshotDataStoreVO.getRole());
+                            if (snapshotInfo != null) {
+                                if (s_logger.isDebugEnabled()) {
+                                    s_logger.debug(String.format("Snapshot [%s] in destroying state found in %s data store [%s]; therefore, it will be destroyed.", snapshotUuid, storeRole, snapshotInfo.getDataStore().getUuid()));
+                                }
+                                _snapshotService.deleteSnapshot(snapshotInfo);
+                            } else if (s_logger.isDebugEnabled()) {
+                                s_logger.debug(String.format("Did not find snapshot [%s] in destroying state in %s data store ID: %d.", snapshotUuid, storeRole, snapshotDataStoreVO.getDataStoreId()));
+                            }
                         } catch (Exception e) {
-                            s_logger.debug("Failed to delete snapshot: " + ssSnapshotVO.getId() + " from storage");
+                            s_logger.error(String.format("Failed to delete snapshot [%s] from storage due to: [%s].", snapshotDataStoreVO.getSnapshotId(), e.getMessage()));
+                            if (s_logger.isDebugEnabled()) {
+                                s_logger.debug(String.format("Failed to delete snapshot [%s] from storage.", snapshotUuid), e);
+                            }
                         }
                     }
                     cleanupSecondaryStorage(recurring);
 
-                    List<VolumeVO> vols = _volsDao.listVolumesToBeDestroyed(new Date(System.currentTimeMillis() - ((long)StorageCleanupDelay.value() << 10)));
+                    List<VolumeVO> vols = volumeDao.listVolumesToBeDestroyed(new Date(System.currentTimeMillis() - ((long)StorageCleanupDelay.value() << 10)));
                     for (VolumeVO vol : vols) {
                         if (Type.ROOT.equals(vol.getVolumeType())) {
                              VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(vol.getInstanceId());
@@ -1369,7 +1573,8 @@
                             // system, but not necessary.
                             handleManagedStorage(vol);
                         } catch (Exception e) {
-                            s_logger.warn("Unable to destroy host-side clustered file system " + vol.getUuid(), e);
+                            s_logger.error(String.format("Unable to destroy host-side clustered file system [%s] due to: [%s].", vol.getUuid(), e.getMessage()));
+                            s_logger.debug(String.format("Unable to destroy host-side clustered file system [%s].", vol.getUuid()), e);
                         }
 
                         try {
@@ -1378,10 +1583,11 @@
                                 volService.ensureVolumeIsExpungeReady(vol.getId());
                                 volService.expungeVolumeAsync(volumeInfo);
                             } else {
-                                s_logger.debug("Volume " + vol.getUuid() + " is already destroyed");
+                                s_logger.debug(String.format("Volume [%s] is already destroyed.", vol.getUuid()));
                             }
                         } catch (Exception e) {
-                            s_logger.warn("Unable to destroy volume " + vol.getUuid(), e);
+                            s_logger.error(String.format("Unable to destroy volume [%s] due to: [%s].", vol.getUuid(), e.getMessage()));
+                            s_logger.debug(String.format("Unable to destroy volume [%s].", vol.getUuid()), e);
                         }
                     }
 
@@ -1395,23 +1601,24 @@
                             }
                             _snapshotDao.expunge(snapshotVO.getId());
                         } catch (Exception e) {
-                            s_logger.warn("Unable to destroy snapshot " + snapshotVO.getUuid(), e);
+                            s_logger.error(String.format("Unable to destroy snapshot [%s] due to: [%s].", snapshotVO.getUuid(), e.getMessage()));
+                            s_logger.debug(String.format("Unable to destroy snapshot [%s].", snapshotVO.getUuid()), e);
                         }
                     }
 
                     // destroy uploaded volumes in abandoned/error state
                     List<VolumeDataStoreVO> volumeDataStores = _volumeDataStoreDao.listByVolumeState(Volume.State.UploadError, Volume.State.UploadAbandoned);
                     for (VolumeDataStoreVO volumeDataStore : volumeDataStores) {
-                        VolumeVO volume = _volumeDao.findById(volumeDataStore.getVolumeId());
+                        VolumeVO volume = volumeDao.findById(volumeDataStore.getVolumeId());
                         if (volume == null) {
-                            s_logger.warn("Uploaded volume with id " + volumeDataStore.getVolumeId() + " not found, so cannot be destroyed");
+                            s_logger.warn(String.format("Uploaded volume [%s] not found, so cannot be destroyed.", volumeDataStore.getVolumeId()));
                             continue;
                         }
                         try {
                             DataStore dataStore = _dataStoreMgr.getDataStore(volumeDataStore.getDataStoreId(), DataStoreRole.Image);
                             EndPoint ep = _epSelector.select(dataStore, volumeDataStore.getExtractUrl());
                             if (ep == null) {
-                                s_logger.warn("There is no secondary storage VM for image store " + dataStore.getName() + ", cannot destroy uploaded volume " + volume.getUuid());
+                                s_logger.warn(String.format("There is no secondary storage VM for image store [%s], cannot destroy uploaded volume [%s].", dataStore.getName(), volume.getUuid()));
                                 continue;
                             }
                             Host host = _hostDao.findById(ep.getId());
@@ -1423,17 +1630,18 @@
                                     // expunge volume from secondary if volume is on image store
                                     VolumeInfo volOnSecondary = volFactory.getVolume(volume.getId(), DataStoreRole.Image);
                                     if (volOnSecondary != null) {
-                                        s_logger.info("Expunging volume " + volume.getUuid() + " uploaded using HTTP POST from secondary data store");
+                                        s_logger.info(String.format("Expunging volume [%s] uploaded using HTTP POST from secondary data store.", volume.getUuid()));
                                         AsyncCallFuture<VolumeApiResult> future = volService.expungeVolumeAsync(volOnSecondary);
                                         VolumeApiResult result = future.get();
                                         if (!result.isSuccess()) {
-                                            s_logger.warn("Failed to expunge volume " + volume.getUuid() + " from the image store " + dataStore.getName() + " due to: " + result.getResult());
+                                            s_logger.warn(String.format("Failed to expunge volume [%s] from the image store [%s] due to: [%s].", volume.getUuid(), dataStore.getName(), result.getResult()));
                                         }
                                     }
                                 }
                             }
                         } catch (Throwable th) {
-                            s_logger.warn("Unable to destroy uploaded volume " + volume.getUuid() + ". Error details: " + th.getMessage());
+                            s_logger.error(String.format("Unable to destroy uploaded volume [%s] due to: [%s].", volume.getUuid(), th.getMessage()));
+                            s_logger.debug(String.format("Unable to destroy uploaded volume [%s].", volume.getUuid()), th);
                         }
                     }
 
@@ -1442,14 +1650,14 @@
                     for (TemplateDataStoreVO templateDataStore : templateDataStores) {
                         VMTemplateVO template = _templateDao.findById(templateDataStore.getTemplateId());
                         if (template == null) {
-                            s_logger.warn("Uploaded template with id " + templateDataStore.getTemplateId() + " not found, so cannot be destroyed");
+                            s_logger.warn(String.format("Uploaded template [%s] not found, so cannot be destroyed.", templateDataStore.getTemplateId()));
                             continue;
                         }
                         try {
                             DataStore dataStore = _dataStoreMgr.getDataStore(templateDataStore.getDataStoreId(), DataStoreRole.Image);
                             EndPoint ep = _epSelector.select(dataStore, templateDataStore.getExtractUrl());
                             if (ep == null) {
-                                s_logger.warn("There is no secondary storage VM for image store " + dataStore.getName() + ", cannot destroy uploaded template " + template.getUuid());
+                                s_logger.warn(String.format("Cannot destroy uploaded template [%s] as there is no secondary storage VM for image store [%s].", template.getUuid(), dataStore.getName()));
                                 continue;
                             }
                             Host host = _hostDao.findById(ep.getId());
@@ -1458,7 +1666,7 @@
                                     AsyncCallFuture<TemplateApiResult> future = _imageSrv.deleteTemplateAsync(tmplFactory.getTemplate(template.getId(), dataStore));
                                     TemplateApiResult result = future.get();
                                     if (!result.isSuccess()) {
-                                        s_logger.warn("Failed to delete template " + template.getUuid() + " from the image store " + dataStore.getName() + " due to: " + result.getResult());
+                                        s_logger.warn(String.format("Failed to delete template [%s] from image store [%s] due to: [%s]", template.getUuid(), dataStore.getName(), result.getResult()));
                                         continue;
                                     }
                                     // remove from template_zone_ref
@@ -1482,7 +1690,8 @@
                                 }
                             }
                         } catch (Throwable th) {
-                            s_logger.warn("Unable to destroy uploaded template " + template.getUuid() + ". Error details: " + th.getMessage());
+                            s_logger.error(String.format("Unable to destroy uploaded template [%s] due to: [%s].", template.getUuid(), th.getMessage()));
+                            s_logger.debug(String.format("Unable to destroy uploaded template [%s].", template.getUuid()), th);
                         }
                     }
                     cleanupInactiveTemplates();
@@ -1510,7 +1719,7 @@
         if (vm == null) {
             return false;
         }
-        List<VolumeVO> vmUsableVolumes = _volumeDao.findUsableVolumesForInstance(vmId);
+        List<VolumeVO> vmUsableVolumes = volumeDao.findUsableVolumesForInstance(vmId);
         for (VolumeVO vol : vmUsableVolumes) {
             if (gcVolume.getPoolId().equals(vol.getPoolId()) && gcVolume.getPath().equals(vol.getPath())) {
                 s_logger.debug(String.format("%s meant for garbage collection could a possible duplicate for %s", gcVolume, vol));
@@ -1659,7 +1868,10 @@
                             s_logger.debug("Deleting snapshot store DB entry: " + destroyedSnapshotStoreVO);
                         }
 
-                        _snapshotDao.remove(destroyedSnapshotStoreVO.getSnapshotId());
+                        List<SnapshotDataStoreVO> imageStoreRefs = _snapshotStoreDao.listBySnapshot(destroyedSnapshotStoreVO.getSnapshotId(), DataStoreRole.Image);
+                        if (imageStoreRefs.size() <= 1) {
+                            _snapshotDao.remove(destroyedSnapshotStoreVO.getSnapshotId());
+                        }
                         SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findDestroyedReferenceBySnapshot(destroyedSnapshotStoreVO.getSnapshotId(), DataStoreRole.Primary);
                         if (snapshotOnPrimary != null) {
                             if (s_logger.isDebugEnabled()) {
@@ -1873,9 +2085,68 @@
         return (PrimaryDataStoreInfo) _dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
     }
 
+
+    @Override
+    public Heuristic createSecondaryStorageHeuristic(CreateSecondaryStorageSelectorCmd cmd) {
+        String name = cmd.getName();
+        String description = cmd.getDescription();
+        long zoneId = cmd.getZoneId();
+        String heuristicRule = cmd.getHeuristicRule();
+        String type = cmd.getType();
+        HeuristicType formattedType = EnumUtils.getEnumIgnoreCase(HeuristicType.class, type);
+
+        if (formattedType == null) {
+            throw new IllegalArgumentException(String.format("The given heuristic type [%s] is not valid for creating a new secondary storage selector." +
+                    " The valid options are %s.", type, Arrays.asList(HeuristicType.values())));
+        }
+
+        HeuristicVO heuristic = secondaryStorageHeuristicDao.findByZoneIdAndType(zoneId, formattedType);
+
+        if (heuristic != null) {
+            DataCenterVO dataCenter = _dcDao.findById(zoneId);
+            throw new CloudRuntimeException(String.format("There is already a heuristic rule in the specified %s with the type [%s].",
+                    dataCenter, type));
+        }
+
+        validateHeuristicRule(heuristicRule);
+
+        HeuristicVO heuristicVO = new HeuristicVO(name, description, zoneId, formattedType.toString(), heuristicRule);
+        return secondaryStorageHeuristicDao.persist(heuristicVO);
+    }
+
+    @Override
+    public Heuristic updateSecondaryStorageHeuristic(UpdateSecondaryStorageSelectorCmd cmd) {
+        long heuristicId = cmd.getId();
+        String heuristicRule = cmd.getHeuristicRule();
+
+        HeuristicVO heuristicVO = secondaryStorageHeuristicDao.findById(heuristicId);
+        validateHeuristicRule(heuristicRule);
+        heuristicVO.setHeuristicRule(heuristicRule);
+
+        return secondaryStorageHeuristicDao.persist(heuristicVO);
+    }
+
+    @Override
+    public void removeSecondaryStorageHeuristic(RemoveSecondaryStorageSelectorCmd cmd) {
+        Long heuristicId = cmd.getId();
+        HeuristicVO heuristicVO = secondaryStorageHeuristicDao.findById(heuristicId);
+
+        if (heuristicVO != null) {
+            secondaryStorageHeuristicDao.remove(heuristicId);
+        } else {
+            throw new CloudRuntimeException("Unable to find an active heuristic with the specified UUID.");
+        }
+    }
+
+    protected void validateHeuristicRule(String heuristicRule) {
+        if (StringUtils.isBlank(heuristicRule)) {
+            throw new IllegalArgumentException("Unable to create a new secondary storage selector as the given heuristic rule is blank.");
+        }
+    }
+
     public void syncDatastoreClusterStoragePool(long datastoreClusterPoolId, List<ModifyStoragePoolAnswer> childDatastoreAnswerList, long hostId) {
         StoragePoolVO datastoreClusterPool = _storagePoolDao.findById(datastoreClusterPoolId);
-        List<String> storageTags = _storagePoolTagsDao.getStoragePoolTags(datastoreClusterPoolId);
+        List<StoragePoolTagVO> storageTags = _storagePoolTagsDao.findStoragePoolTags(datastoreClusterPoolId);
         List<StoragePoolVO> childDatastores = _storagePoolDao.listChildStoragePoolsInDatastoreCluster(datastoreClusterPoolId);
         Set<String> childDatastoreUUIDs = new HashSet<>();
         for (StoragePoolVO childDatastore : childDatastores) {
@@ -1903,18 +2174,18 @@
                     dataStoreVO.setParent(datastoreClusterPoolId);
                     _storagePoolDao.update(dataStoreVO.getId(), dataStoreVO);
                     if (CollectionUtils.isNotEmpty(storageTags)) {
-                        storageTags.addAll(_storagePoolTagsDao.getStoragePoolTags(dataStoreVO.getId()));
+                        storageTags.addAll(_storagePoolTagsDao.findStoragePoolTags(dataStoreVO.getId()));
                     } else {
-                        storageTags = _storagePoolTagsDao.getStoragePoolTags(dataStoreVO.getId());
+                        storageTags = _storagePoolTagsDao.findStoragePoolTags(dataStoreVO.getId());
                     }
                     if (CollectionUtils.isNotEmpty(storageTags)) {
-                        Set<String> set = new LinkedHashSet<>(storageTags);
+                        Set<StoragePoolTagVO> set = new LinkedHashSet<>(storageTags);
                         storageTags.clear();
                         storageTags.addAll(set);
                         if (s_logger.isDebugEnabled()) {
                             s_logger.debug("Updating Storage Pool Tags to :" + storageTags);
                         }
-                        _storagePoolTagsDao.persist(dataStoreVO.getId(), storageTags);
+                        _storagePoolTagsDao.persist(storageTags);
                     }
                 } else {
                     // This is to find datastores which are removed from datastore cluster.
@@ -1922,7 +2193,7 @@
                     childDatastoreUUIDs.remove(dataStoreVO.getUuid());
                 }
             } else {
-                dataStoreVO = createChildDatastoreVO(datastoreClusterPool, childDataStoreAnswer);
+                dataStoreVO = createChildDatastoreVO(datastoreClusterPool, childDataStoreAnswer, storageTags);
             }
             updateStoragePoolHostVOAndBytes(dataStoreVO, hostId, childDataStoreAnswer);
         }
@@ -1963,9 +2234,8 @@
         }
     }
 
-    private StoragePoolVO createChildDatastoreVO(StoragePoolVO datastoreClusterPool, ModifyStoragePoolAnswer childDataStoreAnswer) {
+    private StoragePoolVO createChildDatastoreVO(StoragePoolVO datastoreClusterPool, ModifyStoragePoolAnswer childDataStoreAnswer, List<StoragePoolTagVO> storagePoolTagVOList) {
         StoragePoolInfo childStoragePoolInfo = childDataStoreAnswer.getPoolInfo();
-        List<String> storageTags = _storagePoolTagsDao.getStoragePoolTags(datastoreClusterPool.getId());
 
         StoragePoolVO dataStoreVO = new StoragePoolVO();
         dataStoreVO.setStorageProviderName(datastoreClusterPool.getStorageProviderName());
@@ -1992,7 +2262,15 @@
         if(StringUtils.isNotEmpty(childDataStoreAnswer.getPoolType())) {
             details.put("pool_type", childDataStoreAnswer.getPoolType());
         }
-        _storagePoolDao.persist(dataStoreVO, details, storageTags);
+
+        List<String> storagePoolTags = new ArrayList<>();
+        boolean isTagARule = false;
+        if (CollectionUtils.isNotEmpty(storagePoolTagVOList)) {
+            storagePoolTags = storagePoolTagVOList.parallelStream().map(StoragePoolTagVO::getTag).collect(Collectors.toList());
+            isTagARule = storagePoolTagVOList.get(0).isTagARule();
+        }
+
+        _storagePoolDao.persist(dataStoreVO, details, storagePoolTags, isTagARule);
         return dataStoreVO;
     }
 
@@ -2000,7 +2278,7 @@
 
         for (String childDatastoreUUID : childDatastoreUUIDs) {
             StoragePoolVO dataStoreVO = _storagePoolDao.findPoolByUUID(childDatastoreUUID);
-            List<VolumeVO> allVolumes = _volumeDao.findByPoolId(dataStoreVO.getId());
+            List<VolumeVO> allVolumes = volumeDao.findByPoolId(dataStoreVO.getId());
             allVolumes.removeIf(volumeVO -> volumeVO.getInstanceId() == null);
             allVolumes.removeIf(volumeVO -> volumeVO.getState() != Volume.State.Ready);
             for (VolumeVO volume : allVolumes) {
@@ -2036,7 +2314,7 @@
                         volume.getUuid() + "Host=" + hostId;
 
                 // check for the changed details of volume and update database
-                VolumeVO volumeVO = _volumeDao.findById(volumeId);
+                VolumeVO volumeVO = volumeDao.findById(volumeId);
                 String datastoreName = answer.getContextParam("datastoreName");
                 if (datastoreName != null) {
                     StoragePoolVO storagePoolVO = _storagePoolDao.findByUuid(datastoreName);
@@ -2057,7 +2335,7 @@
                     volumeVO.setChainInfo(chainInfo);
                 }
 
-                _volumeDao.update(volumeVO.getId(), volumeVO);
+                volumeDao.update(volumeVO.getId(), volumeVO);
             }
             dataStoreVO.setParent(0L);
             _storagePoolDao.update(dataStoreVO.getId(), dataStoreVO);
@@ -2414,6 +2692,7 @@
     @Override
     public boolean storagePoolHasEnoughIops(List<Pair<Volume, DiskProfile>> requestedVolumes, StoragePool pool) {
         if (requestedVolumes == null || requestedVolumes.isEmpty() || pool == null) {
+            s_logger.debug(String.format("Cannot check if storage [%s] has enough IOPS to allocate volumes [%s].", pool, requestedVolumes));
             return false;
         }
 
@@ -2444,8 +2723,10 @@
         }
 
         long futureIops = currentIops + requestedIops;
-
-        return futureIops <= pool.getCapacityIops();
+        boolean hasEnoughIops = futureIops <= pool.getCapacityIops();
+        String hasCapacity = hasEnoughIops ? "has" : "does not have";
+        s_logger.debug(String.format("Pool [%s] %s enough IOPS to allocate volumes [%s].", pool, hasCapacity, requestedVolumes));
+        return hasEnoughIops;
     }
 
     @Override
@@ -2456,10 +2737,12 @@
     @Override
     public boolean storagePoolHasEnoughSpace(List<Pair<Volume, DiskProfile>> volumeDiskProfilesList, StoragePool pool, Long clusterId) {
         if (CollectionUtils.isEmpty(volumeDiskProfilesList)) {
+            s_logger.debug(String.format("Cannot check if pool [%s] has enough space to allocate volumes because the volumes list is empty.", pool));
             return false;
         }
 
         if (!checkUsagedSpace(pool)) {
+            s_logger.debug(String.format("Cannot allocate pool [%s] because there is not enough space in this pool.", pool));
             return false;
         }
 
@@ -2478,14 +2761,14 @@
             // might be clearer that this "volume" in "volumeDiskProfilesList" still might have an old value for hv_ss_reverse.
             Volume volume = volumeDiskProfilePair.first();
             DiskProfile diskProfile = volumeDiskProfilePair.second();
-            VolumeVO volumeVO = _volumeDao.findById(volume.getId());
+            VolumeVO volumeVO = volumeDao.findById(volume.getId());
 
             if (volumeVO.getHypervisorSnapshotReserve() == null) {
                 // update the volume's hv_ss_reserve (hypervisor snapshot reserve) from a disk offering (used for managed storage)
                 volService.updateHypervisorSnapshotReserveForVolume(getDiskOfferingVO(volumeVO), volumeVO.getId(), getHypervisorType(volumeVO));
 
                 // hv_ss_reserve field might have been updated; refresh from DB to make use of it in getDataObjectSizeIncludingHypervisorSnapshotReserve
-                volumeVO = _volumeDao.findById(volume.getId());
+                volumeVO = volumeDao.findById(volume.getId());
             }
 
             // this if statement should resolve to true at most once per execution of the for loop its contained within (for a root disk that is
@@ -2722,30 +3005,34 @@
     @Override
     public boolean storagePoolCompatibleWithVolumePool(StoragePool pool, Volume volume) {
         if (pool == null || volume == null) {
+            s_logger.debug(String.format("Cannot check if storage pool [%s] is compatible with volume [%s].", pool, volume));
             return false;
         }
 
         if (volume.getPoolId() == null) {
-            // Volume is not allocated to any pool. Not possible to check compatibility with other pool, let it try
+            s_logger.debug(String.format("Volume [%s] is not allocated to any pool. Cannot check compatibility with pool [%s].", volume, pool));
             return true;
         }
 
         StoragePool volumePool = _storagePoolDao.findById(volume.getPoolId());
         if (volumePool == null) {
-            // Volume pool doesn't exist. Not possible to check compatibility with other pool, let it try
+            s_logger.debug(String.format("Pool [%s] used by volume [%s] does not exist. Cannot check compatibility.", pool, volume));
             return true;
         }
 
         if (volume.getState() == Volume.State.Ready) {
             if (volumePool.getPoolType() == Storage.StoragePoolType.PowerFlex && pool.getPoolType() != Storage.StoragePoolType.PowerFlex) {
+                s_logger.debug(String.format("Pool [%s] with type [%s] does not match volume [%s] pool type [%s].", pool, pool.getPoolType(), volume, volumePool.getPoolType()));
                 return false;
             } else if (volumePool.getPoolType() != Storage.StoragePoolType.PowerFlex && pool.getPoolType() == Storage.StoragePoolType.PowerFlex) {
+                s_logger.debug(String.format("Pool [%s] with type [%s] does not match volume [%s] pool type [%s].", pool, pool.getPoolType(), volume, volumePool.getPoolType()));
                 return false;
             }
         } else {
+            s_logger.debug(String.format("Cannot check compatibility of pool [%s] because volume [%s] is not in [%s] state.", pool, volume, Volume.State.Ready));
             return false;
         }
-
+        s_logger.debug(String.format("Pool [%s] is compatible with volume [%s].", pool, volume));
         return true;
     }
 
@@ -2998,6 +3285,8 @@
     }
 
     @Override
+    @ActionEvent(eventType = EventTypes.EVENT_UPDATE_IMAGE_STORE_ACCESS_STATE,
+            eventDescription = "image store access updated")
     public ImageStore updateImageStoreStatus(Long id, Boolean readonly) {
         // Input validation
         ImageStoreVO imageStoreVO = _imageStoreDao.findById(id);
@@ -3323,9 +3612,9 @@
             if (activeVolumeIds.contains(volumeId)) {
                 continue;
             }
-            Volume volume = _volumeDao.findById(volumeId);
+            Volume volume = volumeDao.findById(volumeId);
             if (volume != null && volume.getState() == Volume.State.Expunged) {
-                _volumeDao.remove(volumeId);
+                volumeDao.remove(volumeId);
             }
         }
 
@@ -3353,6 +3642,18 @@
                 s_logger.warn("caught exception while deleting download url " + templateOnImageStore.getExtractUrl() + " for template id " + templateOnImageStore.getTemplateId(), th);
             }
         }
+
+        Date date = DateUtils.addSeconds(new Date(), -1 * _downloadUrlExpirationInterval);
+        List<ImageStoreObjectDownloadVO> imageStoreObjectDownloadList = _imageStoreObjectDownloadDao.listToExpire(date);
+        for (ImageStoreObjectDownloadVO imageStoreObjectDownloadVO : imageStoreObjectDownloadList) {
+            try {
+                ImageStoreEntity secStore = (ImageStoreEntity)_dataStoreMgr.getDataStore(imageStoreObjectDownloadVO.getStoreId(), DataStoreRole.Image);
+                secStore.deleteExtractUrl(imageStoreObjectDownloadVO.getPath(), imageStoreObjectDownloadVO.getDownloadUrl(), null);
+                _imageStoreObjectDownloadDao.expunge(imageStoreObjectDownloadVO.getId());
+            } catch (Throwable th) {
+                s_logger.warn("caught exception while deleting download url " + imageStoreObjectDownloadVO.getDownloadUrl() + " for object id " + imageStoreObjectDownloadVO.getId(), th);
+            }
+        }
     }
 
     // get bytesReadRate from service_offering, disk_offering and vm.disk.throttling.bytes_read_rate
@@ -3442,6 +3743,7 @@
                 MountDisabledStoragePool,
                 VmwareCreateCloneFull,
                 VmwareAllowParallelExecution,
+                ConvertVmwareInstanceToKvmTimeout,
                 DataStoreDownloadFollowRedirects
         };
     }
@@ -3489,4 +3791,124 @@
         volumeTO.setIopsWriteRate(getDiskIopsWriteRate(offering, diskOffering));
     }
 
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_OBJECT_STORE_CREATE, eventDescription = "creating object storage")
+    public ObjectStore discoverObjectStore(String name, String url, String providerName, Map details)
+            throws IllegalArgumentException, InvalidParameterValueException {
+        DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName);
+
+        if (storeProvider == null) {
+            throw new InvalidParameterValueException("can't find object store provider: " + providerName);
+        }
+
+        // Check Unique object store name
+        ObjectStoreVO objectStore = _objectStoreDao.findByName(name);
+        if (objectStore != null) {
+            throw new InvalidParameterValueException("The object store with name " + name + " already exists, try creating with another name");
+        }
+
+        try {
+            // Check URL
+            UriUtils.validateUrl(url);
+        } catch (final Exception e) {
+            throw new InvalidParameterValueException(url + " is not a valid URL");
+        }
+
+        // Check Unique object store url
+        ObjectStoreVO objectStoreUrl = _objectStoreDao.findByUrl(url);
+        if (objectStoreUrl != null) {
+            throw new InvalidParameterValueException("The object store with url " + url + " already exists");
+        }
+
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("url", url);
+        params.put("name", name);
+        params.put("providerName", storeProvider.getName());
+        params.put("role", DataStoreRole.Object);
+        params.put("details", details);
+
+        DataStoreLifeCycle lifeCycle = storeProvider.getDataStoreLifeCycle();
+
+        DataStore store;
+        try {
+            store = lifeCycle.initialize(params);
+        } catch (Exception e) {
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug("Failed to add object store: " + e.getMessage(), e);
+            }
+            throw new CloudRuntimeException("Failed to add object store: " + e.getMessage(), e);
+        }
+
+        return (ObjectStore)_dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Object);
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_OBJECT_STORE_DELETE, eventDescription = "deleting object storage")
+    public boolean deleteObjectStore(DeleteObjectStoragePoolCmd cmd) {
+        final long storeId = cmd.getId();
+        // Verify that object store exists
+        ObjectStoreVO store = _objectStoreDao.findById(storeId);
+        if (store == null) {
+            throw new InvalidParameterValueException("Object store with id " + storeId + " doesn't exist");
+        }
+
+        // Verify that there are no buckets in the store
+        List<BucketVO> buckets = _bucketDao.listByObjectStoreId(storeId);
+        if(buckets != null && buckets.size() > 0) {
+            throw new InvalidParameterValueException("Cannot delete object store with buckets");
+        }
+
+        // ready to delete
+        Transaction.execute(new TransactionCallbackNoReturn() {
+            @Override
+            public void doInTransactionWithoutResult(TransactionStatus status) {
+                _objectStoreDetailsDao.deleteDetails(storeId);
+                _objectStoreDao.remove(storeId);
+            }
+        });
+        s_logger.debug("Successfully deleted object store with Id: "+storeId);
+        return true;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_OBJECT_STORE_UPDATE, eventDescription = "update object storage")
+    public ObjectStore updateObjectStore(Long id, UpdateObjectStoragePoolCmd cmd) {
+
+        // Input validation
+        ObjectStoreVO objectStoreVO = _objectStoreDao.findById(id);
+        if (objectStoreVO == null) {
+            throw new IllegalArgumentException("Unable to find object store with ID: " + id);
+        }
+
+        if(cmd.getUrl() != null ) {
+            String url = cmd.getUrl();
+            try {
+                // Check URL
+                UriUtils.validateUrl(url);
+            } catch (final Exception e) {
+                throw new InvalidParameterValueException(url + " is not a valid URL");
+            }
+            ObjectStoreEntity objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+            String oldUrl = objectStoreVO.getUrl();
+            objectStoreVO.setUrl(url);
+            _objectStoreDao.update(id, objectStoreVO);
+            //Update URL and check access
+            try {
+                objectStore.listBuckets();
+            } catch (Exception e) {
+                //Revert to old URL on failure
+                objectStoreVO.setUrl(oldUrl);
+                _objectStoreDao.update(id, objectStoreVO);
+                throw new IllegalArgumentException("Unable to access Object Storage with URL: " + cmd.getUrl());
+            }
+        }
+
+        if(cmd.getName() != null ) {
+            objectStoreVO.setName(cmd.getName());
+        }
+        _objectStoreDao.update(id, objectStoreVO);
+        s_logger.debug("Successfully updated object store with Id: "+id);
+        return objectStoreVO;
+    }
 }
diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index 0907a84..02add0d 100644
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@ -31,17 +31,18 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
-import com.cloud.domain.dao.DomainDao;
-import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
+import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiErrorCode;
 import org.apache.cloudstack.api.InternalIdentity;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
+import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
@@ -66,6 +67,7 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
@@ -85,7 +87,9 @@
 import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO;
 import org.apache.cloudstack.jobs.JobInfo;
 import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
+import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO;
 import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
+import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao;
 import org.apache.cloudstack.snapshot.SnapshotHelper;
 import org.apache.cloudstack.storage.command.AttachAnswer;
 import org.apache.cloudstack.storage.command.AttachCommand;
@@ -95,13 +99,14 @@
 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.StoragePoolDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
 import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
 import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
+import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
 import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
 import org.apache.commons.collections.CollectionUtils;
@@ -132,6 +137,7 @@
 import com.cloud.dc.Pod;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.domain.Domain;
+import com.cloud.domain.dao.DomainDao;
 import com.cloud.event.ActionEvent;
 import com.cloud.event.EventTypes;
 import com.cloud.event.UsageEventUtils;
@@ -212,6 +218,7 @@
 import com.cloud.vm.VmDetailConstants;
 import com.cloud.vm.VmWork;
 import com.cloud.vm.VmWorkAttachVolume;
+import com.cloud.vm.VmWorkCheckAndRepairVolume;
 import com.cloud.vm.VmWorkConstants;
 import com.cloud.vm.VmWorkDetachVolume;
 import com.cloud.vm.VmWorkExtractVolume;
@@ -259,6 +266,8 @@
     @Inject
     private SnapshotDao _snapshotDao;
     @Inject
+    private SnapshotPolicyDetailsDao snapshotPolicyDetailsDao;
+    @Inject
     private SnapshotDataStoreDao _snapshotDataStoreDao;
     @Inject
     private ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
@@ -339,6 +348,7 @@
     @Inject
     protected StoragePoolDetailsDao storagePoolDetailsDao;
 
+
     protected Gson _gson;
 
     private static final List<HypervisorType> SupportedHypervisorsForVolResize = Arrays.asList(HypervisorType.KVM, HypervisorType.XenServer,
@@ -368,6 +378,12 @@
             "Time (in milliseconds) to wait before assuming the VM was unable to detach a volume after the hypervisor sends the detach command.",
             true);
 
+    public static ConfigKey<Long> storageTagRuleExecutionTimeout = new ConfigKey<>("Advanced", Long.class, "storage.tag.rule.execution.timeout", "2000", "The maximum runtime,"
+            + " in milliseconds, to execute a storage tag rule; if it is reached, a timeout will happen.", true);
+
+    public static final ConfigKey<Boolean> AllowCheckAndRepairVolume = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.check.and.repair.leaks.before.use", "false",
+            "To check and repair the volume if it has any leaks before performing volume attach or VM start operations", true, ConfigKey.Scope.StoragePool);
+
     private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
 
     private static final Set<Volume.State> STATES_VOLUME_CANNOT_BE_DESTROYED = new HashSet<>(Arrays.asList(Volume.State.Destroy, Volume.State.Expunging, Volume.State.Expunged, Volume.State.Allocated));
@@ -397,7 +413,6 @@
         String format = sanitizeFormat(cmd.getFormat());
         Long diskOfferingId = cmd.getDiskOfferingId();
         String imageStoreUuid = cmd.getImageStoreUuid();
-        DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId);
 
         validateVolume(caller, ownerId, zoneId, volumeName, url, format, diskOfferingId);
 
@@ -407,6 +422,7 @@
 
         RegisterVolumePayload payload = new RegisterVolumePayload(cmd.getUrl(), cmd.getChecksum(), format);
         vol.addPayload(payload);
+        DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId, volume);
 
         volService.registerVolume(vol, store);
         return volume;
@@ -439,7 +455,6 @@
         String format = sanitizeFormat(cmd.getFormat());
         final Long diskOfferingId = cmd.getDiskOfferingId();
         String imageStoreUuid = cmd.getImageStoreUuid();
-        final DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId);
 
         validateVolume(caller, ownerId, zoneId, volumeName, null, format, diskOfferingId);
 
@@ -449,6 +464,8 @@
 
                 VolumeVO volume = persistVolume(owner, zoneId, volumeName, null, format, diskOfferingId, Volume.State.NotUploaded);
 
+                final DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId, volume);
+
                 VolumeInfo vol = volFactory.getVolume(volume.getId());
 
                 RegisterVolumePayload payload = new RegisterVolumePayload(null, cmd.getChecksum(), format);
@@ -549,7 +566,7 @@
 
         sanitizeFormat(format);
 
-        // Check that the the disk offering specified is valid
+        // Check that the disk offering specified is valid
         if (diskOfferingId != null) {
             DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId);
             if ((diskOffering == null) || diskOffering.getRemoved() != null || diskOffering.isComputeOnly()) {
@@ -644,8 +661,10 @@
                 _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume);
                 //url can be null incase of postupload
                 if (url != null) {
+                    long remoteSize = UriUtils.getRemoteSize(url, StorageManager.DataStoreDownloadFollowRedirects.value());
                     _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.secondary_storage,
-                            UriUtils.getRemoteSize(url, StorageManager.DataStoreDownloadFollowRedirects.value()));
+                            remoteSize);
+                    volume.setSize(remoteSize);
                 }
 
                 return volume;
@@ -740,7 +759,7 @@
                 }
             }
 
-            // Check that the the disk offering is specified
+            // Check that the disk offering is specified
             diskOffering = _diskOfferingDao.findById(diskOfferingId);
             if ((diskOffering == null) || diskOffering.getRemoved() != null || diskOffering.isComputeOnly()) {
                 throw new InvalidParameterValueException("Please specify a valid disk offering.");
@@ -820,7 +839,7 @@
                 throw new InvalidParameterValueException("Snapshot id=" + snapshotId + " is not in " + Snapshot.State.BackedUp + " state yet and can't be used for volume creation");
             }
 
-            SnapshotDataStoreVO snapshotStore = _snapshotDataStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
+            SnapshotDataStoreVO snapshotStore = _snapshotDataStoreDao.findOneBySnapshotAndDatastoreRole(snapshotId, DataStoreRole.Primary);
             if (snapshotStore != null) {
                 StoragePoolVO storagePoolVO = _storagePoolDao.findById(snapshotStore.getDataStoreId());
                 if (storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex) {
@@ -837,7 +856,7 @@
 
             if (zoneId == null) {
                 // if zoneId is not provided, we default to create volume in the same zone as the snapshot zone.
-                zoneId = snapshotCheck.getDataCenterId();
+                zoneId = parentVolume.getDataCenterId();
             }
 
             if (diskOffering == null) { // Pure snapshot is being used to create volume.
@@ -868,7 +887,9 @@
                 if (vm == null || vm.getType() != VirtualMachine.Type.User) {
                     throw new InvalidParameterValueException("Please specify a valid User VM.");
                 }
-
+                if (vm.getDataCenterId() != zoneId) {
+                    throw new InvalidParameterValueException("The specified zone is different than zone of the VM");
+                }
                 // Check that the VM is in the correct state
                 if (vm.getState() != State.Running && vm.getState() != State.Stopped) {
                     throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
@@ -1240,7 +1261,9 @@
             if (storagePoolId != null) {
                 StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
 
-                if (storagePoolVO.isManaged() && !storagePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
+                if (storagePoolVO.isManaged() && !List.of(
+                        Storage.StoragePoolType.PowerFlex,
+                        Storage.StoragePoolType.FiberChannel).contains(storagePoolVO.getPoolType())) {
                     Long instanceId = volume.getInstanceId();
 
                     if (instanceId != null) {
@@ -1321,7 +1344,7 @@
                     outcome.get();
                 } catch (InterruptedException e) {
                     throw new RuntimeException("Operation was interrupted", e);
-                } catch (java.util.concurrent.ExecutionException e) {
+                } catch (ExecutionException e) {
                     throw new RuntimeException("Execution exception", e);
                 }
 
@@ -1452,7 +1475,7 @@
             }
         }
 
-        ResizeVolumePayload payload = new ResizeVolumePayload(newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk, instanceName, hosts, isManaged);
+        ResizeVolumePayload payload = new ResizeVolumePayload(newSize, newMinIops, newMaxIops, newDiskOfferingId, newHypervisorSnapshotReserve, shrinkOk, instanceName, hosts, isManaged);
 
         try {
             VolumeInfo vol = volFactory.getVolume(volume.getId());
@@ -1491,6 +1514,15 @@
 
             if (newDiskOfferingId != null) {
                 volume.setDiskOfferingId(newDiskOfferingId);
+                _volumeMgr.saveVolumeDetails(newDiskOfferingId, volume.getId());
+            }
+
+            if (newMinIops != null) {
+                volume.setMinIops(newMinIops);
+            }
+
+            if (newMaxIops != null) {
+                volume.setMaxIops(newMaxIops);
             }
 
             // Update size if volume has same size as before, else it is already updated
@@ -1688,15 +1720,11 @@
         }
     }
 
-    protected boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException {
+    public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException {
         return _volStateMachine.transitTo(vol, event, null, _volsDao);
     }
 
-    @Override
-    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
-    public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
-        VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
-
+    public void validateDestroyVolume(Volume volume, Account caller, boolean expunge, boolean forceExpunge) {
         if (expunge) {
             // When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller.
             final Long userId = caller.getAccountId();
@@ -1706,6 +1734,14 @@
         } else if (volume.getState() == Volume.State.Allocated || volume.getState() == Volume.State.Uploaded) {
             throw new InvalidParameterValueException("The volume in Allocated/Uploaded state can only be expunged not destroyed/recovered");
         }
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
+    public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
+        VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
+
+        validateDestroyVolume(volume, caller, expunge, forceExpunge);
 
         destroyVolumeIfPossible(volume);
 
@@ -1803,7 +1839,159 @@
         s_logger.debug(String.format("Volume [%s] has been successfully recovered, thus a new usage event %s has been published.", volume.getUuid(), EventTypes.EVENT_VOLUME_CREATE));
     }
 
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CHECK, eventDescription = "checking volume and repair if needed", async = true)
+    public Pair<String, String> checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException {
+        long volumeId = cmd.getId();
+        String repair = cmd.getRepair();
 
+        final VolumeVO volume = _volsDao.findById(volumeId);
+        validationsForCheckVolumeOperation(volume);
+
+        Long vmId = volume.getInstanceId();
+        if (vmId != null) {
+            // serialize VM operation
+            return handleCheckAndRepairVolumeJob(vmId, volumeId, repair);
+        } else {
+            return handleCheckAndRepairVolume(volumeId, repair);
+        }
+    }
+
+    private Pair<String, String> handleCheckAndRepairVolume(Long volumeId, String repair) {
+        CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
+        VolumeInfo volumeInfo = volFactory.getVolume(volumeId);
+        volumeInfo.addPayload(payload);
+
+        Pair<String, String> result = volService.checkAndRepairVolume(volumeInfo);
+        return result;
+    }
+
+    private Pair<String, String> handleCheckAndRepairVolumeJob(Long vmId, Long volumeId, String repair) throws ResourceAllocationException {
+        AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
+        if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
+            // avoid re-entrance
+            VmWorkJobVO placeHolder = null;
+            placeHolder = createPlaceHolderWork(vmId);
+            try {
+                Pair<String, String> result = orchestrateCheckAndRepairVolume(volumeId, repair);
+                return result;
+            } finally {
+                _workJobDao.expunge(placeHolder.getId());
+            }
+        } else {
+            Outcome<Pair> outcome = checkAndRepairVolumeThroughJobQueue(vmId, volumeId, repair);
+            try {
+                outcome.get();
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Operation is interrupted", e);
+            } catch (ExecutionException e) {
+                throw new RuntimeException("Execution exception--", e);
+            }
+
+            Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
+            if (jobResult != null) {
+                if (jobResult instanceof ConcurrentOperationException) {
+                    throw (ConcurrentOperationException)jobResult;
+                } else if (jobResult instanceof ResourceAllocationException) {
+                    throw (ResourceAllocationException)jobResult;
+                } else if (jobResult instanceof Throwable) {
+                    Throwable throwable = (Throwable) jobResult;
+                    throw new RuntimeException(String.format("Unexpected exception: %s", throwable.getMessage()), throwable);
+                }
+            }
+
+            // retrieve the entity url from job result
+            if (jobResult != null && jobResult instanceof Pair) {
+                return (Pair<String, String>) jobResult;
+            }
+
+            return null;
+        }
+    }
+
+    protected void validationsForCheckVolumeOperation(VolumeVO volume) {
+        Account caller = CallContext.current().getCallingAccount();
+        _accountMgr.checkAccess(caller, null, true, volume);
+
+        String volumeName = volume.getName();
+        Long vmId = volume.getInstanceId();
+        if (vmId != null) {
+            validateVMforCheckVolumeOperation(vmId, volumeName);
+        }
+
+        if (volume.getState() != Volume.State.Ready) {
+            throw new InvalidParameterValueException(String.format("Volume: %s is not in Ready state", volumeName));
+        }
+
+        HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId());
+        if (!HypervisorType.KVM.equals(hypervisorType)) {
+            throw new InvalidParameterValueException(String.format("Check and Repair volumes is supported only for KVM hypervisor"));
+        }
+
+        if (!Arrays.asList(ImageFormat.QCOW2, ImageFormat.VDI).contains(volume.getFormat())) {
+            throw new InvalidParameterValueException("Volume format is not supported for checking and repair");
+        }
+    }
+
+    private void validateVMforCheckVolumeOperation(Long vmId, String volumeName) {
+        Account caller = CallContext.current().getCallingAccount();
+        UserVmVO vm = _userVmDao.findById(vmId);
+        if (vm == null) {
+            throw new InvalidParameterValueException(String.format("VM not found, please check the VM to which this volume %s is attached", volumeName));
+        }
+
+        _accountMgr.checkAccess(caller, null, true, vm);
+
+        if (vm.getState() != State.Stopped) {
+            throw new InvalidParameterValueException(String.format("VM to which the volume %s is attached should be in stopped state", volumeName));
+        }
+    }
+
+    private Pair<String, String> orchestrateCheckAndRepairVolume(Long volumeId, String repair) {
+
+        VolumeInfo volume = volFactory.getVolume(volumeId);
+
+        if (volume == null) {
+            throw new InvalidParameterValueException("Checking volume and repairing failed due to volume:" + volumeId + " doesn't exist");
+        }
+
+        CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
+        volume.addPayload(payload);
+
+        return volService.checkAndRepairVolume(volume);
+    }
+
+    public Outcome<Pair> checkAndRepairVolumeThroughJobQueue(final Long vmId, final Long volumeId, String repair) {
+
+        final CallContext context = CallContext.current();
+        final User callingUser = context.getCallingUser();
+        final Account callingAccount = context.getCallingAccount();
+
+        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
+
+        VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId());
+
+        workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER);
+        workJob.setCmd(VmWorkCheckAndRepairVolume.class.getName());
+
+        workJob.setAccountId(callingAccount.getId());
+        workJob.setUserId(callingUser.getId());
+        workJob.setStep(VmWorkJobVO.Step.Starting);
+        workJob.setVmType(VirtualMachine.Type.Instance);
+        workJob.setVmInstanceId(vm.getId());
+        workJob.setRelated(AsyncJobExecutionContext.getOriginJobId());
+
+        // save work context info (there are some duplications)
+        VmWorkCheckAndRepairVolume workInfo = new VmWorkCheckAndRepairVolume(callingUser.getId(), callingAccount.getId(), vm.getId(),
+                VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, repair);
+        workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
+
+        _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
+
+        AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId());
+
+        return new VmJobCheckAndRepairVolumeOutcome(workJob);
+    }
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CHANGE_DISK_OFFERING, eventDescription = "Changing disk offering of a volume")
@@ -1859,6 +2047,7 @@
 
             if (newDiskOffering != null) {
                 volume.setDiskOfferingId(newDiskOfferingId);
+                _volumeMgr.saveVolumeDetails(newDiskOfferingId, volume.getId());
             }
 
             _volsDao.update(volume.getId(), volume);
@@ -1973,7 +2162,7 @@
                     outcome.get();
                 } catch (InterruptedException e) {
                     throw new RuntimeException("Operation was interrupted", e);
-                } catch (java.util.concurrent.ExecutionException e) {
+                } catch (ExecutionException e) {
                     throw new RuntimeException("Execution exception", e);
                 }
 
@@ -2759,7 +2948,7 @@
                 outcome.get();
             } catch (InterruptedException e) {
                 throw new RuntimeException("Operation is interrupted", e);
-            } catch (java.util.concurrent.ExecutionException e) {
+            } catch (ExecutionException e) {
                 throw new RuntimeException("Execution excetion", e);
             }
 
@@ -3167,7 +3356,7 @@
                     outcome.get();
                 } catch (InterruptedException e) {
                     throw new RuntimeException("Operation is interrupted", e);
-                } catch (java.util.concurrent.ExecutionException e) {
+                } catch (ExecutionException e) {
                     throw new RuntimeException("Execution excetion", e);
                 }
 
@@ -3260,7 +3449,7 @@
         }
         if (!doesTargetStorageSupportDiskOffering(destPool, newDiskOffering)) {
             throw new InvalidParameterValueException(String.format("Migration failed: target pool [%s, tags:%s] has no matching tags for volume [%s, uuid:%s, tags:%s]", destPool.getName(),
-                    getStoragePoolTags(destPool), volume.getName(), volume.getUuid(), newDiskOffering.getTags()));
+                    storagePoolTagsDao.getStoragePoolTags(destPool.getId()), volume.getName(), volume.getUuid(), newDiskOffering.getTags()));
         }
         if (volume.getVolumeType().equals(Volume.Type.ROOT)) {
             VMInstanceVO vm = null;
@@ -3323,17 +3512,31 @@
 
     @Override
     public boolean doesTargetStorageSupportDiskOffering(StoragePool destPool, String diskOfferingTags) {
-        if (StringUtils.isBlank(diskOfferingTags)) {
+        Pair<List<String>, Boolean> storagePoolTags = getStoragePoolTags(destPool);
+        if ((storagePoolTags == null || !storagePoolTags.second()) && org.apache.commons.lang.StringUtils.isBlank(diskOfferingTags)) {
+            if (storagePoolTags == null) {
+                s_logger.debug(String.format("Destination storage pool [%s] does not have any tags, and so does the disk offering. Therefore, they are compatible", destPool.getUuid()));
+            } else {
+                s_logger.debug("Destination storage pool has tags [%s], and the disk offering has no tags. Therefore, they are compatible.");
+            }
             return true;
         }
-        String storagePoolTags = getStoragePoolTags(destPool);
-        if (StringUtils.isBlank(storagePoolTags)) {
+        if (storagePoolTags == null || CollectionUtils.isEmpty(storagePoolTags.first())) {
+            s_logger.debug(String.format("Destination storage pool [%s] has no tags, while disk offering has tags [%s]. Therefore, they are not compatible", destPool.getUuid(),
+                    diskOfferingTags));
             return false;
         }
-        String[] storageTagsAsStringArray = StringUtils.split(storagePoolTags, ",");
-        String[] newDiskOfferingTagsAsStringArray = StringUtils.split(diskOfferingTags, ",");
+        List<String> storageTagsList = storagePoolTags.first();
+        String[] newDiskOfferingTagsAsStringArray = org.apache.commons.lang.StringUtils.split(diskOfferingTags, ",");
 
-        return CollectionUtils.isSubCollection(Arrays.asList(newDiskOfferingTagsAsStringArray), Arrays.asList(storageTagsAsStringArray));
+        boolean result;
+        if (storagePoolTags.second()) {
+            result =  TagAsRuleHelper.interpretTagAsRule(storageTagsList.get(0), diskOfferingTags, storageTagRuleExecutionTimeout.value());
+        } else {
+            result = CollectionUtils.isSubCollection(Arrays.asList(newDiskOfferingTagsAsStringArray), storageTagsList);
+        }
+        s_logger.debug(String.format("Destination storage pool [%s] accepts tags [%s]? %s", destPool.getUuid(), diskOfferingTags, result));
+        return result;
     }
 
     public static boolean doesNewDiskOfferingHasTagsAsOldDiskOffering(DiskOfferingVO oldDO, DiskOfferingVO newDO) {
@@ -3349,14 +3552,17 @@
     }
 
     /**
-     *  Retrieves the storage pool tags as a {@link String}. If the storage pool does not have tags we return a null value.
+     *  Returns a {@link Pair}, where the first value is the list of the StoragePool tags, and the second value is whether the returned tags are to be interpreted as a rule,
+     *  or a normal list of tags.
+     *  <br><br>
+     *  If the storage pool does not have tags we return a null value.
      */
-    protected String getStoragePoolTags(StoragePool destPool) {
-        List<String> destPoolTags = storagePoolTagsDao.getStoragePoolTags(destPool.getId());
+    protected Pair<List<String>, Boolean> getStoragePoolTags(StoragePool destPool) {
+        List<StoragePoolTagVO> destPoolTags = storagePoolTagsDao.findStoragePoolTags(destPool.getId());
         if (CollectionUtils.isEmpty(destPoolTags)) {
             return null;
         }
-        return StringUtils.join(destPoolTags, ",");
+        return new Pair<>(destPoolTags.parallelStream().map(StoragePoolTagVO::getTag).collect(Collectors.toList()), destPoolTags.get(0).isTagARule());
     }
 
     private Volume orchestrateMigrateVolume(VolumeVO volume, StoragePool destPool, boolean liveMigrateVolume, DiskOfferingVO newDiskOffering) {
@@ -3404,22 +3610,39 @@
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "taking snapshot", async = true)
-    public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags)
+    public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm,
+         Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags, List<Long> zoneIds)
             throws ResourceAllocationException {
-        final Snapshot snapshot = takeSnapshotInternal(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup);
+        final Snapshot snapshot = takeSnapshotInternal(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup, zoneIds);
         if (snapshot != null && MapUtils.isNotEmpty(tags)) {
             taggedResourceService.createTags(Collections.singletonList(snapshot.getUuid()), ResourceTag.ResourceObjectType.Snapshot, tags, null);
         }
         return snapshot;
     }
 
-    private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
+    private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapshotId, Account account,
+          boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, List<Long> zoneIds)
             throws ResourceAllocationException {
         Account caller = CallContext.current().getCallingAccount();
         VolumeInfo volume = volFactory.getVolume(volumeId);
         if (volume == null) {
             throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
         }
+        if (policyId != null && policyId > 0) {
+            if (CollectionUtils.isNotEmpty(zoneIds)) {
+                throw new InvalidParameterValueException(String.format("%s can not be specified for snapshots linked with snapshot policy", ApiConstants.ZONE_ID_LIST));
+            }
+            List<SnapshotPolicyDetailVO> details = snapshotPolicyDetailsDao.findDetails(policyId, ApiConstants.ZONE_ID);
+            zoneIds = details.stream().map(d -> Long.valueOf(d.getValue())).collect(Collectors.toList());
+        }
+        if (CollectionUtils.isNotEmpty(zoneIds)) {
+            for (Long destZoneId : zoneIds) {
+                DataCenterVO dstZone = _dcDao.findById(destZoneId);
+                if (dstZone == null) {
+                    throw new InvalidParameterValueException("Please specify a valid destination zone.");
+                }
+            }
+        }
 
         _accountMgr.checkAccess(caller, null, true, volume);
 
@@ -3448,19 +3671,21 @@
                 VmWorkJobVO placeHolder = null;
                 placeHolder = createPlaceHolderWork(vm.getId());
                 try {
-                    return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup);
+                    return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm,
+                            locationType, asyncBackup, zoneIds);
                 } finally {
                     _workJobDao.expunge(placeHolder.getId());
                 }
 
             } else {
-                Outcome<Snapshot> outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId, snapshotId, account.getId(), quiescevm, locationType, asyncBackup);
+                Outcome<Snapshot> outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId,
+                        snapshotId, account.getId(), quiescevm, locationType, asyncBackup, zoneIds);
 
                 try {
                     outcome.get();
                 } catch (InterruptedException e) {
                     throw new RuntimeException("Operation is interrupted", e);
-                } catch (java.util.concurrent.ExecutionException e) {
+                } catch (ExecutionException e) {
                     throw new RuntimeException("Execution excetion", e);
                 }
 
@@ -3484,12 +3709,16 @@
             payload.setAccount(account);
             payload.setQuiescevm(quiescevm);
             payload.setAsyncBackup(asyncBackup);
+            if (CollectionUtils.isNotEmpty(zoneIds)) {
+                payload.setZoneIds(zoneIds);
+            }
             volume.addPayload(payload);
             return volService.takeSnapshot(volume);
         }
     }
 
-    private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
+    private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account,
+        boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, List<Long> zoneIds)
             throws ResourceAllocationException {
 
         VolumeInfo volume = volFactory.getVolume(volumeId);
@@ -3516,6 +3745,9 @@
         payload.setQuiescevm(quiescevm);
         payload.setLocationType(locationType);
         payload.setAsyncBackup(asyncBackup);
+        if (CollectionUtils.isNotEmpty(zoneIds)) {
+            payload.setZoneIds(zoneIds);
+        }
         volume.addPayload(payload);
 
         return volService.takeSnapshot(volume);
@@ -3531,7 +3763,7 @@
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "allocating snapshot", create = true)
-    public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException {
+    public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List<Long> zoneIds) throws ResourceAllocationException {
         Account caller = CallContext.current().getCallingAccount();
 
         VolumeInfo volume = volFactory.getVolume(volumeId);
@@ -3540,7 +3772,7 @@
         }
         DataCenter zone = _dcDao.findById(volume.getDataCenterId());
         if (zone == null) {
-            throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId());
+            throw new InvalidParameterValueException(String.format("Can't find zone for the volume ID: %s", volume.getUuid()));
         }
 
         if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
@@ -3554,7 +3786,6 @@
         if (ImageFormat.DIR.equals(volume.getFormat())) {
             throw new InvalidParameterValueException("Snapshot not supported for volume:" + volumeId);
         }
-
         if (volume.getTemplateId() != null) {
             VMTemplateVO template = _templateDao.findById(volume.getTemplateId());
             Long instanceId = volume.getInstanceId();
@@ -3582,7 +3813,35 @@
             throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it");
         }
 
-        return snapshotMgr.allocSnapshot(volumeId, policyId, snapshotName, locationType, false);
+        if (CollectionUtils.isNotEmpty(zoneIds)) {
+            if (policyId != null && policyId > 0) {
+                throw new InvalidParameterValueException(String.format("%s parameter can not be specified with %s parameter", ApiConstants.ZONE_ID_LIST, ApiConstants.POLICY_ID));
+            }
+            if (Snapshot.LocationType.PRIMARY.equals(locationType)) {
+                throw new InvalidParameterValueException(String.format("%s cannot be specified with snapshot %s as %s", ApiConstants.ZONE_ID_LIST, ApiConstants.LOCATION_TYPE, Snapshot.LocationType.PRIMARY));
+            }
+            if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) {
+                throw new InvalidParameterValueException("Backing up of snapshot has been disabled. Snapshot can not be taken for multiple zones");
+            }
+            if (DataCenter.Type.Edge.equals(zone.getType())) {
+                throw new InvalidParameterValueException("Backing up of snapshot is not supported by the zone of the volume. Snapshot can not be taken for multiple zones");
+            }
+            for (Long zoneId : zoneIds) {
+                DataCenter dataCenter = _dcDao.findById(zoneId);
+                if (dataCenter == null) {
+                    throw new InvalidParameterValueException("Unable to find the specified zone");
+                }
+                if (Grouping.AllocationState.Disabled.equals(dataCenter.getAllocationState()) && !_accountMgr.isRootAdmin(caller.getId())) {
+                    throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + dataCenter.getName());
+                }
+                if (DataCenter.Type.Edge.equals(dataCenter.getType())) {
+                    throw new InvalidParameterValueException("Snapshot functionality is not supported on zone %s");
+                }
+            }
+        }
+
+
+        return snapshotMgr.allocSnapshot(volumeId, policyId, snapshotName, locationType, false, zoneIds);
     }
 
     @Override
@@ -3638,7 +3897,7 @@
             throw new InvalidParameterValueException("Cannot perform this operation, unsupported on storage pool type " + storagePool.getPoolType());
         }
 
-        return snapshotMgr.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, snapshotName, null, true);
+        return snapshotMgr.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, snapshotName, null, true, null);
     }
 
     @Override
@@ -3743,7 +4002,7 @@
                     outcome.get();
                 } catch (InterruptedException e) {
                     throw new RuntimeException("Operation is interrupted", e);
-                } catch (java.util.concurrent.ExecutionException e) {
+                } catch (ExecutionException e) {
                     throw new RuntimeException("Execution excetion", e);
                 }
 
@@ -4168,6 +4427,12 @@
             // if we don't have a host, the VM we are attaching the disk to has never been started before
             if (host != null) {
                 try {
+                    volService.checkAndRepairVolumeBasedOnConfig(volFactory.getVolume(volumeToAttach.getId()), host);
+                } catch (Exception e) {
+                    s_logger.debug(String.format("Unable to check and repair volume [%s] on host [%s], due to %s.", volumeToAttach.getName(), host, e.getMessage()));
+                }
+
+                try {
                     volService.grantAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
                 } catch (Exception e) {
                     volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
@@ -4338,7 +4603,7 @@
         String ioPolicy = null;
         if (vm.getHypervisorType() == HypervisorType.KVM && vm.getDetails() != null && vm.getDetail(VmDetailConstants.IO_POLICY) != null) {
             ioPolicy = vm.getDetail(VmDetailConstants.IO_POLICY);
-            if (IoDriverPolicy.STORAGE_SPECIFIC.toString().equals(ioPolicy)) {
+            if (ApiConstants.IoDriverPolicy.STORAGE_SPECIFIC.toString().equals(ioPolicy)) {
                 String storageIoPolicyDriver = StorageManager.STORAGE_POOL_IO_POLICY.valueIn(poolId);
                 ioPolicy = storageIoPolicyDriver != null ? storageIoPolicyDriver : null;
             }
@@ -4512,6 +4777,24 @@
         }
     }
 
+    public class VmJobCheckAndRepairVolumeOutcome extends OutcomeImpl<Pair> {
+
+        public VmJobCheckAndRepairVolumeOutcome(final AsyncJob job) {
+            super(Pair.class, job, VmJobCheckInterval.value(), new Predicate() {
+                @Override
+                public boolean checkCondition() {
+                    AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId());
+                    assert (jobVo != null);
+                    if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) {
+                        return true;
+                    }
+
+                    return false;
+                }
+            }, AsyncJob.Topics.JOB_STATE);
+        }
+    }
+
     public Outcome<Volume> attachVolumeToVmThroughJobQueue(final Long vmId, final Long volumeId, final Long deviceId) {
 
         final CallContext context = CallContext.current();
@@ -4672,7 +4955,7 @@
     }
 
     public Outcome<Snapshot> takeVolumeSnapshotThroughJobQueue(final Long vmId, final Long volumeId, final Long policyId, final Long snapshotId, final Long accountId, final boolean quiesceVm,
-                                                               final Snapshot.LocationType locationType, final boolean asyncBackup) {
+                                                               final Snapshot.LocationType locationType, final boolean asyncBackup, final List<Long> zoneIds) {
 
         final CallContext context = CallContext.current();
         final User callingUser = context.getCallingUser();
@@ -4694,7 +4977,7 @@
 
         // save work context info (there are some duplications)
         VmWorkTakeVolumeSnapshot workInfo = new VmWorkTakeVolumeSnapshot(callingUser.getId(), accountId != null ? accountId : callingAccount.getId(), vm.getId(),
-                VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType, asyncBackup);
+                VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType, asyncBackup, zoneIds);
         workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
 
         _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
@@ -4744,10 +5027,18 @@
     @ReflectionUse
     private Pair<JobInfo.Status, String> orchestrateTakeVolumeSnapshot(VmWorkTakeVolumeSnapshot work) throws Exception {
         Account account = _accountDao.findById(work.getAccountId());
-        orchestrateTakeVolumeSnapshot(work.getVolumeId(), work.getPolicyId(), work.getSnapshotId(), account, work.isQuiesceVm(), work.getLocationType(), work.isAsyncBackup());
+        orchestrateTakeVolumeSnapshot(work.getVolumeId(), work.getPolicyId(), work.getSnapshotId(), account,
+                work.isQuiesceVm(), work.getLocationType(), work.isAsyncBackup(), work.getZoneIds());
         return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(work.getSnapshotId()));
     }
 
+    @ReflectionUse
+    private Pair<JobInfo.Status, String> orchestrateCheckAndRepairVolume(VmWorkCheckAndRepairVolume work) throws Exception {
+        Account account = _accountDao.findById(work.getAccountId());
+        Pair<String, String> result = orchestrateCheckAndRepairVolume(work.getVolumeId(), work.getRepair());
+        return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result));
+    }
+
     @Override
     public Pair<JobInfo.Status, String> handleVmWorkJob(VmWork work) throws Exception {
         return _jobHandlerProxy.handleVmWorkJob(work);
@@ -4784,7 +5075,8 @@
                 AllowUserExpungeRecoverVolume,
                 MatchStoragePoolTagsWithDiskOffering,
                 UseHttpsToUpload,
-                WaitDetachDevice
+                WaitDetachDevice,
+                AllowCheckAndRepairVolume
         };
     }
 }
diff --git a/server/src/main/java/com/cloud/storage/download/DownloadListener.java b/server/src/main/java/com/cloud/storage/download/DownloadListener.java
index 9f52819..7cd2e2a 100644
--- a/server/src/main/java/com/cloud/storage/download/DownloadListener.java
+++ b/server/src/main/java/com/cloud/storage/download/DownloadListener.java
@@ -181,6 +181,8 @@
                 DownloadProgressCommand dcmd = new DownloadProgressCommand(getCommand(), getJobId(), reqType);
                 if (object.getType() == DataObjectType.VOLUME) {
                     dcmd.setResourceType(ResourceType.VOLUME);
+                } else if (object.getType() == DataObjectType.SNAPSHOT) {
+                    dcmd.setResourceType(ResourceType.SNAPSHOT);
                 }
                 _ssAgent.sendMessageAsync(dcmd, new UploadListener.Callback(_ssAgent.getId(), this));
             } catch (Exception e) {
diff --git a/server/src/main/java/com/cloud/storage/download/DownloadMonitor.java b/server/src/main/java/com/cloud/storage/download/DownloadMonitor.java
index b93c982..028a957 100644
--- a/server/src/main/java/com/cloud/storage/download/DownloadMonitor.java
+++ b/server/src/main/java/com/cloud/storage/download/DownloadMonitor.java
@@ -32,4 +32,6 @@
 
     public void downloadVolumeToStorage(DataObject volume, AsyncCompletionCallback<DownloadAnswer> callback);
 
+    void downloadSnapshotToStorage(DataObject volume, AsyncCompletionCallback<DownloadAnswer> callback);
+
 }
diff --git a/server/src/main/java/com/cloud/storage/download/DownloadMonitorImpl.java b/server/src/main/java/com/cloud/storage/download/DownloadMonitorImpl.java
index 1954cde..90782dd 100644
--- a/server/src/main/java/com/cloud/storage/download/DownloadMonitorImpl.java
+++ b/server/src/main/java/com/cloud/storage/download/DownloadMonitorImpl.java
@@ -25,9 +25,6 @@
 
 import javax.inject.Inject;
 
-import org.apache.log4j.Logger;
-import org.springframework.stereotype.Component;
-
 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.EndPoint;
@@ -39,17 +36,22 @@
 import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
 import org.apache.cloudstack.storage.command.DownloadProgressCommand;
 import org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
 
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.storage.DownloadAnswer;
-import com.cloud.utils.net.Proxy;
 import com.cloud.configuration.Config;
+import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.RegisterVolumePayload;
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
@@ -58,6 +60,7 @@
 import com.cloud.utils.component.ComponentContext;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.Proxy;
 
 @Component
 public class DownloadMonitorImpl extends ManagerBase implements DownloadMonitor {
@@ -68,6 +71,8 @@
     @Inject
     private VolumeDataStoreDao _volumeStoreDao;
     @Inject
+    private SnapshotDataStoreDao snapshotDataStoreDao;
+    @Inject
     private AgentManager _agentMgr;
     @Inject
     private ConfigurationDao _configDao;
@@ -115,6 +120,12 @@
         return (downloadsInProgress.size() == 0);
     }
 
+    public boolean isSnapshotUpdateable(Long snapshotId, Long storeId) {
+        List<SnapshotDataStoreVO> downloadsInProgress =
+                snapshotDataStoreDao.listBySnasphotStoreDownloadStatus(snapshotId, storeId, Status.DOWNLOAD_IN_PROGRESS, Status.DOWNLOADED);
+        return downloadsInProgress.isEmpty();
+    }
+
     private void initiateTemplateDownload(DataObject template, AsyncCompletionCallback<DownloadAnswer> callback) {
         boolean downloadJobExists = false;
         TemplateDataStoreVO vmTemplateStore;
@@ -169,6 +180,63 @@
         }
     }
 
+    private void initiateSnapshotDownload(DataObject snapshot, AsyncCompletionCallback<DownloadAnswer> callback) {
+        boolean downloadJobExists = false;
+        DataStore store = snapshot.getDataStore();
+
+        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), snapshot.getId());
+        if (snapshotStore == null) {
+            snapshotStore =
+                    new SnapshotDataStoreVO(store.getId(), snapshot.getId());
+            snapshotStore.setLastUpdated(new Date());
+            snapshotStore.setDownloadPercent(0);
+            snapshotStore.setDownloadState(Status.NOT_DOWNLOADED);
+            snapshotStore.setLocalDownloadPath(null);
+            snapshotStore.setErrorString(null);
+            snapshotStore.setJobId("jobid0000");
+            snapshotStore.setRole(store.getRole());
+            snapshotStore = snapshotDataStoreDao.persist(snapshotStore);
+        } else if ((snapshotStore.getJobId() != null) && (snapshotStore.getJobId().length() > 2)) {
+            downloadJobExists = true;
+        }
+
+        Long maxSizeInBytes = getMaxSnapshotSizeInBytes();
+        if (snapshotStore != null) {
+            start();
+            DownloadCommand dcmd = new DownloadCommand((SnapshotObjectTO)(snapshot.getTO()), maxSizeInBytes, snapshot.getUri());
+            dcmd.setProxy(getHttpProxy());
+            if (downloadJobExists) {
+                dcmd = new DownloadProgressCommand(dcmd, snapshotStore.getJobId(), RequestType.GET_OR_RESTART);
+                dcmd.setResourceType(ResourceType.SNAPSHOT);
+            }
+            EndPoint ep = _epSelector.select(snapshot);
+            if (ep == null) {
+                String errMsg = "There is no secondary storage VM for downloading snapshot to image store " + store.getName();
+                LOGGER.warn(errMsg);
+                throw new CloudRuntimeException(errMsg);
+            }
+            DownloadListener dl = new DownloadListener(ep, store, snapshot, _timer, this, dcmd, callback);
+            ComponentContext.inject(dl);  // initialize those auto-wired field in download listener.
+            if (downloadJobExists) {
+                // due to handling existing download job issues, we still keep
+                // downloadState in template_store_ref to avoid big change in
+                // DownloadListener to use
+                // new ObjectInDataStore.State transition. TODO: fix this later
+                // to be able to remove downloadState from template_store_ref.
+                LOGGER.info("found existing download job");
+                dl.setCurrState(snapshotStore.getDownloadState());
+            }
+
+            try {
+                ep.sendMessageAsync(dcmd, new UploadListener.Callback(ep.getId(), dl));
+            } catch (Exception e) {
+                LOGGER.warn("Unable to start /resume download of snapshot " + snapshot.getId() + " to " + store.getName(), e);
+                dl.setDisconnected();
+                dl.scheduleStatusCheck(RequestType.GET_OR_RESTART);
+            }
+        }
+    }
+
     @Override
     public void downloadTemplateToStorage(DataObject template, AsyncCompletionCallback<DownloadAnswer> callback) {
         if(template != null) {
@@ -245,6 +313,26 @@
         }
     }
 
+    @Override
+    public void downloadSnapshotToStorage(DataObject snapshot, AsyncCompletionCallback<DownloadAnswer> callback) {
+        long snapshotId = snapshot.getId();
+        DataStore store = snapshot.getDataStore();
+        if (isSnapshotUpdateable(snapshotId, store.getId())) {
+            if (snapshot.getUri() != null) {
+                initiateSnapshotDownload(snapshot, callback);
+            } else {
+                LOGGER.info("Snapshot url is null, cannot download");
+                DownloadAnswer ans = new DownloadAnswer("Snapshot url is null", Status.UNKNOWN);
+                callback.complete(ans);
+            }
+        } else {
+            LOGGER.info("Snapshot download is already in progress or already downloaded");
+            DownloadAnswer ans =
+                    new DownloadAnswer("Snapshot download is already in progress or already downloaded", Status.UNKNOWN);
+            callback.complete(ans);
+        }
+    }
+
     private Long getMaxTemplateSizeInBytes() {
         try {
             return Long.parseLong(_configDao.getValue("max.template.iso.size")) * 1024L * 1024L * 1024L;
@@ -261,6 +349,14 @@
         }
     }
 
+    private Long getMaxSnapshotSizeInBytes() {
+        try {
+            return Long.parseLong(_configDao.getValue("storage.max.volume.upload.size")) * 1024L * 1024L * 1024L;
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
     private Proxy getHttpProxy() {
         if (_proxy == null) {
             return null;
diff --git a/server/src/main/java/com/cloud/storage/listener/SnapshotStateListener.java b/server/src/main/java/com/cloud/storage/listener/SnapshotStateListener.java
index d8d036f..c68b05c 100644
--- a/server/src/main/java/com/cloud/storage/listener/SnapshotStateListener.java
+++ b/server/src/main/java/com/cloud/storage/listener/SnapshotStateListener.java
@@ -106,7 +106,7 @@
         try {
             s_eventBus.publish(eventMsg);
         } catch (EventBusException e) {
-            s_logger.warn("Failed to publish state change event on the the event bus.");
+            s_logger.warn("Failed to publish state change event on the event bus.");
         }
     }
 
diff --git a/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java b/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java
index 63ae604..d610104 100644
--- a/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java
+++ b/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java
@@ -101,7 +101,7 @@
                 scCmd.getHypervisorType() == HypervisorType.Ovm || scCmd.getHypervisorType() == HypervisorType.Hyperv ||
                 scCmd.getHypervisorType() == HypervisorType.LXC || scCmd.getHypervisorType() == HypervisorType.Ovm3) {
                 List<StoragePoolVO> pools = _poolDao.listBy(host.getDataCenterId(), host.getPodId(), host.getClusterId(), ScopeType.CLUSTER);
-                List<StoragePoolVO> zoneStoragePoolsByTags = _poolDao.findZoneWideStoragePoolsByTags(host.getDataCenterId(), null);
+                List<StoragePoolVO> zoneStoragePoolsByTags = _poolDao.findZoneWideStoragePoolsByTags(host.getDataCenterId(), null, false);
                 List<StoragePoolVO> zoneStoragePoolsByHypervisor = _poolDao.findZoneWideStoragePoolsByHypervisor(host.getDataCenterId(), scCmd.getHypervisorType());
                 zoneStoragePoolsByTags.retainAll(zoneStoragePoolsByHypervisor);
                 pools.addAll(zoneStoragePoolsByTags);
diff --git a/server/src/main/java/com/cloud/storage/listener/VolumeStateListener.java b/server/src/main/java/com/cloud/storage/listener/VolumeStateListener.java
index bc4ae4c..d2a4dc9 100644
--- a/server/src/main/java/com/cloud/storage/listener/VolumeStateListener.java
+++ b/server/src/main/java/com/cloud/storage/listener/VolumeStateListener.java
@@ -122,7 +122,7 @@
         try {
             s_eventBus.publish(eventMsg);
         } catch (EventBusException e) {
-            s_logger.warn("Failed to state change event on the the event bus.");
+            s_logger.warn("Failed to state change event on the event bus.");
         }
     }
 
diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java
index 7219e0d..dd63371 100644
--- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java
+++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java
@@ -70,8 +70,6 @@
      */
     boolean deleteSnapshotDirsForAccount(long accountId);
 
-    String getSecondaryStorageURL(SnapshotVO snapshot);
-
     //void deleteSnapshotsDirForVolume(String secondaryStoragePoolUrl, Long dcId, Long accountId, Long volumeId);
 
     boolean canOperateOnVolume(Volume volume);
diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
index aeb095b..940860d 100755
--- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java
@@ -19,25 +19,32 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
 import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
 import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
 import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd;
 import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
 import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
@@ -45,6 +52,7 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
@@ -52,10 +60,13 @@
 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.ZoneScope;
+import org.apache.cloudstack.framework.async.AsyncCallFuture;
 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.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO;
+import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao;
 import org.apache.cloudstack.snapshot.SnapshotHelper;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
@@ -64,6 +75,7 @@
 import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import org.apache.log4j.Logger;
@@ -91,9 +103,11 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.PermissionDeniedException;
 import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.exception.StorageUnavailableException;
 import com.cloud.host.HostVO;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.org.Grouping;
 import com.cloud.projects.Project.ListProjectResourcesCriteria;
 import com.cloud.resource.ResourceManager;
 import com.cloud.server.ResourceTag.ResourceObjectType;
@@ -112,6 +126,7 @@
 import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
@@ -119,6 +134,7 @@
 import com.cloud.storage.dao.SnapshotDao;
 import com.cloud.storage.dao.SnapshotPolicyDao;
 import com.cloud.storage.dao.SnapshotScheduleDao;
+import com.cloud.storage.dao.SnapshotZoneDao;
 import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.template.TemplateConstants;
@@ -174,7 +190,9 @@
     @Inject
     PrimaryDataStoreDao _storagePoolDao;
     @Inject
-    SnapshotPolicyDao _snapshotPolicyDao = null;
+    SnapshotPolicyDao _snapshotPolicyDao;
+    @Inject
+    SnapshotPolicyDetailsDao snapshotPolicyDetailsDao;
     @Inject
     SnapshotScheduleDao _snapshotScheduleDao;
     @Inject
@@ -225,6 +243,8 @@
     @Inject
     DataCenterDao dataCenterDao;
     @Inject
+    SnapshotZoneDao snapshotZoneDao;
+    @Inject
     VMSnapshotDetailsDao vmSnapshotDetailsDao;
     @Inject
     SnapshotDataFactory snapshotDataFactory;
@@ -235,6 +255,17 @@
 
     private ScheduledExecutorService backupSnapshotExecutor;
 
+    protected DataStore getSnapshotZoneImageStore(long snapshotId, long zoneId) {
+        List<SnapshotDataStoreVO> snapshotImageStoreList = _snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
+        for (SnapshotDataStoreVO ref : snapshotImageStoreList) {
+            Long entryZoneId = dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole());
+            if (entryZoneId != null && entryZoneId.equals(zoneId)) {
+                return dataStoreMgr.getDataStore(ref.getDataStoreId(), ref.getRole());
+            }
+        }
+        return null;
+    }
+
     protected boolean isBackupSnapshotToSecondaryForZone(long zoneId) {
         if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) {
             return false;
@@ -341,7 +372,7 @@
 
         DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot);
 
-        SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotId, dataStoreRole);
+        SnapshotInfo snapshotInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshotId, dataStoreRole, volume.getDataCenterId());
 
         if (snapshotInfo == null) {
             throw new CloudRuntimeException(String.format("snapshot %s [%s] does not exists in data store", snapshot.getName(), snapshot.getUuid()));
@@ -414,7 +445,7 @@
         // does the caller have the authority to act on this volume
         _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
 
-        SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
+        SnapshotInfo snapshot = snapshotFactory.getSnapshotOnPrimaryStore(snapshotId);
         if (snapshot == null) {
             s_logger.debug("Failed to create snapshot");
             throw new CloudRuntimeException("Failed to create snapshot");
@@ -439,7 +470,7 @@
 
     @Override
     public Snapshot archiveSnapshot(Long snapshotId) {
-        SnapshotInfo snapshotOnPrimary = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
+        SnapshotInfo snapshotOnPrimary = snapshotFactory.getSnapshotOnPrimaryStore(snapshotId);
 
         if (snapshotOnPrimary == null || !snapshotOnPrimary.getStatus().equals(ObjectInDataStoreStateMachine.State.Ready)) {
             throw new CloudRuntimeException("Can only archive snapshots present on primary storage. " + "Cannot find snapshot " + snapshotId + " on primary storage");
@@ -539,7 +570,7 @@
         if ((storagePool.getPoolType() == StoragePoolType.NetworkFilesystem || storagePool.getPoolType() == StoragePoolType.Filesystem) && vmSnapshot.getType() == VMSnapshot.Type.Disk) {
             List<VMSnapshotDetailsVO> vmSnapshotDetails = vmSnapshotDetailsDao.findDetails(vmSnapshotId, "kvmStorageSnapshot");
             for (VMSnapshotDetailsVO vmSnapshotDetailsVO : vmSnapshotDetails) {
-                SnapshotInfo sInfo = snapshotDataFactory.getSnapshot(Long.parseLong(vmSnapshotDetailsVO.getValue()), DataStoreRole.Primary);
+                SnapshotInfo sInfo = snapshotDataFactory.getSnapshot(Long.parseLong(vmSnapshotDetailsVO.getValue()), storagePool.getId(), DataStoreRole.Primary);
                 if (sInfo.getVolumeId() == volumeId) {
                     snapshotOnPrimaryStore.setState(ObjectInDataStoreStateMachine.State.Ready);
                     snapshotOnPrimaryStore.setInstallPath(sInfo.getPath());
@@ -608,7 +639,7 @@
             if (policy != null) {
                 s_logger.debug("Max snaps: " + policy.getMaxSnaps() + " exceeded for snapshot policy with Id: " + policyId + ". Deleting oldest snapshot: " + oldSnapId);
             }
-            if (deleteSnapshot(oldSnapId)) {
+            if (deleteSnapshot(oldSnapId, null)) {
                 //log Snapshot delete event
                 ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, oldestSnapshot.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_SNAPSHOT_DELETE,
                         "Successfully deleted oldest snapshot: " + oldSnapId, oldSnapId, ApiCommandResourceType.Snapshot.toString(), 0);
@@ -617,14 +648,42 @@
         }
     }
 
+    protected Pair<List<SnapshotDataStoreVO>, List<Long>> getStoreRefsAndZonesForSnapshotDelete(long snapshotId, Long zoneId) {
+        List<SnapshotDataStoreVO> snapshotStoreRefs = new ArrayList<>();
+        List<SnapshotDataStoreVO> allSnapshotStoreRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
+        List<Long> zoneIds = new ArrayList<>();
+        if (zoneId != null) {
+            DataCenterVO zone = dataCenterDao.findById(zoneId);
+            if (zone == null) {
+                throw new InvalidParameterValueException("Unable to find a zone with the specified id");
+            }
+            for (SnapshotDataStoreVO snapshotStore : allSnapshotStoreRefs) {
+                Long entryZoneId = dataStoreMgr.getStoreZoneId(snapshotStore.getDataStoreId(), snapshotStore.getRole());
+                if (zoneId.equals(entryZoneId)) {
+                    snapshotStoreRefs.add(snapshotStore);
+                }
+            }
+            zoneIds.add(zoneId);
+        } else {
+            snapshotStoreRefs = allSnapshotStoreRefs;
+            for (SnapshotDataStoreVO snapshotStore : snapshotStoreRefs) {
+                Long entryZoneId = dataStoreMgr.getStoreZoneId(snapshotStore.getDataStoreId(), snapshotStore.getRole());
+                if (!zoneIds.contains(entryZoneId)) {
+                    zoneIds.add(entryZoneId);
+                }
+            }
+        }
+        return new Pair<>(snapshotStoreRefs, zoneIds);
+    }
+
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_DELETE, eventDescription = "deleting snapshot", async = true)
-    public boolean deleteSnapshot(long snapshotId) {
+    public boolean deleteSnapshot(long snapshotId, Long zoneId) {
         Account caller = CallContext.current().getCallingAccount();
 
         // Verify parameters
-        SnapshotVO snapshotCheck = _snapshotDao.findById(snapshotId);
+        final SnapshotVO snapshotCheck = _snapshotDao.findById(snapshotId);
 
         if (snapshotCheck == null) {
             throw new InvalidParameterValueException("unable to find a snapshot with id " + snapshotId);
@@ -640,35 +699,36 @@
 
         _accountMgr.checkAccess(caller, null, true, snapshotCheck);
 
-        SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshotCheck, SnapshotOperation.DELETE);
+        SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshotCheck, zoneId, SnapshotOperation.DELETE);
 
         if (snapshotStrategy == null) {
             s_logger.error("Unable to find snapshot strategy to handle snapshot with id '" + snapshotId + "'");
 
             return false;
         }
-
-        DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshotCheck);
-
-        SnapshotDataStoreVO snapshotStoreRef = _snapshotStoreDao.findBySnapshot(snapshotId, dataStoreRole);
+        Pair<List<SnapshotDataStoreVO>, List<Long>> storeRefAndZones = getStoreRefsAndZonesForSnapshotDelete(snapshotId, zoneId);
+        List<SnapshotDataStoreVO> snapshotStoreRefs = storeRefAndZones.first();
+        List<Long> zoneIds = storeRefAndZones.second();
 
         try {
-            boolean result = snapshotStrategy.deleteSnapshot(snapshotId);
-
+            boolean result = snapshotStrategy.deleteSnapshot(snapshotId, zoneId);
             if (result) {
-                annotationDao.removeByEntityType(AnnotationService.EntityType.SNAPSHOT.name(), snapshotCheck.getUuid());
-
-                if (snapshotCheck.getState() == Snapshot.State.BackedUp) {
-                    UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), snapshotCheck.getDataCenterId(), snapshotId,
-                            snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid());
+                for (Long zId : zoneIds) {
+                    if (snapshotCheck.getState() == Snapshot.State.BackedUp) {
+                        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), zId, snapshotId,
+                                snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid());
+                    }
                 }
+                final SnapshotVO postDeleteSnapshotEntry = _snapshotDao.findById(snapshotId);
+                if (postDeleteSnapshotEntry == null || Snapshot.State.Destroyed.equals(postDeleteSnapshotEntry.getState())) {
+                    annotationDao.removeByEntityType(AnnotationService.EntityType.SNAPSHOT.name(), snapshotCheck.getUuid());
 
-                if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed) {
-                    _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot);
+                    if (snapshotCheck.getState() != Snapshot.State.Error && snapshotCheck.getState() != Snapshot.State.Destroyed) {
+                        _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.snapshot);
+                    }
                 }
-
-                if (snapshotCheck.getState() == Snapshot.State.BackedUp) {
-                    if (snapshotStoreRef != null) {
+                for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) {
+                    if (ObjectInDataStoreStateMachine.State.Ready.equals(snapshotStoreRef.getState()) && !DataStoreRole.Primary.equals(snapshotStoreRef.getRole())) {
                         _resourceLimitMgr.decrementResourceCount(snapshotCheck.getAccountId(), ResourceType.secondary_storage, new Long(snapshotStoreRef.getPhysicalSize()));
                     }
                 }
@@ -683,18 +743,6 @@
     }
 
     @Override
-    public String getSecondaryStorageURL(SnapshotVO snapshot) {
-        SnapshotDataStoreVO snapshotStore = _snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
-        if (snapshotStore != null) {
-            DataStore store = dataStoreMgr.getDataStore(snapshotStore.getDataStoreId(), DataStoreRole.Image);
-            if (store != null) {
-                return store.getUri();
-            }
-        }
-        throw new CloudRuntimeException("Can not find secondary storage hosting the snapshot");
-    }
-
-    @Override
     public Pair<List<? extends Snapshot>, Integer> listSnapshots(ListSnapshotsCmd cmd) {
         Long volumeId = cmd.getVolumeId();
         String name = cmd.getSnapshotName();
@@ -863,12 +911,12 @@
                     s_logger.error("Unable to find snapshot strategy to handle snapshot with id '" + snapshot.getId() + "'");
                     continue;
                 }
-                SnapshotDataStoreVO snapshotStoreRef = _snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
+                List<SnapshotDataStoreVO> snapshotStoreRefs = _snapshotStoreDao.listReadyBySnapshot(snapshot.getId(), DataStoreRole.Image);
 
-                if (snapshotStrategy.deleteSnapshot(snapshot.getId())) {
+                if (snapshotStrategy.deleteSnapshot(snapshot.getId(), null)) {
                     if (Type.MANUAL == snapshot.getRecurringType()) {
                         _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.snapshot);
-                        if (snapshotStoreRef != null) {
+                        for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) {
                             _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.secondary_storage, new Long(snapshotStoreRef.getPhysicalSize()));
                         }
                     }
@@ -884,6 +932,23 @@
         return success;
     }
 
+    protected void validatePolicyZones(List<Long> zoneIds, VolumeVO volume, Account caller) {
+        if (CollectionUtils.isEmpty(zoneIds)) {
+            return;
+        }
+        if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) {
+            throw new InvalidParameterValueException("Backing up of snapshot has been disabled. Snapshot can not be taken for multiple zones");
+        }
+        final DataCenterVO zone = dataCenterDao.findById(volume.getDataCenterId());
+        if (DataCenter.Type.Edge.equals(zone.getType())) {
+            throw new InvalidParameterValueException("Backing up of snapshot is not supported by the zone of the volume. Snapshots can not be taken for multiple zones");
+        }
+        boolean isRootAdminCaller = _accountMgr.isRootAdmin(caller.getId());
+        for (Long zoneId : zoneIds) {
+            getCheckedDestinationZoneForSnapshotCopy(zoneId, isRootAdminCaller);
+        }
+    }
+
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_POLICY_CREATE, eventDescription = "creating snapshot policy")
@@ -905,7 +970,8 @@
 
         String volumeDescription = volume.getVolumeDescription();
 
-        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
+        final Account caller = CallContext.current().getCallingAccount();
+        _accountMgr.checkAccess(caller, null, true, volume);
 
         // If display is false we don't actually schedule snapshots.
         if (volume.getState() != Volume.State.Ready && display) {
@@ -984,13 +1050,16 @@
             }
         }
 
+        final List<Long> zoneIds = cmd.getZoneIds();
+        validatePolicyZones(zoneIds, volume, caller);
+
         Map<String, String> tags = cmd.getTags();
         boolean active = true;
 
-        return persistSnapshotPolicy(volume, schedule, timezoneId, intvType, maxSnaps, display, active, tags);
+        return persistSnapshotPolicy(volume, schedule, timezoneId, intvType, maxSnaps, display, active, tags, zoneIds);
     }
 
-    protected SnapshotPolicyVO persistSnapshotPolicy(VolumeVO volume, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, boolean active, Map<String, String> tags) {
+    protected SnapshotPolicyVO persistSnapshotPolicy(VolumeVO volume, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, boolean active, Map<String, String> tags, List<Long> zoneIds) {
         long volumeId = volume.getId();
         String volumeDescription = volume.getVolumeDescription();
 
@@ -998,7 +1067,7 @@
         boolean isLockAcquired = createSnapshotPolicyLock.lock(5);
 
         if (!isLockAcquired) {
-            throw new CloudRuntimeException(String.format("Unable to aquire lock for creating snapshot policy [%s] for %s.", intervalType, volumeDescription));
+            throw new CloudRuntimeException(String.format("Unable to acquire lock for creating snapshot policy [%s] for %s.", intervalType, volumeDescription));
         }
 
         s_logger.debug(String.format("Acquired lock for creating snapshot policy [%s] for volume %s.", intervalType, volumeDescription));
@@ -1007,9 +1076,9 @@
             SnapshotPolicyVO policy = _snapshotPolicyDao.findOneByVolumeInterval(volumeId, intervalType);
 
             if (policy == null) {
-                policy = createSnapshotPolicy(volumeId, schedule, timezone, intervalType, maxSnaps, display);
+                policy = createSnapshotPolicy(volumeId, schedule, timezone, intervalType, maxSnaps, display, zoneIds);
             } else {
-                updateSnapshotPolicy(policy, schedule, timezone, intervalType, maxSnaps, active, display);
+                updateSnapshotPolicy(policy, schedule, timezone, intervalType, maxSnaps, active, display, zoneIds);
             }
 
             createTagsForSnapshotPolicy(tags, policy);
@@ -1021,15 +1090,22 @@
         }
     }
 
-    protected SnapshotPolicyVO createSnapshotPolicy(long volumeId, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display) {
+    protected SnapshotPolicyVO createSnapshotPolicy(long volumeId, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, List<Long> zoneIds) {
         SnapshotPolicyVO policy = new SnapshotPolicyVO(volumeId, schedule, timezone, intervalType, maxSnaps, display);
         policy = _snapshotPolicyDao.persist(policy);
+        if (CollectionUtils.isNotEmpty(zoneIds)) {
+            List<SnapshotPolicyDetailVO> details = new ArrayList<>();
+            for (Long zoneId : zoneIds) {
+                details.add(new SnapshotPolicyDetailVO(policy.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId)));
+            }
+            snapshotPolicyDetailsDao.saveDetails(details);
+        }
         _snapSchedMgr.scheduleNextSnapshotJob(policy);
         s_logger.debug(String.format("Created snapshot policy %s.", new ReflectionToStringBuilder(policy, ToStringStyle.JSON_STYLE).setExcludeFieldNames("id", "uuid", "active")));
         return policy;
     }
 
-    protected void updateSnapshotPolicy(SnapshotPolicyVO policy, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean active, boolean display) {
+    protected void updateSnapshotPolicy(SnapshotPolicyVO policy, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean active, boolean display, List<Long> zoneIds) {
         String previousPolicy = new ReflectionToStringBuilder(policy, ToStringStyle.JSON_STYLE).setExcludeFieldNames("id", "uuid").toString();
         boolean previousDisplay = policy.isDisplay();
         policy.setSchedule(schedule);
@@ -1039,6 +1115,15 @@
         policy.setActive(active);
         policy.setDisplay(display);
         _snapshotPolicyDao.update(policy.getId(), policy);
+        if (CollectionUtils.isNotEmpty(zoneIds)) {
+            List<SnapshotPolicyDetailVO> details = snapshotPolicyDetailsDao.listDetails(policy.getId());
+            details = details.stream().filter(d -> !ApiConstants.ZONE_ID.equals(d.getName())).collect(Collectors.toList());
+            for (Long zoneId : zoneIds) {
+                details.add(new SnapshotPolicyDetailVO(policy.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId)));
+            }
+            snapshotPolicyDetailsDao.saveDetails(details);
+        }
+
         _snapSchedMgr.scheduleOrCancelNextSnapshotJobOnDisplayChange(policy, previousDisplay);
         taggedResourceService.deleteTags(Collections.singletonList(policy.getUuid()), ResourceObjectType.SnapshotPolicy, null);
         s_logger.debug(String.format("Updated snapshot policy %s to %s.", previousPolicy, new ReflectionToStringBuilder(policy, ToStringStyle.JSON_STYLE)
@@ -1059,10 +1144,12 @@
         s_logger.debug(String.format("Copying snapshot policies %s from volume %s to volume %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(policies,
           "id", "uuid"), srcVolume.getVolumeDescription(), destVolume.getVolumeDescription()));
 
-        policies.forEach(policy ->
-          persistSnapshotPolicy(destVolume, policy.getSchedule(), policy.getTimezone(), intervalTypes[policy.getInterval()], policy.getMaxSnaps(),
-            policy.isDisplay(), policy.isActive(), taggedResourceService.getTagsFromResource(ResourceObjectType.SnapshotPolicy, policy.getId()))
-        );
+        for (SnapshotPolicyVO policy : policies) {
+            List<SnapshotPolicyDetailVO> details = snapshotPolicyDetailsDao.findDetails(policy.getId(), ApiConstants.ZONE_ID);
+            List<Long> zoneIds = details.stream().map(d -> Long.valueOf(d.getValue())).collect(Collectors.toList());
+            persistSnapshotPolicy(destVolume, policy.getSchedule(), policy.getTimezone(), intervalTypes[policy.getInterval()], policy.getMaxSnaps(),
+                    policy.isDisplay(), policy.isActive(), taggedResourceService.getTagsFromResource(ResourceObjectType.SnapshotPolicy, policy.getId()), zoneIds);
+        }
     }
 
     protected boolean deletePolicy(Long policyId) {
@@ -1298,7 +1385,7 @@
             boolean backupSnapToSecondary = isBackupSnapshotToSecondaryForZone(snapshot.getDataCenterId());
 
             if (backupSnapToSecondary) {
-                backupSnapshotToSecondary(payload.getAsyncBackup(), snapshotStrategy, snapshotOnPrimary);
+                backupSnapshotToSecondary(payload.getAsyncBackup(), snapshotStrategy, snapshotOnPrimary, payload.getZoneIds());
             } else {
                 s_logger.debug("skipping backup of snapshot [uuid=" + snapshot.getUuid() + "] to secondary due to configuration");
                 snapshotOnPrimary.markBackedUp();
@@ -1306,18 +1393,24 @@
 
             try {
                 postCreateSnapshot(volume.getId(), snapshotId, payload.getSnapshotPolicyId());
+                snapshotZoneDao.addSnapshotToZone(snapshotId, snapshot.getDataCenterId());
 
                 DataStoreRole dataStoreRole = backupSnapToSecondary ? snapshotHelper.getDataStoreRole(snapshot) : DataStoreRole.Primary;
 
-                SnapshotDataStoreVO snapshotStoreRef = _snapshotStoreDao.findBySnapshot(snapshotId, dataStoreRole);
-                if (snapshotStoreRef == null) {
+                List<SnapshotDataStoreVO> snapshotStoreRefs = _snapshotStoreDao.listReadyBySnapshot(snapshotId, dataStoreRole);
+                if (CollectionUtils.isEmpty(snapshotStoreRefs)) {
                     throw new CloudRuntimeException(String.format("Could not find snapshot %s [%s] on [%s]", snapshot.getName(), snapshot.getUuid(), snapshot.getLocationType()));
                 }
+                SnapshotDataStoreVO snapshotStoreRef = snapshotStoreRefs.get(0);
                 UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_CREATE, snapshot.getAccountId(), snapshot.getDataCenterId(), snapshotId, snapshot.getName(), null, null,
                         snapshotStoreRef.getPhysicalSize(), volume.getSize(), snapshot.getClass().getName(), snapshot.getUuid());
 
                 // Correct the resource count of snapshot in case of delta snapshots.
                 _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.secondary_storage, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize()));
+
+                if (!payload.getAsyncBackup() && backupSnapToSecondary) {
+                    copyNewSnapshotToZones(snapshotId, snapshot.getDataCenterId(), payload.getZoneIds());
+                }
             } catch (Exception e) {
                 s_logger.debug("post process snapshot failed", e);
             }
@@ -1339,9 +1432,9 @@
         return snapshot;
     }
 
-    protected void backupSnapshotToSecondary(boolean asyncBackup, SnapshotStrategy snapshotStrategy, SnapshotInfo snapshotOnPrimary) {
+    protected void backupSnapshotToSecondary(boolean asyncBackup, SnapshotStrategy snapshotStrategy, SnapshotInfo snapshotOnPrimary, List<Long> zoneIds) {
         if (asyncBackup) {
-            backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshotOnPrimary, snapshotBackupRetries - 1, snapshotStrategy), 0, TimeUnit.SECONDS);
+            backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshotOnPrimary, snapshotBackupRetries - 1, snapshotStrategy, zoneIds), 0, TimeUnit.SECONDS);
         } else {
             SnapshotInfo backupedSnapshot = snapshotStrategy.backupSnapshot(snapshotOnPrimary);
             if (backupedSnapshot != null) {
@@ -1355,10 +1448,13 @@
         int attempts;
         SnapshotStrategy snapshotStrategy;
 
-        public BackupSnapshotTask(SnapshotInfo snap, int maxRetries, SnapshotStrategy strategy) {
+        List<Long> zoneIds;
+
+        public BackupSnapshotTask(SnapshotInfo snap, int maxRetries, SnapshotStrategy strategy, List<Long> zoneIds) {
             snapshot = snap;
             attempts = maxRetries;
             snapshotStrategy = strategy;
+            this.zoneIds = zoneIds;
         }
 
         @Override
@@ -1370,11 +1466,12 @@
 
                 if (backupedSnapshot != null) {
                     snapshotStrategy.postSnapshotCreation(snapshot);
+                    copyNewSnapshotToZones(snapshot.getId(), snapshot.getDataCenterId(), zoneIds);
                 }
             } catch (final Exception e) {
                 if (attempts >= 0) {
                     s_logger.debug("Backing up of snapshot failed, for snapshot with ID " + snapshot.getSnapshotId() + ", left with " + attempts + " more attempts");
-                    backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshot, --attempts, snapshotStrategy), snapshotBackupRetryInterval, TimeUnit.SECONDS);
+                    backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshot, --attempts, snapshotStrategy, zoneIds), snapshotBackupRetryInterval, TimeUnit.SECONDS);
                 } else {
                     s_logger.debug("Done with " + snapshotBackupRetries + " attempts in  backing up of snapshot with ID " + snapshot.getSnapshotId());
                     snapshotSrv.cleanupOnSnapshotBackupFailure(snapshot);
@@ -1423,7 +1520,7 @@
         List<SnapshotVO> snapshots = _snapshotDao.listAllByStatus(Snapshot.State.Destroying);
         for (SnapshotVO snapshotVO : snapshots) {
             try {
-                if (!deleteSnapshot(snapshotVO.getId())) {
+                if (!deleteSnapshot(snapshotVO.getId(), null)) {
                     s_logger.debug("Failed to delete snapshot in destroying state with id " + snapshotVO.getUuid());
                 }
             } catch (Exception e) {
@@ -1503,7 +1600,7 @@
 
     @Override
     public void cleanupSnapshotsByVolume(Long volumeId) {
-        List<SnapshotInfo> infos = snapshotFactory.getSnapshots(volumeId, DataStoreRole.Primary);
+        List<SnapshotInfo> infos = snapshotFactory.getSnapshotsForVolumeAndStoreRole(volumeId, DataStoreRole.Primary);
         for (SnapshotInfo info : infos) {
             try {
                 if (info != null) {
@@ -1518,11 +1615,11 @@
 
     @Override
     public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException {
-        return allocSnapshot(volumeId, policyId, snapshotName, locationType, false);
+        return allocSnapshot(volumeId, policyId, snapshotName, locationType, false, null);
     }
 
     @Override
-    public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, Boolean isFromVmSnapshot) throws ResourceAllocationException {
+    public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, Boolean isFromVmSnapshot, List<Long> zoneIds) throws ResourceAllocationException {
         Account caller = CallContext.current().getCallingAccount();
         VolumeInfo volume = volFactory.getVolume(volumeId);
         supportedByHypervisor(volume, isFromVmSnapshot);
@@ -1600,4 +1697,268 @@
             }
         }
     }
+
+    private boolean checkAndProcessSnapshotAlreadyExistInStore(long snapshotId, DataStore dstSecStore) {
+        SnapshotDataStoreVO dstSnapshotStore = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, dstSecStore.getId(), snapshotId);
+        if (dstSnapshotStore == null) {
+            return false;
+        }
+        if (dstSnapshotStore.getState() == ObjectInDataStoreStateMachine.State.Ready) {
+            if (!dstSnapshotStore.isDisplay()) {
+                s_logger.debug(String.format("Snapshot ID: %d is in ready state on image store ID: %d, marking it displayable for view", snapshotId, dstSnapshotStore.getDataStoreId()));
+                dstSnapshotStore.setDisplay(true);
+                _snapshotStoreDao.update(dstSnapshotStore.getId(), dstSnapshotStore);
+            }
+            return true; // already downloaded on this image store
+        }
+        if (List.of(VMTemplateStorageResourceAssoc.Status.ABANDONED,
+                VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR,
+                VMTemplateStorageResourceAssoc.Status.NOT_DOWNLOADED,
+                VMTemplateStorageResourceAssoc.Status.UNKNOWN).contains(dstSnapshotStore.getDownloadState()) ||
+                !List.of(ObjectInDataStoreStateMachine.State.Creating,
+                        ObjectInDataStoreStateMachine.State.Copying).contains(dstSnapshotStore.getState())) {
+            _snapshotStoreDao.removeBySnapshotStore(snapshotId, dstSecStore.getId(), DataStoreRole.Image);
+        }
+        return false;
+    }
+
+    @DB
+    private boolean copySnapshotToZone(SnapshotDataStoreVO snapshotDataStoreVO, DataStore srcSecStore,
+           DataCenterVO dstZone, DataStore dstSecStore, Account account)
+            throws ResourceAllocationException {
+        final long snapshotId = snapshotDataStoreVO.getSnapshotId();
+        final long dstZoneId = dstZone.getId();
+        if (checkAndProcessSnapshotAlreadyExistInStore(snapshotId, dstSecStore)) {
+            return true;
+        }
+        _resourceLimitMgr.checkResourceLimit(account, ResourceType.secondary_storage, snapshotDataStoreVO.getSize());
+        // snapshotId may refer to ID of a removed parent snapshot
+        SnapshotInfo snapshotOnSecondary = snapshotFactory.getSnapshot(snapshotId, srcSecStore);
+        String copyUrl = null;
+        try {
+            AsyncCallFuture<CreateCmdResult> future = snapshotSrv.queryCopySnapshot(snapshotOnSecondary);
+            CreateCmdResult result = future.get();
+            if (!result.isFailed()) {
+                copyUrl = result.getPath();
+            }
+        } catch (InterruptedException | ExecutionException | ResourceUnavailableException ex) {
+            s_logger.error(String.format("Failed to prepare URL for copy for snapshot ID: %d on store: %s", snapshotId, srcSecStore.getName()), ex);
+        }
+        if (StringUtils.isEmpty(copyUrl)) {
+            s_logger.error(String.format("Unable to prepare URL for copy for snapshot ID: %d on store: %s", snapshotId, srcSecStore.getName()));
+            return false;
+        }
+        s_logger.debug(String.format("Copying snapshot ID: %d to destination zones using download URL: %s", snapshotId, copyUrl));
+        try {
+            AsyncCallFuture<SnapshotResult> future = snapshotSrv.copySnapshot(snapshotOnSecondary, copyUrl, dstSecStore);
+            SnapshotResult result = future.get();
+            if (result.isFailed()) {
+                s_logger.debug(String.format("Copy snapshot ID: %d failed for image store %s: %s", snapshotId, dstSecStore.getName(), result.getResult()));
+                return false;
+            }
+            snapshotZoneDao.addSnapshotToZone(snapshotId, dstZoneId);
+            _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.secondary_storage, snapshotDataStoreVO.getSize());
+            if (account.getId() != Account.ACCOUNT_ID_SYSTEM) {
+                SnapshotVO snapshotVO = _snapshotDao.findByIdIncludingRemoved(snapshotId);
+                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_COPY, account.getId(), dstZoneId, snapshotId, null, null, null, snapshotVO.getSize(),
+                        snapshotVO.getSize(), snapshotVO.getClass().getName(), snapshotVO.getUuid());
+            }
+            return true;
+        } catch (InterruptedException | ExecutionException | ResourceUnavailableException ex) {
+            s_logger.debug(String.format("Failed to copy snapshot ID: %d to image store: %s", snapshotId, dstSecStore.getName()));
+        }
+        return false;
+    }
+
+    @DB
+    private boolean copySnapshotChainToZone(SnapshotVO snapshotVO, DataStore srcSecStore, DataCenterVO destZone, Account account)
+            throws StorageUnavailableException, ResourceAllocationException {
+        final long snapshotId = snapshotVO.getId();
+        final long destZoneId = destZone.getId();
+        SnapshotDataStoreVO currentSnap = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, srcSecStore.getId(), snapshotId);;
+        List<SnapshotDataStoreVO> snapshotChain = new ArrayList<>();
+        long size = 0L;
+        DataStore dstSecStore = null;
+        do {
+            dstSecStore = getSnapshotZoneImageStore(currentSnap.getSnapshotId(), destZone.getId());
+            if (dstSecStore != null) {
+                s_logger.debug(String.format("Snapshot ID: %d is already present in secondary storage: %s" +
+                        " in zone %s in ready state, don't need to copy any further",
+                        currentSnap.getSnapshotId(), dstSecStore.getName(), destZone));
+                if (snapshotId == currentSnap.getSnapshotId()) {
+                    checkAndProcessSnapshotAlreadyExistInStore(snapshotId, dstSecStore);
+                }
+                break;
+            }
+            snapshotChain.add(currentSnap);
+            size += currentSnap.getSize();
+            currentSnap = currentSnap.getParentSnapshotId() == 0 ?
+                    null :
+                    _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, srcSecStore.getId(), currentSnap.getParentSnapshotId());
+        } while (currentSnap != null);
+        if (CollectionUtils.isEmpty(snapshotChain)) {
+            return true;
+        }
+        try {
+            _resourceLimitMgr.checkResourceLimit(account, ResourceType.secondary_storage, size);
+        } catch (ResourceAllocationException e) {
+            s_logger.error(String.format("Unable to allocate secondary storage resources for snapshot chain for %s with size: %d", snapshotVO, size), e);
+            return false;
+        }
+        Collections.reverse(snapshotChain);
+        if (dstSecStore == null) {
+            // find all eligible image stores for the destination zone
+            List<DataStore> dstSecStores = dataStoreMgr.getImageStoresByScopeExcludingReadOnly(new ZoneScope(destZoneId));
+            if (CollectionUtils.isEmpty(dstSecStores)) {
+                throw new StorageUnavailableException("Destination zone is not ready, no image store associated", DataCenter.class, destZoneId);
+            }
+            dstSecStore = dataStoreMgr.getImageStoreWithFreeCapacity(dstSecStores);
+            if (dstSecStore == null) {
+                throw new StorageUnavailableException("Destination zone is not ready, no image store with free capacity", DataCenter.class, destZoneId);
+            }
+        }
+        s_logger.debug(String.format("Copying snapshot chain for snapshot ID: %d on secondary store: %s of zone ID: %d", snapshotId, dstSecStore.getName(), destZoneId));
+        for (SnapshotDataStoreVO snapshotDataStoreVO : snapshotChain) {
+            if (!copySnapshotToZone(snapshotDataStoreVO, srcSecStore, destZone, dstSecStore, account)) {
+                s_logger.error(String.format("Failed to copy snapshot: %s to zone: %s due to failure to copy snapshot ID: %d from snapshot chain",
+                        snapshotVO, destZone, snapshotDataStoreVO.getSnapshotId()));
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @DB
+    private List<String> copySnapshotToZones(SnapshotVO snapshotVO, DataStore srcSecStore, List<DataCenterVO> dstZones) throws StorageUnavailableException, ResourceAllocationException {
+        AccountVO account = _accountDao.findById(snapshotVO.getAccountId());
+        List<String> failedZones = new ArrayList<>();
+        for (DataCenterVO destZone : dstZones) {
+            if (!copySnapshotChainToZone(snapshotVO, srcSecStore, destZone, account)) {
+                failedZones.add(destZone.getName());
+            }
+        }
+        return failedZones;
+    }
+
+    protected Pair<SnapshotVO, Long> getCheckedSnapshotForCopy(final long snapshotId, final List<Long> destZoneIds, Long sourceZoneId) {
+        SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
+        if (snapshot == null) {
+            throw new InvalidParameterValueException("Unable to find snapshot with id");
+        }
+        // Verify snapshot is BackedUp and is on secondary store
+        if (!Snapshot.State.BackedUp.equals(snapshot.getState())) {
+            throw new InvalidParameterValueException("Snapshot is not backed up");
+        }
+        if (snapshot.getLocationType() != null && !Snapshot.LocationType.SECONDARY.equals(snapshot.getLocationType())) {
+            throw new InvalidParameterValueException("Snapshot is not backed up");
+        }
+        if (CollectionUtils.isEmpty(destZoneIds)) {
+            throw new InvalidParameterValueException("Please specify valid destination zone(s).");
+        }
+        Volume volume = _volsDao.findById(snapshot.getVolumeId());
+        if (sourceZoneId == null) {
+            sourceZoneId = volume.getDataCenterId();
+        }
+        if (destZoneIds.contains(sourceZoneId)) {
+            throw new InvalidParameterValueException("Please specify different source and destination zones.");
+        }
+        DataCenterVO sourceZone = dataCenterDao.findById(sourceZoneId);
+        if (sourceZone == null) {
+            throw new InvalidParameterValueException("Please specify a valid source zone.");
+        }
+        return new Pair<>(snapshot, sourceZoneId);
+    }
+
+    protected DataCenterVO getCheckedDestinationZoneForSnapshotCopy(long zoneId, boolean isRootAdmin) {
+        DataCenterVO dstZone = dataCenterDao.findById(zoneId);
+        if (dstZone == null) {
+            throw new InvalidParameterValueException("Please specify a valid destination zone.");
+        }
+        if (Grouping.AllocationState.Disabled.equals(dstZone.getAllocationState()) && !isRootAdmin) {
+            throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + dstZone.getName());
+        }
+        if (DataCenter.Type.Edge.equals(dstZone.getType())) {
+            s_logger.error(String.format("Edge zone %s specified for snapshot copy", dstZone));
+            throw new InvalidParameterValueException(String.format("Snapshot copy is not supported by zone %s", dstZone.getName()));
+        }
+        return dstZone;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_COPY, eventDescription = "copying snapshot", create = false)
+    public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableException, ResourceAllocationException {
+        final Long snapshotId = cmd.getId();
+        Long sourceZoneId = cmd.getSourceZoneId();
+        List<Long> destZoneIds = cmd.getDestinationZoneIds();
+        Account caller = CallContext.current().getCallingAccount();
+        Pair<SnapshotVO, Long> snapshotZonePair = getCheckedSnapshotForCopy(snapshotId, destZoneIds, sourceZoneId);
+        SnapshotVO snapshot = snapshotZonePair.first();
+        sourceZoneId = snapshotZonePair.second();
+        Map<Long, DataCenterVO> dataCenterVOs = new HashMap<>();
+        boolean isRootAdminCaller = _accountMgr.isRootAdmin(caller.getId());
+        for (Long destZoneId: destZoneIds) {
+            DataCenterVO dstZone = getCheckedDestinationZoneForSnapshotCopy(destZoneId, isRootAdminCaller);
+            dataCenterVOs.put(destZoneId, dstZone);
+        }
+        _accountMgr.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, true, snapshot);
+        DataStore srcSecStore = getSnapshotZoneImageStore(snapshotId, sourceZoneId);
+        if (srcSecStore == null) {
+            throw new InvalidParameterValueException(String.format("There is no snapshot ID: %s ready on image store", snapshot.getUuid()));
+        }
+        List<String> failedZones = copySnapshotToZones(snapshot, srcSecStore, new ArrayList<>(dataCenterVOs.values()));
+        if (destZoneIds.size() > failedZones.size()){
+            if (!failedZones.isEmpty()) {
+                s_logger.error(String.format("There were failures when copying snapshot to zones: %s",
+                        StringUtils.joinWith(", ", failedZones.toArray())));
+            }
+            return snapshot;
+        } else {
+            throw new CloudRuntimeException("Failed to copy snapshot");
+        }
+    }
+
+    protected void copyNewSnapshotToZones(long snapshotId, long zoneId, List<Long> destZoneIds) {
+        if (CollectionUtils.isEmpty(destZoneIds)) {
+            return;
+        }
+        List<String> failedZones = new ArrayList<>();
+        SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
+        long startEventId = ActionEventUtils.onStartedActionEvent(CallContext.current().getCallingUserId(),
+                CallContext.current().getCallingAccountId(), EventTypes.EVENT_SNAPSHOT_COPY,
+                String.format("Copying snapshot ID: %s", snapshotVO.getUuid()), snapshotId,
+                ApiCommandResourceType.Snapshot.toString(), true, 0);
+        DataStore dataStore = getSnapshotZoneImageStore(snapshotId, zoneId);
+        String completedEventLevel = EventVO.LEVEL_ERROR;
+        String completedEventMsg = String.format("Copying snapshot ID: %s failed", snapshotVO.getUuid());
+        if (dataStore == null) {
+            s_logger.error(String.format("Unable to find an image store for zone ID: %d where snapshot %s is in Ready state", zoneId, snapshotVO));
+            ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
+                    CallContext.current().getCallingAccountId(), completedEventLevel, EventTypes.EVENT_SNAPSHOT_COPY,
+                    completedEventMsg, snapshotId, ApiCommandResourceType.Snapshot.toString(), startEventId);
+            return;
+        }
+        List<DataCenterVO> dataCenterVOs = new ArrayList<>();
+        for (Long destZoneId: destZoneIds) {
+            DataCenterVO dstZone = dataCenterDao.findById(destZoneId);
+            dataCenterVOs.add(dstZone);
+        }
+        try {
+            failedZones = copySnapshotToZones(snapshotVO, dataStore, dataCenterVOs);
+            if (CollectionUtils.isNotEmpty(failedZones)) {
+                s_logger.error(String.format("There were failures while copying snapshot %s to zones: %s",
+                        snapshotVO, StringUtils.joinWith(", ", failedZones.toArray())));
+            }
+        } catch (ResourceAllocationException | StorageUnavailableException | CloudRuntimeException e) {
+            s_logger.error(String.format("Error while copying snapshot %s to zones: %s", snapshotVO, StringUtils.joinWith(",", destZoneIds.toArray())));
+        }
+        if (failedZones.size() < destZoneIds.size()) {
+            final List<String> failedZonesFinal = failedZones;
+            String zoneNames = StringUtils.joinWith(", ", dataCenterVOs.stream().filter(x -> !failedZonesFinal.contains(x.getUuid())).map(DataCenterVO::getName).collect(Collectors.toList()));
+            completedEventLevel = EventVO.LEVEL_INFO;
+            completedEventMsg = String.format("Completed copying snapshot ID: %s to zone(s): %s", snapshotVO.getUuid(), zoneNames);
+        }
+        ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(),
+                CallContext.current().getCallingAccountId(), completedEventLevel, EventTypes.EVENT_SNAPSHOT_COPY,
+                completedEventMsg, snapshotId, ApiCommandResourceType.Snapshot.toString(), startEventId);
+    }
 }
diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java
index d4fe083..3051d5f 100644
--- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java
+++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java
@@ -264,10 +264,10 @@
     @DB
     protected void scheduleSnapshots() {
         String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, _currentTimestamp);
-        s_logger.debug("Snapshot scheduler.poll is being called at " + displayTime);
+        s_logger.debug(String.format("Snapshot scheduler is being called at [%s].", displayTime));
 
         final List<SnapshotScheduleVO> snapshotsToBeExecuted = _snapshotScheduleDao.getSchedulesToExecute(_currentTimestamp);
-        s_logger.debug("Got " + snapshotsToBeExecuted.size() + " snapshots to be executed at " + displayTime);
+        s_logger.debug(String.format("There are [%s] scheduled snapshots to be executed at [%s].", snapshotsToBeExecuted.size(), displayTime));
 
         for (final SnapshotScheduleVO snapshotToBeExecuted : snapshotsToBeExecuted) {
             SnapshotScheduleVO tmpSnapshotScheduleVO = null;
@@ -275,34 +275,18 @@
             final long policyId = snapshotToBeExecuted.getPolicyId();
             final long volumeId = snapshotToBeExecuted.getVolumeId();
             try {
-                final VolumeVO volume = _volsDao.findById(volumeId);
-                if (volume.getPoolId() == null) {
-                    // this volume is not attached
+                final VolumeVO volume = _volsDao.findByIdIncludingRemoved(snapshotToBeExecuted.getVolumeId());
+
+                if (!canSnapshotBeScheduled(snapshotToBeExecuted, volume)) {
                     continue;
                 }
-                Account volAcct = _acctDao.findById(volume.getAccountId());
-                if (volAcct == null || volAcct.getState() == Account.State.DISABLED) {
-                    // this account has been removed, so don't trigger recurring snapshot
-                    if (s_logger.isDebugEnabled()) {
-                        s_logger.debug("Skip snapshot for volume " + volume.getUuid() + " since its account has been removed or disabled");
-                    }
-                    continue;
-                }
-                if (_snapshotPolicyDao.findById(policyId) == null) {
-                    _snapshotScheduleDao.remove(snapshotToBeExecuted.getId());
-                }
-                if (s_logger.isDebugEnabled()) {
-                    final Date scheduledTimestamp = snapshotToBeExecuted.getScheduledTimestamp();
-                    displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
-                    s_logger.debug("Scheduling 1 snapshot for volume id " + volumeId + " (volume name:" +
-                            volume.getName() + ") for schedule id: " + snapshotToBeExecuted.getId() + " at " + displayTime);
-                }
 
                 tmpSnapshotScheduleVO = _snapshotScheduleDao.acquireInLockTable(snapshotScheId);
                 final Long eventId =
                     ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, volume.getAccountId(), EventTypes.EVENT_SNAPSHOT_CREATE, "creating snapshot for volume Id:" +
                         volume.getUuid(), volumeId, ApiCommandResourceType.Volume.toString(), true, 0);
 
+                s_logger.trace(String.format("Mapping parameters required to generate a CreateSnapshotCmd for snapshot [%s].", snapshotToBeExecuted.getUuid()));
                 final Map<String, String> params = new HashMap<String, String>();
                 params.put(ApiConstants.VOLUME_ID, "" + volumeId);
                 params.put(ApiConstants.POLICY_ID, "" + policyId);
@@ -319,24 +303,27 @@
                     }
                 }
 
+                s_logger.trace(String.format("Generating a CreateSnapshotCmd for snapshot [%s] with parameters: [%s].", snapshotToBeExecuted.getUuid(), params.toString()));
                 final CreateSnapshotCmd cmd = new CreateSnapshotCmd();
                 ComponentContext.inject(cmd);
                 _dispatcher.dispatchCreateCmd(cmd, params);
                 params.put("id", "" + cmd.getEntityId());
                 params.put("ctxStartEventId", "1");
 
+                final Date scheduledTimestamp = snapshotToBeExecuted.getScheduledTimestamp();
+                displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
+                s_logger.debug(String.format("Scheduling snapshot [%s] for volume [%s] at [%s].", snapshotToBeExecuted.getUuid(), volume.getVolumeDescription(), displayTime));
                 AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, volume.getAccountId(), CreateSnapshotCmd.class.getName(),
                         ApiGsonHelper.getBuilder().create().toJson(params), cmd.getEntityId(),
                         cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
                 job.setDispatcher(_asyncDispatcher.getName());
-
                 final long jobId = _asyncMgr.submitAsyncJob(job);
+                s_logger.debug(String.format("Scheduled snapshot [%s] for volume [%s] as job [%s].", snapshotToBeExecuted.getUuid(), volume.getVolumeDescription(), job.getUuid()));
 
                 tmpSnapshotScheduleVO.setAsyncJobId(jobId);
                 _snapshotScheduleDao.update(snapshotScheId, tmpSnapshotScheduleVO);
             } catch (final Exception e) {
-                // TODO Logging this exception is enough?
-                s_logger.warn("Scheduling snapshot failed due to " + e.toString());
+                s_logger.error(String.format("The scheduling of snapshot [%s] for volume [%s] failed due to [%s].", snapshotToBeExecuted.getUuid(), volumeId, e.toString()), e);
             } finally {
                 if (tmpSnapshotScheduleVO != null) {
                     _snapshotScheduleDao.releaseFromLockTable(snapshotScheId);
@@ -345,7 +332,59 @@
         }
     }
 
-    private Date scheduleNextSnapshotJob(final SnapshotScheduleVO snapshotSchedule) {
+    /**
+     * Verifies if a snapshot for a volume can be scheduled or not based on volume and account status, and removes it from the snapshot scheduler if its policy was removed.
+     *
+     * @param snapshotToBeScheduled the snapshot to be scheduled
+     * @param volume the volume associated with the snapshot to be scheduled
+     * @return <code>true</code> if the snapshot can be scheduled, and <code>false</code> otherwise.
+     */
+    protected boolean canSnapshotBeScheduled(final SnapshotScheduleVO snapshotToBeScheduled, final VolumeVO volume) {
+        if (volume.getRemoved() != null) {
+            s_logger.warn(String.format("Skipping snapshot [%s] for volume [%s] because it has been removed. Having a snapshot scheduled for a volume that has been "
+                            + "removed is an inconsistency; please, check your database.", snapshotToBeScheduled.getUuid(), volume.getVolumeDescription()));
+            return false;
+        }
+
+        if (volume.getPoolId() == null) {
+            s_logger.debug(String.format("Skipping snapshot [%s] for volume [%s] because it is not attached to any storage pool.", snapshotToBeScheduled.getUuid(),
+                    volume.getVolumeDescription()));
+            return false;
+        }
+
+        if (isAccountRemovedOrDisabled(snapshotToBeScheduled, volume)) {
+            return false;
+        }
+
+        if (_snapshotPolicyDao.findById(snapshotToBeScheduled.getPolicyId()) == null) {
+            s_logger.debug(String.format("Snapshot's policy [%s] for volume [%s] has been removed; therefore, this snapshot will be removed from the snapshot scheduler.",
+                    snapshotToBeScheduled.getPolicyId(), volume.getVolumeDescription()));
+            _snapshotScheduleDao.remove(snapshotToBeScheduled.getId());
+        }
+
+        s_logger.debug(String.format("Snapshot [%s] for volume [%s] can be executed.", snapshotToBeScheduled.getUuid(), volume.getVolumeDescription()));
+        return true;
+    }
+
+    protected boolean isAccountRemovedOrDisabled(final SnapshotScheduleVO snapshotToBeExecuted, final VolumeVO volume) {
+        Account volAcct = _acctDao.findById(volume.getAccountId());
+
+        if (volAcct == null) {
+            s_logger.debug(String.format("Skipping snapshot [%s] for volume [%s] because its account [%s] has been removed.", snapshotToBeExecuted.getUuid(),
+                    volume.getVolumeDescription(), volume.getAccountId()));
+            return true;
+        }
+
+        if (volAcct.getState() == Account.State.DISABLED) {
+            s_logger.debug(String.format("Skipping snapshot [%s] for volume [%s] because its account [%s] is disabled.", snapshotToBeExecuted.getUuid(),
+                    volume.getVolumeDescription(), volAcct.getUuid()));
+            return true;
+        }
+
+        return false;
+    }
+
+    protected Date scheduleNextSnapshotJob(final SnapshotScheduleVO snapshotSchedule) {
         if (snapshotSchedule == null) {
             return null;
         }
diff --git a/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java
index 928e542..c02f413 100644
--- a/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java
+++ b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java
@@ -81,6 +81,7 @@
         s_typeMap.put(ResourceTag.ResourceObjectType.UserVm, UserVmVO.class);
         s_typeMap.put(ResourceTag.ResourceObjectType.Volume, VolumeVO.class);
         s_typeMap.put(ResourceTag.ResourceObjectType.Template, VMTemplateVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.VnfTemplate, VMTemplateVO.class);
         s_typeMap.put(ResourceTag.ResourceObjectType.ISO, VMTemplateVO.class);
         s_typeMap.put(ResourceTag.ResourceObjectType.Snapshot, SnapshotVO.class);
         s_typeMap.put(ResourceTag.ResourceObjectType.Network, NetworkVO.class);
diff --git a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java
index 2a536fa..d8132df 100644
--- a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java
+++ b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java
@@ -59,9 +59,11 @@
 import org.apache.cloudstack.framework.async.AsyncRpcContext;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
 import org.apache.cloudstack.framework.messagebus.PublishScope;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
 import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper;
 import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
 import org.apache.cloudstack.utils.security.DigestHelper;
 import org.apache.commons.collections.CollectionUtils;
@@ -108,7 +110,7 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 
 public class HypervisorTemplateAdapter extends TemplateAdapterBase {
-    private final static Logger s_logger = Logger.getLogger(HypervisorTemplateAdapter.class);
+    protected final static Logger s_logger = Logger.getLogger(HypervisorTemplateAdapter.class);
     @Inject
     DownloadMonitor _downloadMonitor;
     @Inject
@@ -146,6 +148,8 @@
     @Inject
     private AnnotationDao annotationDao;
     @Inject
+    private HeuristicRuleHelper heuristicRuleHelper;
+    @Inject
     VMInstanceDao _vmInstanceDao;
 
     @Override
@@ -154,21 +158,21 @@
     }
 
     /**
-     * Validate on random running KVM host that URL is reachable
+     * Validate on random running host that URL is reachable
      * @param url url
      */
-    private Long performDirectDownloadUrlValidation(final String format, final String url, final List<Long> zoneIds,
-                boolean followRedirects) {
+    private Long performDirectDownloadUrlValidation(final String format, final Hypervisor.HypervisorType hypervisor,
+                                                    final String url, final List<Long> zoneIds, final boolean followRedirects) {
         HostVO host = null;
         if (zoneIds != null && !zoneIds.isEmpty()) {
             for (Long zoneId : zoneIds) {
-                host = resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM, zoneId);
+                host = resourceManager.findOneRandomRunningHostByHypervisor(hypervisor, zoneId);
                 if (host != null) {
                     break;
                 }
             }
         } else {
-            host = resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM, null);
+            host = resourceManager.findOneRandomRunningHostByHypervisor(hypervisor, null);
         }
 
         if (host == null) {
@@ -177,8 +181,7 @@
         Integer socketTimeout = DirectDownloadManager.DirectDownloadSocketTimeout.value();
         Integer connectRequestTimeout = DirectDownloadManager.DirectDownloadConnectionRequestTimeout.value();
         Integer connectTimeout = DirectDownloadManager.DirectDownloadConnectTimeout.value();
-        CheckUrlCommand cmd = new CheckUrlCommand(format, url, connectTimeout, connectRequestTimeout, socketTimeout,
-                followRedirects);
+        CheckUrlCommand cmd = new CheckUrlCommand(format, url, connectTimeout, connectRequestTimeout, socketTimeout, followRedirects);
         s_logger.debug("Performing URL " + url + " validation on host " + host.getId());
         Answer answer = _agentMgr.easySend(host.getId(), cmd);
         if (answer == null || !answer.getResult()) {
@@ -210,8 +213,8 @@
                 zoneIds =  new ArrayList<>();
                 zoneIds.add(cmd.getZoneId());
             }
-            Long templateSize = performDirectDownloadUrlValidation(ImageFormat.ISO.getFileExtension(), url, zoneIds,
-                    followRedirects);
+            Long templateSize = performDirectDownloadUrlValidation(ImageFormat.ISO.getFileExtension(),
+                    Hypervisor.HypervisorType.KVM, url, zoneIds, followRedirects);
             profile.setSize(templateSize);
         }
         profile.setUrl(url);
@@ -236,11 +239,12 @@
         TemplateProfile profile = super.prepare(cmd);
         String url = profile.getUrl();
         UriUtils.validateUrl(cmd.getFormat(), url, cmd.isDirectDownload());
+        Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.getType(cmd.getHypervisor());
         boolean followRedirects = StorageManager.DataStoreDownloadFollowRedirects.value();
         if (cmd.isDirectDownload()) {
             DigestHelper.validateChecksumString(cmd.getChecksum());
-            Long templateSize = performDirectDownloadUrlValidation(cmd.getFormat(), url, cmd.getZoneIds(),
-                    followRedirects);
+            Long templateSize = performDirectDownloadUrlValidation(cmd.getFormat(),
+                    hypervisor, url, cmd.getZoneIds(), followRedirects);
             profile.setSize(templateSize);
         }
         profile.setUrl(url);
@@ -278,17 +282,7 @@
         }
 
         if (!profile.isDirectDownload()) {
-            List<Long> zones = profile.getZoneIdList();
-
-            //zones is null when this template is to be registered to all zones
-            if (zones == null){
-                createTemplateWithinZone(null, profile, template);
-            }
-            else {
-                for (Long zId : zones) {
-                    createTemplateWithinZone(zId, profile, template);
-                }
-            }
+            createTemplateWithinZones(profile, template);
         } else {
             //KVM direct download templates bypassing Secondary Storage
             persistDirectDownloadTemplate(template.getId(), profile.getSize());
@@ -298,46 +292,67 @@
         return template;
     }
 
-    private void createTemplateWithinZone(Long zId, TemplateProfile profile, VMTemplateVO template) {
-        // find all eligible image stores for this zone scope
-        List<DataStore> imageStores = storeMgr.getImageStoresByScopeExcludingReadOnly(new ZoneScope(zId));
-        if (imageStores == null || imageStores.size() == 0) {
-            throw new CloudRuntimeException("Unable to find image store to download template " + profile.getTemplate());
+    /**
+     * For each zone ID in {@link TemplateProfile#zoneIdList}, verifies if there is active heuristic rules for allocating template and returns the
+     * {@link DataStore} returned by the heuristic rule. If there is not an active heuristic rule, then allocate it to a random {@link DataStore}, if the ISO/template is private
+     * or allocate it to all {@link DataStore} in the zone, if it is public.
+     * @param profile
+     * @param template
+     */
+    protected void createTemplateWithinZones(TemplateProfile profile, VMTemplateVO template) {
+        List<Long> zonesIds = profile.getZoneIdList();
+
+        if (zonesIds == null) {
+            zonesIds = _dcDao.listAllZones().stream().map(DataCenterVO::getId).collect(Collectors.toList());
         }
 
+        List<DataStore> imageStores = getImageStoresThrowsExceptionIfNotFound(zonesIds, profile);
+
+        for (long zoneId : zonesIds) {
+            DataStore imageStore = verifyHeuristicRulesForZone(template, zoneId);
+
+            if (imageStore == null) {
+                standardImageStoreAllocation(imageStores, template);
+            } else {
+                validateSecondaryStorageAndCreateTemplate(List.of(imageStore), template, null);
+            }
+        }
+    }
+
+    protected List<DataStore> getImageStoresThrowsExceptionIfNotFound(List<Long> zonesIds, TemplateProfile profile) {
+        List<DataStore> imageStores = storeMgr.getImageStoresByZoneIds(zonesIds.toArray(new Long[0]));
+        if (imageStores == null || imageStores.size() == 0) {
+            throw new CloudRuntimeException(String.format("Unable to find image store to download the template [%s].", profile.getTemplate()));
+        }
+        return imageStores;
+    }
+
+    protected DataStore verifyHeuristicRulesForZone(VMTemplateVO template, Long zoneId) {
+        HeuristicType heuristicType;
+        if (ImageFormat.ISO.equals(template.getFormat())) {
+            heuristicType = HeuristicType.ISO;
+        } else {
+            heuristicType = HeuristicType.TEMPLATE;
+        }
+        return heuristicRuleHelper.getImageStoreIfThereIsHeuristicRule(zoneId, heuristicType, template);
+    }
+
+    protected void standardImageStoreAllocation(List<DataStore> imageStores, VMTemplateVO template) {
         Set<Long> zoneSet = new HashSet<Long>();
         Collections.shuffle(imageStores);
-        // For private templates choose a random store. TODO - Have a better algorithm based on size, no. of objects, load etc.
+        validateSecondaryStorageAndCreateTemplate(imageStores, template, zoneSet);
+    }
+
+    protected void validateSecondaryStorageAndCreateTemplate(List<DataStore> imageStores, VMTemplateVO template, Set<Long> zoneSet) {
         for (DataStore imageStore : imageStores) {
-            // skip data stores for a disabled zone
             Long zoneId = imageStore.getScope().getScopeId();
-            if (zoneId != null) {
-                DataCenterVO zone = _dcDao.findById(zoneId);
-                if (zone == null) {
-                    s_logger.warn("Unable to find zone by id " + zoneId + ", so skip downloading template to its image store " + imageStore.getId());
-                    continue;
-                }
 
-                // Check if zone is disabled
-                if (Grouping.AllocationState.Disabled == zone.getAllocationState()) {
-                    s_logger.info("Zone " + zoneId + " is disabled. Skip downloading template to its image store " + imageStore.getId());
-                    continue;
-                }
-
-                // Check if image store has enough capacity for template
-                if (!_statsCollector.imageStoreHasEnoughCapacity(imageStore)) {
-                    s_logger.info("Image store doesn't have enough capacity. Skip downloading template to this image store " + imageStore.getId());
-                    continue;
-                }
-                // We want to download private template to one of the image store in a zone
-                if (isPrivateTemplate(template) && zoneSet.contains(zoneId)) {
-                    continue;
-                } else {
-                    zoneSet.add(zoneId);
-                }
+            if (!isZoneAndImageStoreAvailable(imageStore, zoneId, zoneSet, isPrivateTemplate(template))) {
+                continue;
             }
+
             TemplateInfo tmpl = imageFactory.getTemplate(template.getId(), imageStore);
-            CreateTemplateContext<TemplateApiResult> context = new CreateTemplateContext<TemplateApiResult>(null, tmpl);
+            CreateTemplateContext<TemplateApiResult> context = new CreateTemplateContext<>(null, tmpl);
             AsyncCallbackDispatcher<HypervisorTemplateAdapter, TemplateApiResult> caller = AsyncCallbackDispatcher.create(this);
             caller.setCallback(caller.getTarget().createTemplateAsyncCallBack(null, null));
             caller.setContext(context);
@@ -345,6 +360,44 @@
         }
     }
 
+    protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId, Set<Long> zoneSet, boolean isTemplatePrivate) {
+        if (zoneId == null) {
+            s_logger.warn(String.format("Zone ID is null, cannot allocate ISO/template in image store [%s].", imageStore));
+            return false;
+        }
+
+        DataCenterVO zone = _dcDao.findById(zoneId);
+        if (zone == null) {
+            s_logger.warn(String.format("Unable to find zone by id [%s], so skip downloading template to its image store [%s].", zoneId, imageStore.getId()));
+            return false;
+        }
+
+        if (Grouping.AllocationState.Disabled == zone.getAllocationState()) {
+            s_logger.info(String.format("Zone [%s] is disabled. Skip downloading template to its image store [%s].", zoneId, imageStore.getId()));
+            return false;
+        }
+
+        if (!_statsCollector.imageStoreHasEnoughCapacity(imageStore)) {
+            s_logger.info(String.format("Image store doesn't have enough capacity. Skip downloading template to this image store [%s].", imageStore.getId()));
+            return false;
+        }
+
+        if (zoneSet == null) {
+            s_logger.info(String.format("Zone set is null; therefore, the ISO/template should be allocated in every secondary storage of zone [%s].", zone));
+            return true;
+        }
+
+        if (isTemplatePrivate && zoneSet.contains(zoneId)) {
+            s_logger.info(String.format("The template is private and it is already allocated in a secondary storage in zone [%s]; therefore, image store [%s] will be skipped.",
+                    zone, imageStore));
+            return false;
+        }
+
+        s_logger.info(String.format("Private template will be allocated in image store [%s] in zone [%s].", imageStore, zone));
+        zoneSet.add(zoneId);
+        return true;
+    }
+
     @Override
     public List<TemplateOrVolumePostUploadCommand> createTemplateForPostUpload(final TemplateProfile profile) {
         // persist entry in vm_template, vm_template_details and template_zone_ref tables, not that entry at template_store_ref is not created here, and created in createTemplateAsync.
@@ -359,80 +412,27 @@
                     throw new CloudRuntimeException("Unable to persist the template " + profile.getTemplate());
                 }
 
-                if (profile.getZoneIdList() != null && profile.getZoneIdList().size() > 1)
-                    throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time");
+                List<Long> zoneIdList = profile.getZoneIdList();
 
-                Long zoneId = null;
-                if (profile.getZoneIdList() != null)
-                    zoneId = profile.getZoneIdList().get(0);
-
-                // find all eligible image stores for this zone scope
-                List<DataStore> imageStores = storeMgr.getImageStoresByScopeExcludingReadOnly(new ZoneScope(zoneId));
-                if (imageStores == null || imageStores.size() == 0) {
-                    throw new CloudRuntimeException("Unable to find image store to download template " + profile.getTemplate());
+                if (zoneIdList == null) {
+                    throw new CloudRuntimeException("Zone ID is null, cannot upload ISO/template.");
                 }
 
+                if (zoneIdList.size() > 1)
+                    throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time.");
+
+                Long zoneId = zoneIdList.get(0);
+                DataStore imageStore = verifyHeuristicRulesForZone(template, zoneId);
                 List<TemplateOrVolumePostUploadCommand> payloads = new LinkedList<>();
-                Set<Long> zoneSet = new HashSet<Long>();
-                Collections.shuffle(imageStores); // For private templates choose a random store. TODO - Have a better algorithm based on size, no. of objects, load etc.
-                for (DataStore imageStore : imageStores) {
-                    // skip data stores for a disabled zone
-                    Long zoneId_is = imageStore.getScope().getScopeId();
-                    if (zoneId != null) {
-                        DataCenterVO zone = _dcDao.findById(zoneId_is);
-                        if (zone == null) {
-                            s_logger.warn("Unable to find zone by id " + zoneId_is +
-                                        ", so skip downloading template to its image store " + imageStore.getId());
-                            continue;
-                        }
 
-                        // Check if zone is disabled
-                        if (Grouping.AllocationState.Disabled == zone.getAllocationState()) {
-                            s_logger.info("Zone " + zoneId_is +
-                                    " is disabled, so skip downloading template to its image store " + imageStore.getId());
-                            continue;
-                        }
+                if (imageStore == null) {
+                    List<DataStore> imageStores = getImageStoresThrowsExceptionIfNotFound(List.of(zoneId), profile);
+                    postUploadAllocation(imageStores, template, payloads);
+                } else {
+                    postUploadAllocation(List.of(imageStore), template, payloads);
 
-                        // We want to download private template to one of the image store in a zone
-                        if (isPrivateTemplate(template) && zoneSet.contains(zoneId_is)) {
-                            continue;
-                        } else {
-                            zoneSet.add(zoneId_is);
-                        }
-
-                    }
-
-                    TemplateInfo tmpl = imageFactory.getTemplate(template.getId(), imageStore);
-                    //imageService.createTemplateAsync(tmpl, imageStore, caller);
-
-                    // persist template_store_ref entry
-                    DataObject templateOnStore = imageStore.create(tmpl);
-                    // update template_store_ref and template state
-
-                    EndPoint ep = _epSelector.select(templateOnStore);
-                    if (ep == null) {
-                        String errMsg = "There is no secondary storage VM for downloading template to image store " + imageStore.getName();
-                        s_logger.warn(errMsg);
-                        throw new CloudRuntimeException(errMsg);
-                    }
-
-                    TemplateOrVolumePostUploadCommand payload = new TemplateOrVolumePostUploadCommand(template.getId(), template.getUuid(), tmpl.getInstallPath(), tmpl
-                            .getChecksum(), tmpl.getType().toString(), template.getUniqueName(), template.getFormat().toString(), templateOnStore.getDataStore().getUri(),
-                            templateOnStore.getDataStore().getRole().toString());
-                    //using the existing max template size configuration
-                    payload.setMaxUploadSize(_configDao.getValue(Config.MaxTemplateAndIsoSize.key()));
-
-                    Long accountId = template.getAccountId();
-                    Account account = _accountDao.findById(accountId);
-                    Domain domain = _domainDao.findById(account.getDomainId());
-
-                    payload.setDefaultMaxSecondaryStorageInGB(_resourceLimitMgr.findCorrectResourceLimitForAccountAndDomain(account, domain, ResourceType.secondary_storage));
-                    payload.setAccountId(accountId);
-                    payload.setRemoteEndPoint(ep.getPublicAddr());
-                    payload.setRequiresHvm(template.requiresHvm());
-                    payload.setDescription(template.getDisplayText());
-                    payloads.add(payload);
                 }
+
                 if(payloads.isEmpty()) {
                     throw new CloudRuntimeException("unable to find zone or an image store with enough capacity");
                 }
@@ -442,6 +442,52 @@
         });
     }
 
+    /**
+     * If the template/ISO is marked as private, then it is allocated to a random secondary storage; otherwise, allocates to every storage pool in every zone given by the
+     * {@link TemplateProfile#zoneIdList}.
+     */
+    private void postUploadAllocation(List<DataStore> imageStores, VMTemplateVO template, List<TemplateOrVolumePostUploadCommand> payloads) {
+        Set<Long> zoneSet = new HashSet<Long>();
+        Collections.shuffle(imageStores);
+        for (DataStore imageStore : imageStores) {
+            Long zoneId_is = imageStore.getScope().getScopeId();
+
+            if (!isZoneAndImageStoreAvailable(imageStore, zoneId_is, zoneSet, isPrivateTemplate(template))) {
+                continue;
+            }
+
+            TemplateInfo tmpl = imageFactory.getTemplate(template.getId(), imageStore);
+
+            // persist template_store_ref entry
+            DataObject templateOnStore = imageStore.create(tmpl);
+
+            // update template_store_ref and template state
+            EndPoint ep = _epSelector.select(templateOnStore);
+            if (ep == null) {
+                String errMsg = "There is no secondary storage VM for downloading template to image store " + imageStore.getName();
+                s_logger.warn(errMsg);
+                throw new CloudRuntimeException(errMsg);
+            }
+
+            TemplateOrVolumePostUploadCommand payload = new TemplateOrVolumePostUploadCommand(template.getId(), template.getUuid(), tmpl.getInstallPath(), tmpl
+                    .getChecksum(), tmpl.getType().toString(), template.getUniqueName(), template.getFormat().toString(), templateOnStore.getDataStore().getUri(),
+                    templateOnStore.getDataStore().getRole().toString());
+            //using the existing max template size configuration
+            payload.setMaxUploadSize(_configDao.getValue(Config.MaxTemplateAndIsoSize.key()));
+
+            Long accountId = template.getAccountId();
+            Account account = _accountDao.findById(accountId);
+            Domain domain = _domainDao.findById(account.getDomainId());
+
+            payload.setDefaultMaxSecondaryStorageInGB(_resourceLimitMgr.findCorrectResourceLimitForAccountAndDomain(account, domain, ResourceType.secondary_storage));
+            payload.setAccountId(accountId);
+            payload.setRemoteEndPoint(ep.getPublicAddr());
+            payload.setRequiresHvm(template.requiresHvm());
+            payload.setDescription(template.getDisplayText());
+            payloads.add(payload);
+        }
+    }
+
     private boolean isPrivateTemplate(VMTemplateVO template){
 
         // if public OR featured OR system template
diff --git a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java
index a9cd947..74347d1 100644
--- a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java
+++ b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java
@@ -275,7 +275,8 @@
         Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId());
         _accountMgr.checkAccess(caller, null, true, owner);
 
-        boolean isRouting = (cmd.isRoutingType() == null) ? false : cmd.isRoutingType();
+        TemplateType templateType = templateMgr.validateTemplateType(cmd, _accountMgr.isAdmin(caller.getAccountId()),
+                CollectionUtils.isEmpty(cmd.getZoneIds()));
 
         List<Long> zoneId = cmd.getZoneIds();
         // ignore passed zoneId if we are using region wide image store
@@ -305,7 +306,7 @@
         }
         return prepare(false, CallContext.current().getCallingUserId(), cmd.getTemplateName(), cmd.getDisplayText(), cmd.getBits(), cmd.isPasswordEnabled(), cmd.getRequiresHvm(),
                 cmd.getUrl(), cmd.isPublic(), cmd.isFeatured(), cmd.isExtractable(), cmd.getFormat(), cmd.getOsTypeId(), zoneId, hypervisorType, cmd.getChecksum(), true,
-                cmd.getTemplateTag(), owner, details, cmd.isSshKeyEnabled(), null, cmd.isDynamicallyScalable(), isRouting ? TemplateType.ROUTING : TemplateType.USER,
+                cmd.getTemplateTag(), owner, details, cmd.isSshKeyEnabled(), null, cmd.isDynamicallyScalable(), templateType,
                 cmd.isDirectDownload(), cmd.isDeployAsIs());
 
     }
diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
index d1fd204..2ed4208 100755
--- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
+++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java
@@ -34,10 +34,9 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import com.cloud.user.UserData;
-import com.cloud.storage.VolumeApiService;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.BaseListTemplateOrIsoPermissionsCmd;
 import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd;
 import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoPermissionsCmd;
@@ -55,8 +54,10 @@
 import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd;
 import org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd;
 import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
 import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd;
 import org.apache.cloudstack.api.command.user.template.UpdateTemplatePermissionsCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
 import org.apache.cloudstack.api.command.user.userdata.LinkUserDataToTemplateCmd;
 import org.apache.cloudstack.api.response.GetUploadParamsResponse;
 import org.apache.cloudstack.context.CallContext;
@@ -85,6 +86,9 @@
 import org.apache.cloudstack.framework.messagebus.MessageBus;
 import org.apache.cloudstack.framework.messagebus.PublishScope;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+import org.apache.cloudstack.snapshot.SnapshotHelper;
 import org.apache.cloudstack.storage.command.AttachCommand;
 import org.apache.cloudstack.storage.command.CommandResult;
 import org.apache.cloudstack.storage.command.DettachCommand;
@@ -96,7 +100,10 @@
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper;
 import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
+import org.apache.cloudstack.storage.template.VnfTemplateManager;
+import org.apache.cloudstack.storage.template.VnfTemplateUtils;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
 import org.apache.commons.collections.CollectionUtils;
@@ -164,6 +171,7 @@
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VMTemplateZoneVO;
 import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeApiService;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.GuestOSDao;
 import com.cloud.storage.dao.LaunchPermissionDao;
@@ -181,6 +189,7 @@
 import com.cloud.user.AccountService;
 import com.cloud.user.AccountVO;
 import com.cloud.user.ResourceLimitService;
+import com.cloud.user.UserData;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.uservm.UserVm;
 import com.cloud.utils.DateUtil;
@@ -209,7 +218,6 @@
 import com.google.common.base.Joiner;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import org.apache.cloudstack.snapshot.SnapshotHelper;
 
 public class TemplateManagerImpl extends ManagerBase implements TemplateManager, TemplateApiService, Configurable {
     private final static Logger s_logger = Logger.getLogger(TemplateManagerImpl.class);
@@ -303,14 +311,26 @@
 
     @Inject
     protected SnapshotHelper snapshotHelper;
+    @Inject
+    VnfTemplateManager vnfTemplateManager;
+
+    @Inject
+    private SecondaryStorageHeuristicDao secondaryStorageHeuristicDao;
+
+    @Inject
+    private HeuristicRuleHelper heuristicRuleHelper;
 
     private TemplateAdapter getAdapter(HypervisorType type) {
         TemplateAdapter adapter = null;
         if (type == HypervisorType.BareMetal) {
             adapter = AdapterBase.getAdapterByName(_adapters, TemplateAdapterType.BareMetal.getName());
         } else {
-            // see HypervisorTemplateAdapter
-            adapter = AdapterBase.getAdapterByName(_adapters, TemplateAdapterType.Hypervisor.getName());
+            // Get template adapter according to hypervisor
+            adapter = AdapterBase.getAdapterByName(_adapters, type.name());
+            // Otherwise, default to generic hypervisor template adapter
+            if (adapter == null) {
+                adapter = AdapterBase.getAdapterByName(_adapters, TemplateAdapterType.Hypervisor.getName());
+            }
         }
 
         if (adapter == null) {
@@ -356,6 +376,9 @@
 
         if (template != null) {
             CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid());
+            if (cmd instanceof RegisterVnfTemplateCmd) {
+                vnfTemplateManager.persistVnfTemplate(template.getId(), (RegisterVnfTemplateCmd) cmd);
+            }
             return template;
         } else {
             throw new CloudRuntimeException("Failed to create a template");
@@ -437,17 +460,21 @@
     }
 
     @Override
-    public DataStore getImageStore(String storeUuid, Long zoneId) {
+    public DataStore getImageStore(String storeUuid, Long zoneId, VolumeVO volume) {
         DataStore imageStore = null;
         if (storeUuid != null) {
             imageStore = _dataStoreMgr.getDataStore(storeUuid, DataStoreRole.Image);
         } else {
-            imageStore = _dataStoreMgr.getImageStoreWithFreeCapacity(zoneId);
+            imageStore = heuristicRuleHelper.getImageStoreIfThereIsHeuristicRule(zoneId, HeuristicType.VOLUME, volume);
             if (imageStore == null) {
-                throw new CloudRuntimeException("cannot find an image store for zone " + zoneId);
+                imageStore = _dataStoreMgr.getImageStoreWithFreeCapacity(zoneId);
             }
         }
 
+        if (imageStore == null) {
+            throw new CloudRuntimeException(String.format("Cannot find an image store for zone [%s].", zoneId));
+        }
+
         return imageStore;
     }
 
@@ -1325,6 +1352,8 @@
             throw new InvalidParameterValueException("Please specify a valid template.");
         }
 
+        VnfTemplateUtils.validateApiCommandParams(cmd, template);
+
         TemplateAdapter adapter = getAdapter(template.getHypervisorType());
         TemplateProfile profile = adapter.prepareDelete(cmd);
         return adapter.delete(profile);
@@ -1626,7 +1655,12 @@
             long zoneId = 0;
             if (snapshotId != null) {
                 snapshot = _snapshotDao.findById(snapshotId);
-                zoneId = snapshot.getDataCenterId();
+                if (command.getZoneId() == null) {
+                    VolumeVO snapshotVolume = _volumeDao.findByIdIncludingRemoved(snapshot.getVolumeId());
+                    zoneId = snapshotVolume.getDataCenterId();
+                } else {
+                    zoneId = command.getZoneId();
+                }
             } else if (volumeId != null) {
                 volume = _volumeDao.findById(volumeId);
                 zoneId = volume.getDataCenterId();
@@ -1641,7 +1675,7 @@
                 DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot);
                 kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole);
 
-                snapInfo = _snapshotFactory.getSnapshot(snapshotId, dataStoreRole);
+                snapInfo = _snapshotFactory.getSnapshotWithRoleAndZone(snapshotId, dataStoreRole, zoneId);
                 if (dataStoreRole == DataStoreRole.Image || kvmSnapshotOnlyInPrimaryStorage) {
                     snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage);
                     _accountMgr.checkAccess(caller, null, true, snapInfo);
@@ -1777,12 +1811,16 @@
         boolean isDynamicScalingEnabled = cmd.isDynamicallyScalable();
         // check whether template owner can create public templates
         boolean allowPublicUserTemplates = AllowPublicUserTemplates.valueIn(templateOwner.getId());
+        final Long zoneId = cmd.getZoneId();
         if (!isAdmin && !allowPublicUserTemplates && isPublic) {
             throw new PermissionDeniedException("Failed to create template " + name + ", only private templates can be created.");
         }
 
         Long volumeId = cmd.getVolumeId();
         Long snapshotId = cmd.getSnapshotId();
+        if (zoneId != null && snapshotId == null) {
+            throw new InvalidParameterValueException("Failed to create private template record, zone ID can only be specified together with snapshot ID.");
+        }
         if ((volumeId == null) && (snapshotId == null)) {
             throw new InvalidParameterValueException("Failed to create private template record, neither volume ID nor snapshot ID were specified.");
         }
@@ -1856,6 +1894,16 @@
             hyperType = snapshot.getHypervisorType();
         }
 
+        if (zoneId != null) {
+            DataCenterVO zone = _dcDao.findById(zoneId);
+            if (zone == null) {
+                throw new InvalidParameterValueException("Failed to create private template record, invalid zone specified");
+            }
+            if (DataCenter.Type.Edge.equals(zone.getType())) {
+                throw new InvalidParameterValueException("Failed to create private template record, Edge zones do not support template creation from snapshots");
+            }
+        }
+
         _resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.template);
         _resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.secondary_storage, new Long(volume != null ? volume.getSize() : snapshot.getSize()).longValue());
 
@@ -2090,22 +2138,11 @@
         // update template type
         TemplateType templateType = null;
         if (cmd instanceof UpdateTemplateCmd) {
-            String newType = ((UpdateTemplateCmd)cmd).getTemplateType();
-            if (newType != null) {
-                if (!_accountService.isRootAdmin(account.getId())) {
-                    throw new PermissionDeniedException("Parameter templatetype can only be specified by a Root Admin, permission denied");
-                }
-                try {
-                    templateType = TemplateType.valueOf(newType.toUpperCase());
-                } catch (IllegalArgumentException ex) {
-                   throw new InvalidParameterValueException("Please specify a valid templatetype: ROUTING / SYSTEM / USER / BUILTIN / PERHOST");
-                }
-            }
-            if (templateType != null && cmd.isRoutingType() != null && (TemplateType.ROUTING.equals(templateType) != cmd.isRoutingType())) {
-                throw new InvalidParameterValueException("Please specify a valid templatetype (consistent with isrouting parameter).");
-            }
-            if (templateType != null && (templateType == TemplateType.SYSTEM || templateType == TemplateType.BUILTIN) && !template.isCrossZones()) {
-                throw new InvalidParameterValueException("System and Builtin templates must be cross zone");
+            boolean isAdmin = _accountMgr.isAdmin(account.getId());
+            templateType = validateTemplateType(cmd, isAdmin, template.isCrossZones());
+            if (cmd instanceof UpdateVnfTemplateCmd) {
+                VnfTemplateUtils.validateApiCommandParams(cmd, template);
+                vnfTemplateManager.updateVnfTemplate(template.getId(), (UpdateVnfTemplateCmd) cmd);
             }
         }
 
@@ -2225,6 +2262,51 @@
         return _tmpltDao.findById(id);
     }
 
+    @Override
+    public TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones) {
+        if (!(cmd instanceof UpdateTemplateCmd) && !(cmd instanceof RegisterTemplateCmd)) {
+            return null;
+        }
+        TemplateType templateType = null;
+        String newType = null;
+        Boolean isRoutingType = null;
+        if (cmd instanceof UpdateTemplateCmd) {
+            newType = ((UpdateTemplateCmd)cmd).getTemplateType();
+            isRoutingType = ((UpdateTemplateCmd)cmd).isRoutingType();
+        } else if (cmd instanceof RegisterTemplateCmd) {
+            newType = ((RegisterTemplateCmd)cmd).getTemplateType();
+            isRoutingType = ((RegisterTemplateCmd)cmd).isRoutingType();
+        }
+        if (newType != null) {
+            try {
+                templateType = TemplateType.valueOf(newType.toUpperCase());
+            } catch (IllegalArgumentException ex) {
+                throw new InvalidParameterValueException(String.format("Please specify a valid templatetype: %s",
+                        org.apache.commons.lang3.StringUtils.join(",", TemplateType.values())));
+            }
+        }
+        if (templateType != null) {
+            if (isRoutingType != null && (TemplateType.ROUTING.equals(templateType) != isRoutingType)) {
+                throw new InvalidParameterValueException("Please specify a valid templatetype (consistent with isrouting parameter).");
+            } else if ((templateType == TemplateType.SYSTEM || templateType == TemplateType.BUILTIN) && !isCrossZones) {
+                throw new InvalidParameterValueException("System and Builtin templates must be cross zone.");
+            } else if ((cmd instanceof RegisterVnfTemplateCmd || cmd instanceof UpdateVnfTemplateCmd) && !TemplateType.VNF.equals(templateType)) {
+                throw new InvalidParameterValueException("The template type must be VNF for VNF templates, but the actual type is " + templateType);
+            }
+        } else if (cmd instanceof RegisterTemplateCmd) {
+            boolean isRouting = Boolean.TRUE.equals(isRoutingType);
+            templateType = (cmd instanceof RegisterVnfTemplateCmd) ? TemplateType.VNF : (isRouting ? TemplateType.ROUTING : TemplateType.USER);
+        }
+        if (templateType != null && !isAdmin && !Arrays.asList(TemplateType.USER, TemplateType.VNF).contains(templateType)) {
+            if (cmd instanceof RegisterTemplateCmd) {
+                throw new InvalidParameterValueException(String.format("Users can not register template with template type %s.", templateType));
+            } else if (cmd instanceof UpdateTemplateCmd) {
+                throw new InvalidParameterValueException(String.format("Users can not update template to template type %s.", templateType));
+            }
+        }
+        return templateType;
+    }
+
     void validateDetails(VMTemplateVO template, Map<String, String> details) {
         if (MapUtils.isEmpty(details)) {
             return;
@@ -2247,7 +2329,8 @@
             String msg = String.format("Invalid %s: %s specified. Valid values are: %s",
                 ApiConstants.BOOT_MODE, bootMode, Arrays.toString(ApiConstants.BootMode.values()));
             s_logger.error(msg);
-            throw new InvalidParameterValueException(msg);        }
+            throw new InvalidParameterValueException(msg);
+        }
     }
 
     void verifyTemplateId(Long id) {
diff --git a/server/src/main/java/com/cloud/test/TestAppender.java b/server/src/main/java/com/cloud/test/TestAppender.java
index d8e97fa..9a6ec62 100644
--- a/server/src/main/java/com/cloud/test/TestAppender.java
+++ b/server/src/main/java/com/cloud/test/TestAppender.java
@@ -46,6 +46,7 @@
 import static org.apache.log4j.Level.FATAL;
 import static org.apache.log4j.Level.INFO;
 import static org.apache.log4j.Level.OFF;
+import static org.apache.log4j.Level.WARN;
 
 /**
 *
@@ -152,6 +153,7 @@
             expectedPatterns.put(FATAL, new HashSet<PatternResult>());
             expectedPatterns.put(INFO, new HashSet<PatternResult>());
             expectedPatterns.put(OFF, new HashSet<PatternResult>());
+            expectedPatterns.put(WARN, new HashSet<PatternResult>());
         }
         public TestAppenderBuilder addExpectedPattern(final Level level, final String pattern) {
             checkArgument(level != null, "addExpectedPattern requires a non-null level");
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index 3684657..86a359a 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -32,6 +32,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.Mac;
@@ -52,6 +53,7 @@
 import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
 import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
 import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
@@ -814,6 +816,9 @@
             return false;
         }
 
+        account.setState(State.REMOVED);
+        _accountDao.update(accountId, account);
+
         if (s_logger.isDebugEnabled()) {
             s_logger.debug("Removed account " + accountId);
         }
@@ -2327,6 +2332,15 @@
     }
 
     @Override
+    public List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId) {
+        List<UserAccountVO> userAccountByEmail = _userAccountDao.getUserAccountByEmail(email, domainId);
+        List<UserAccount> userAccounts = userAccountByEmail.stream()
+                .map(userAccountVO -> (UserAccount) userAccountVO)
+                .collect(Collectors.toList());
+        return userAccounts;
+    }
+
+    @Override
     public Account getActiveAccountById(long accountId) {
         return _accountDao.findById(accountId);
     }
@@ -2470,7 +2484,13 @@
     @Override
     public UserAccount authenticateUser(final String username, final String password, final Long domainId, final InetAddress loginIpAddress, final Map<String, Object[]> requestParameters) {
         UserAccount user = null;
-        if (password != null && !password.isEmpty()) {
+        final String[] oAuthProviderArray = (String[])requestParameters.get(ApiConstants.PROVIDER);
+        final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
+        String oauthProvider = ((oAuthProviderArray == null) ? null : oAuthProviderArray[0]);
+        String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
+
+
+        if ((password != null && !password.isEmpty()) || (oauthProvider != null && secretCode != null)) {
             user = getUserAccount(username, password, domainId, requestParameters);
         } else {
             String key = _configDao.getValue("security.singlesignon.key");
@@ -2623,11 +2643,16 @@
         HashSet<ActionOnFailedAuthentication> actionsOnFailedAuthenticaion = new HashSet<ActionOnFailedAuthentication>();
         User.Source userSource = userAccount != null ? userAccount.getSource() : User.Source.UNKNOWN;
         for (UserAuthenticator authenticator : _userAuthenticators) {
-            if (userSource != User.Source.UNKNOWN) {
+            final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
+            String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
+            if (userSource != User.Source.UNKNOWN && secretCode == null) {
                 if (!authenticator.getName().equalsIgnoreCase(userSource.name())) {
                     continue;
                 }
             }
+            if (secretCode != null && !authenticator.getName().equals("oauth2")) {
+                continue;
+            }
             Pair<Boolean, ActionOnFailedAuthentication> result = authenticator.authenticate(username, password, domainId, requestParameters);
             if (result.first()) {
                 authenticated = true;
diff --git a/server/src/main/java/com/cloud/user/DomainManagerImpl.java b/server/src/main/java/com/cloud/user/DomainManagerImpl.java
index 2dd356a..1551309 100644
--- a/server/src/main/java/com/cloud/user/DomainManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/DomainManagerImpl.java
@@ -17,10 +17,14 @@
 package com.cloud.user;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.inject.Inject;
 
@@ -28,16 +32,20 @@
 import com.cloud.api.query.dao.VpcOfferingJoinDao;
 import com.cloud.api.query.vo.NetworkOfferingJoinVO;
 import com.cloud.api.query.vo.VpcOfferingJoinVO;
+import com.cloud.configuration.Resource;
 import com.cloud.domain.dao.DomainDetailsDao;
 import com.cloud.network.vpc.dao.VpcOfferingDao;
 import com.cloud.network.vpc.dao.VpcOfferingDetailsDao;
 import com.cloud.offerings.dao.NetworkOfferingDao;
 import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
+import com.cloud.exception.ResourceAllocationException;
+import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
 import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
+import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
 import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@@ -151,6 +159,10 @@
     private DomainDetailsDao _domainDetailsDao;
     @Inject
     private AnnotationDao annotationDao;
+    @Inject
+    private ResourceLimitService resourceLimitService;
+    @Inject
+    private AffinityGroupDomainMapDao affinityGroupDomainMapDao;
 
     @Inject
     MessageBus _messageBus;
@@ -272,7 +284,7 @@
         List<DomainVO> domains = _domainDao.search(sc, null);
 
         if (!domains.isEmpty()) {
-            throw new InvalidParameterValueException("Domain with name " + name + " already exists for the parent id=" + parentId);
+            throw new InvalidParameterValueException(String.format("Domain with name [%s] already exists for the parent with ID [%s].", name, parentId));
         }
     }
 
@@ -910,4 +922,192 @@
         }
     }
 
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_DOMAIN_MOVE, eventDescription = "moving Domain")
+    @DB
+    public Domain moveDomainAndChildrenToNewParentDomain(MoveDomainCmd cmd) throws ResourceAllocationException {
+        Long idOfDomainToBeMoved = cmd.getDomainId();
+        Long idOfNewParentDomain = cmd.getParentDomainId();
+
+        if (idOfDomainToBeMoved == Domain.ROOT_DOMAIN) {
+            throw new InvalidParameterValueException("The domain to be moved cannot be the ROOT domain.");
+        }
+
+        if (idOfDomainToBeMoved.equals(idOfNewParentDomain)) {
+            throw new InvalidParameterValueException("The domain to be moved and the new parent domain cannot be the same.");
+        }
+
+        DomainVO domainToBeMoved = returnDomainIfExistsAndIsActive(idOfDomainToBeMoved);
+        s_logger.debug(String.format("Found the domain [%s] as the domain to be moved.", domainToBeMoved));
+
+        DomainVO newParentDomain = returnDomainIfExistsAndIsActive(idOfNewParentDomain);
+        s_logger.debug(String.format("Found the domain [%s] as the new parent domain of the domain to be moved [%s].", newParentDomain, domainToBeMoved));
+
+        Account caller = getCaller();
+        _accountMgr.checkAccess(caller, domainToBeMoved);
+        _accountMgr.checkAccess(caller, newParentDomain);
+
+        Long idOfCurrentParentOfDomainToBeMoved = domainToBeMoved.getParent();
+        if (idOfCurrentParentOfDomainToBeMoved.equals(idOfNewParentDomain)) {
+            throw new InvalidParameterValueException(String.format("The current parent domain of the domain to be moved is equal to the new parent domain [%s].", newParentDomain));
+        }
+
+        if (newParentDomain.getPath().startsWith(domainToBeMoved.getPath())) {
+            throw new InvalidParameterValueException("The new parent domain of the domain cannot be one of its children.");
+        }
+
+        validateUniqueDomainName(domainToBeMoved.getName(), idOfNewParentDomain);
+
+        validateNewParentDomainResourceLimits(domainToBeMoved, newParentDomain);
+
+        String currentPathOfDomainToBeMoved = domainToBeMoved.getPath();
+        String domainToBeMovedName = domainToBeMoved.getName().concat("/");
+        String newPathOfDomainToBeMoved = newParentDomain.getPath().concat(domainToBeMovedName);
+
+        validateNewParentDomainCanAccessAllDomainToBeMovedResources(domainToBeMoved, newParentDomain, currentPathOfDomainToBeMoved, newPathOfDomainToBeMoved);
+
+        DomainVO parentOfDomainToBeMoved = _domainDao.findById(idOfCurrentParentOfDomainToBeMoved);
+        Transaction.execute(new TransactionCallbackNoReturn() {
+            @Override
+            public void doInTransactionWithoutResult(TransactionStatus status) {
+                s_logger.debug(String.format("Setting the new parent of the domain to be moved [%s] as [%s].", domainToBeMoved, newParentDomain));
+                domainToBeMoved.setParent(idOfNewParentDomain);
+
+                updateDomainAndChildrenPathAndLevel(domainToBeMoved, newParentDomain, currentPathOfDomainToBeMoved, newPathOfDomainToBeMoved);
+
+                updateResourceCounts(idOfCurrentParentOfDomainToBeMoved, idOfNewParentDomain);
+
+                updateChildCounts(parentOfDomainToBeMoved, newParentDomain);
+            }
+        });
+
+        return domainToBeMoved;
+    }
+
+    protected void validateNewParentDomainResourceLimits(DomainVO domainToBeMoved, DomainVO newParentDomain) throws ResourceAllocationException {
+        long domainToBeMovedId = domainToBeMoved.getId();
+        long newParentDomainId = newParentDomain.getId();
+        for (Resource.ResourceType resourceType : Resource.ResourceType.values()) {
+            long currentDomainResourceCount = _resourceCountDao.getResourceCount(domainToBeMovedId, ResourceOwnerType.Domain, resourceType);
+            long newParentDomainResourceCount = _resourceCountDao.getResourceCount(newParentDomainId, ResourceOwnerType.Domain, resourceType);
+            long newParentDomainResourceLimit = resourceLimitService.findCorrectResourceLimitForDomain(newParentDomain, resourceType);
+
+            if (newParentDomainResourceLimit == Resource.RESOURCE_UNLIMITED) {
+                return;
+            }
+
+            if (currentDomainResourceCount + newParentDomainResourceCount > newParentDomainResourceLimit) {
+                String message = String.format("Cannot move domain [%s] to parent domain [%s] as maximum domain resource limit of type [%s] would be exceeded. The current resource "
+                        + "count for domain [%s] is [%s], the resource count for the new parent domain [%s] is [%s], and the limit is [%s].", domainToBeMoved.getUuid(),
+                        newParentDomain.getUuid(), resourceType, domainToBeMoved.getUuid(), currentDomainResourceCount, newParentDomain.getUuid(), newParentDomainResourceCount,
+                        newParentDomainResourceLimit);
+                s_logger.error(message);
+                throw new ResourceAllocationException(message, resourceType);
+            }
+        }
+    }
+
+    protected void validateNewParentDomainCanAccessAllDomainToBeMovedResources(DomainVO domainToBeMoved, DomainVO newParentDomain, String currentPathOfDomainToBeMoved,
+                                                                               String newPathOfDomainToBeMoved) {
+        Map<Long, List<String>> idsOfDomainsWithNetworksUsedByDomainToBeMoved = _networkDomainDao.listDomainsOfSharedNetworksUsedByDomainPath(currentPathOfDomainToBeMoved);
+        validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsWithNetworksUsedByDomainToBeMoved, "Networks");
+
+        Map<Long, List<String>> idsOfDomainsOfAffinityGroupsUsedByDomainToBeMoved =
+                affinityGroupDomainMapDao.listDomainsOfAffinityGroupsUsedByDomainPath(currentPathOfDomainToBeMoved);
+        validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfAffinityGroupsUsedByDomainToBeMoved, "Affinity groups");
+
+        Map<Long, List<String>> idsOfDomainsOfServiceOfferingsUsedByDomainToBeMoved =
+                serviceOfferingJoinDao.listDomainsOfServiceOfferingsUsedByDomainPath(currentPathOfDomainToBeMoved);
+        validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfServiceOfferingsUsedByDomainToBeMoved, "Service offerings");
+
+        Map<Long, List<String>> idsOfDomainsOfNetworkOfferingsUsedByDomainToBeMoved =
+                networkOfferingJoinDao.listDomainsOfNetworkOfferingsUsedByDomainPath(currentPathOfDomainToBeMoved);
+        validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfNetworkOfferingsUsedByDomainToBeMoved, "Network offerings");
+
+        Map<Long, List<String>> idsOfDomainsOfDedicatedResourcesUsedByDomainToBeMoved =
+                _dedicatedDao.listDomainsOfDedicatedResourcesUsedByDomainPath(currentPathOfDomainToBeMoved);
+        validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfDedicatedResourcesUsedByDomainToBeMoved, "Dedicated resources");
+    }
+
+    protected void validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(String newPathOfDomainToBeMoved, DomainVO domainToBeMoved, DomainVO newParentDomain,
+                                                                              Map<Long, List<String>> idsOfDomainsWithResourcesUsedByDomainToBeMoved, String resourceToLog) {
+        Map<DomainVO, List<String>> domainsOfResourcesInaccessibleToNewParentDomain = new HashMap<>();
+        for (Map.Entry<Long, List<String>> entry : idsOfDomainsWithResourcesUsedByDomainToBeMoved.entrySet()) {
+            DomainVO domainWithResourceUsedByDomainToBeMoved = _domainDao.findById(entry.getKey());
+
+            Pattern pattern = Pattern.compile(domainWithResourceUsedByDomainToBeMoved.getPath().replace("/", "\\/").concat(".*"));
+            Matcher matcher = pattern.matcher(newPathOfDomainToBeMoved);
+            if (!matcher.matches()) {
+                domainsOfResourcesInaccessibleToNewParentDomain.put(domainWithResourceUsedByDomainToBeMoved, entry.getValue());
+            }
+        }
+
+        if (!domainsOfResourcesInaccessibleToNewParentDomain.isEmpty()) {
+            s_logger.error(String.format("The new parent domain [%s] does not have access to domains [%s] used by [%s] in the domain to be moved [%s].",
+                    newParentDomain, domainsOfResourcesInaccessibleToNewParentDomain.keySet(), domainsOfResourcesInaccessibleToNewParentDomain.values(), domainToBeMoved));
+            throw new InvalidParameterValueException(String.format("New parent domain [%s] does not have access to [%s] used by domain [%s], therefore, domain [%s] cannot be moved.",
+                    newParentDomain, resourceToLog, domainToBeMoved, domainToBeMoved));
+        }
+    }
+
+    protected DomainVO returnDomainIfExistsAndIsActive(Long idOfDomain) {
+        s_logger.debug(String.format("Checking if domain with ID [%s] exists and is active.", idOfDomain));
+        DomainVO domain = _domainDao.findById(idOfDomain);
+
+        if (domain == null) {
+            throw new InvalidParameterValueException(String.format("Unable to find a domain with the specified ID [%s].", idOfDomain));
+        } else if (domain.getState().equals(Domain.State.Inactive)) {
+            throw new InvalidParameterValueException(String.format("Unable to use the domain [%s] as it is in state [%s].", domain, Domain.State.Inactive));
+        }
+
+        return domain;
+    }
+
+    protected void updateDomainAndChildrenPathAndLevel(DomainVO domainToBeMoved, DomainVO newParentDomain, String oldPath, String newPath) {
+        Integer oldRootLevel = domainToBeMoved.getLevel();
+        Integer newLevel = newParentDomain.getLevel() + 1;
+
+        updateDomainPathAndLevel(domainToBeMoved, oldPath, newPath, oldRootLevel, newLevel);
+
+        List<DomainVO> childrenDomain = _domainDao.findAllChildren(oldPath, domainToBeMoved.getId());
+        for (DomainVO childDomain : childrenDomain) {
+            updateDomainPathAndLevel(childDomain, oldPath, newPath, oldRootLevel, newLevel);
+        }
+    }
+
+    protected void updateDomainPathAndLevel(DomainVO domain, String oldPath, String newPath, Integer oldRootLevel, Integer newLevel) {
+        String finalPath = StringUtils.replaceOnce(domain.getPath(), oldPath, newPath);
+        domain.setPath(finalPath);
+
+        Integer currentLevel = domain.getLevel();
+        int finalLevel = newLevel + currentLevel - oldRootLevel;
+        domain.setLevel(finalLevel);
+
+        s_logger.debug(String.format("Updating the path to [%s] and the level to [%s] of the domain [%s].", finalPath, finalLevel, domain));
+        _domainDao.update(domain.getId(), domain);
+    }
+
+    protected void updateResourceCounts(Long idOfOldParentDomain, Long idOfNewParentDomain) {
+        s_logger.debug(String.format("Updating the resource counts of the old parent domain [%s] and of the new parent domain [%s].", idOfOldParentDomain, idOfNewParentDomain));
+        resourceLimitService.recalculateResourceCount(null, idOfOldParentDomain, null);
+        resourceLimitService.recalculateResourceCount(null, idOfNewParentDomain, null);
+    }
+
+    protected void updateChildCounts(DomainVO oldParentDomain, DomainVO newParentDomain) {
+        int finalOldParentChildCount = oldParentDomain.getChildCount() - 1;
+
+        oldParentDomain.setChildCount(finalOldParentChildCount);
+        oldParentDomain.setNextChildSeq(finalOldParentChildCount + 1);
+
+        s_logger.debug(String.format("Updating the child count of the old parent domain [%s] to [%s].", oldParentDomain, finalOldParentChildCount));
+        _domainDao.update(oldParentDomain.getId(), oldParentDomain);
+
+        int finalNewParentChildCount = newParentDomain.getChildCount() + 1;
+
+        newParentDomain.setChildCount(finalNewParentChildCount);
+        newParentDomain.setNextChildSeq(finalNewParentChildCount + 1);
+
+        s_logger.debug(String.format("Updating the child count of the new parent domain [%s] to [%s].", newParentDomain, finalNewParentChildCount));
+        _domainDao.update(newParentDomain.getId(), newParentDomain);
+    }
 }
diff --git a/server/src/main/java/com/cloud/user/PasswordPolicy.java b/server/src/main/java/com/cloud/user/PasswordPolicy.java
index 42ef7b4..c5278ba 100644
--- a/server/src/main/java/com/cloud/user/PasswordPolicy.java
+++ b/server/src/main/java/com/cloud/user/PasswordPolicy.java
@@ -25,7 +25,8 @@
             Integer.class,
             "password.policy.minimum.special.characters",
             "0",
-            "Minimum number of special characters that the user's password must have. The value 0 means the user's password does not require any special characters.",
+            "Minimum number of special characters that the user's password must have. Any character that is neither a letter nor numeric is considered special. " +
+                    "The value 0 means the user's password does not require any special characters.",
             true,
             ConfigKey.Scope.Domain);
 
@@ -43,7 +44,7 @@
             Integer.class,
             "password.policy.minimum.uppercase.letters",
             "0",
-            "Minimum number of uppercase letters that the user's password must have. The value 0 means the user's password does not require any uppercase letters.",
+            "Minimum number of uppercase letters [A-Z] that the user's password must have. The value 0 means the user's password does not require any uppercase letters.",
             true,
             ConfigKey.Scope.Domain);
 
@@ -52,7 +53,7 @@
             Integer.class,
             "password.policy.minimum.lowercase.letters",
             "0",
-            "Minimum number of lowercase letters that the user's password must have. The value 0 means the user's password does not require any lowercase letters.",
+            "Minimum number of lowercase letters [a-z] that the user's password must have. The value 0 means the user's password does not require any lowercase letters.",
             true,
             ConfigKey.Scope.Domain);
 
@@ -61,7 +62,7 @@
             Integer.class,
             "password.policy.minimum.digits",
             "0",
-            "Minimum number of digits that the user's password must have. The value 0 means the user's password does not require any digits.",
+            "Minimum number of numeric characters [0-9] that the user's password must have. The value 0 means the user's password does not require any numeric characters.",
             true,
             ConfigKey.Scope.Domain);
 
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 9a5e768..566fcb3 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -18,6 +18,8 @@
 
 import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH;
 import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
+import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
 
 import java.io.IOException;
 import java.io.StringReader;
@@ -69,6 +71,7 @@
 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.DeployVMCmd;
+import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
 import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd;
 import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
 import org.apache.cloudstack.api.command.user.vm.RemoveNicFromVMCmd;
@@ -123,8 +126,12 @@
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.template.VnfTemplateManager;
+import org.apache.cloudstack.userdata.UserDataManager;
 import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
 import org.apache.cloudstack.utils.security.ParserUtils;
+import org.apache.cloudstack.vm.schedule.VMScheduleManager;
+import org.apache.cloudstack.vm.UnmanagedVMsManager;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
@@ -133,6 +140,7 @@
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import org.apache.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.w3c.dom.Document;
@@ -230,7 +238,6 @@
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
 import com.cloud.host.dao.HostDao;
-import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
 import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
@@ -349,6 +356,7 @@
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallbackNoReturn;
+import com.cloud.utils.db.TransactionCallbackWithException;
 import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
 import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.db.UUIDManager;
@@ -560,6 +568,8 @@
     @Inject
     private VmStatsDao vmStatsDao;
     @Inject
+    private DataCenterDao dataCenterDao;
+    @Inject
     private MessageBus messageBus;
     @Inject
     protected CommandSetupHelper commandSetupHelper;
@@ -582,6 +592,9 @@
     @Inject
     private AutoScaleManager autoScaleManager;
 
+    @Inject
+    VMScheduleManager vmScheduleManager;
+
     private ScheduledExecutorService _executor = null;
     private ScheduledExecutorService _vmIpFetchExecutor = null;
     private int _expungeInterval;
@@ -611,6 +624,12 @@
     @Inject
     private ManagementService _mgr;
 
+    @Inject
+    private UserDataManager userDataManager;
+
+    @Inject
+    VnfTemplateManager vnfTemplateManager;
+
     private static final ConfigKey<Integer> VmIpFetchWaitInterval = new ConfigKey<Integer>("Advanced", Integer.class, "externaldhcp.vmip.retrieval.interval", "180",
             "Wait Interval (in seconds) for shared network vm dhcp ip addr fetch for next iteration ", true);
 
@@ -648,6 +667,14 @@
             HypervisorType.Simulator
     ));
 
+    protected static final List<HypervisorType> ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS = Arrays.asList(
+            HypervisorType.KVM,
+            HypervisorType.XenServer,
+            HypervisorType.VMware,
+            HypervisorType.Simulator,
+            HypervisorType.Custom
+    );
+
     private static final List<HypervisorType> HYPERVISORS_THAT_CAN_DO_STORAGE_MIGRATION_ON_NON_USER_VMS = Arrays.asList(HypervisorType.KVM, HypervisorType.VMware);
 
     @Override
@@ -939,7 +966,7 @@
             userDataDetails = cmd.getUserdataDetails().toString();
         }
         userData = finalizeUserData(userData, userDataId, template);
-        userData = validateUserData(userData, cmd.getHttpMethod());
+        userData = userDataManager.validateUserData(userData, cmd.getHttpMethod());
 
         userVm.setUserDataId(userDataId);
         userVm.setUserData(userData);
@@ -1158,9 +1185,8 @@
     private UserVm forceRebootVirtualMachine(long vmId, long hostId, boolean enterSetup) {
         try {
             if (stopVirtualMachine(vmId, false) != null) {
-                Map<VirtualMachineProfile.Param,Object> params = null;
+                Map<VirtualMachineProfile.Param,Object> params = new HashMap<>();
                 if (enterSetup) {
-                    params = new HashMap();
                     params.put(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE);
                 }
                 return startVirtualMachine(vmId, null, null, hostId, params, null, false).first();
@@ -2126,11 +2152,11 @@
                 Long maxIopsInNewDiskOffering = null;
                 boolean autoMigrate = false;
                 boolean shrinkOk = false;
-                if (customParameters.containsKey(ApiConstants.MIN_IOPS)) {
-                    minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MIN_IOPS));
+                if (customParameters.containsKey(MIN_IOPS)) {
+                    minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MIN_IOPS));
                 }
-                if (customParameters.containsKey(ApiConstants.MAX_IOPS)) {
-                    minIopsInNewDiskOffering = Long.parseLong(customParameters.get(ApiConstants.MAX_IOPS));
+                if (customParameters.containsKey(MAX_IOPS)) {
+                    minIopsInNewDiskOffering = Long.parseLong(customParameters.get(MAX_IOPS));
                 }
                 if (customParameters.containsKey(ApiConstants.AUTO_MIGRATE)) {
                     autoMigrate = Boolean.parseBoolean(customParameters.get(ApiConstants.AUTO_MIGRATE));
@@ -2978,7 +3004,7 @@
         if (userData != null) {
             // check and replace newlines
             userData = userData.replace("\\n", "");
-            userData = validateUserData(userData, httpMethod);
+            userData = userDataManager.validateUserData(userData, httpMethod);
             // update userData on domain router.
             updateUserdata = true;
         } else {
@@ -3226,7 +3252,7 @@
         ServiceOfferingVO offering = serviceOfferingDao.findById(vmInstance.getId(), serviceOfferingId);
         if (offering != null && offering.getRemoved() == null) {
             if (offering.isVolatileVm()) {
-                return restoreVMInternal(caller, vmInstance, null);
+                return restoreVMInternal(caller, vmInstance);
             }
         } else {
             throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId + " corresponding to the vm");
@@ -4112,7 +4138,7 @@
             _accountMgr.checkAccess(owner, AccessType.UseEntry, false, template);
 
             // check if the user data is correct
-            userData = validateUserData(userData, httpmethod);
+            userData = userDataManager.validateUserData(userData, httpmethod);
 
             // Find an SSH public key corresponding to the key pair name, if one is
             // given
@@ -4375,7 +4401,7 @@
      * @throws InvalidParameterValueException if the hypervisor does not support rootdisksize override
      */
     protected void verifyIfHypervisorSupportsRootdiskSizeOverride(HypervisorType hypervisorType) {
-        if (!(hypervisorType == HypervisorType.KVM || hypervisorType == HypervisorType.XenServer || hypervisorType == HypervisorType.VMware || hypervisorType == HypervisorType.Simulator)) {
+        if (!ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS.contains(hypervisorType)) {
             throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override");
         }
     }
@@ -4473,17 +4499,9 @@
             vm.setDisplayVm(true);
         }
 
-        if (isImport) {
-            vm.setDataCenterId(zone.getId());
-            vm.setHostId(host.getId());
-            if (lastHost != null) {
-                vm.setLastHostId(lastHost.getId());
-            }
-            vm.setPowerState(powerState);
-            if (powerState == VirtualMachine.PowerState.PowerOn) {
-                vm.setState(State.Running);
-            }
-        }
+        setVmRequiredFieldsForImport(isImport, vm, zone, hypervisorType, host, lastHost, powerState);
+
+        setVncPasswordForKvmIfAvailable(customParameters, vm);
 
         vm.setUserVmType(vmType);
         _vmDao.persist(vm);
@@ -4571,6 +4589,23 @@
         return vm;
     }
 
+    protected void setVmRequiredFieldsForImport(boolean isImport, UserVmVO vm, DataCenter zone, HypervisorType hypervisorType,
+                                                Host host, Host lastHost, VirtualMachine.PowerState powerState) {
+        if (isImport) {
+            vm.setDataCenterId(zone.getId());
+            if (List.of(HypervisorType.VMware, HypervisorType.KVM).contains(hypervisorType) && host != null) {
+                vm.setHostId(host.getId());
+            }
+            if (lastHost != null) {
+                vm.setLastHostId(lastHost.getId());
+            }
+            vm.setPowerState(powerState);
+            if (powerState == VirtualMachine.PowerState.PowerOn) {
+                vm.setState(State.Running);
+            }
+        }
+    }
+
     private void updateVMDiskController(UserVmVO vm, Map<String, String> customParameters, GuestOSVO guestOS) {
         // If hypervisor is vSphere and OS is OS X, set special settings.
         if (guestOS.getDisplayName().toLowerCase().contains("apple mac os")) {
@@ -4870,7 +4905,6 @@
         Long hostId = cmd.getHostId();
         Map<VirtualMachineProfile.Param, Object> additionalParams =  new HashMap<>();
         Map<Long, DiskOffering> diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap();
-        Map<String, String> details = cmd.getDetails();
         if (cmd instanceof DeployVMCmdByAdmin) {
             DeployVMCmdByAdmin adminCmd = (DeployVMCmdByAdmin)cmd;
             podId = adminCmd.getPodId();
@@ -4883,6 +4917,11 @@
         if (cmd.getBootIntoSetup() != null) {
             additionalParams.put(VirtualMachineProfile.Param.BootIntoSetup, cmd.getBootIntoSetup());
         }
+
+        if (StringUtils.isNotBlank(cmd.getPassword())) {
+            additionalParams.put(VirtualMachineProfile.Param.VmPassword, cmd.getPassword());
+        }
+
         return startVirtualMachine(vmId, podId, clusterId, hostId, diskOfferingMap, additionalParams, cmd.getDeploymentPlanner());
     }
 
@@ -5091,6 +5130,8 @@
         Answer startAnswer = cmds.getAnswer(StartAnswer.class);
         String returnedIp = null;
         String originalIp = null;
+        String originalVncPassword = profile.getVirtualMachine().getVncPassword();
+        String returnedVncPassword = null;
         if (startAnswer != null) {
             StartAnswer startAns = (StartAnswer)startAnswer;
             VirtualMachineTO vmTO = startAns.getVirtualMachine();
@@ -5099,6 +5140,7 @@
                     returnedIp = nicTO.getIp();
                 }
             }
+            returnedVncPassword = vmTO.getVncPassword();
         }
 
         List<NicVO> nics = _nicDao.listByVmId(vm.getId());
@@ -5150,6 +5192,8 @@
             }
         }
 
+        updateVncPasswordIfItHasChanged(originalVncPassword, returnedVncPassword, profile);
+
         // get system ip and create static nat rule for the vm
         try {
             _rulesMgr.getSystemIpAndEnableStaticNatForVm(profile.getVirtualMachine(), false);
@@ -5184,6 +5228,14 @@
         return true;
     }
 
+    protected void updateVncPasswordIfItHasChanged(String originalVncPassword, String returnedVncPassword, VirtualMachineProfile profile) {
+        if (returnedVncPassword != null && !originalVncPassword.equals(returnedVncPassword)) {
+            UserVmVO userVm = _vmDao.findById(profile.getId());
+            userVm.setVncPassword(returnedVncPassword);
+            _vmDao.update(userVm.getId(), userVm);
+        }
+    }
+
     @Override
     public void finalizeExpunge(VirtualMachine vm) {
     }
@@ -5260,21 +5312,21 @@
     }
 
     @Override
-    public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long hostId, Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
-            throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
+    public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long hostId, @NotNull Map<VirtualMachineProfile.Param, Object> additionalParams,
+            String deploymentPlannerToUse) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
         return startVirtualMachine(vmId, null, null, hostId, additionalParams, deploymentPlannerToUse);
     }
 
     @Override
     public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId,
-            Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
+            @NotNull Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse)
             throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
         return startVirtualMachine(vmId, podId, clusterId, hostId, additionalParams, deploymentPlannerToUse, true);
     }
 
     @Override
     public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId,
-            Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse, boolean isExplicitHost)
+            @NotNull Map<VirtualMachineProfile.Param, Object> additionalParams, String deploymentPlannerToUse, boolean isExplicitHost)
             throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
         // Input validation
         final Account callerAccount = CallContext.current().getCallingAccount();
@@ -5373,15 +5425,7 @@
             // Check that the password was passed in and is valid
             template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId());
 
-            String password = "saved_password";
-            if (template.isEnablePassword()) {
-                if (vm.getDetail("password") != null) {
-                    password = DBEncryptionUtil.decrypt(vm.getDetail("password"));
-                } else {
-                    password = _mgr.generateRandomPassword();
-                    vm.setPassword(password);
-                }
-            }
+            String password = getCurrentVmPasswordOrDefineNewPassword(String.valueOf(additionalParams.getOrDefault(VirtualMachineProfile.Param.VmPassword, "")), vm, template);
 
             if (!validPassword(password)) {
                 throw new InvalidParameterValueException("A valid password for this virtual machine was not provided.");
@@ -5394,7 +5438,7 @@
             params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password);
         }
 
-        if(null != additionalParams && additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) {
+        if(additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) {
             if (! HypervisorType.VMware.equals(vm.getHypervisorType())) {
                 throw new InvalidParameterValueException(ApiConstants.BOOT_INTO_SETUP + " makes no sense for " + vm.getHypervisorType());
             }
@@ -5437,6 +5481,39 @@
         return vmParamPair;
     }
 
+    /**
+     * If the template is password enabled and the VM already has a password, returns it.
+     * If the template is password enabled and the VM does not have a password, sets the password to the password defined by the user and returns it. If no password is informed,
+     * sets it to a random password and returns it.
+     * If the template is not password enabled, returns saved_password.
+     * @param newPassword The new password informed by the user in order to set the password of the VM.
+     * @param vm The VM to retrieve the password from.
+     * @param template The template to be checked if the password is enabled or not.
+     * @return The password of the VM or saved_password.
+     */
+    protected String getCurrentVmPasswordOrDefineNewPassword(String newPassword, UserVmVO vm, VMTemplateVO template) {
+        String password = "saved_password";
+
+        if (template.isEnablePassword()) {
+            if (vm.getDetail("password") != null) {
+                s_logger.debug(String.format("Decrypting VM [%s] current password.", vm));
+                password = DBEncryptionUtil.decrypt(vm.getDetail("password"));
+            } else if (StringUtils.isNotBlank(newPassword)) {
+                s_logger.debug(String.format("A password for VM [%s] was informed. Setting VM password to value defined by user.", vm));
+                password = newPassword;
+                vm.setPassword(password);
+            } else {
+                s_logger.debug(String.format("Setting VM [%s] password to a randomly generated password.", vm));
+                password = _mgr.generateRandomPassword();
+                vm.setPassword(password);
+            }
+        } else if (StringUtils.isNotBlank(newPassword)) {
+            s_logger.debug(String.format("A password was informed; however, the template [%s] is not password enabled. Ignoring the parameter.", template));
+        }
+
+        return password;
+    }
+
     private Map<VirtualMachineProfile.Param, Object> createParameterInParameterMap(Map<VirtualMachineProfile.Param, Object> params, Map<VirtualMachineProfile.Param, Object> parameterMap, VirtualMachineProfile.Param parameter,
             Object parameterValue) {
         if (s_logger.isTraceEnabled()) {
@@ -5781,9 +5858,9 @@
                     }
                     if (userDataId != null) {
                         UserData apiUserDataVO = userDataDao.findById(userDataId);
-                        return doConcateUserDatas(templateUserDataVO.getUserData(), apiUserDataVO.getUserData());
+                        return userDataManager.concatenateUserData(templateUserDataVO.getUserData(), apiUserDataVO.getUserData(), null);
                     } else if (StringUtils.isNotEmpty(userData)) {
-                        return doConcateUserDatas(templateUserDataVO.getUserData(), userData);
+                        return userDataManager.concatenateUserData(templateUserDataVO.getUserData(), userData, null);
                     } else {
                         return templateUserDataVO.getUserData();
                     }
@@ -5801,16 +5878,6 @@
         return null;
     }
 
-    private String doConcateUserDatas(String userdata1, String userdata2) {
-        byte[] userdata1Bytes = Base64.decodeBase64(userdata1.getBytes());
-        byte[] userdata2Bytes = Base64.decodeBase64(userdata2.getBytes());
-        byte[] finalUserDataBytes = new byte[userdata1Bytes.length + userdata2Bytes.length];
-        System.arraycopy(userdata1Bytes, 0, finalUserDataBytes, 0, userdata1Bytes.length);
-        System.arraycopy(userdata2Bytes, 0, finalUserDataBytes, userdata1Bytes.length, userdata2Bytes.length);
-
-        return Base64.encodeBase64String(finalUserDataBytes);
-    }
-
     @Override
     public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException,
     StorageUnavailableException, ResourceAllocationException {
@@ -5859,6 +5926,11 @@
         if (template == null) {
             throw new InvalidParameterValueException("Unable to use template " + templateId);
         }
+        if (TemplateType.VNF.equals(template.getTemplateType())) {
+            vnfTemplateManager.validateVnfApplianceNics(template, cmd.getNetworkIds());
+        } else if (cmd instanceof DeployVnfApplianceCmd) {
+            throw new InvalidParameterValueException("Can't deploy VNF appliance from a non-VNF template");
+        }
 
         ServiceOfferingJoinVO svcOffering = serviceOfferingJoinDao.findById(serviceOfferingId);
 
@@ -5906,6 +5978,7 @@
         }
 
         String userData = cmd.getUserData();
+        userData = userDataManager.validateUserData(userData, cmd.getHttpMethod());
         Long userDataId = cmd.getUserdataId();
         String userDataDetails = null;
         if (MapUtils.isNotEmpty(cmd.getUserdataDetails())) {
@@ -5939,14 +6012,14 @@
             if (networkIds != null) {
                 throw new InvalidParameterValueException("Can't specify network Ids in Basic zone");
             } else {
-                vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId,
+                vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId,
                         size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(),
                         cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(),
                         dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId);
             }
         } else {
             if (zone.isSecurityGroupEnabled())  {
-                vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd), owner, name,
+                vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name,
                         displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard,
                         cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(),
                         dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, null);
@@ -5958,6 +6031,9 @@
                 vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, group,
                         cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(),
                         cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId);
+                if (cmd instanceof DeployVnfApplianceCmd) {
+                    vnfTemplateManager.createIsolatedNetworkRulesForVnfAppliance(zone, template, owner, vm, (DeployVnfApplianceCmd) cmd);
+                }
             }
         }
 
@@ -6236,13 +6312,27 @@
         }
     }
 
+    protected List<Long> getSecurityGroupIdList(SecurityGroupAction cmd, DataCenter zone, VirtualMachineTemplate template, Account owner) {
+        List<Long> securityGroupIdList = getSecurityGroupIdList(cmd);
+        if (cmd instanceof DeployVnfApplianceCmd) {
+            SecurityGroup securityGroup = vnfTemplateManager.createSecurityGroupForVnfAppliance(zone, template, owner, (DeployVnfApplianceCmd) cmd);
+            if (securityGroup != null) {
+                if (securityGroupIdList == null) {
+                    securityGroupIdList = new ArrayList<>();
+                }
+                securityGroupIdList.add(securityGroup.getId());
+            }
+        }
+        return securityGroupIdList;
+    }
+
     // this is an opportunity to verify that parameters that came in via the Details Map are OK
     // for example, minIops and maxIops should either both be specified or neither be specified and,
     // if specified, minIops should be <= maxIops
     private void verifyDetails(Map<String,String> details) {
         if (details != null) {
-            String minIops = details.get("minIops");
-            String maxIops = details.get("maxIops");
+            String minIops = details.get(MIN_IOPS);
+            String maxIops = details.get(MAX_IOPS);
 
             verifyMinAndMaxIops(minIops, maxIops);
 
@@ -6415,22 +6505,8 @@
     }
 
     public boolean isVMUsingLocalStorage(VMInstanceVO vm) {
-        boolean usesLocalStorage = false;
-
         List<VolumeVO> volumes = _volsDao.findByInstance(vm.getId());
-        for (VolumeVO vol : volumes) {
-            DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
-            if (diskOffering.isUseLocalStorage()) {
-                usesLocalStorage = true;
-                break;
-            }
-            StoragePoolVO storagePool = _storagePoolDao.findById(vol.getPoolId());
-            if (storagePool.isLocal()) {
-                usesLocalStorage = true;
-                break;
-            }
-        }
-        return usesLocalStorage;
+        return isAnyVmVolumeUsingLocalStorage(volumes);
     }
 
     @Override
@@ -6489,7 +6565,7 @@
 
         DeployDestination dest = null;
         if (destinationHost == null) {
-            dest = chooseVmMigrationDestination(vm, srcHost);
+            dest = chooseVmMigrationDestination(vm, srcHost, null);
         } else {
             dest = checkVmMigrationDestination(vm, srcHost, destinationHost);
         }
@@ -6504,7 +6580,7 @@
         return findMigratedVm(vm.getId(), vm.getType());
     }
 
-    private DeployDestination chooseVmMigrationDestination(VMInstanceVO vm, Host srcHost) {
+    private DeployDestination chooseVmMigrationDestination(VMInstanceVO vm, Host srcHost, Long poolId) {
         vm.setLastHostId(null); // Last host does not have higher priority in vm migration
         final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId());
         final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offering, null, null);
@@ -6512,7 +6588,7 @@
         final Host host = _hostDao.findById(srcHostId);
         ExcludeList excludes = new ExcludeList();
         excludes.addHost(srcHostId);
-        final DataCenterDeployment plan = _itMgr.getMigrationDeployment(vm, host, null, excludes);
+        final DataCenterDeployment plan = _itMgr.getMigrationDeployment(vm, host, poolId, excludes);
         try {
             return _planningMgr.planDeployment(profile, plan, excludes, null);
         } catch (final AffinityConflictException e2) {
@@ -6812,8 +6888,21 @@
         return implicitPlannerUsed;
     }
 
-    private boolean isVmVolumesOnZoneWideStore(VMInstanceVO vm) {
-        final List<VolumeVO> volumes = _volsDao.findCreatedByInstance(vm.getId());
+    protected boolean isAnyVmVolumeUsingLocalStorage(final List<VolumeVO> volumes) {
+        for (VolumeVO vol : volumes) {
+            DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
+            if (diskOffering.isUseLocalStorage()) {
+                return true;
+            }
+            StoragePoolVO storagePool = _storagePoolDao.findById(vol.getPoolId());
+            if (storagePool.isLocal()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isAllVmVolumesOnZoneWideStore(final List<VolumeVO> volumes) {
         if (CollectionUtils.isEmpty(volumes)) {
             return false;
         }
@@ -6837,6 +6926,10 @@
             throw new InvalidParameterValueException("Cannot migrate VM, host with ID: " + srcHostId + " for VM not found");
         }
 
+        if (destinationHost == null) {
+            return new Pair<>(srcHost, null);
+        }
+
         // Check if source and destination hosts are valid and migrating to same host
         if (destinationHost.getId() == srcHostId) {
             throw new InvalidParameterValueException(String.format("Cannot migrate VM as it is already present on host %s (ID: %s), please specify valid destination host to migrate the VM",
@@ -6956,6 +7049,25 @@
         return volToPoolObjectMap;
     }
 
+    protected boolean isVmCanBeMigratedWithoutStorage(Host srcHost, Host destinationHost, List<VolumeVO> volumes,
+          Map<String, String> volumeToPool) {
+        return !isAnyVmVolumeUsingLocalStorage(volumes) &&
+                MapUtils.isEmpty(volumeToPool) && destinationHost != null
+                && (destinationHost.getClusterId().equals(srcHost.getClusterId()) || isAllVmVolumesOnZoneWideStore(volumes));
+    }
+
+    protected Host chooseVmMigrationDestinationUsingVolumePoolMap(VMInstanceVO vm, Host srcHost, Map<Long, Long> volToPoolObjectMap) {
+        Long poolId = null;
+        if (MapUtils.isNotEmpty(volToPoolObjectMap)) {
+            poolId = new ArrayList<>(volToPoolObjectMap.values()).get(0);
+        }
+        DeployDestination deployDestination = chooseVmMigrationDestination(vm, srcHost, poolId);
+        if (deployDestination == null || deployDestination.getHost() == null) {
+            throw new CloudRuntimeException("Unable to find suitable destination to migrate VM " + vm.getInstanceName());
+        }
+        return deployDestination.getHost();
+    }
+
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_VM_MIGRATE, eventDescription = "migrating VM", async = true)
     public VirtualMachine migrateVirtualMachineWithVolume(Long vmId, Host destinationHost, Map<String, String> volumeToPool) throws ResourceUnavailableException,
@@ -7000,8 +7112,8 @@
         Pair<Host, Host> sourceDestinationHosts = getHostsForMigrateVmWithStorage(vm, destinationHost);
         Host srcHost = sourceDestinationHosts.first();
 
-        if (!isVMUsingLocalStorage(vm) && MapUtils.isEmpty(volumeToPool)
-                && (destinationHost.getClusterId().equals(srcHost.getClusterId()) || isVmVolumesOnZoneWideStore(vm))){
+        final List<VolumeVO> volumes = _volsDao.findCreatedByInstance(vm.getId());
+        if (isVmCanBeMigratedWithoutStorage(srcHost, destinationHost, volumes, volumeToPool)) {
             // If volumes do not have to be migrated
             // call migrateVirtualMachine for non-user VMs else throw exception
             if (!VirtualMachine.Type.User.equals(vm.getType())) {
@@ -7013,6 +7125,10 @@
 
         Map<Long, Long> volToPoolObjectMap = getVolumePoolMappingForMigrateVmWithStorage(vm, volumeToPool);
 
+        if (destinationHost == null) {
+            destinationHost = chooseVmMigrationDestinationUsingVolumePoolMap(vm, srcHost, volToPoolObjectMap);
+        }
+
         checkHostsDedication(vm, srcHost.getId(), destinationHost.getId());
 
         _itMgr.migrateWithStorage(vm.getUuid(), srcHost.getId(), destinationHost.getId(), volToPoolObjectMap);
@@ -7548,6 +7664,20 @@
         return false;
     }
 
+    private DiskOfferingVO validateAndGetDiskOffering(Long diskOfferingId, UserVmVO vm, Account caller) {
+        DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId);
+        if (diskOffering == null) {
+            throw new InvalidParameterValueException("Cannot find disk offering with ID " + diskOfferingId);
+        }
+        DataCenterVO zone = dataCenterDao.findById(vm.getDataCenterId());
+        _accountMgr.checkAccess(caller, diskOffering, zone);
+        ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId());
+        if (serviceOffering.getDiskOfferingStrictness() && !serviceOffering.getDiskOfferingId().equals(diskOfferingId)) {
+            throw new InvalidParameterValueException("VM's service offering has a strict disk offering requirement, and the specified disk offering does not match");
+        }
+        return diskOffering;
+    }
+
     @Override
     public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException {
         // Input validation
@@ -7555,6 +7685,11 @@
 
         long vmId = cmd.getVmId();
         Long newTemplateId = cmd.getTemplateId();
+        Long rootDiskOfferingId = cmd.getRootDiskOfferingId();
+        boolean expunge = cmd.getExpungeRootDisk();
+        Map<String, String> details = cmd.getDetails();
+
+        verifyDetails(details);
 
         UserVmVO vm = _vmDao.findById(vmId);
         if (vm == null) {
@@ -7562,20 +7697,38 @@
             ex.addProxyObject(String.valueOf(vmId), "vmId");
             throw ex;
         }
-
         _accountMgr.checkAccess(caller, null, true, vm);
 
+        DiskOffering diskOffering = rootDiskOfferingId != null ? validateAndGetDiskOffering(rootDiskOfferingId, vm, caller) : null;
+        VMTemplateVO template = _templateDao.findById(newTemplateId);
+        if (template.getSize() != null) {
+            String rootDiskSize = details.get(VmDetailConstants.ROOT_DISK_SIZE);
+            Long templateSize = template.getSize();
+            if (StringUtils.isNumeric(rootDiskSize)) {
+                if (Long.parseLong(rootDiskSize) * GiB_TO_BYTES < templateSize) {
+                    throw new InvalidParameterValueException(String.format("Root disk size [%s] is smaller than the template size [%s]", rootDiskSize, templateSize));
+                }
+            } else if (diskOffering != null && diskOffering.getDiskSize() < templateSize) {
+                throw new InvalidParameterValueException(String.format("Disk size for selected offering [%s] is less than the template's size [%s]", diskOffering.getDiskSize(), templateSize));
+            }
+        }
+
         //check if there are any active snapshots on volumes associated with the VM
         s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId);
         if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) {
             throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on ROOT volume, Re-install VM is not permitted, please try again later.");
         }
         s_logger.debug("Found no ongoing snapshots on volume of type ROOT, for the vm with id " + vmId);
-        return restoreVMInternal(caller, vm, newTemplateId);
+        return restoreVMInternal(caller, vm, newTemplateId, rootDiskOfferingId, expunge, details);
     }
 
-    public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException {
-        return _itMgr.restoreVirtualMachine(vm.getId(), newTemplateId);
+    public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map<String, String> details) throws InsufficientCapacityException, ResourceUnavailableException {
+        return _itMgr.restoreVirtualMachine(vm.getId(), newTemplateId, rootDiskOfferingId, expunge, details);
+    }
+
+
+    public UserVm restoreVMInternal(Account caller, UserVmVO vm) throws InsufficientCapacityException, ResourceUnavailableException {
+        return restoreVMInternal(caller, vm, null, null, false, null);
     }
 
     private VMTemplateVO getRestoreVirtualMachineTemplate(Account caller, Long newTemplateId, List<VolumeVO> rootVols, UserVmVO vm) {
@@ -7620,7 +7773,9 @@
     }
 
     @Override
-    public UserVm restoreVirtualMachine(final Account caller, final long vmId, final Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException {
+    public UserVm restoreVirtualMachine(final Account caller, final long vmId, final Long newTemplateId,
+            final Long rootDiskOfferingId,
+            final boolean expunge, final Map<String, String> details) throws InsufficientCapacityException, ResourceUnavailableException {
         Long userId = caller.getId();
         _userDao.findById(userId);
         UserVmVO vm = _vmDao.findById(vmId);
@@ -7677,51 +7832,65 @@
             }
         }
 
-        List<Volume> newVols = new ArrayList<>();
+        DiskOffering diskOffering = rootDiskOfferingId != null ? _diskOfferingDao.findById(rootDiskOfferingId) : null;
         for (VolumeVO root : rootVols) {
-            if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ){
-                Long templateId = root.getTemplateId();
-                boolean isISO = false;
-                if (templateId == null) {
-                    // Assuming that for a vm deployed using ISO, template ID is set to NULL
-                    isISO = true;
-                    templateId = vm.getIsoId();
-                }
+            if ( !Volume.State.Allocated.equals(root.getState()) || newTemplateId != null ) {
+                _volumeService.validateDestroyVolume(root, caller, expunge, false);
+                final UserVmVO userVm = vm;
+                Pair<UserVmVO, Volume> vmAndNewVol = Transaction.execute(new TransactionCallbackWithException<Pair<UserVmVO, Volume>, CloudRuntimeException>() {
+                    @Override
+                    public Pair<UserVmVO, Volume> doInTransaction(final TransactionStatus status) throws CloudRuntimeException {
+                        Long templateId = root.getTemplateId();
+                        boolean isISO = false;
+                        if (templateId == null) {
+                            // Assuming that for a vm deployed using ISO, template ID is set to NULL
+                            isISO = true;
+                            templateId = userVm.getIsoId();
+                        }
 
-                /* If new template/ISO is provided allocate a new volume from new template/ISO otherwise allocate new volume from original template/ISO */
-                Volume newVol = null;
-                if (newTemplateId != null) {
-                    if (isISO) {
-                        newVol = volumeMgr.allocateDuplicateVolume(root, null);
-                        vm.setIsoId(newTemplateId);
-                        vm.setGuestOSId(template.getGuestOSId());
-                        vm.setTemplateId(newTemplateId);
-                    } else {
-                        newVol = volumeMgr.allocateDuplicateVolume(root, newTemplateId);
-                        vm.setGuestOSId(template.getGuestOSId());
-                        vm.setTemplateId(newTemplateId);
+                        /* If new template/ISO is provided allocate a new volume from new template/ISO otherwise allocate new volume from original template/ISO */
+                        Volume newVol = null;
+                        if (newTemplateId != null) {
+                            if (isISO) {
+                                newVol = volumeMgr.allocateDuplicateVolume(root, null);
+                                userVm.setIsoId(newTemplateId);
+                                userVm.setGuestOSId(template.getGuestOSId());
+                                userVm.setTemplateId(newTemplateId);
+                            } else {
+                                newVol = volumeMgr.allocateDuplicateVolume(root, newTemplateId);
+                                userVm.setGuestOSId(template.getGuestOSId());
+                                userVm.setTemplateId(newTemplateId);
+                            }
+                            // check and update VM if it can be dynamically scalable with the new template
+                            updateVMDynamicallyScalabilityUsingTemplate(userVm, newTemplateId);
+                        } else {
+                            newVol = volumeMgr.allocateDuplicateVolume(root, null);
+                        }
+
+                        updateVolume(newVol, template, userVm, diskOffering, details);
+                        volumeMgr.saveVolumeDetails(newVol.getDiskOfferingId(), newVol.getId());
+
+                        // 1. Save usage event and update resource count for user vm volumes
+                        try {
+                            _resourceLimitMgr.incrementResourceCount(newVol.getAccountId(), ResourceType.volume, newVol.isDisplay());
+                            _resourceLimitMgr.incrementResourceCount(newVol.getAccountId(),  ResourceType.primary_storage, newVol.isDisplay(), new Long(newVol.getSize()));
+                        } catch (final CloudRuntimeException e) {
+                            throw e;
+                        } catch (final Exception e) {
+                            s_logger.error("Unable to restore VM " + userVm.getUuid(), e);
+                            throw new CloudRuntimeException(e);
+                        }
+
+                        // 2. Create Usage event for the newly created volume
+                        UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, newVol.getAccountId(), newVol.getDataCenterId(), newVol.getId(), newVol.getName(), newVol.getDiskOfferingId(), template.getId(), newVol.getSize());
+                        _usageEventDao.persist(usageEvent);
+
+                        return new Pair<>(userVm, newVol);
                     }
-                    // check and update VM if it can be dynamically scalable with the new template
-                    updateVMDynamicallyScalabilityUsingTemplate(vm, newTemplateId);
-                } else {
-                    newVol = volumeMgr.allocateDuplicateVolume(root, null);
-                }
-                newVols.add(newVol);
+                });
 
-                if (userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.ROOT_DISK_SIZE) == null && !newVol.getSize().equals(template.getSize())) {
-                    VolumeVO resizedVolume = (VolumeVO) newVol;
-                    if (template.getSize() != null) {
-                        resizedVolume.setSize(template.getSize());
-                        _volsDao.update(resizedVolume.getId(), resizedVolume);
-                    }
-                }
-
-                // 1. Save usage event and update resource count for user vm volumes
-                _resourceLimitMgr.incrementResourceCount(newVol.getAccountId(), ResourceType.volume, newVol.isDisplay());
-                _resourceLimitMgr.incrementResourceCount(newVol.getAccountId(), ResourceType.primary_storage, newVol.isDisplay(), new Long(newVol.getSize()));
-                // 2. Create Usage event for the newly created volume
-                UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, newVol.getAccountId(), newVol.getDataCenterId(), newVol.getId(), newVol.getName(), newVol.getDiskOfferingId(), template.getId(), newVol.getSize());
-                _usageEventDao.persist(usageEvent);
+                vm = vmAndNewVol.first();
+                Volume newVol = vmAndNewVol.second();
 
                 handleManagedStorage(vm, root);
 
@@ -7729,7 +7898,7 @@
 
                 // Detach, destroy and create the usage event for the old root volume.
                 _volsDao.detachVolume(root.getId());
-                volumeMgr.destroyVolume(root);
+                _volumeService.destroyVolume(root.getId(), caller, expunge, false);
 
                 // For VMware hypervisor since the old root volume is replaced by the new root volume, force expunge old root volume if it has been created in storage
                 if (vm.getHypervisorType() == HypervisorType.VMware) {
@@ -7792,6 +7961,48 @@
 
     }
 
+    private void updateVolume(Volume vol, VMTemplateVO template, UserVmVO userVm, DiskOffering diskOffering, Map<String, String> details) {
+        VolumeVO resizedVolume = (VolumeVO) vol;
+
+        if (userVmDetailsDao.findDetail(userVm.getId(), VmDetailConstants.ROOT_DISK_SIZE) == null && !vol.getSize().equals(template.getSize())) {
+            if (template.getSize() != null) {
+                resizedVolume.setSize(template.getSize());
+            }
+        }
+
+        if (diskOffering != null) {
+            resizedVolume.setDiskOfferingId(diskOffering.getId());
+            resizedVolume.setSize(diskOffering.getDiskSize());
+            if (diskOffering.isCustomized()) {
+                resizedVolume.setSize(vol.getSize());
+            }
+            if (diskOffering.getMinIops() != null) {
+                resizedVolume.setMinIops(diskOffering.getMinIops());
+            }
+            if (diskOffering.getMaxIops() != null) {
+                resizedVolume.setMaxIops(diskOffering.getMaxIops());
+            }
+        }
+
+        if (MapUtils.isNotEmpty(details)) {
+            if (StringUtils.isNumeric(details.get(VmDetailConstants.ROOT_DISK_SIZE))) {
+                Long rootDiskSize = Long.parseLong(details.get(VmDetailConstants.ROOT_DISK_SIZE)) * GiB_TO_BYTES;
+                resizedVolume.setSize(rootDiskSize);
+            }
+
+            String minIops = details.get(MIN_IOPS);
+            String maxIops = details.get(MAX_IOPS);
+
+            if (StringUtils.isNumeric(minIops)) {
+                resizedVolume.setMinIops(Long.parseLong(minIops));
+            }
+            if (StringUtils.isNumeric(maxIops)) {
+                resizedVolume.setMinIops(Long.parseLong(maxIops));
+            }
+        }
+        _volsDao.update(resizedVolume.getId(), resizedVolume);
+    }
+
     private void updateVMDynamicallyScalabilityUsingTemplate(UserVmVO vm, Long newTemplateId) {
         ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId());
         VMTemplateVO newTemplate = _templateDao.findById(newTemplateId);
@@ -8122,11 +8333,12 @@
     public UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String instanceName, 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 sshPublicKeys,
-                           final String hostName, final HypervisorType hypervisorType, final Map<String, String> customParameters, final VirtualMachine.PowerState powerState) throws InsufficientCapacityException {
+                           final String hostName, final HypervisorType hypervisorType, final Map<String, String> customParameters,
+                           final VirtualMachine.PowerState powerState, final LinkedHashMap<String, List<NicProfile>> networkNicMap) throws InsufficientCapacityException {
         if (zone == null) {
             throw new InvalidParameterValueException("Unable to import virtual machine with invalid zone");
         }
-        if (host == null) {
+        if (host == null && hypervisorType == HypervisorType.VMware) {
             throw new InvalidParameterValueException("Unable to import virtual machine with invalid host");
         }
 
@@ -8142,7 +8354,7 @@
         final Boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId());
         return commitUserVm(true, zone, host, lastHost, template, hostName, displayName, owner,
                 null, null, userData, null, null, isDisplayVm, keyboard,
-                accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKeys, null,
+                accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKeys, networkNicMap,
                 id, instanceName, uuidName, hypervisorType, customParameters,
                 null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null);
     }
@@ -8162,8 +8374,9 @@
                 return false;
             }
 
-            if (vm.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
-                throw new UnsupportedServiceException("Unmanaging a VM is currently allowed for VMware VMs only");
+            if (!UnmanagedVMsManager.isSupported(vm.getHypervisorType())) {
+                throw new UnsupportedServiceException("Unmanaging a VM is currently not supported on hypervisor " +
+                        vm.getHypervisorType().toString());
             }
 
             List<VolumeVO> volumes = _volsDao.findByInstance(vm.getId());
@@ -8342,4 +8555,11 @@
     public Boolean getDestroyRootVolumeOnVmDestruction(Long domainId){
         return DestroyRootVolumeOnVmDestruction.valueIn(domainId);
     }
+
+    private void setVncPasswordForKvmIfAvailable(Map<String, String> customParameters, UserVmVO vm){
+        if (customParameters.containsKey(VmDetailConstants.KVM_VNC_PASSWORD)
+                && StringUtils.isNotEmpty(customParameters.get(VmDetailConstants.KVM_VNC_PASSWORD))) {
+            vm.setVncPassword(customParameters.get(VmDetailConstants.KVM_VNC_PASSWORD));
+        }
+    }
 }
diff --git a/server/src/main/java/com/cloud/vm/UserVmStateListener.java b/server/src/main/java/com/cloud/vm/UserVmStateListener.java
index e4df6bb..e9f7e7c 100644
--- a/server/src/main/java/com/cloud/vm/UserVmStateListener.java
+++ b/server/src/main/java/com/cloud/vm/UserVmStateListener.java
@@ -152,7 +152,7 @@
         try {
             s_eventBus.publish(eventMsg);
         } catch (org.apache.cloudstack.framework.events.EventBusException e) {
-            s_logger.warn("Failed to publish state change event on the the event bus.");
+            s_logger.warn("Failed to publish state change event on the event bus.");
         }
 
     }
diff --git a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java
index f2f4a6f..eeaff61 100644
--- a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java
@@ -56,7 +56,6 @@
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.component.PluggableService;
-import com.cloud.utils.db.Filter;
 import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallback;
 import com.cloud.utils.db.TransactionStatus;
@@ -95,7 +94,7 @@
     }
 
     @Override
-    public Role findRole(Long id) {
+    public Role findRole(Long id, boolean ignorePrivateRoles) {
         if (id == null || id < 1L) {
             logger.trace(String.format("Role ID is invalid [%s]", id));
             return null;
@@ -105,14 +104,41 @@
             logger.trace(String.format("Role not found [id=%s]", id));
             return null;
         }
-        Account account = getCurrentAccount();
-        if (!accountManager.isRootAdmin(account.getId()) && RoleType.Admin == role.getRoleType()) {
-            logger.debug(String.format("Role [id=%s, name=%s] is of 'Admin' type and is only visible to 'Root admins'.", id, role.getName()));
+        if (!isCallerRootAdmin() && (RoleType.Admin == role.getRoleType() || (!role.isPublicRole() && ignorePrivateRoles))) {
+            logger.debug(String.format("Role [id=%s, name=%s] is either of 'Admin' type or is private and is only visible to 'Root admins'.", id, role.getName()));
             return null;
         }
         return role;
     }
 
+    @Override
+    public List<Role> findRoles(List<Long> ids, boolean ignorePrivateRoles) {
+        List<Role> result = new ArrayList<>();
+        if (CollectionUtils.isEmpty(ids)) {
+            logger.trace(String.format("Role IDs are invalid [%s]", ids));
+            return result;
+        }
+
+        List<RoleVO> roles = roleDao.searchByIds(ids.toArray(new Long[0]));
+        if (CollectionUtils.isEmpty(roles)) {
+            logger.trace(String.format("Roles not found [ids=%s]", ids));
+            return result;
+        }
+        for (Role role : roles) {
+            if (!isCallerRootAdmin() && (RoleType.Admin == role.getRoleType() || (!role.isPublicRole() && ignorePrivateRoles))) {
+                logger.debug(String.format("Role [id=%s, name=%s] is either of 'Admin' type or is private and is only visible to 'Root admins'.", role.getId(), role.getName()));
+                continue;
+            }
+            result.add(role);
+        }
+        return result;
+    }
+
+    @Override
+    public Role findRole(Long id) {
+        return findRole(id, false);
+    }
+
     /**
      * Simple call to {@link CallContext#current()} to retrieve the current calling account.
      * This method facilitates unit testing, it avoids mocking static methods.
@@ -140,7 +166,7 @@
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_ROLE_CREATE, eventDescription = "creating Role")
-    public Role createRole(final String name, final RoleType roleType, final String description) {
+    public Role createRole(final String name, final RoleType roleType, final String description, boolean publicRole) {
         checkCallerAccess();
         if (roleType == null || roleType == RoleType.Unknown) {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided");
@@ -148,7 +174,9 @@
         return Transaction.execute(new TransactionCallback<RoleVO>() {
             @Override
             public RoleVO doInTransaction(TransactionStatus status) {
-                RoleVO role = roleDao.persist(new RoleVO(name, roleType, description));
+                RoleVO role = new RoleVO(name, roleType, description);
+                role.setPublicRole(publicRole);
+                role = roleDao.persist(role);
                 CallContext.current().putContextParameter(Role.class, role.getUuid());
                 return role;
             }
@@ -157,12 +185,14 @@
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_ROLE_CREATE, eventDescription = "creating role by cloning another role")
-    public Role createRole(String name, Role role, String description) {
+    public Role createRole(String name, Role role, String description, boolean publicRole) {
         checkCallerAccess();
         return Transaction.execute(new TransactionCallback<RoleVO>() {
             @Override
             public RoleVO doInTransaction(TransactionStatus status) {
-                RoleVO newRoleVO = roleDao.persist(new RoleVO(name, role.getRoleType(), description));
+                RoleVO newRole = new RoleVO(name, role.getRoleType(), description);
+                newRole.setPublicRole(publicRole);
+                RoleVO newRoleVO = roleDao.persist(newRole);
                 if (newRoleVO == null) {
                     throw new CloudRuntimeException("Unable to create the role: " + name + ", failed to persist in DB");
                 }
@@ -181,7 +211,7 @@
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_ROLE_IMPORT, eventDescription = "importing Role")
-    public Role importRole(String name, RoleType type, String description, List<Map<String, Object>> rules, boolean forced) {
+    public Role importRole(String name, RoleType type, String description, List<Map<String, Object>> rules, boolean forced, boolean isPublicRole) {
         checkCallerAccess();
         if (StringUtils.isEmpty(name)) {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role name provided");
@@ -190,7 +220,7 @@
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided");
         }
 
-        List<RoleVO> existingRoles = roleDao.findByName(name);
+        List<RoleVO> existingRoles = roleDao.findByName(name, isCallerRootAdmin());
         if (CollectionUtils.isNotEmpty(existingRoles) && !forced) {
             throw new CloudRuntimeException("Role already exists");
         }
@@ -199,7 +229,7 @@
             @Override
             public RoleVO doInTransaction(TransactionStatus status) {
                 RoleVO newRole = null;
-                RoleVO existingRole = roleDao.findByNameAndType(name, type);
+                RoleVO existingRole = roleDao.findByNameAndType(name, type, isCallerRootAdmin());
                 if (existingRole != null) {
                     if (existingRole.isDefault()) {
                         throw new CloudRuntimeException("Failed to import the role: " + name + ", default role cannot be overriden");
@@ -216,11 +246,14 @@
                     existingRole.setName(name);
                     existingRole.setRoleType(type);
                     existingRole.setDescription(description);
+                    existingRole.setPublicRole(isPublicRole);
                     roleDao.update(existingRole.getId(), existingRole);
 
                     newRole = existingRole;
                 } else {
-                    newRole = roleDao.persist(new RoleVO(name, type, description));
+                    RoleVO role = new RoleVO(name, type, description);
+                    role.setPublicRole(isPublicRole);
+                    newRole = roleDao.persist(role);
                 }
 
                 if (newRole == null) {
@@ -243,16 +276,23 @@
 
     @Override
     @ActionEvent(eventType = EventTypes.EVENT_ROLE_UPDATE, eventDescription = "updating Role")
-    public Role updateRole(final Role role, final String name, final RoleType roleType, final String description) {
+    public Role updateRole(final Role role, final String name, final RoleType roleType, final String description, Boolean publicRole) {
         checkCallerAccess();
-        if (role.isDefault()) {
-            throw new PermissionDeniedException("Default roles cannot be updated");
-        }
 
         if (roleType != null && roleType == RoleType.Unknown) {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unknown is not a valid role type");
         }
         RoleVO roleVO = (RoleVO)role;
+
+        if (role.isDefault()) {
+            if (publicRole == null || roleType != null || !StringUtils.isAllEmpty(name, description)) {
+                throw new PermissionDeniedException("Default roles cannot be updated (with the exception of making it private/public).");
+            }
+            roleVO.setPublicRole(publicRole);
+            roleDao.update(role.getId(), roleVO);
+            return role;
+        }
+
         if (StringUtils.isNotEmpty(name)) {
             roleVO.setName(name);
         }
@@ -268,6 +308,10 @@
             roleVO.setDescription(description);
         }
 
+        if (publicRole == null) {
+            publicRole = role.isPublicRole();
+        }
+        roleVO.setPublicRole(publicRole);
         roleDao.update(role.getId(), roleVO);
         return role;
     }
@@ -363,7 +407,7 @@
     @Override
     public Pair<List<Role>, Integer> findRolesByName(String name, String keyword, Long startIndex, Long limit) {
         if (StringUtils.isNotBlank(name) || StringUtils.isNotBlank(keyword)) {
-            Pair<List<RoleVO>, Integer> data = roleDao.findAllByName(name, keyword, startIndex, limit);
+            Pair<List<RoleVO>, Integer> data = roleDao.findAllByName(name, keyword, startIndex, limit, isCallerRootAdmin());
             int removed = removeRootAdminRolesIfNeeded(data.first());
             return new Pair<List<Role>,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second() - removed));
         }
@@ -375,8 +419,7 @@
      *  The actual removal is executed via {@link #removeRootAdminRoles(List)}. Therefore, if the method is called by a 'root admin', we do nothing here.
      */
     protected int removeRootAdminRolesIfNeeded(List<? extends Role> roles) {
-        Account account = getCurrentAccount();
-        if (!accountManager.isRootAdmin(account.getId())) {
+        if (!isCallerRootAdmin()) {
             return removeRootAdminRoles(roles);
         }
         return 0;
@@ -408,10 +451,10 @@
 
     @Override
     public Pair<List<Role>, Integer> findRolesByType(RoleType roleType, Long startIndex, Long limit) {
-        if (roleType == null || RoleType.Admin == roleType && !accountManager.isRootAdmin(getCurrentAccount().getId())) {
+        if (roleType == null || RoleType.Admin == roleType && !isCallerRootAdmin()) {
             return new Pair<List<Role>, Integer>(Collections.emptyList(), 0);
         }
-        Pair<List<RoleVO>, Integer> data = roleDao.findAllByRoleType(roleType, startIndex, limit);
+        Pair<List<RoleVO>, Integer> data = roleDao.findAllByRoleType(roleType, startIndex, limit, isCallerRootAdmin());
         return new Pair<List<Role>,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second()));
     }
 
@@ -424,8 +467,7 @@
 
     @Override
     public Pair<List<Role>, Integer> listRoles(Long startIndex, Long limit) {
-        Pair<List<RoleVO>, Integer> data = roleDao.searchAndCount(null,
-                new Filter(RoleVO.class, "id", Boolean.TRUE, startIndex, limit));
+        Pair<List<RoleVO>, Integer> data = roleDao.listAllRoles(startIndex, limit, isCallerRootAdmin());
         int removed = removeRootAdminRolesIfNeeded(data.first());
         return new Pair<List<Role>,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second() - removed));
     }
@@ -439,6 +481,10 @@
         return Collections.emptyList();
     }
 
+    private boolean isCallerRootAdmin() {
+        return accountManager.isRootAdmin(getCurrentAccount().getId());
+    }
+
     @Override
     public Permission getRolePermission(String permission) {
         if (StringUtils.isEmpty(permission)) {
diff --git a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java
index cc59846..6a9d40c 100644
--- a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java
@@ -26,6 +26,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.cluster.ManagementServerHostVO;
 import com.cloud.user.dao.UserDataDao;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.Role;
@@ -50,6 +51,7 @@
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
+import com.cloud.cluster.dao.ManagementServerHostDao;
 import com.cloud.dc.ClusterVO;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.HostPodVO;
@@ -158,6 +160,8 @@
     @Inject
     private UserDataDao userDataDao;
     @Inject
+    private ManagementServerHostDao managementServerHostDao;
+    @Inject
     EntityManager entityManager;
 
     private static final List<RoleType> adminRoles = Collections.singletonList(RoleType.Admin);
@@ -192,6 +196,8 @@
         s_typeMap.put(EntityType.VR, ApiCommandResourceType.DomainRouter);
         s_typeMap.put(EntityType.SYSTEM_VM, ApiCommandResourceType.SystemVm);
         s_typeMap.put(EntityType.AUTOSCALE_VM_GROUP, ApiCommandResourceType.AutoScaleVmGroup);
+        s_typeMap.put(EntityType.MANAGEMENT_SERVER, ApiCommandResourceType.Host);
+        s_typeMap.put(EntityType.OBJECT_STORAGE, ApiCommandResourceType.ObjectStore);
     }
 
     public List<KubernetesClusterHelper> getKubernetesClusterHelpers() {
@@ -532,6 +538,8 @@
                 return kubernetesClusterHelpers.get(0).findByUuid(entityUuid);
             case AUTOSCALE_VM_GROUP:
                 return autoScaleVmGroupDao.findByUuid(entityUuid);
+            case MANAGEMENT_SERVER:
+                return managementServerHostDao.findByUuid(entityUuid);
             default:
                 throw new CloudRuntimeException("Invalid entity type " + type);
         }
@@ -607,6 +615,9 @@
             case SYSTEM_VM:
                 VMInstanceVO instance = vmInstanceDao.findByUuid(entityUuid);
                 return instance != null ? instance.getInstanceName() : null;
+            case MANAGEMENT_SERVER:
+                ManagementServerHostVO mgmtServer = managementServerHostDao.findByUuid(entityUuid);
+                return mgmtServer != null ? mgmtServer.getName() : null;
             default:
                 return null;
         }
diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
index bbdf730..36978ab 100644
--- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java
@@ -27,6 +27,9 @@
 import java.util.Timer;
 import java.util.TimerTask;
 
+import com.cloud.storage.VolumeApiService;
+import com.cloud.utils.fsm.NoTransitionException;
+import com.cloud.vm.VirtualMachineManager;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
@@ -53,6 +56,7 @@
 import org.apache.cloudstack.backup.dao.BackupOfferingDao;
 import org.apache.cloudstack.backup.dao.BackupScheduleDao;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
 import org.apache.cloudstack.framework.jobs.AsyncJobManager;
@@ -148,6 +152,12 @@
     private ApiDispatcher apiDispatcher;
     @Inject
     private AsyncJobManager asyncJobManager;
+    @Inject
+    private VirtualMachineManager virtualMachineManager;
+    @Inject
+    private VolumeApiService volumeApiService;
+    @Inject
+    private VolumeOrchestrationService volumeOrchestrationService;
 
     private AsyncJobDispatcher asyncJobDispatcher;
     private Timer backupTimer;
@@ -602,25 +612,108 @@
 
         final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId());
         if (offering == null) {
-            throw new CloudRuntimeException("Failed to find backup offering of the VM backup");
+            throw new CloudRuntimeException("Failed to find backup offering of the VM backup.");
         }
 
-        ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE,
-                String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()),
-                vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),
-                true, 0);
+        String backupDetailsInMessage = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "uuid", "externalId", "vmId", "type", "status", "date");
+        tryRestoreVM(backup, vm, offering, backupDetailsInMessage);
+        updateVolumeState(vm, Volume.Event.RestoreSucceeded, Volume.State.Ready);
+        updateVmState(vm, VirtualMachine.Event.RestoringSuccess, VirtualMachine.State.Stopped);
 
-        final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
-        if (!backupProvider.restoreVMFromBackup(vm, backup)) {
-            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
-                    String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()),
-                    vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
-            throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid());
-        }
         return importRestoredVM(vm.getDataCenterId(), vm.getDomainId(), vm.getAccountId(), vm.getUserId(),
                 vm.getInstanceName(), vm.getHypervisorType(), backup);
     }
 
+    /**
+     * Tries to restore a VM from a backup. <br/>
+     * First update the VM state to {@link VirtualMachine.Event#RestoringRequested} and its volume states to {@link Volume.Event#RestoreRequested}, <br/>
+     * and then try to restore the backup. <br/>
+     *
+     * If restore fails, then update the VM state to {@link VirtualMachine.Event#RestoringFailed}, and its volumes to {@link Volume.Event#RestoreFailed} and throw an {@link CloudRuntimeException}.
+     */
+    protected void tryRestoreVM(BackupVO backup, VMInstanceVO vm, BackupOffering offering, String backupDetailsInMessage) {
+        try {
+            updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring);
+            updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring);
+            ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE,
+                    String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()),
+                    vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),
+                    true, 0);
+
+            final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
+            if (!backupProvider.restoreVMFromBackup(vm, backup)) {
+                ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
+                        String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()),
+                        vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
+                throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid());
+            }
+        // The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to
+        // ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed.
+        } catch (Exception e) {
+            LOG.error(String.format("Failed to restore backup [%s] due to: [%s].", backupDetailsInMessage, e.getMessage()), e);
+            updateVolumeState(vm, Volume.Event.RestoreFailed, Volume.State.Ready);
+            updateVmState(vm, VirtualMachine.Event.RestoringFailed, VirtualMachine.State.Stopped);
+            throw new CloudRuntimeException(String.format("Error restoring VM from backup [%s].", backupDetailsInMessage));
+        }
+    }
+
+    /**
+     * Tries to update the state of given VM, given specified event
+     * @param vm The VM to update its state
+     * @param event The event to update the VM state
+     * @param next The desired state, just needed to add more context to the logs
+     */
+    private void updateVmState(VMInstanceVO vm, VirtualMachine.Event event, VirtualMachine.State next) {
+        LOG.debug(String.format("Trying to update state of VM [%s] with event [%s].", vm, event));
+        Transaction.execute(TransactionLegacy.CLOUD_DB, (TransactionCallback<VMInstanceVO>) status -> {
+            try {
+                if (!virtualMachineManager.stateTransitTo(vm, event, vm.getHostId())) {
+                    throw new CloudRuntimeException(String.format("Unable to change state of VM [%s] to [%s].", vm, next));
+                }
+            } catch (NoTransitionException e) {
+                String errMsg = String.format("Failed to update state of VM [%s] with event [%s] due to [%s].", vm, event, e.getMessage());
+                LOG.error(errMsg, e);
+                throw new RuntimeException(errMsg);
+            }
+            return null;
+        });
+    }
+
+    /**
+     * Tries to update all volume states of given VM, given specified event
+     * @param vm The VM to which the volumes belong
+     * @param event The event to update the volume states
+     * @param next The desired state, just needed to add more context to the logs
+     */
+    private void updateVolumeState(VMInstanceVO vm, Volume.Event event, Volume.State next) {
+        Transaction.execute(TransactionLegacy.CLOUD_DB, (TransactionCallback<VolumeVO>) status -> {
+            for (VolumeVO volume : volumeDao.findIncludingRemovedByInstanceAndType(vm.getId(), null)) {
+                tryToUpdateStateOfSpecifiedVolume(volume, event, next);
+            }
+            return null;
+        });
+    }
+
+    /**
+     * Tries to update the state of just one volume using any passed {@link Volume.Event}. Throws an {@link RuntimeException} when fails.
+     * @param volume The volume to update it state
+     * @param event The event to update the volume state
+     * @param next The desired state, just needed to add more context to the logs
+     *
+     */
+    private void tryToUpdateStateOfSpecifiedVolume(VolumeVO volume, Volume.Event event, Volume.State next) {
+        LOG.debug(String.format("Trying to update state of volume [%s] with event [%s].", volume, event));
+        try {
+            if (!volumeApiService.stateTransitTo(volume, event)) {
+                throw new CloudRuntimeException(String.format("Unable to change state of volume [%s] to [%s].", volume, next));
+            }
+        } catch (NoTransitionException e) {
+            String errMsg = String.format("Failed to update state of volume [%s] with event [%s] due to [%s].", volume, event, e.getMessage());
+            LOG.error(errMsg, e);
+            throw new RuntimeException(errMsg);
+        }
+    }
+
     private Backup.VolumeInfo getVolumeInfo(List<Backup.VolumeInfo> backedUpVolumes, String volumeUuid) {
         for (Backup.VolumeInfo volInfo : backedUpVolumes) {
             if (volInfo.getUuid().equals(volumeUuid)) {
@@ -1099,28 +1192,40 @@
                     }
 
                     final Map<VirtualMachine, Backup.Metric> metrics = backupProvider.getBackupMetrics(dataCenter.getId(), new ArrayList<>(vms));
-                    try {
-                        for (final VirtualMachine vm : metrics.keySet()) {
-                            final Backup.Metric metric = metrics.get(vm);
-                            if (metric != null) {
-                                // Sync out-of-band backups
-                                backupProvider.syncBackups(vm, metric);
-                                // Emit a usage event, update usage metric for the VM by the usage server
-                                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_USAGE_METRIC, vm.getAccountId(),
-                                        vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(),
-                                        vm.getBackupOfferingId(), null, metric.getBackupSize(), metric.getDataSize(),
-                                        Backup.class.getSimpleName(), vm.getUuid());
-                            }
-                        }
-                    } catch (final Throwable e) {
-                        LOG.error(String.format("Failed to sync backup usage metrics and out-of-band backups due to: [%s].", e.getMessage()), e);
-                    }
+                    syncBackupMetrics(backupProvider, metrics);
                 }
             } catch (final Throwable t) {
                 LOG.error(String.format("Error trying to run backup-sync background task due to: [%s].", t.getMessage()), t);
             }
         }
 
+        /**
+         * Tries to sync the VM backups. If one backup synchronization fails, only this VM backups are skipped, and the entire process does not stop.
+         */
+        private void syncBackupMetrics(final BackupProvider backupProvider, final Map<VirtualMachine, Backup.Metric> metrics) {
+            for (final VirtualMachine vm : metrics.keySet()) {
+                tryToSyncVMBackups(backupProvider, metrics, vm);
+            }
+        }
+
+        private void tryToSyncVMBackups(BackupProvider backupProvider, Map<VirtualMachine, Backup.Metric> metrics, VirtualMachine vm) {
+            try {
+                final Backup.Metric metric = metrics.get(vm);
+                if (metric != null) {
+                    LOG.debug(String.format("Trying to sync backups of VM [%s] using backup provider [%s].", vm.getUuid(), backupProvider.getName()));
+                    // Sync out-of-band backups
+                    backupProvider.syncBackups(vm, metric);
+                    // Emit a usage event, update usage metric for the VM by the usage server
+                    UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_USAGE_METRIC, vm.getAccountId(),
+                            vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(),
+                            vm.getBackupOfferingId(), null, metric.getBackupSize(), metric.getDataSize(),
+                            Backup.class.getSimpleName(), vm.getUuid());
+                }
+            } catch (final Exception e) {
+                LOG.error(String.format("Failed to sync backup usage metrics and out-of-band backups of VM [%s] due to: [%s].", vm.getUuid(), e.getMessage()), e);
+            }
+        }
+
         @Override
         public Long getDelay() {
             return BackupSyncPollingInterval.value() * 1000L;
diff --git a/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
new file mode 100644
index 0000000..9fe00fa
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
@@ -0,0 +1,853 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster;
+
+import com.cloud.api.ApiGsonHelper;
+import com.cloud.api.query.dao.HostJoinDao;
+import com.cloud.api.query.vo.HostJoinVO;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.domain.Domain;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.event.EventVO;
+import com.cloud.event.dao.EventDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.org.Cluster;
+import com.cloud.server.ManagementServer;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.Pair;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VmDetailConstants;
+import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.admin.cluster.ExecuteClusterDrsPlanCmd;
+import org.apache.cloudstack.api.command.admin.cluster.GenerateClusterDrsPlanCmd;
+import org.apache.cloudstack.api.command.admin.cluster.ListClusterDrsPlanCmd;
+import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
+import org.apache.cloudstack.api.response.ClusterDrsPlanMigrationResponse;
+import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.cluster.dao.ClusterDrsPlanDao;
+import org.apache.cloudstack.cluster.dao.ClusterDrsPlanMigrationDao;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
+import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
+import org.apache.cloudstack.jobs.JobInfo;
+import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.stream.Collectors;
+
+import static com.cloud.org.Grouping.AllocationState.Disabled;
+
+public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsService, PluggableService {
+
+    private static final Logger logger = Logger.getLogger(ClusterDrsServiceImpl.class);
+
+    private static final String CLUSTER_LOCK_STR = "drs.plan.cluster.%s";
+
+    AsyncJobDispatcher asyncJobDispatcher;
+
+    @Inject
+    AsyncJobManager asyncJobManager;
+
+    @Inject
+    ClusterDao clusterDao;
+
+    @Inject
+    HostDao hostDao;
+
+    @Inject
+    EventDao eventDao;
+
+    @Inject
+    HostJoinDao hostJoinDao;
+
+    @Inject
+    VMInstanceDao vmInstanceDao;
+
+    @Inject
+    ClusterDrsPlanDao drsPlanDao;
+
+    @Inject
+    ClusterDrsPlanMigrationDao drsPlanMigrationDao;
+
+    @Inject
+    ServiceOfferingDao serviceOfferingDao;
+
+    @Inject
+    ManagementServer managementServer;
+
+    List<ClusterDrsAlgorithm> drsAlgorithms = new ArrayList<>();
+
+    Map<String, ClusterDrsAlgorithm> drsAlgorithmMap = new HashMap<>();
+
+    public AsyncJobDispatcher getAsyncJobDispatcher() {
+        return asyncJobDispatcher;
+    }
+
+    public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
+        asyncJobDispatcher = dispatcher;
+    }
+
+    public void setDrsAlgorithms(final List<ClusterDrsAlgorithm> drsAlgorithms) {
+        this.drsAlgorithms = drsAlgorithms;
+    }
+
+    @Override
+    public boolean start() {
+        drsAlgorithmMap.clear();
+        for (final ClusterDrsAlgorithm algorithm : drsAlgorithms) {
+            drsAlgorithmMap.put(algorithm.getName(), algorithm);
+        }
+
+        final TimerTask schedulerPollTask = new ManagedContextTimerTask() {
+            @Override
+            protected void runInContext() {
+                try {
+                    poll(new Date());
+                } catch (final Exception e) {
+                    logger.error("Error while running DRS", e);
+                }
+            }
+        };
+        Timer vmSchedulerTimer = new Timer("VMSchedulerPollTask");
+        vmSchedulerTimer.schedule(schedulerPollTask, 5000L, 60 * 1000L);
+        return true;
+    }
+
+    @Override
+    public void poll(Date timestamp) {
+        Date currentTimestamp = DateUtils.round(timestamp, Calendar.MINUTE);
+        String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+        logger.debug(String.format("ClusterDRS.poll is being called at %s", displayTime));
+
+        GlobalLock lock = GlobalLock.getInternLock("clusterDRS.poll");
+        try {
+            if (lock.lock(30)) {
+                try {
+                    updateOldPlanMigrations();
+                    // Executing processPlans() twice to update the migration status of plans which
+                    // are completed and
+                    // if required generate new plans.
+                    processPlans();
+                    generateDrsPlanForAllClusters();
+                    processPlans();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        } finally {
+            lock.releaseRef();
+        }
+        GlobalLock cleanupLock = GlobalLock.getInternLock("clusterDRS.cleanup");
+        try {
+            if (cleanupLock.lock(30)) {
+                try {
+                    cleanUpOldDrsPlans();
+                } finally {
+                    cleanupLock.unlock();
+                }
+            }
+        } finally {
+            cleanupLock.releaseRef();
+        }
+    }
+
+    /**
+     * Fetches the plans which are in progress and updates their migration status.
+     */
+    void updateOldPlanMigrations() {
+        List<ClusterDrsPlanVO> plans = drsPlanDao.listByStatus(ClusterDrsPlan.Status.IN_PROGRESS);
+        for (ClusterDrsPlanVO plan : plans) {
+            try {
+                updateDrsPlanMigrations(plan);
+            } catch (Exception e) {
+                logger.error(String.format("Unable to update DRS plan details [id=%d]", plan.getId()), e);
+            }
+        }
+    }
+
+    /**
+     * Updates the job status of the plan details for the given plan.
+     *
+     * @param plan
+     *         the plan to update
+     */
+    void updateDrsPlanMigrations(ClusterDrsPlanVO plan) {
+        List<ClusterDrsPlanMigrationVO> migrations = drsPlanMigrationDao.listPlanMigrationsInProgress(plan.getId());
+        if (migrations == null || migrations.isEmpty()) {
+            plan.setStatus(ClusterDrsPlan.Status.COMPLETED);
+            drsPlanDao.update(plan.getId(), plan);
+            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, EventVO.LEVEL_INFO,
+                    EventTypes.EVENT_CLUSTER_DRS, true,
+                    String.format("DRS execution task completed for cluster [id=%s]", plan.getClusterId()),
+                    plan.getClusterId(), ApiCommandResourceType.Cluster.toString(), plan.getEventId());
+            return;
+        }
+
+        for (ClusterDrsPlanMigrationVO migration : migrations) {
+            try {
+                AsyncJobVO job = asyncJobManager.getAsyncJob(migration.getJobId());
+                if (job == null) {
+                    logger.warn(String.format("Unable to find async job [id=%d] for DRS plan migration [id=%d]",
+                            migration.getJobId(), migration.getId()));
+                    migration.setStatus(JobInfo.Status.FAILED);
+                    drsPlanMigrationDao.update(migration.getId(), migration);
+                    continue;
+                }
+                if (job.getStatus() != JobInfo.Status.IN_PROGRESS) {
+                    migration.setStatus(job.getStatus());
+                    drsPlanMigrationDao.update(migration.getId(), migration);
+                }
+            } catch (Exception e) {
+                logger.error(String.format("Unable to update DRS plan migration [id=%d]", migration.getId()), e);
+            }
+        }
+    }
+
+    /**
+     * Generates DRS for all clusters that meet the criteria for automated DRS.
+     */
+    void generateDrsPlanForAllClusters() {
+        List<ClusterVO> clusterList = clusterDao.listAll();
+
+        for (ClusterVO cluster : clusterList) {
+            if (cluster.getAllocationState() == Disabled || ClusterDrsEnabled.valueIn(
+                    cluster.getId()).equals(Boolean.FALSE)) {
+                continue;
+            }
+
+            ClusterDrsPlanVO lastPlan = drsPlanDao.listLatestPlanForClusterId(cluster.getId());
+
+            // If the last plan is ready or in progress or was executed within the last interval, skip this cluster.
+            // This is to avoid generating plans for clusters which are already being processed and to avoid
+            // generating plans for clusters which have been processed recently.This doesn't consider the type
+            // (manual or automated) of the last plan.
+            if (lastPlan != null && (lastPlan.getStatus() == ClusterDrsPlan.Status.READY ||
+                    lastPlan.getStatus() == ClusterDrsPlan.Status.IN_PROGRESS ||
+                    (lastPlan.getStatus() == ClusterDrsPlan.Status.COMPLETED &&
+                            lastPlan.getCreated().compareTo(DateUtils.addMinutes(new Date(), -1 * ClusterDrsInterval.valueIn(cluster.getId()))) > 0)
+            )) {
+                continue;
+            }
+
+            long eventId = ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM,
+                    EventTypes.EVENT_CLUSTER_DRS,
+                    String.format("Generating DRS plan for cluster %s", cluster.getUuid()), cluster.getId(),
+                    ApiCommandResourceType.Cluster.toString(), true, 0);
+            GlobalLock clusterLock = GlobalLock.getInternLock(String.format(CLUSTER_LOCK_STR, cluster.getId()));
+            try {
+                if (clusterLock.lock(30)) {
+                    try {
+                        List<Ternary<VirtualMachine, Host, Host>> plan = getDrsPlan(cluster,
+                                ClusterDrsMaxMigrations.valueIn(cluster.getId()));
+                        savePlan(cluster.getId(), plan, eventId, ClusterDrsPlan.Type.AUTOMATED,
+                                ClusterDrsPlan.Status.READY);
+                        logger.info(String.format("Generated DRS plan for cluster %s [id=%s]", cluster.getName(),
+                                cluster.getUuid()));
+                    } catch (Exception e) {
+                        logger.error(
+                                String.format("Unable to generate DRS plans for cluster %s [id=%s]", cluster.getName(),
+                                        cluster.getUuid()),
+                                e);
+                    } finally {
+                        clusterLock.unlock();
+                    }
+                }
+            } finally {
+                clusterLock.releaseRef();
+            }
+        }
+    }
+
+    /**
+     * Generate DRS plan for the given cluster with the specified iteration percentage.
+     *
+     * @param cluster
+     *         The cluster to generate DRS for.
+     * @param maxIterations
+     *         The percentage of VMs to consider for migration
+     *         during each iteration. Value between 0 and 1.
+     *
+     * @return List of Ternary object containing VM to be migrated, source host and
+     *         destination host.
+     *
+     * @throws ConfigurationException
+     *         If there is an error in the DRS configuration.
+     */
+    List<Ternary<VirtualMachine, Host, Host>> getDrsPlan(Cluster cluster,
+            int maxIterations) throws ConfigurationException {
+        List<Ternary<VirtualMachine, Host, Host>> migrationPlan = new ArrayList<>();
+
+        if (cluster.getAllocationState() == Disabled || maxIterations <= 0) {
+            return Collections.emptyList();
+        }
+        ClusterDrsAlgorithm algorithm = getDrsAlgorithm(ClusterDrsAlgorithm.valueIn(cluster.getId()));
+        List<HostVO> hostList = hostDao.findByClusterId(cluster.getId());
+        List<VirtualMachine> vmList = new ArrayList<>(vmInstanceDao.listByClusterId(cluster.getId()));
+
+        int iteration = 0;
+
+        Map<Long, Host> hostMap = hostList.stream().collect(Collectors.toMap(HostVO::getId, host -> host));
+
+        Map<Long, List<VirtualMachine>> hostVmMap = getHostVmMap(hostList, vmList);
+        Map<Long, List<Long>> originalHostIdVmIdMap = new HashMap<>();
+        for (HostVO host : hostList) {
+            originalHostIdVmIdMap.put(host.getId(), new ArrayList<>());
+            for (VirtualMachine vm : hostVmMap.get(host.getId())) {
+                originalHostIdVmIdMap.get(host.getId()).add(vm.getId());
+            }
+        }
+
+        List<HostJoinVO> hostJoinList = hostJoinDao.searchByIds(
+                hostList.stream().map(HostVO::getId).toArray(Long[]::new));
+
+        Map<Long, Ternary<Long, Long, Long>> hostCpuMap = hostJoinList.stream().collect(Collectors.toMap(HostJoinVO::getId,
+                hostJoin -> new Ternary<>(hostJoin.getCpuUsedCapacity(), hostJoin.getCpuReservedCapacity(), hostJoin.getCpus() * hostJoin.getSpeed())));
+        Map<Long, Ternary<Long, Long, Long>> hostMemoryMap = hostJoinList.stream().collect(Collectors.toMap(HostJoinVO::getId,
+                hostJoin -> new Ternary<>(hostJoin.getMemUsedCapacity(), hostJoin.getMemReservedCapacity(), hostJoin.getTotalMemory())));
+
+        Map<Long, ServiceOffering> vmIdServiceOfferingMap = new HashMap<>();
+
+        for (VirtualMachine vm : vmList) {
+            vmIdServiceOfferingMap.put(vm.getId(),
+                    serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()));
+        }
+
+        while (iteration < maxIterations && algorithm.needsDrs(cluster.getId(), new ArrayList<>(hostCpuMap.values()),
+                new ArrayList<>(hostMemoryMap.values()))) {
+            Pair<VirtualMachine, Host> bestMigration = getBestMigration(cluster, algorithm, vmList,
+                    vmIdServiceOfferingMap, hostCpuMap, hostMemoryMap);
+            VirtualMachine vm = bestMigration.first();
+            Host destHost = bestMigration.second();
+            if (destHost == null || vm == null || originalHostIdVmIdMap.get(destHost.getId()).contains(vm.getId())) {
+                logger.debug("VM migrating to it's original host or no host found for migration");
+                break;
+            }
+            logger.debug(String.format("Plan for VM %s to migrate from host %s to host %s", vm.getUuid(),
+                    hostMap.get(vm.getHostId()).getUuid(), destHost.getUuid()));
+
+            ServiceOffering serviceOffering = vmIdServiceOfferingMap.get(vm.getId());
+            migrationPlan.add(new Ternary<>(vm, hostMap.get(vm.getHostId()), hostMap.get(destHost.getId())));
+
+            hostVmMap.get(vm.getHostId()).remove(vm);
+            hostVmMap.get(destHost.getId()).add(vm);
+            hostVmMap.get(vm.getHostId()).remove(vm);
+            hostVmMap.get(destHost.getId()).add(vm);
+
+            long vmCpu = (long) serviceOffering.getCpu() * serviceOffering.getSpeed();
+            long vmMemory = serviceOffering.getRamSize() * 1024L * 1024L;
+
+            // Updating the map as per the migration
+            hostCpuMap.get(vm.getHostId()).first(hostCpuMap.get(vm.getHostId()).first() - vmCpu);
+            hostCpuMap.get(destHost.getId()).first(hostCpuMap.get(destHost.getId()).first() + vmCpu);
+            hostMemoryMap.get(vm.getHostId()).first(hostMemoryMap.get(vm.getHostId()).first() - vmMemory);
+            hostMemoryMap.get(destHost.getId()).first(hostMemoryMap.get(destHost.getId()).first() + vmMemory);
+            vm.setHostId(destHost.getId());
+            iteration++;
+        }
+        return migrationPlan;
+    }
+
+    private ClusterDrsAlgorithm getDrsAlgorithm(String algoName) {
+        if (drsAlgorithmMap.containsKey(algoName)) {
+            return drsAlgorithmMap.get(algoName);
+        }
+        throw new CloudRuntimeException("Invalid algorithm configured!");
+    }
+
+    Map<Long, List<VirtualMachine>> getHostVmMap(List<HostVO> hostList, List<VirtualMachine> vmList) {
+        Map<Long, List<VirtualMachine>> hostVmMap = new HashMap<>();
+        for (HostVO host : hostList) {
+            hostVmMap.put(host.getId(), new ArrayList<>());
+        }
+        for (VirtualMachine vm : vmList) {
+            hostVmMap.get(vm.getHostId()).add(vm);
+        }
+        return hostVmMap;
+    }
+
+    /**
+     * Returns the best migration for a given cluster using the specified DRS
+     * algorithm.
+     *
+     * @param cluster
+     *         the cluster to perform DRS on
+     * @param algorithm
+     *         the DRS algorithm to use
+     * @param vmList
+     *         the list of virtual machines to consider for
+     *         migration
+     * @param vmIdServiceOfferingMap
+     *         a map of virtual machine IDs to their
+     *         corresponding service offerings
+     * @param hostCpuCapacityMap
+     *         a map of host IDs to their corresponding CPU
+     *         capacity
+     * @param hostMemoryCapacityMap
+     *         a map of host IDs to their corresponding memory
+     *         capacity
+     *
+     * @return a pair of the virtual machine and host that represent the best
+     *         migration, or null if no migration is
+     *         possible
+     */
+    Pair<VirtualMachine, Host> getBestMigration(Cluster cluster, ClusterDrsAlgorithm algorithm,
+            List<VirtualMachine> vmList,
+            Map<Long, ServiceOffering> vmIdServiceOfferingMap,
+            Map<Long, Ternary<Long, Long, Long>> hostCpuCapacityMap,
+            Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap) throws ConfigurationException {
+        double improvement = 0;
+        Pair<VirtualMachine, Host> bestMigration = new Pair<>(null, null);
+
+        for (VirtualMachine vm : vmList) {
+            if (vm.getType().isUsedBySystem() || vm.getState() != VirtualMachine.State.Running ||
+                    (MapUtils.isNotEmpty(vm.getDetails()) &&
+                            vm.getDetails().get(VmDetailConstants.SKIP_DRS).equalsIgnoreCase("true"))
+            ) {
+                continue;
+            }
+            Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> hostsForMigrationOfVM = managementServer
+                    .listHostsForMigrationOfVM(
+                            vm, 0L, 500L, null, vmList);
+            List<? extends Host> compatibleDestinationHosts = hostsForMigrationOfVM.first().first();
+            List<? extends Host> suitableDestinationHosts = hostsForMigrationOfVM.second();
+
+            Map<Host, Boolean> requiresStorageMotion = hostsForMigrationOfVM.third();
+
+            for (Host destHost : compatibleDestinationHosts) {
+                if (!suitableDestinationHosts.contains(destHost)) {
+                    continue;
+                }
+                Ternary<Double, Double, Double> metrics = algorithm.getMetrics(cluster.getId(), vm,
+                        vmIdServiceOfferingMap.get(vm.getId()), destHost, hostCpuCapacityMap, hostMemoryCapacityMap,
+                        requiresStorageMotion.get(destHost));
+
+                Double currentImprovement = metrics.first();
+                Double cost = metrics.second();
+                Double benefit = metrics.third();
+                if (benefit > cost && (currentImprovement > improvement)) {
+                    bestMigration = new Pair<>(vm, destHost);
+                    improvement = currentImprovement;
+                }
+            }
+        }
+        return bestMigration;
+    }
+
+
+    /**
+     * Saves a DRS plan for a given cluster and returns the saved plan along with the list of migrations to be executed.
+     *
+     * @param clusterId
+     *         the ID of the cluster for which the DRS plan is being saved
+     * @param plan
+     *         the list of virtual machine migrations to be executed as part of the DRS plan
+     * @param eventId
+     *         the ID of the event that triggered the DRS plan
+     * @param type
+     *         the type of the DRS plan
+     *
+     * @return a pair of the saved DRS plan and the list of migrations to be executed
+     */
+    Pair<ClusterDrsPlanVO, List<ClusterDrsPlanMigrationVO>> savePlan(Long clusterId,
+            List<Ternary<VirtualMachine, Host, Host>> plan,
+            Long eventId, ClusterDrsPlan.Type type,
+            ClusterDrsPlan.Status status) {
+        return Transaction.execute(
+                (TransactionCallback<Pair<ClusterDrsPlanVO, List<ClusterDrsPlanMigrationVO>>>) txStatus -> {
+                    ClusterDrsPlanVO drsPlan = drsPlanDao.persist(
+                            new ClusterDrsPlanVO(clusterId, eventId, type, status));
+                    List<ClusterDrsPlanMigrationVO> planMigrations = new ArrayList<>();
+                    for (Ternary<VirtualMachine, Host, Host> migration : plan) {
+                        VirtualMachine vm = migration.first();
+                        Host srcHost = migration.second();
+                        Host destHost = migration.third();
+                        planMigrations.add(drsPlanMigrationDao.persist(
+                                new ClusterDrsPlanMigrationVO(drsPlan.getId(), vm.getId(), srcHost.getId(),
+                                        destHost.getId())));
+                    }
+                    return new Pair<>(drsPlan, planMigrations);
+                });
+    }
+
+    /**
+     * Processes all DRS plans that are in the READY status.
+     */
+    void processPlans() {
+        List<ClusterDrsPlanVO> plans = drsPlanDao.listByStatus(ClusterDrsPlan.Status.READY);
+        for (ClusterDrsPlanVO plan : plans) {
+            try {
+                executeDrsPlan(plan);
+            } catch (Exception e) {
+                logger.error(String.format("Unable to execute DRS plan [id=%d]", plan.getId()), e);
+            }
+        }
+    }
+
+    /**
+     * Executes the DRS plan by migrating virtual machines to their destination hosts.
+     * If there are no migrations to be executed, the plan is marked as completed.
+     *
+     * @param plan
+     *         the DRS plan to be executed
+     */
+    void executeDrsPlan(ClusterDrsPlanVO plan) {
+        List<ClusterDrsPlanMigrationVO> planMigrations = drsPlanMigrationDao.listPlanMigrationsToExecute(plan.getId());
+        if (planMigrations == null || planMigrations.isEmpty()) {
+            plan.setStatus(ClusterDrsPlan.Status.COMPLETED);
+            drsPlanDao.update(plan.getId(), plan);
+            ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, EventVO.LEVEL_INFO,
+                    EventTypes.EVENT_CLUSTER_DRS, true,
+                    String.format("DRS execution task completed for cluster [id=%s]", plan.getClusterId()),
+                    plan.getClusterId(), ApiCommandResourceType.Cluster.toString(), plan.getEventId());
+            return;
+        }
+
+        plan.setStatus(ClusterDrsPlan.Status.IN_PROGRESS);
+        drsPlanDao.update(plan.getId(), plan);
+
+        for (ClusterDrsPlanMigrationVO migration : planMigrations) {
+            try {
+                VirtualMachine vm = vmInstanceDao.findById(migration.getVmId());
+                Host host = hostDao.findById(migration.getDestHostId());
+                if (vm == null || host == null) {
+                    throw new CloudRuntimeException(String.format("vm %s or host %s is not found", migration.getVmId(),
+                            migration.getDestHostId()));
+                }
+
+                logger.debug(
+                        String.format("Executing DRS plan %s for vm %s to host %s", plan.getId(), vm.getInstanceName(),
+                                host.getName()));
+                long jobId = createMigrateVMAsyncJob(vm, host, plan.getEventId());
+                AsyncJobVO job = asyncJobManager.getAsyncJob(jobId);
+                migration.setJobId(jobId);
+                migration.setStatus(job.getStatus());
+                drsPlanMigrationDao.update(migration.getId(), migration);
+            } catch (Exception e) {
+                logger.warn(String.format("Unable to execute DRS plan %s due to %s", plan.getUuid(), e.getMessage()));
+                migration.setStatus(JobInfo.Status.FAILED);
+                drsPlanMigrationDao.update(migration.getId(), migration);
+            }
+        }
+    }
+
+    /**
+     * Creates an asynchronous job to migrate a virtual machine to a specified host.
+     *
+     * @param vm
+     *         the virtual machine to be migrated
+     * @param host
+     *         the destination host for the virtual machine
+     * @param eventId
+     *         the ID of the event that triggered the migration
+     *
+     * @return the ID of the created asynchronous job
+     */
+    long createMigrateVMAsyncJob(VirtualMachine vm, Host host, long eventId) {
+        final Map<String, String> params = new HashMap<>();
+        params.put("ctxUserId", String.valueOf(User.UID_SYSTEM));
+        params.put("ctxAccountId", String.valueOf(Account.ACCOUNT_ID_SYSTEM));
+        params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
+        params.put(ApiConstants.HOST_ID, String.valueOf(host.getId()));
+        params.put(ApiConstants.VIRTUAL_MACHINE_ID, String.valueOf(vm.getId()));
+
+        final MigrateVMCmd cmd = new MigrateVMCmd();
+        ComponentContext.inject(cmd);
+
+        AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, MigrateVMCmd.class.getName(),
+                ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(),
+                ApiCommandResourceType.VirtualMachine.toString(), null);
+        job.setDispatcher(asyncJobDispatcher.getName());
+
+        return asyncJobManager.submitAsyncJob(job);
+    }
+
+    /**
+     * Removes old DRS migrations records that have expired based on the configured interval.
+     */
+    void cleanUpOldDrsPlans() {
+        Date date = DateUtils.addDays(new Date(), -1 * ClusterDrsPlanExpireInterval.value());
+        int rowsRemoved = drsPlanDao.expungeBeforeDate(date);
+        logger.debug(String.format("Removed %d old drs migration plans", rowsRemoved));
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return ClusterDrsService.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[]{ClusterDrsPlanExpireInterval, ClusterDrsEnabled, ClusterDrsInterval, ClusterDrsMaxMigrations,
+                ClusterDrsAlgorithm, ClusterDrsImbalanceThreshold, ClusterDrsMetric, ClusterDrsMetricType, ClusterDrsMetricUseRatio,
+                ClusterDrsImbalanceSkipThreshold};
+    }
+
+    @Override
+    public List<Class<?>> getCommands() {
+        List<Class<?>> cmdList = new ArrayList<>();
+        cmdList.add(ListClusterDrsPlanCmd.class);
+        cmdList.add(GenerateClusterDrsPlanCmd.class);
+        cmdList.add(ExecuteClusterDrsPlanCmd.class);
+        return cmdList;
+    }
+
+    /**
+     * Generates a DRS plan for the given cluster and returns a list of migration responses.
+     *
+     * @param cmd
+     *         the command containing the cluster ID and number of migrations for the DRS plan
+     *
+     * @return a list response of migration responses for the generated DRS plan
+     *
+     * @throws InvalidParameterValueException
+     *         if the cluster is not found, is disabled, or is not a cloud stack managed cluster, or if the number of
+     *         migrations is invalid
+     * @throws CloudRuntimeException
+     *         if there is an error scheduling the DRS plan
+     */
+    @Override
+    public ClusterDrsPlanResponse generateDrsPlan(GenerateClusterDrsPlanCmd cmd) {
+        Cluster cluster = clusterDao.findById(cmd.getId());
+        if (cluster == null) {
+            throw new InvalidParameterValueException("Unable to find the cluster by id=" + cmd.getId());
+        }
+        if (cluster.getAllocationState() == Disabled) {
+            throw new InvalidParameterValueException(
+                    String.format("Unable to execute DRS on the cluster %s as it is disabled", cluster.getName()));
+        }
+        if (cmd.getMaxMigrations() <= 0) {
+            throw new InvalidParameterValueException(
+                    String.format("Unable to execute DRS on the cluster %s as the number of migrations [%s] is invalid",
+                            cluster.getName(), cmd.getMaxMigrations()));
+        }
+
+        try {
+            List<Ternary<VirtualMachine, Host, Host>> plan = getDrsPlan(cluster, cmd.getMaxMigrations());
+            long eventId = ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM,
+                    Domain.ROOT_DOMAIN,
+                    EventTypes.EVENT_CLUSTER_DRS_GENERATE,
+                    String.format("Generating DRS plan for cluster %s", cluster.getUuid()), cluster.getId(),
+                    ApiCommandResourceType.Cluster.toString());
+            List<ClusterDrsPlanMigrationVO> migrations;
+            ClusterDrsPlanVO drsPlan = new ClusterDrsPlanVO(
+                    cluster.getId(), eventId, ClusterDrsPlan.Type.MANUAL, ClusterDrsPlan.Status.UNDER_REVIEW);
+            migrations = new ArrayList<>();
+            for (Ternary<VirtualMachine, Host, Host> migration : plan) {
+                VirtualMachine vm = migration.first();
+                Host srcHost = migration.second();
+                Host destHost = migration.third();
+                migrations.add(new ClusterDrsPlanMigrationVO(0L, vm.getId(), srcHost.getId(), destHost.getId()));
+            }
+
+            CallContext.current().setEventResourceType(ApiCommandResourceType.Cluster);
+            CallContext.current().setEventResourceId(cluster.getId());
+
+            String eventUuid = null;
+            EventVO event = eventDao.findById(drsPlan.getEventId());
+            if (event != null) {
+                eventUuid = event.getUuid();
+            }
+
+            return new ClusterDrsPlanResponse(
+                    cluster.getUuid(), drsPlan, eventUuid, getResponseObjectForMigrations(migrations));
+        } catch (ConfigurationException e) {
+            throw new CloudRuntimeException("Unable to schedule DRS", e);
+        }
+    }
+
+    /**
+     * Returns a list of ClusterDrsPlanMigrationResponse objects for the given list of ClusterDrsPlanMigrationVO
+     * objects.
+     *
+     * @param migrations
+     *         the list of ClusterDrsPlanMigrationVO objects
+     *
+     * @return a list of ClusterDrsPlanMigrationResponse objects
+     */
+    List<ClusterDrsPlanMigrationResponse> getResponseObjectForMigrations(List<ClusterDrsPlanMigrationVO> migrations) {
+        if (migrations == null) {
+            return Collections.emptyList();
+        }
+        List<ClusterDrsPlanMigrationResponse> responses = new ArrayList<>();
+
+        for (ClusterDrsPlanMigrationVO migration : migrations) {
+            VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(migration.getVmId());
+            HostVO srcHost = hostDao.findByIdIncludingRemoved(migration.getSrcHostId());
+            HostVO destHost = hostDao.findByIdIncludingRemoved(migration.getDestHostId());
+            responses.add(new ClusterDrsPlanMigrationResponse(
+                    vm.getUuid(), vm.getInstanceName(),
+                    srcHost.getUuid(), srcHost.getName(),
+                    destHost.getUuid(), destHost.getName(),
+                    migration.getJobId(), migration.getStatus()));
+        }
+
+        return responses;
+    }
+
+    @Override
+    public ClusterDrsPlanResponse executeDrsPlan(ExecuteClusterDrsPlanCmd cmd) {
+
+        Map<VirtualMachine, Host> vmToHostMap = cmd.getVmToHostMap();
+        Long clusterId = cmd.getId();
+
+        if (vmToHostMap.isEmpty()) {
+            throw new InvalidParameterValueException("migrateto can not be empty.");
+        }
+
+        Cluster cluster = clusterDao.findById(clusterId);
+
+        if (cluster == null) {
+            throw new InvalidParameterValueException("cluster not found");
+        }
+
+        return executeDrsPlan(cluster, vmToHostMap);
+
+    }
+
+    private ClusterDrsPlanResponse executeDrsPlan(Cluster cluster, Map<VirtualMachine, Host> vmToHostMap) {
+        // To ensure that no other plan is generated for this cluster, we take a lock
+        GlobalLock clusterLock = GlobalLock.getInternLock(String.format(CLUSTER_LOCK_STR, cluster.getId()));
+        ClusterDrsPlanVO drsPlan = null;
+        List<ClusterDrsPlanMigrationVO> migrations = null;
+        try {
+            if (clusterLock.lock(5)) {
+                try {
+                    List<ClusterDrsPlanVO> readyPlans = drsPlanDao.listByClusterIdAndStatus(cluster.getId(),
+                            ClusterDrsPlan.Status.READY);
+                    if (readyPlans != null && !readyPlans.isEmpty()) {
+                        throw new InvalidParameterValueException(
+                                String.format(
+                                        "Unable to execute DRS plan as there is already a plan [id=%s] in READY state",
+                                        readyPlans.get(0).getUuid()));
+                    }
+                    List<ClusterDrsPlanVO> inProgressPlans = drsPlanDao.listByClusterIdAndStatus(cluster.getId(),
+                            ClusterDrsPlan.Status.IN_PROGRESS);
+
+                    if (inProgressPlans != null && !inProgressPlans.isEmpty()) {
+                        throw new InvalidParameterValueException(
+                                String.format("Unable to execute DRS plan as there is already a plan [id=%s] in In " +
+                                                "Progress",
+                                        inProgressPlans.get(0).getUuid()));
+                    }
+
+                    List<Ternary<VirtualMachine, Host, Host>> plan = new ArrayList<>();
+                    for (Map.Entry<VirtualMachine, Host> entry : vmToHostMap.entrySet()) {
+                        VirtualMachine vm = entry.getKey();
+                        Host destHost = entry.getValue();
+                        Host srcHost = hostDao.findById(vm.getHostId());
+                        plan.add(new Ternary<>(vm, srcHost, destHost));
+                    }
+
+                    Pair<ClusterDrsPlanVO, List<ClusterDrsPlanMigrationVO>> pair = savePlan(cluster.getId(), plan,
+                            CallContext.current().getStartEventId(), ClusterDrsPlan.Type.MANUAL,
+                            ClusterDrsPlan.Status.READY);
+                    drsPlan = pair.first();
+                    migrations = pair.second();
+
+                    executeDrsPlan(drsPlan);
+                } finally {
+                    clusterLock.unlock();
+                }
+            }
+        } finally {
+            clusterLock.releaseRef();
+        }
+
+        String eventId = null;
+        if (drsPlan != null) {
+            EventVO event = eventDao.findById(drsPlan.getEventId());
+            eventId = event.getUuid();
+        }
+
+        return new ClusterDrsPlanResponse(
+                cluster.getUuid(), drsPlan, eventId, getResponseObjectForMigrations(migrations));
+    }
+
+    @Override
+    public ListResponse<ClusterDrsPlanResponse> listDrsPlan(ListClusterDrsPlanCmd cmd) {
+        Long clusterId = cmd.getClusterId();
+        Long planId = cmd.getId();
+
+        if (planId != null && clusterId != null) {
+            throw new InvalidParameterValueException("Only one of clusterId or planId can be specified");
+        }
+
+        ClusterVO cluster = clusterDao.findById(clusterId);
+        if (clusterId != null && cluster == null) {
+            throw new InvalidParameterValueException("Unable to find the cluster by id=" + clusterId);
+        }
+
+        Pair<List<ClusterDrsPlanVO>, Integer> result = drsPlanDao.searchAndCount(clusterId, planId, cmd.getStartIndex(),
+                cmd.getPageSizeVal());
+
+        ListResponse<ClusterDrsPlanResponse> response = new ListResponse<>();
+        List<ClusterDrsPlanResponse> responseList = new ArrayList<>();
+
+        for (ClusterDrsPlan plan : result.first()) {
+            if (cluster == null || plan.getClusterId() != cluster.getId()) {
+                cluster = clusterDao.findById(plan.getClusterId());
+            }
+            List<ClusterDrsPlanMigrationVO> migrations = drsPlanMigrationDao.listByPlanId(plan.getId());
+            EventVO event = eventDao.findById(plan.getEventId());
+
+            responseList.add(new ClusterDrsPlanResponse(
+                    cluster.getUuid(), plan, event.getUuid(), getResponseObjectForMigrations(migrations)));
+        }
+
+        response.setResponses(responseList, result.second());
+        return response;
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java
index 115ee71..f13f6e0 100644
--- a/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java
+++ b/server/src/main/java/org/apache/cloudstack/diagnostics/to/DiagnosticsDataTO.java
@@ -33,6 +33,10 @@
         this.dataStoreTO = dataStoreTO;
     }
 
+    public DiagnosticsDataTO(DataStoreTO dataStoreTO) {
+        this.dataStoreTO = dataStoreTO;
+    }
+
     @Override
     public DataObjectType getObjectType() {
         return DataObjectType.ARCHIVE;
diff --git a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java
index b00612d..a11593a 100644
--- a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java
+++ b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java
@@ -19,17 +19,6 @@
 
 package org.apache.cloudstack.snapshot;
 
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.storage.DataStoreRole;
-import com.cloud.storage.Snapshot;
-import com.cloud.storage.SnapshotVO;
-import com.cloud.storage.VolumeVO;
-import com.cloud.storage.Storage.StoragePoolType;
-import com.cloud.storage.dao.SnapshotDao;
-
-import com.cloud.utils.exception.CloudRuntimeException;
-
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -38,6 +27,7 @@
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
@@ -57,6 +47,16 @@
 import org.apache.commons.lang3.builder.ToStringStyle;
 import org.apache.log4j.Logger;
 
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+
 public class SnapshotHelper {
     private final Logger logger = Logger.getLogger(this.getClass());
 
@@ -110,8 +110,13 @@
             logger.warn(String.format("Unable to delete the temporary snapshot [%s] on secondary storage due to [%s]. We still will expunge the database reference, consider"
               + " manually deleting the file [%s].", snapInfo.getId(), ex.getMessage(), snapInfo.getPath()), ex);
         }
-
-        snapshotDataStoreDao.expungeReferenceBySnapshotIdAndDataStoreRole(snapInfo.getId(), DataStoreRole.Image);
+        long storeId = snapInfo.getDataStore().getId();
+        if (!DataStoreRole.Image.equals(snapInfo.getDataStore().getRole())) {
+            long zoneId = dataStorageManager.getStoreZoneId(storeId, snapInfo.getDataStore().getRole());
+            SnapshotInfo imageStoreSnapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapInfo.getId(), DataStoreRole.Image, zoneId);
+            storeId = imageStoreSnapInfo.getDataStore().getId();
+        }
+        snapshotDataStoreDao.expungeReferenceBySnapshotIdAndDataStoreRole(snapInfo.getId(), storeId, DataStoreRole.Image);
     }
 
     /**
@@ -127,12 +132,12 @@
             return snapInfo;
         }
 
-        snapInfo = getSnapshotInfoByIdAndRole(snapshot.getId(), DataStoreRole.Primary);
+        snapInfo = getSnapshotInfoByIdAndRole(snapshot.getId(), DataStoreRole.Primary, null);
 
         SnapshotStrategy snapshotStrategy = storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotStrategy.SnapshotOperation.BACKUP);
         snapshotStrategy.backupSnapshot(snapInfo);
 
-        return getSnapshotInfoByIdAndRole(snapshot.getId(), kvmSnapshotOnlyInPrimaryStorage ? DataStoreRole.Image : dataStoreRole);
+        return getSnapshotInfoByIdAndRole(snapshot.getId(), kvmSnapshotOnlyInPrimaryStorage ? DataStoreRole.Image : dataStoreRole, dataStorageManager.getStoreZoneId(snapInfo.getDataStore().getId(), snapInfo.getDataStore().getRole()));
     }
 
     /**
@@ -140,8 +145,13 @@
      * @return The snapshot info if it exists, else throws an exception.
      * @throws CloudRuntimeException
      */
-    protected SnapshotInfo getSnapshotInfoByIdAndRole(long snapshotId, DataStoreRole dataStoreRole) throws CloudRuntimeException{
-        SnapshotInfo snapInfo = snapshotFactory.getSnapshot(snapshotId, dataStoreRole);
+    protected SnapshotInfo getSnapshotInfoByIdAndRole(long snapshotId, DataStoreRole dataStoreRole, Long zoneId) throws CloudRuntimeException {
+        SnapshotInfo snapInfo = null;
+        if (DataStoreRole.Primary.equals(dataStoreRole)) {
+            snapInfo = snapshotFactory.getSnapshotOnPrimaryStore(snapshotId);
+        } else {
+            snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshotId, dataStoreRole, zoneId);
+        }
 
         if (snapInfo != null) {
             return snapInfo;
@@ -168,7 +178,7 @@
     }
 
     public DataStoreRole getDataStoreRole(Snapshot snapshot) {
-        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
+        SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
 
         if (snapshotStore == null) {
             return DataStoreRole.Image;
diff --git a/server/src/main/java/org/apache/cloudstack/storage/browser/StorageBrowserImpl.java b/server/src/main/java/org/apache/cloudstack/storage/browser/StorageBrowserImpl.java
new file mode 100644
index 0000000..8828ac4
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/browser/StorageBrowserImpl.java
@@ -0,0 +1,413 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.browser;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.api.query.MutualExclusiveIdsManagerBase;
+import com.cloud.api.query.dao.ImageStoreJoinDao;
+import com.cloud.api.query.vo.ImageStoreJoinVO;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.Upload;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.api.command.admin.storage.DownloadImageStoreObjectCmd;
+import org.apache.cloudstack.api.command.admin.storage.ListImageStoreObjectsCmd;
+import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolObjectsCmd;
+import org.apache.cloudstack.api.response.ExtractResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.diagnostics.to.DiagnosticsDataObject;
+import org.apache.cloudstack.diagnostics.to.DiagnosticsDataTO;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreObjectDownloadVO;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
+import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.EnumUtils;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Component
+public class StorageBrowserImpl extends MutualExclusiveIdsManagerBase implements StorageBrowser {
+
+    @Inject
+    ImageStoreJoinDao imageStoreJoinDao;
+
+    @Inject
+    ImageStoreObjectDownloadDao imageStoreObjectDownloadDao;
+
+    @Inject
+    DataStoreManager dataStoreMgr;
+
+    @Inject
+    TemplateDataStoreDao templateDataStoreDao;
+
+    @Inject
+    SnapshotDataStoreDao snapshotDataStoreDao;
+
+    @Inject
+    SnapshotDao snapshotDao;
+
+    @Inject
+    EndPointSelector endPointSelector;
+
+    @Inject
+    VMTemplatePoolDao templatePoolDao;
+
+    @Inject
+    VMTemplateDao templateDao;
+
+    @Inject
+    VolumeDao volumeDao;
+
+    @Inject
+    VolumeDataStoreDao volumeDataStoreDao;
+
+    @Override
+    public List<Class<?>> getCommands() {
+        List<Class<?>> cmdList = new ArrayList<>();
+        cmdList.add(ListImageStoreObjectsCmd.class);
+        cmdList.add(ListStoragePoolObjectsCmd.class);
+        cmdList.add(DownloadImageStoreObjectCmd.class);
+        return cmdList;
+    }
+
+    @Override
+    public ListResponse<DataStoreObjectResponse> listImageStoreObjects(ListImageStoreObjectsCmd cmd) {
+        Long imageStoreId = cmd.getStoreId();
+        String path = cmd.getPath();
+
+        ImageStoreJoinVO imageStore = imageStoreJoinDao.findById(imageStoreId);
+        DataStore dataStore = dataStoreMgr.getDataStore(imageStoreId, imageStore.getRole());
+        ListDataStoreObjectsAnswer answer = listObjectsInStore(dataStore, path, cmd.getStartIndex().intValue(), cmd.getPageSizeVal().intValue());
+
+        return getResponse(dataStore, answer);
+    }
+
+    @Override
+    public ListResponse<DataStoreObjectResponse> listPrimaryStoreObjects(ListStoragePoolObjectsCmd cmd) {
+        Long storeId = cmd.getStoreId();
+        String path = cmd.getPath();
+
+        DataStore dataStore = dataStoreMgr.getDataStore(storeId, DataStoreRole.Primary);
+        ListDataStoreObjectsAnswer answer = listObjectsInStore(dataStore, path, cmd.getStartIndex().intValue(), cmd.getPageSizeVal().intValue());
+
+        return getResponse(dataStore, answer);
+    }
+
+    @Override
+    public ExtractResponse downloadImageStoreObject(DownloadImageStoreObjectCmd cmd) {
+        ImageStoreEntity imageStore = (ImageStoreEntity) dataStoreMgr.getDataStore(cmd.getStoreId(), DataStoreRole.Image);
+
+        String path = cmd.getPath();
+        if (path.startsWith("/")) {
+            path = path.substring(1);
+        }
+
+        ImageStoreObjectDownloadVO imageStoreObj = imageStoreObjectDownloadDao.findByStoreIdAndPath(cmd.getStoreId(), path);
+
+        if (imageStoreObj == null) {
+            try {
+                String fileExt = path.substring(path.lastIndexOf(".") + 1);
+                Storage.ImageFormat format = EnumUtils.getEnumIgnoreCase(Storage.ImageFormat.class, fileExt);
+
+                DiagnosticsDataTO dataTO = new DiagnosticsDataTO(imageStore.getTO());
+                DiagnosticsDataObject dataObject = new DiagnosticsDataObject(dataTO, imageStore);
+                String downloadUrl = imageStore.createEntityExtractUrl(path, format, dataObject);
+                imageStoreObj = imageStoreObjectDownloadDao.persist(new ImageStoreObjectDownloadVO(imageStore.getId(), path, downloadUrl));
+            } catch (Exception e) {
+                throw new CloudRuntimeException("Failed to create download url for image store object", e);
+            }
+        }
+        ExtractResponse response = new ExtractResponse(null, null, CallContext.current().getCallingAccountUuid(), null, null);
+        if (imageStoreObj != null) {
+            response.setUrl(imageStoreObj.getDownloadUrl());
+            response.setName(cmd.getPath().substring(cmd.getPath().lastIndexOf("/") + 1));
+            response.setState(Upload.Status.DOWNLOAD_URL_CREATED.toString());
+        } else {
+            response.setState(Upload.Status.DOWNLOAD_URL_NOT_CREATED.toString());
+        }
+        return response;
+    }
+
+    ListDataStoreObjectsAnswer listObjectsInStore(DataStore dataStore, String path, int startIndex, int pageSize) {
+        EndPoint ep = endPointSelector.select(dataStore);
+
+        if (ep == null) {
+            throw new CloudRuntimeException("No remote endpoint to send command");
+        }
+
+        ListDataStoreObjectsCommand listDSCmd = new ListDataStoreObjectsCommand(dataStore.getTO(), path, startIndex, pageSize);
+        listDSCmd.setWait(15);
+        Answer answer = null;
+        try {
+            answer = ep.sendMessage(listDSCmd);
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Failed to list datastore objects", e);
+        }
+
+        if (answer == null || !answer.getResult() || !(answer instanceof ListDataStoreObjectsAnswer)) {
+            throw new CloudRuntimeException("Failed to list datastore objects");
+        }
+
+        ListDataStoreObjectsAnswer dsAnswer = (ListDataStoreObjectsAnswer) answer;
+        if (!dsAnswer.isPathExists()) {
+            throw new IllegalArgumentException("Path " + path + " doesn't exist in store: " + dataStore.getUuid());
+        }
+        return dsAnswer;
+    }
+
+    ListResponse<DataStoreObjectResponse> getResponse(DataStore dataStore, ListDataStoreObjectsAnswer answer) {
+        List<DataStoreObjectResponse> responses = new ArrayList<>();
+
+        List<String> paths = getFormattedPaths(answer.getPaths());
+        List<String> absPaths = answer.getAbsPaths();
+
+        Map<String, SnapshotVO> pathSnapshotMap;
+
+        Map<String, VMTemplateVO> pathTemplateMap;
+
+        Map<String, VolumeVO> pathVolumeMap;
+
+        if (dataStore.getRole() != DataStoreRole.Primary) {
+            pathTemplateMap = getPathTemplateMapForSecondaryDS(dataStore.getId(), paths);
+            pathSnapshotMap = getPathSnapshotMapForSecondaryDS(dataStore.getId(), paths);
+            pathVolumeMap = getPathVolumeMapForSecondaryDS(dataStore.getId(), paths);
+        } else {
+            pathTemplateMap = getPathTemplateMapForPrimaryDS(dataStore.getId(), paths);
+            pathSnapshotMap = getPathSnapshotMapForPrimaryDS(dataStore.getId(), paths, absPaths);
+            pathVolumeMap = getPathVolumeMapForPrimaryDS(dataStore.getId(), paths);
+        }
+
+        for (int i = 0; i < paths.size(); i++) {
+            DataStoreObjectResponse response = new DataStoreObjectResponse(
+                    answer.getNames().get(i),
+                    answer.getIsDirs().get(i),
+                    answer.getSizes().get(i),
+                    new Date(answer.getLastModified().get(i)));
+            String filePath = paths.get(i);
+            if (pathTemplateMap.get(filePath) != null) {
+                response.setTemplateId(pathTemplateMap.get(filePath).getUuid());
+                response.setFormat(pathTemplateMap.get(filePath).getFormat().toString());
+            }
+            if (pathSnapshotMap.get(filePath) != null) {
+                response.setSnapshotId(pathSnapshotMap.get(filePath).getUuid());
+            }
+            if (pathVolumeMap.get(filePath) != null) {
+                response.setVolumeId(pathVolumeMap.get(filePath).getUuid());
+            }
+            responses.add(response);
+        }
+
+        ListResponse<DataStoreObjectResponse> listResponse = new ListResponse<>();
+        listResponse.setResponses(responses, answer.getCount());
+        return listResponse;
+    }
+
+    List<String> getFormattedPaths(List<String> paths) {
+        List<String> formattedPaths = new ArrayList<>();
+        for (String path : paths) {
+            String normalizedPath = Path.of(path).normalize().toString();
+            if (normalizedPath.startsWith("/")) {
+                formattedPaths.add(normalizedPath.substring(1));
+            } else {
+                formattedPaths.add(normalizedPath);
+            }
+        }
+        return formattedPaths;
+    }
+
+    Map<String, VMTemplateVO> getPathTemplateMapForSecondaryDS(Long dataStoreId, List<String> paths) {
+        Map<String, VMTemplateVO> pathTemplateMap = new HashMap<>();
+        List<TemplateDataStoreVO> templateList = templateDataStoreDao.listByStoreIdAndInstallPaths(dataStoreId, paths);
+        if (!CollectionUtils.isEmpty(templateList)) {
+            List<VMTemplateVO> templates = templateDao.listByIds(templateList.stream().map(TemplateDataStoreVO::getTemplateId).collect(Collectors.toList()));
+
+            Map<Long, VMTemplateVO> templateMap = templates.stream().collect(
+                    Collectors.toMap(VMTemplateVO::getId, template -> template));
+
+            for (TemplateDataStoreVO templateDataStore : templateList) {
+                pathTemplateMap.put(templateDataStore.getInstallPath(),
+                        templateMap.get(templateDataStore.getTemplateId()));
+            }
+        }
+        return pathTemplateMap;
+    }
+
+    Map<String, SnapshotVO> getPathSnapshotMapForSecondaryDS(Long dataStoreId, List<String> paths) {
+        Map<String, SnapshotVO> snapshotPathMap = new HashMap<>();
+        List<SnapshotDataStoreVO> snapshotDataStoreList = snapshotDataStoreDao.listByStoreAndInstallPaths(dataStoreId, DataStoreRole.Image, paths);
+        if (!CollectionUtils.isEmpty(snapshotDataStoreList)) {
+            List<SnapshotVO> snapshots = snapshotDao.listByIds(
+                    snapshotDataStoreList.stream().map(SnapshotDataStoreVO::getSnapshotId).toArray());
+
+            Map<Long, SnapshotVO> snapshotMap = snapshots.stream().collect(
+                    Collectors.toMap(Snapshot::getId, snapshot -> snapshot));
+
+            for (SnapshotDataStoreVO snapshotDataStore : snapshotDataStoreList) {
+                snapshotPathMap.put(snapshotDataStore.getInstallPath(), snapshotMap.get(snapshotDataStore.getSnapshotId()));
+            }
+        }
+
+        return snapshotPathMap;
+    }
+
+    Map<String, VMTemplateVO> getPathTemplateMapForPrimaryDS(Long dataStoreId, List<String> paths) {
+        Map<String, VMTemplateVO> pathTemplateMap = new HashMap<>();
+        // get a map of paths without extension to path. We do this because extension is not saved in database for xen server.
+        Map<String, String> pathWithoutExtensionMap = new HashMap<>();
+        for (String path : paths) {
+            if (path.contains(".")) {
+                String pathWithoutExtension = path.substring(0, path.lastIndexOf("."));
+                pathWithoutExtensionMap.put(pathWithoutExtension, path);
+            }
+        }
+        List<String> pathList = Stream.concat(paths.stream(), pathWithoutExtensionMap.keySet().stream()).collect(Collectors.toList());
+        List<VMTemplateStoragePoolVO> templateStoragePoolList = templatePoolDao.listByPoolIdAndInstallPath(dataStoreId, pathList);
+        if (!CollectionUtils.isEmpty(templateStoragePoolList)) {
+            List<VMTemplateVO> templates = templateDao.listByIds
+                    (templateStoragePoolList.stream().map(VMTemplateStoragePoolVO::getTemplateId).collect(Collectors.toList()));
+
+            Map<Long, VMTemplateVO> templateMap = templates.stream().collect(
+                    Collectors.toMap(VMTemplateVO::getId, template -> template));
+
+            for (VMTemplateStoragePoolVO templatePool : templateStoragePoolList) {
+                pathTemplateMap.put(templatePool.getInstallPath(), templateMap.get(templatePool.getTemplateId()));
+                if (pathWithoutExtensionMap.get(templatePool.getInstallPath()) != null) {
+                    pathTemplateMap.put(pathWithoutExtensionMap.get(templatePool.getInstallPath()), templateMap.get(templatePool.getTemplateId()));
+                }
+            }
+        }
+        return pathTemplateMap;
+    }
+
+    Map<String, SnapshotVO> getPathSnapshotMapForPrimaryDS(Long dataStoreId, List<String> paths,
+            List<String> absPaths) {
+        Map<String, SnapshotVO> snapshotPathMap = new HashMap<>();
+        // get a map of paths without extension to path. We do this because extension is not saved in database for xen server.
+        Map<String, String> absPathWithoutExtensionMap = new HashMap<>();
+        for (String path : absPaths) {
+            if (path.contains(".")) {
+                String pathWithoutExtension = path.substring(0, path.lastIndexOf("."));
+                absPathWithoutExtensionMap.put(pathWithoutExtension, path);
+            }
+        }
+        List<String> absPathList = Stream.concat(absPaths.stream(), absPathWithoutExtensionMap.keySet().stream()).collect(Collectors.toList());
+        // For primary dataStore, we query using absolutePaths
+        List<SnapshotDataStoreVO> snapshotDataStoreList = snapshotDataStoreDao.listByStoreAndInstallPaths(dataStoreId, DataStoreRole.Primary, absPathList);
+        if (!CollectionUtils.isEmpty(snapshotDataStoreList)) {
+            List<SnapshotVO> snapshots = snapshotDao.listByIds(snapshotDataStoreList.stream().map(SnapshotDataStoreVO::getSnapshotId).toArray());
+
+            Map<Long, SnapshotVO> snapshotMap = snapshots.stream().collect(
+                    Collectors.toMap(Snapshot::getId, snapshot -> snapshot));
+
+            // In case of primary data store, absolute path is stored in database.
+            // We use this map to create a mapping between relative path and absolute path
+            // which is used to create a mapping between relative path and snapshot.
+            Map<String, String> absolutePathPathMap = new HashMap<>();
+            for (int i = 0; i < paths.size(); i++) {
+                absolutePathPathMap.put(absPaths.get(i), paths.get(i));
+            }
+
+            for (SnapshotDataStoreVO snapshotDataStore : snapshotDataStoreList) {
+                snapshotPathMap.put(absolutePathPathMap.get(snapshotDataStore.getInstallPath()),
+                        snapshotMap.get(snapshotDataStore.getSnapshotId()));
+
+                if (absPathWithoutExtensionMap.get(snapshotDataStore.getInstallPath()) != null) {
+                    snapshotPathMap.put(
+                            absolutePathPathMap.get(
+                                    absPathWithoutExtensionMap.get(
+                                            snapshotDataStore.getInstallPath()
+                                    )), snapshotMap.get(snapshotDataStore.getSnapshotId()));
+                }
+            }
+        }
+
+        return snapshotPathMap;
+    }
+
+    Map<String, VolumeVO> getPathVolumeMapForPrimaryDS(Long dataStoreId, List<String> paths) {
+        Map<String, VolumeVO> volumePathMap = new HashMap<>();
+
+        // get a map of paths without extension to path. We do this because extension is not saved in database for xen server.
+        Map<String, String> pathWithoutExtensionMap = new HashMap<>();
+        for (String path : paths) {
+            if (path.contains(".")) {
+                String pathWithoutExtension = path.substring(0, path.lastIndexOf("."));
+                pathWithoutExtensionMap.put(pathWithoutExtension, path);
+            }
+        }
+        List<String> pathList = Stream.concat(paths.stream(), pathWithoutExtensionMap.keySet().stream()).collect(Collectors.toList());
+        List<VolumeVO> volumeList = volumeDao.listByPoolIdAndPaths(dataStoreId, pathList);
+        if (!CollectionUtils.isEmpty(volumeList)) {
+            for (VolumeVO volume : volumeList) {
+                volumePathMap.put(volume.getPath(), volume);
+                if (pathWithoutExtensionMap.get(volume.getPath()) != null) {
+                    volumePathMap.put(pathWithoutExtensionMap.get(volume.getPath()), volume);
+                }
+            }
+        }
+        return volumePathMap;
+    }
+
+    Map<String, VolumeVO> getPathVolumeMapForSecondaryDS(Long dataStoreId, List<String> paths) {
+        Map<String, VolumeVO> volumePathMap = new HashMap<>();
+        List<VolumeDataStoreVO> volumeList = volumeDataStoreDao.listByStoreIdAndInstallPaths(dataStoreId, paths);
+        if (!CollectionUtils.isEmpty(volumeList)) {
+            List<Long> volumeIdList = volumeList.stream().map(VolumeDataStoreVO::getVolumeId).collect(Collectors.toList());
+            List<VolumeVO> volumeVOS = volumeDao.listByIds(volumeIdList);
+            Map<Long, VolumeVO> volumeMap = volumeVOS.stream().collect(Collectors.toMap(VolumeVO::getId, volume -> volume));
+
+            for (VolumeDataStoreVO volumeDataStore : volumeList) {
+                volumePathMap.put(volumeDataStore.getInstallPath(), volumeMap.get(volumeDataStore.getVolumeId()));
+            }
+        }
+        return volumePathMap;
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java
new file mode 100644
index 0000000..9dfc75e
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java
@@ -0,0 +1,278 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.heuristics;
+
+import com.cloud.api.ApiDBUtils;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StorageStats;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.user.AccountVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.secstorage.HeuristicVO;
+import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.storage.heuristics.presetvariables.Account;
+import org.apache.cloudstack.storage.heuristics.presetvariables.Domain;
+import org.apache.cloudstack.storage.heuristics.presetvariables.PresetVariables;
+import org.apache.cloudstack.storage.heuristics.presetvariables.SecondaryStorage;
+import org.apache.cloudstack.storage.heuristics.presetvariables.Snapshot;
+import org.apache.cloudstack.storage.heuristics.presetvariables.Template;
+import org.apache.cloudstack.storage.heuristics.presetvariables.Volume;
+import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for building and injecting the heuristics preset variables into the JS script.
+ */
+public class HeuristicRuleHelper {
+
+    protected static final Logger LOGGER = Logger.getLogger(HeuristicRuleHelper.class);
+
+    private static final Long HEURISTICS_SCRIPT_TIMEOUT = StorageManager.HEURISTICS_SCRIPT_TIMEOUT.value();
+
+    @Inject
+    private DataStoreManager dataStoreManager;
+
+    @Inject
+    private ImageStoreDao imageStoreDao;
+
+    @Inject
+    private SecondaryStorageHeuristicDao secondaryStorageHeuristicDao;
+
+    @Inject
+    private DomainDao domainDao;
+
+    @Inject
+    private AccountDao accountDao;
+
+    /**
+     * Returns the {@link DataStore} object if the zone, specified by the ID, has an active heuristic rule for the given {@link HeuristicType}.
+     * It returns null otherwise.
+     * @param zoneId used to search for the heuristic rules.
+     * @param heuristicType used for checking if there is a heuristic rule for the given {@link HeuristicType}.
+     * @param obj can be from the following classes: {@link VMTemplateVO}, {@link SnapshotInfo} and {@link VolumeVO}.
+     *           They are used to retrieve attributes for injecting in the JS rule.
+     * @return the corresponding {@link DataStore} if there is a heuristic rule, returns null otherwise.
+     */
+    public DataStore getImageStoreIfThereIsHeuristicRule(Long zoneId, HeuristicType heuristicType, Object obj) {
+        HeuristicVO heuristicsVO = secondaryStorageHeuristicDao.findByZoneIdAndType(zoneId, heuristicType);
+
+        if (heuristicsVO == null) {
+            LOGGER.debug(String.format("No heuristic rules found for zone with ID [%s] and heuristic type [%s]. Returning null.", zoneId, heuristicType));
+            return null;
+        } else {
+            LOGGER.debug(String.format("Found the heuristic rule %s to apply for zone with ID [%s].", heuristicsVO, zoneId));
+            return interpretHeuristicRule(heuristicsVO.getHeuristicRule(), heuristicType, obj, zoneId);
+        }
+    }
+
+    /**
+     * Build the preset variables ({@link Template}, {@link Snapshot} and {@link Volume}) for the JS script.
+     */
+    protected void buildPresetVariables(JsInterpreter jsInterpreter, HeuristicType heuristicType, long zoneId, Object obj) {
+        PresetVariables presetVariables = new PresetVariables();
+        Long accountId = null;
+
+        switch (heuristicType) {
+            case TEMPLATE:
+            case ISO:
+                presetVariables.setTemplate(setTemplatePresetVariable((VMTemplateVO) obj));
+                accountId = ((VMTemplateVO) obj).getAccountId();
+                break;
+            case SNAPSHOT:
+                presetVariables.setSnapshot(setSnapshotPresetVariable((SnapshotInfo) obj));
+                accountId = ((SnapshotInfo) obj).getAccountId();
+                break;
+            case VOLUME:
+                presetVariables.setVolume(setVolumePresetVariable((VolumeVO) obj));
+                accountId = ((VolumeVO) obj).getAccountId();
+                break;
+        }
+        presetVariables.setAccount(setAccountPresetVariable(accountId));
+        presetVariables.setSecondaryStorages(setSecondaryStoragesVariable(zoneId));
+
+        injectPresetVariables(jsInterpreter, presetVariables);
+    }
+
+    /**
+     * Inject the {@link PresetVariables} ({@link Template}, {@link Snapshot} and {@link Volume}) into the JS {@link JsInterpreter}.
+     * For each type, they can be accessed using the following variables in the script:
+     * <ul>
+     *     <li>ISO/Template: using the variable <b>iso</b> or <b>template</b>, e.g. <b>iso.format</b> </li>
+     *     <li>Snapshot: using the variable <b>snapshot</b>, e.g. <b>snapshot.size</b></li>
+     *     <li>Volume: using the variable <b>volume</b>, e.g. <b>volume.format</b></li>
+     * </ul>
+     * @param jsInterpreter the JS script
+     * @param presetVariables used for injecting in the JS interpreter.
+     */
+    protected void injectPresetVariables(JsInterpreter jsInterpreter, PresetVariables presetVariables) {
+        jsInterpreter.injectVariable("secondaryStorages", presetVariables.getSecondaryStorages().toString());
+
+        if (presetVariables.getTemplate() != null) {
+            jsInterpreter.injectVariable("template", presetVariables.getTemplate().toString());
+            jsInterpreter.injectVariable("iso", presetVariables.getTemplate().toString());
+        }
+
+        if (presetVariables.getSnapshot() != null) {
+            jsInterpreter.injectVariable("snapshot", presetVariables.getSnapshot().toString());
+        }
+
+        if (presetVariables.getVolume() != null) {
+            jsInterpreter.injectVariable("volume", presetVariables.getVolume().toString());
+        }
+
+        if (presetVariables.getAccount() != null) {
+            jsInterpreter.injectVariable("account", presetVariables.getAccount().toString());
+        }
+    }
+
+    protected List<SecondaryStorage> setSecondaryStoragesVariable(long zoneId) {
+        List<SecondaryStorage> secondaryStorageList = new ArrayList<>();
+        List<ImageStoreVO> imageStoreVOS = imageStoreDao.listStoresByZoneId(zoneId);
+
+        for (ImageStoreVO imageStore : imageStoreVOS) {
+            SecondaryStorage secondaryStorage = new SecondaryStorage();
+
+            secondaryStorage.setName(imageStore.getName());
+            secondaryStorage.setId(imageStore.getUuid());
+            secondaryStorage.setProtocol(imageStore.getProtocol());
+            StorageStats storageStats = ApiDBUtils.getSecondaryStorageStatistics(imageStore.getId());
+
+            if (storageStats != null) {
+                secondaryStorage.setUsedDiskSize(storageStats.getByteUsed());
+                secondaryStorage.setTotalDiskSize(storageStats.getCapacityBytes());
+            }
+
+            secondaryStorageList.add(secondaryStorage);
+        }
+        return secondaryStorageList;
+    }
+
+    protected Template setTemplatePresetVariable(VMTemplateVO templateVO) {
+        Template template = new Template();
+
+        template.setName(templateVO.getName());
+        template.setFormat(templateVO.getFormat());
+        template.setHypervisorType(templateVO.getHypervisorType());
+
+        return template;
+    }
+
+    protected Volume setVolumePresetVariable(VolumeVO volumeVO) {
+        Volume volume = new Volume();
+
+        volume.setName(volumeVO.getName());
+        volume.setFormat(volumeVO.getFormat());
+        volume.setSize(volumeVO.getSize());
+
+        return volume;
+    }
+
+    protected Snapshot setSnapshotPresetVariable(SnapshotInfo snapshotInfo) {
+        Snapshot snapshot = new Snapshot();
+
+        snapshot.setName(snapshotInfo.getName());
+        snapshot.setSize(snapshotInfo.getSize());
+        snapshot.setHypervisorType(snapshotInfo.getHypervisorType());
+
+        return snapshot;
+    }
+
+    protected Account setAccountPresetVariable(Long accountId) {
+        if (accountId == null) {
+            return null;
+        }
+
+        AccountVO account = accountDao.findById(accountId);
+        if (account == null) {
+            return null;
+        }
+
+        Account accountVariable = new Account();
+        accountVariable.setName(account.getName());
+        accountVariable.setId(account.getUuid());
+
+        accountVariable.setDomain(setDomainPresetVariable(account.getDomainId()));
+
+        return accountVariable;
+    }
+
+    protected Domain setDomainPresetVariable(long domainId) {
+        DomainVO domain = domainDao.findById(domainId);
+        if (domain == null) {
+            return null;
+        }
+        Domain domainVariable = new Domain();
+        domainVariable.setName(domain.getName());
+        domainVariable.setId(domain.getUuid());
+
+        return domainVariable;
+    }
+
+    /**
+     * This method calls {@link HeuristicRuleHelper#buildPresetVariables(JsInterpreter, HeuristicType, long, Object)} for building the preset variables and
+     * execute the JS script specified in the <b>rule</b> ({@link String}) parameter. The script is pre-injected with the preset variables, to allow the JS script to reference them
+     * in the code scope.
+     * <br>
+     * <br>
+     * The JS script needs to return a valid UUID ({@link String}) of a secondary storage, otherwise a {@link CloudRuntimeException} is thrown.
+     * @param rule the {@link String} representing the JS script.
+     * @param heuristicType used for building the preset variables accordingly to the  {@link HeuristicType} specified.
+     * @param obj can be from the following classes: {@link VMTemplateVO}, {@link SnapshotInfo} and {@link VolumeVO}.
+     *           They are used to retrieve attributes for injecting in the JS rule.
+     * @param zoneId used for injecting the {@link SecondaryStorage} preset variables.
+     * @return the {@link DataStore} returned by the script.
+     */
+    public DataStore interpretHeuristicRule(String rule, HeuristicType heuristicType, Object obj, long zoneId) {
+        try (JsInterpreter jsInterpreter = new JsInterpreter(HEURISTICS_SCRIPT_TIMEOUT)) {
+            buildPresetVariables(jsInterpreter, heuristicType, zoneId, obj);
+            Object scriptReturn = jsInterpreter.executeScript(rule);
+
+            if (!(scriptReturn instanceof String)) {
+                throw new CloudRuntimeException(String.format("Error while interpreting heuristic rule [%s], the rule did not return a String.", rule));
+            }
+
+            DataStore dataStore = dataStoreManager.getImageStoreByUuid((String) scriptReturn);
+
+            if (dataStore == null) {
+                throw new CloudRuntimeException(String.format("Unable to find a secondary storage with the UUID [%s] returned by the heuristic rule [%s]. Check if the rule is " +
+                        "returning a valid UUID.", scriptReturn, rule));
+            }
+
+            return dataStore;
+        } catch (IOException ex) {
+            String message = String.format("Error while executing script [%s].", rule);
+            LOGGER.error(message, ex);
+            throw new CloudRuntimeException(message, ex);
+        }
+    }
+
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Account.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Account.java
new file mode 100644
index 0000000..67750e8
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Account.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.storage.heuristics.presetvariables;
+
+public class Account extends GenericHeuristicPresetVariable {
+    private String id;
+
+    private Domain domain;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+        fieldNamesToIncludeInToString.add("id");
+    }
+
+    public Domain getDomain() {
+        return domain;
+    }
+
+    public void setDomain(Domain domain) {
+        this.domain = domain;
+        fieldNamesToIncludeInToString.add("domain");
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Domain.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Domain.java
new file mode 100644
index 0000000..6565c06
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Domain.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.storage.heuristics.presetvariables;
+
+public class Domain extends GenericHeuristicPresetVariable{
+    private String id;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+        fieldNamesToIncludeInToString.add("id");
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/GenericHeuristicPresetVariable.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/GenericHeuristicPresetVariable.java
new file mode 100644
index 0000000..f8ded3a
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/GenericHeuristicPresetVariable.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.storage.heuristics.presetvariables;
+
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class GenericHeuristicPresetVariable {
+
+    protected transient Set<String> fieldNamesToIncludeInToString = new HashSet<>();
+
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+        fieldNamesToIncludeInToString.add("name");
+    }
+
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, fieldNamesToIncludeInToString.toArray(new String[0]));
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/PresetVariables.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/PresetVariables.java
new file mode 100644
index 0000000..d048749
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/PresetVariables.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.storage.heuristics.presetvariables;
+
+import java.util.List;
+
+public class PresetVariables {
+
+    private Account account;
+
+    private List<SecondaryStorage> secondaryStorages;
+
+    private Template template;
+
+    private Snapshot snapshot;
+
+    private Volume volume;
+
+    public List<SecondaryStorage> getSecondaryStorages() {
+        return secondaryStorages;
+    }
+
+    public void setSecondaryStorages(List<SecondaryStorage> secondaryStorages) {
+        this.secondaryStorages = secondaryStorages;
+    }
+
+    public Template getTemplate() {
+        return template;
+    }
+
+    public void setTemplate(Template template) {
+        this.template = template;
+    }
+
+    public Snapshot getSnapshot() {
+        return snapshot;
+    }
+
+    public void setSnapshot(Snapshot snapshot) {
+        this.snapshot = snapshot;
+    }
+
+    public Volume getVolume() {
+        return volume;
+    }
+
+    public void setVolume(Volume volume) {
+        this.volume = volume;
+    }
+
+    public Account getAccount() {
+        return account;
+    }
+
+    public void setAccount(Account account) {
+        this.account = account;
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/SecondaryStorage.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/SecondaryStorage.java
new file mode 100644
index 0000000..ad7058d
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/SecondaryStorage.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.storage.heuristics.presetvariables;
+
+public class SecondaryStorage extends GenericHeuristicPresetVariable {
+
+    private String id;
+
+    private Long usedDiskSize;
+
+    private Long totalDiskSize;
+
+    private String protocol;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+        fieldNamesToIncludeInToString.add("id");
+    }
+
+    public Long getUsedDiskSize() {
+        return usedDiskSize;
+    }
+
+    public void setUsedDiskSize(Long usedDiskSize) {
+        this.usedDiskSize = usedDiskSize;
+        fieldNamesToIncludeInToString.add("usedDiskSize");
+    }
+
+    public Long getTotalDiskSize() {
+        return totalDiskSize;
+    }
+
+    public void setTotalDiskSize(Long totalDiskSize) {
+        this.totalDiskSize = totalDiskSize;
+        fieldNamesToIncludeInToString.add("totalDiskSize");
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+        fieldNamesToIncludeInToString.add("protocol");
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Snapshot.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Snapshot.java
new file mode 100644
index 0000000..34acd39
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Snapshot.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 org.apache.cloudstack.storage.heuristics.presetvariables;
+
+import com.cloud.hypervisor.Hypervisor;
+
+public class Snapshot extends GenericHeuristicPresetVariable {
+
+    private Long size;
+
+    private Hypervisor.HypervisorType hypervisorType;
+
+    public Long getSize() {
+        return size;
+    }
+
+    public void setSize(Long size) {
+        this.size = size;
+        fieldNamesToIncludeInToString.add("size");
+    }
+
+    public Hypervisor.HypervisorType getHypervisorType() {
+        return hypervisorType;
+    }
+
+    public void setHypervisorType(Hypervisor.HypervisorType hypervisorType) {
+        this.hypervisorType = hypervisorType;
+        fieldNamesToIncludeInToString.add("hypervisorType");
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.java
new file mode 100644
index 0000000..297c95f
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.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.storage.heuristics.presetvariables;
+
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.Storage;
+
+public class Template extends GenericHeuristicPresetVariable {
+
+    private Hypervisor.HypervisorType hypervisorType;
+
+    private Storage.ImageFormat format;
+
+    private Storage.TemplateType templateType;
+
+    public Hypervisor.HypervisorType getHypervisorType() {
+        return hypervisorType;
+    }
+
+    public void setHypervisorType(Hypervisor.HypervisorType hypervisorType) {
+        this.hypervisorType = hypervisorType;
+        fieldNamesToIncludeInToString.add("hypervisorType");
+    }
+
+    public Storage.ImageFormat getFormat() {
+        return format;
+    }
+
+    public void setFormat(Storage.ImageFormat format) {
+        this.format = format;
+        fieldNamesToIncludeInToString.add("format");
+    }
+
+    public Storage.TemplateType getTemplateType() {
+        return templateType;
+    }
+
+    public void setTemplateType(Storage.TemplateType templateType) {
+        this.templateType = templateType;
+        fieldNamesToIncludeInToString.add("templateType");
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Volume.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Volume.java
new file mode 100644
index 0000000..4e5e81b
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Volume.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 org.apache.cloudstack.storage.heuristics.presetvariables;
+
+import com.cloud.storage.Storage;
+
+public class Volume extends GenericHeuristicPresetVariable {
+
+    private Long size;
+
+    private Storage.ImageFormat format;
+
+    public Long getSize() {
+        return size;
+    }
+
+    public void setSize(Long size) {
+        this.size = size;
+        fieldNamesToIncludeInToString.add("size");
+    }
+
+    public Storage.ImageFormat getFormat() {
+        return format;
+    }
+
+    public void setFormat(Storage.ImageFormat format) {
+        this.format = format;
+        fieldNamesToIncludeInToString.add("format");
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java
new file mode 100644
index 0000000..bfd29cc
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java
@@ -0,0 +1,304 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.object;
+
+import com.amazonaws.services.s3.internal.BucketNameUtils;
+import com.amazonaws.services.s3.model.IllegalBucketNameException;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.storage.BucketVO;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.dao.BucketDao;
+import com.cloud.usage.BucketStatisticsVO;
+import com.cloud.usage.dao.BucketStatisticsDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd;
+import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class BucketApiServiceImpl extends ManagerBase implements BucketApiService, Configurable {
+    private final static Logger s_logger = Logger.getLogger(BucketApiServiceImpl.class);
+
+    @Inject
+    private ObjectStoreDao _objectStoreDao;
+    @Inject
+    DataStoreManager _dataStoreMgr;
+    @Inject
+    private BucketDao _bucketDao;
+    @Inject
+    private AccountManager _accountMgr;
+
+    @Inject
+    private BucketStatisticsDao _bucketStatisticsDao;
+
+    private ScheduledExecutorService _executor = null;
+
+    private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 3;
+
+    protected BucketApiServiceImpl() {
+
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Bucket-Usage"));
+        return true;
+    }
+
+    @Override
+    public boolean start() {
+        _executor.scheduleWithFixedDelay(new BucketUsageTask(), 60L, 3600L, TimeUnit.SECONDS);
+        return true;
+    }
+
+    @Override
+    public boolean stop() {
+        _executor.shutdown();
+        return true;
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return BucketApiService.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {
+        };
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_BUCKET_CREATE, eventDescription = "creating bucket", create = true)
+    public Bucket allocBucket(CreateBucketCmd cmd) {
+        try {
+            BucketNameUtils.validateBucketName(cmd.getBucketName());
+        } catch (IllegalBucketNameException e) {
+            s_logger.error("Invalid Bucket Name: " +cmd.getBucketName(), e);
+            throw new InvalidParameterValueException("Invalid Bucket Name: "+e.getMessage());
+        }
+        //ToDo check bucket exists
+        long ownerId = cmd.getEntityOwnerId();
+        Account owner = _accountMgr.getActiveAccountById(ownerId);
+        ObjectStoreVO objectStoreVO = _objectStoreDao.findById(cmd.getObjectStoragePoolId());
+        ObjectStoreEntity  objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+        try {
+            if(!objectStore.createUser(ownerId)) {
+                s_logger.error("Failed to create user in objectstore "+ objectStore.getName());
+                return null;
+            }
+        } catch (CloudRuntimeException e) {
+            s_logger.error("Error while checking object store user.", e);
+            return null;
+        }
+
+        BucketVO bucket = new BucketVO(ownerId, owner.getDomainId(), cmd.getObjectStoragePoolId(), cmd.getBucketName(), cmd.getQuota(),
+                                    cmd.isVersioning(), cmd.isEncryption(), cmd.isObjectLocking(), cmd.getPolicy());
+        _bucketDao.persist(bucket);
+        return bucket;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_BUCKET_CREATE, eventDescription = "creating bucket", async = true)
+    public Bucket createBucket(CreateBucketCmd cmd) {
+        ObjectStoreVO objectStoreVO = _objectStoreDao.findById(cmd.getObjectStoragePoolId());
+        ObjectStoreEntity  objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+        BucketVO bucket = _bucketDao.findById(cmd.getEntityId());
+        boolean objectLock = false;
+        boolean bucketCreated = false;
+        if(cmd.isObjectLocking()) {
+            objectLock = true;
+        }
+        try {
+            objectStore.createBucket(bucket, objectLock);
+            bucketCreated = true;
+
+            if (cmd.isVersioning()) {
+                objectStore.setBucketVersioning(bucket.getName());
+            }
+
+            if (cmd.isEncryption()) {
+                objectStore.setBucketEncryption(bucket.getName());
+            }
+
+            if (cmd.getQuota() != null) {
+                objectStore.setQuota(bucket.getName(), cmd.getQuota());
+            }
+
+            if (cmd.getPolicy() != null) {
+                objectStore.setBucketPolicy(bucket.getName(), cmd.getPolicy());
+            }
+
+            bucket.setState(Bucket.State.Created);
+            _bucketDao.update(bucket.getId(), bucket);
+        } catch (Exception e) {
+            s_logger.debug("Failed to create bucket with name: "+bucket.getName(), e);
+            if(bucketCreated) {
+                objectStore.deleteBucket(bucket.getName());
+            }
+            _bucketDao.remove(bucket.getId());
+            throw new CloudRuntimeException("Failed to create bucket with name: "+bucket.getName()+". "+e.getMessage());
+        }
+        return bucket;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_BUCKET_DELETE, eventDescription = "deleting bucket")
+    public boolean deleteBucket(long bucketId, Account caller) {
+        Bucket bucket = _bucketDao.findById(bucketId);
+        if (bucket == null) {
+            throw new InvalidParameterValueException("Unable to find bucket with ID: " + bucketId);
+        }
+        _accountMgr.checkAccess(caller, null, true, bucket);
+        ObjectStoreVO objectStoreVO = _objectStoreDao.findById(bucket.getObjectStoreId());
+        ObjectStoreEntity  objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+        if (objectStore.deleteBucket(bucket.getName())) {
+            return _bucketDao.remove(bucketId);
+        }
+        return false;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_BUCKET_UPDATE, eventDescription = "updating bucket")
+    public boolean updateBucket(UpdateBucketCmd cmd, Account caller) {
+        BucketVO bucket = _bucketDao.findById(cmd.getId());
+        if (bucket == null) {
+            throw new InvalidParameterValueException("Unable to find bucket with ID: " + cmd.getId());
+        }
+        _accountMgr.checkAccess(caller, null, true, bucket);
+        ObjectStoreVO objectStoreVO = _objectStoreDao.findById(bucket.getObjectStoreId());
+        ObjectStoreEntity  objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+        try {
+            if (cmd.getEncryption() != null) {
+                if (cmd.getEncryption()) {
+                    objectStore.setBucketEncryption(bucket.getName());
+                } else {
+                    objectStore.deleteBucketEncryption(bucket.getName());
+                }
+                bucket.setEncryption(cmd.getEncryption());
+            }
+
+            if (cmd.getVersioning() != null) {
+                if (cmd.getVersioning()) {
+                    objectStore.setBucketVersioning(bucket.getName());
+                } else {
+                    objectStore.deleteBucketVersioning(bucket.getName());
+                }
+                bucket.setVersioning(cmd.getVersioning());
+            }
+
+            if (cmd.getPolicy() != null) {
+                objectStore.setBucketPolicy(bucket.getName(), cmd.getPolicy());
+                bucket.setPolicy(cmd.getPolicy());
+            }
+
+            if (cmd.getQuota() != null) {
+                objectStore.setQuota(bucket.getName(), cmd.getQuota());
+                bucket.setQuota(cmd.getQuota());
+            }
+            _bucketDao.update(bucket.getId(), bucket);
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Error while updating bucket: " +bucket.getName() +". "+e.getMessage());
+        }
+
+        return true;
+    }
+
+    public void getBucketUsage() {
+        //ToDo track usage one last time when object store or bucket is removed
+        List<ObjectStoreVO> objectStores = _objectStoreDao.listObjectStores();
+        for(ObjectStoreVO objectStoreVO: objectStores) {
+            ObjectStoreEntity  objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+            Map<String, Long> bucketSizes = objectStore.getAllBucketsUsage();
+            List<BucketVO> buckets = _bucketDao.listByObjectStoreId(objectStoreVO.getId());
+            for(BucketVO bucket : buckets) {
+                Long size = bucketSizes.get(bucket.getName());
+                if( size != null){
+                    bucket.setSize(size);
+                    _bucketDao.update(bucket.getId(), bucket);
+                }
+            }
+        }
+    }
+
+    private class BucketUsageTask extends ManagedContextRunnable {
+        public BucketUsageTask() {
+        }
+
+        @Override
+        protected void runInContext() {
+            GlobalLock scanLock = GlobalLock.getInternLock("BucketUsage");
+            try {
+                if (scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) {
+                    try {
+                        List<ObjectStoreVO> objectStores = _objectStoreDao.listObjectStores();
+                        for(ObjectStoreVO objectStoreVO: objectStores) {
+                            ObjectStoreEntity  objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object);
+                            Map<String, Long> bucketSizes = objectStore.getAllBucketsUsage();
+                            List<BucketVO> buckets = _bucketDao.listByObjectStoreId(objectStoreVO.getId());
+                            for(BucketVO bucket : buckets) {
+                                Long size = bucketSizes.get(bucket.getName());
+                                if( size != null){
+                                    bucket.setSize(size);
+                                    _bucketDao.update(bucket.getId(), bucket);
+
+                                    //Update Bucket Usage stats
+                                    BucketStatisticsVO bucketStatisticsVO = _bucketStatisticsDao.findBy(bucket.getAccountId(), bucket.getId());
+                                    if(bucketStatisticsVO != null) {
+                                        bucketStatisticsVO.setSize(size);
+                                        _bucketStatisticsDao.update(bucketStatisticsVO.getId(), bucketStatisticsVO);
+                                    } else {
+                                        bucketStatisticsVO = new BucketStatisticsVO(bucket.getAccountId(), bucket.getId());
+                                        bucketStatisticsVO.setSize(size);
+                                        _bucketStatisticsDao.persist(bucketStatisticsVO);
+                                    }
+                                }
+                            }
+                        }
+                        s_logger.debug("Completed updating bucket usage for all object stores");
+                    } catch (Exception e) {
+                        s_logger.error("Error while fetching bucket usage", e);
+                    } finally {
+                        scanLock.unlock();
+                    }
+                }
+            } finally {
+                scanLock.releaseRef();
+            }
+        }
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java b/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java
new file mode 100644
index 0000000..0371be8
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java
@@ -0,0 +1,372 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.template;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.exception.InsufficientAddressCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.IpAddress;
+import com.cloud.network.IpAddressManager;
+import com.cloud.network.Network;
+import com.cloud.network.NetworkModel;
+import com.cloud.network.NetworkService;
+import com.cloud.network.VNF;
+import com.cloud.network.dao.FirewallRulesDao;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.firewall.FirewallService;
+import com.cloud.network.rules.FirewallRule;
+import com.cloud.network.rules.FirewallRuleVO;
+import com.cloud.network.rules.RulesService;
+import com.cloud.network.security.SecurityGroup;
+import com.cloud.network.security.SecurityGroupManager;
+import com.cloud.network.security.SecurityGroupService;
+import com.cloud.network.security.SecurityGroupVO;
+import com.cloud.network.security.SecurityRule;
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.storage.dao.VnfTemplateDetailsDao;
+import com.cloud.storage.dao.VnfTemplateNicDao;
+import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.user.Account;
+import com.cloud.uservm.UserVm;
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.NetUtils;
+import com.cloud.vm.NicVO;
+import com.cloud.vm.dao.NicDao;
+import org.apache.cloudstack.api.command.admin.template.ListVnfTemplatesCmdByAdmin;
+import org.apache.cloudstack.api.command.admin.template.RegisterVnfTemplateCmdByAdmin;
+import org.apache.cloudstack.api.command.admin.template.UpdateVnfTemplateCmdByAdmin;
+import org.apache.cloudstack.api.command.admin.vm.DeployVnfApplianceCmdByAdmin;
+import org.apache.cloudstack.api.command.user.template.DeleteVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.log4j.Logger;
+
+
+public class VnfTemplateManagerImpl extends ManagerBase implements VnfTemplateManager, PluggableService, Configurable {
+
+    static final Logger LOGGER = Logger.getLogger(VnfTemplateManagerImpl.class);
+
+    public static final String VNF_SECURITY_GROUP_NAME = "VNF_SecurityGroup_";
+    public static final String ACCESS_METHOD_SEPARATOR = ",";
+    public static final Integer ACCESS_DEFAULT_SSH_PORT = 22;
+    public static final Integer ACCESS_DEFAULT_HTTP_PORT = 80;
+    public static final Integer ACCESS_DEFAULT_HTTPS_PORT = 443;
+
+    @Inject
+    VnfTemplateDetailsDao vnfTemplateDetailsDao;
+    @Inject
+    VnfTemplateNicDao vnfTemplateNicDao;
+    @Inject
+    SecurityGroupManager securityGroupManager;
+    @Inject
+    SecurityGroupService securityGroupService;
+    @Inject
+    NetworkModel networkModel;
+    @Inject
+    IpAddressManager ipAddressManager;
+    @Inject
+    NicDao nicDao;
+    @Inject
+    NetworkDao networkDao;
+    @Inject
+    NetworkService networkService;
+    @Inject
+    RulesService rulesService;
+    @Inject
+    FirewallRulesDao firewallRulesDao;
+    @Inject
+    FirewallService firewallService;
+
+    @Override
+    public List<Class<?>> getCommands() {
+        final List<Class<?>> cmdList = new ArrayList<>();
+        if (!VnfTemplateAndApplianceEnabled.value()) {
+            return cmdList;
+        }
+        cmdList.add(RegisterVnfTemplateCmd.class);
+        cmdList.add(RegisterVnfTemplateCmdByAdmin.class);
+        cmdList.add(ListVnfTemplatesCmd.class);
+        cmdList.add(ListVnfTemplatesCmdByAdmin.class);
+        cmdList.add(UpdateVnfTemplateCmd.class);
+        cmdList.add(UpdateVnfTemplateCmdByAdmin.class);
+        cmdList.add(DeleteVnfTemplateCmd.class);
+        cmdList.add(DeployVnfApplianceCmd.class);
+        cmdList.add(DeployVnfApplianceCmdByAdmin.class);
+        return cmdList;
+    }
+
+    @Override
+    public void persistVnfTemplate(long templateId, RegisterVnfTemplateCmd cmd) {
+        persistVnfTemplateNics(templateId, cmd.getVnfNics());
+        persistVnfTemplateDetails(templateId, cmd);
+    }
+
+    private void persistVnfTemplateNics(long templateId, List<VNF.VnfNic> nics) {
+        for (VNF.VnfNic nic : nics) {
+            VnfTemplateNicVO vnfTemplateNicVO = new VnfTemplateNicVO(templateId, nic.getDeviceId(), nic.getName(), nic.isRequired(), nic.isManagement(), nic.getDescription());
+            vnfTemplateNicDao.persist(vnfTemplateNicVO);
+        }
+    }
+
+    private void persistVnfTemplateDetails(long templateId, RegisterVnfTemplateCmd cmd) {
+        persistVnfTemplateDetails(templateId, cmd.getVnfDetails());
+    }
+
+    private void persistVnfTemplateDetails(long templateId, Map<String, String> vnfDetails) {
+        for (Map.Entry<String, String> entry:  vnfDetails.entrySet()) {
+            String value = entry.getValue();
+            if (VNF.AccessDetail.ACCESS_METHODS.name().equalsIgnoreCase(entry.getKey())) {
+                value = Arrays.stream(value.split(ACCESS_METHOD_SEPARATOR)).sorted().collect(Collectors.joining(ACCESS_METHOD_SEPARATOR));
+            }
+            vnfTemplateDetailsDao.addDetail(templateId, entry.getKey().toLowerCase(), value, true);
+        }
+    }
+
+    @Override
+    public void updateVnfTemplate(long templateId, UpdateVnfTemplateCmd cmd) {
+        updateVnfTemplateDetails(templateId, cmd);
+        updateVnfTemplateNics(templateId, cmd);
+    }
+
+    private void updateVnfTemplateDetails(long templateId, UpdateVnfTemplateCmd cmd) {
+        boolean cleanupVnfDetails = cmd.isCleanupVnfDetails();
+        if (cleanupVnfDetails) {
+            vnfTemplateDetailsDao.removeDetails(templateId);
+        } else if (MapUtils.isNotEmpty(cmd.getVnfDetails())) {
+            vnfTemplateDetailsDao.removeDetails(templateId);
+            persistVnfTemplateDetails(templateId, cmd.getVnfDetails());
+        }
+    }
+
+    private void updateVnfTemplateNics(long templateId, UpdateVnfTemplateCmd cmd) {
+        boolean cleanupVnfNics = cmd.isCleanupVnfNics();
+        if (cleanupVnfNics) {
+            vnfTemplateNicDao.deleteByTemplateId(templateId);
+        } else if (CollectionUtils.isNotEmpty(cmd.getVnfNics())) {
+            vnfTemplateNicDao.deleteByTemplateId(templateId);
+            persistVnfTemplateNics(templateId, cmd.getVnfNics());
+        }
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return VnfTemplateManager.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey[] { VnfTemplateAndApplianceEnabled };
+    }
+
+    @Override
+    public void validateVnfApplianceNics(VirtualMachineTemplate template, List<Long> networkIds) {
+        if (CollectionUtils.isEmpty(networkIds)) {
+            throw new InvalidParameterValueException("VNF nics list is empty");
+        }
+        List<VnfTemplateNicVO> vnfNics = vnfTemplateNicDao.listByTemplateId(template.getId());
+        for (VnfTemplateNicVO vnfNic : vnfNics) {
+            if (vnfNic.isRequired() && networkIds.size() <= vnfNic.getDeviceId()) {
+                throw new InvalidParameterValueException("VNF nic is required but not found: " + vnfNic);
+            }
+        }
+    }
+
+    protected Set<Integer> getOpenPortsForVnfAppliance(VirtualMachineTemplate template) {
+        Set<Integer> ports = new HashSet<>();
+        VnfTemplateDetailVO accessMethodsDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.ACCESS_METHODS.name().toLowerCase());
+        if (accessMethodsDetail == null || accessMethodsDetail.getValue() == null) {
+            return ports;
+        }
+        String[] accessMethods = accessMethodsDetail.getValue().split(ACCESS_METHOD_SEPARATOR);
+        for (String accessMethod : accessMethods) {
+            if (VNF.AccessMethod.SSH_WITH_KEY.toString().equalsIgnoreCase(accessMethod)
+                    || VNF.AccessMethod.SSH_WITH_PASSWORD.toString().equalsIgnoreCase(accessMethod)) {
+                VnfTemplateDetailVO accessDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.SSH_PORT.name().toLowerCase());
+                if (accessDetail == null) {
+                    ports.add(ACCESS_DEFAULT_SSH_PORT);
+                } else {
+                    ports.add(NumbersUtil.parseInt(accessDetail.getValue(), ACCESS_DEFAULT_SSH_PORT));
+                }
+            } else if (VNF.AccessMethod.HTTP.toString().equalsIgnoreCase(accessMethod)) {
+                VnfTemplateDetailVO accessDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.HTTP_PORT.name().toLowerCase());
+                if (accessDetail == null) {
+                    ports.add(ACCESS_DEFAULT_HTTP_PORT);
+                } else {
+                    ports.add(NumbersUtil.parseInt(accessDetail.getValue(), ACCESS_DEFAULT_HTTP_PORT));
+                }
+            } else if (VNF.AccessMethod.HTTPS.toString().equalsIgnoreCase(accessMethod)) {
+                VnfTemplateDetailVO accessDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.HTTPS_PORT.name().toLowerCase());
+                if (accessDetail == null) {
+                    ports.add(ACCESS_DEFAULT_HTTPS_PORT);
+                } else {
+                    ports.add(NumbersUtil.parseInt(accessDetail.getValue(), ACCESS_DEFAULT_HTTPS_PORT));
+                }
+            }
+        }
+        return ports;
+    }
+
+    private Set<Long> getDeviceIdsOfVnfManagementNics(VirtualMachineTemplate template) {
+        Set<Long> deviceIds = new HashSet<>();
+        for (VnfTemplateNicVO nic : vnfTemplateNicDao.listByTemplateId(template.getId())) {
+            if (nic.isManagement()) {
+                deviceIds.add(nic.getDeviceId());
+            }
+        }
+        return deviceIds;
+    }
+
+    protected Map<Network, String> getManagementNetworkAndIp(VirtualMachineTemplate template, UserVm vm) {
+        Map<Network, String> networkAndIpMap = new HashMap<>();
+        Set<Long> managementDeviceIds = getDeviceIdsOfVnfManagementNics(template);
+        for (NicVO nic : nicDao.listByVmId(vm.getId())) {
+            if (managementDeviceIds.contains((long) nic.getDeviceId()) && nic.getIPv4Address() != null) {
+                Network network = networkDao.findById(nic.getNetworkId());
+                if (network == null || !Network.GuestType.Isolated.equals(network.getGuestType())) {
+                    continue;
+                }
+                if (!networkModel.areServicesSupportedInNetwork(network.getId(), Network.Service.StaticNat)) {
+                    LOGGER.info(String.format("Network ID: %s does not support static nat, " +
+                            "skipping this network configuration for VNF appliance", network.getUuid()));
+                    continue;
+                }
+                if (network.getVpcId() != null) {
+                    LOGGER.info(String.format("Network ID: %s is a VPC tier, " +
+                            "skipping this network configuration for VNF appliance", network.getUuid()));
+                    continue;
+                }
+                if (!networkModel.areServicesSupportedInNetwork(network.getId(), Network.Service.Firewall)) {
+                    LOGGER.info(String.format("Network ID: %s does not support firewall, " +
+                            "skipping this network configuration for VNF appliance", network.getUuid()));
+                    continue;
+                }
+                networkAndIpMap.put(network, nic.getIPv4Address());
+            }
+        }
+        return networkAndIpMap;
+    }
+
+    @Override
+    public SecurityGroup createSecurityGroupForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner,
+                                                            DeployVnfApplianceCmd cmd) {
+        if (zone == null || !zone.isSecurityGroupEnabled()) {
+            return null;
+        }
+        if (!cmd.getVnfConfigureManagement()) {
+            return null;
+        }
+        LOGGER.debug("Creating security group and rules for VNF appliance");
+        Set<Integer> ports = getOpenPortsForVnfAppliance(template);
+        if (ports.size() == 0) {
+            LOGGER.debug("No need to create security group and rules for VNF appliance as there is no ports to be open");
+            return null;
+        }
+        String securityGroupName = VNF_SECURITY_GROUP_NAME.concat(Long.toHexString(System.currentTimeMillis()));
+        SecurityGroupVO securityGroupVO = securityGroupManager.createSecurityGroup(securityGroupName,
+                "Security group for VNF appliance", owner.getDomainId(), owner.getId(), owner.getAccountName());
+        if (securityGroupVO == null) {
+            throw new CloudRuntimeException(String.format("Failed to create security group: %s", securityGroupName));
+        }
+        List<String> cidrList = cmd.getVnfCidrlist();
+        for (Integer port : ports) {
+            securityGroupService.authorizeSecurityGroupRule(securityGroupVO.getId(), NetUtils.TCP_PROTO, port, port,
+                    null, null, cidrList, null, SecurityRule.SecurityRuleType.IngressRule);
+        }
+        return securityGroupVO;
+    }
+
+    @Override
+    public void createIsolatedNetworkRulesForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner,
+                                                          UserVm vm, DeployVnfApplianceCmd cmd)
+            throws InsufficientAddressCapacityException, ResourceAllocationException, ResourceUnavailableException {
+
+        Map<Network, String> networkAndIpMap = getManagementNetworkAndIp(template, vm);
+        Set<Integer> ports = getOpenPortsForVnfAppliance(template);
+        for (Map.Entry<Network, String> entry : networkAndIpMap.entrySet()) {
+            Network network = entry.getKey();
+            LOGGER.debug("Creating network rules for VNF appliance on isolated network " + network.getUuid());
+            String ip = entry.getValue();
+            IpAddress publicIp = networkService.allocateIP(owner, zone.getId(), network.getId(), null, null);
+            if (publicIp == null) {
+                continue;
+            }
+            publicIp = ipAddressManager.associateIPToGuestNetwork(publicIp.getId(), network.getId(), false);
+            if (publicIp.isSourceNat()) {
+                // If isolated network is not implemented, the first acquired Public IP will be Source NAT IP
+                publicIp = networkService.allocateIP(owner, zone.getId(), network.getId(), null, null);
+                if (publicIp == null) {
+                    continue;
+                }
+                publicIp = ipAddressManager.associateIPToGuestNetwork(publicIp.getId(), network.getId(), false);
+            }
+            final IpAddress publicIpFinal = publicIp;
+            final List<String> cidrList = cmd.getVnfCidrlist();
+            try {
+                boolean result = rulesService.enableStaticNat(publicIp.getId(), vm.getId(), network.getId(), ip);
+                if (!result) {
+                    throw new CloudRuntimeException(String.format("Failed to create static nat for vm: %s", vm.getUuid()));
+                }
+            } catch (NetworkRuleConflictException e) {
+                throw new CloudRuntimeException(String.format("Failed to create static nat for vm %s due to %s", vm.getUuid(), e.getMessage()));
+            }
+            if (network.getVpcId() == null) {
+                Transaction.execute(new TransactionCallbackWithExceptionNoReturn<>() {
+                    @Override
+                    public void doInTransactionWithoutResult(final TransactionStatus status) throws CloudRuntimeException {
+                        for (Integer port : ports) {
+                            FirewallRuleVO newFirewallRule = new FirewallRuleVO(null, publicIpFinal.getId(), port, port, NetUtils.TCP_PROTO,
+                                    network.getId(), owner.getAccountId(), owner.getDomainId(), FirewallRule.Purpose.Firewall,
+                                    cidrList, null, null, null, FirewallRule.TrafficType.Ingress);
+                            newFirewallRule.setDisplay(true);
+                            newFirewallRule.setState(FirewallRule.State.Add);
+                            firewallRulesDao.persist(newFirewallRule);
+                        }
+                    }
+                });
+                firewallService.applyIngressFwRules(publicIp.getId(), owner);
+            }
+            LOGGER.debug("Created network rules for VNF appliance on isolated network " + network.getUuid());
+        }
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
index 79d8c7f..e809ebb 100644
--- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
@@ -17,64 +17,46 @@
 
 package org.apache.cloudstack.vm;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-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.api.ApiConstants;
-import org.apache.cloudstack.api.ApiErrorCode;
-import org.apache.cloudstack.api.ResponseGenerator;
-import org.apache.cloudstack.api.ResponseObject;
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
-import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
-import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd;
-import org.apache.cloudstack.api.response.ListResponse;
-import org.apache.cloudstack.api.response.NicResponse;
-import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse;
-import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
-import org.apache.cloudstack.api.response.UserVmResponse;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.collections.MapUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckVolumeAnswer;
+import com.cloud.agent.api.CheckVolumeCommand;
+import com.cloud.agent.api.ConvertInstanceAnswer;
+import com.cloud.agent.api.ConvertInstanceCommand;
+import com.cloud.agent.api.CopyRemoteVolumeAnswer;
+import com.cloud.agent.api.CopyRemoteVolumeCommand;
+import com.cloud.agent.api.GetRemoteVmsAnswer;
+import com.cloud.agent.api.GetRemoteVmsCommand;
 import com.cloud.agent.api.GetUnmanagedInstancesAnswer;
 import com.cloud.agent.api.GetUnmanagedInstancesCommand;
 import com.cloud.agent.api.PrepareUnmanageVMInstanceAnswer;
 import com.cloud.agent.api.PrepareUnmanageVMInstanceCommand;
-import com.cloud.capacity.CapacityManager;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.RemoteInstanceTO;
+import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.configuration.Config;
 import com.cloud.configuration.Resource;
 import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
 import com.cloud.deploy.DataCenterDeployment;
 import com.cloud.deploy.DeployDestination;
 import com.cloud.deploy.DeploymentPlanner;
 import com.cloud.deploy.DeploymentPlanningManager;
 import com.cloud.event.ActionEvent;
+import com.cloud.event.ActionEventUtils;
 import com.cloud.event.EventTypes;
+import com.cloud.event.EventVO;
 import com.cloud.event.UsageEventUtils;
+import com.cloud.exception.AgentUnavailableException;
 import com.cloud.exception.InsufficientAddressCapacityException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.OperationTimedoutException;
 import com.cloud.exception.PermissionDeniedException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.UnsupportedServiceException;
@@ -83,24 +65,37 @@
 import com.cloud.host.Status;
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.HypervisorGuru;
+import com.cloud.hypervisor.HypervisorGuruManager;
+import com.cloud.network.IpAddressManager;
 import com.cloud.network.Network;
 import com.cloud.network.NetworkModel;
 import com.cloud.network.Networks;
+import com.cloud.network.PhysicalNetwork;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.dao.PhysicalNetworkDao;
 import com.cloud.offering.DiskOffering;
+import com.cloud.offering.NetworkOffering;
 import com.cloud.offering.ServiceOffering;
+import com.cloud.offerings.NetworkOfferingVO;
+import com.cloud.offerings.dao.NetworkOfferingDao;
 import com.cloud.org.Cluster;
 import com.cloud.resource.ResourceManager;
+import com.cloud.resource.ResourceState;
 import com.cloud.serializer.GsonHelper;
 import com.cloud.server.ManagementService;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.GuestOS;
 import com.cloud.storage.GuestOSHypervisor;
+import com.cloud.storage.ScopeType;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.VMTemplateStoragePoolVO;
 import com.cloud.storage.VMTemplateVO;
@@ -111,6 +106,7 @@
 import com.cloud.storage.dao.GuestOSDao;
 import com.cloud.storage.dao.GuestOSHypervisorDao;
 import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VolumeDao;
@@ -121,7 +117,9 @@
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.UserDao;
 import com.cloud.uservm.UserVm;
+import com.cloud.utils.LogUtils;
 import com.cloud.utils.Pair;
+import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
 import com.cloud.vm.DiskProfile;
@@ -132,18 +130,69 @@
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineName;
 import com.cloud.vm.VirtualMachineProfile;
 import com.cloud.vm.VirtualMachineProfileImpl;
 import com.cloud.vm.VmDetailConstants;
 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.snapshot.dao.VMSnapshotDao;
 import com.google.gson.Gson;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ResponseGenerator;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
+import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd;
+import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
+import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd;
+import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.NicResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
+import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
 
 public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
     public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso";
+    public static final String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template";
     private static final Logger LOGGER = Logger.getLogger(UnmanagedVMsManagerImpl.class);
+    private static final List<Hypervisor.HypervisorType> importUnmanagedInstancesSupportedHypervisors =
+            Arrays.asList(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM);
 
     @Inject
     private AgentManager agentManager;
@@ -170,6 +219,8 @@
     @Inject
     private ResourceLimitService resourceLimitService;
     @Inject
+    private UserVmDetailsDao userVmDetailsDao;
+    @Inject
     private UserVmManager userVmManager;
     @Inject
     private ResponseGenerator responseGenerator;
@@ -186,8 +237,6 @@
     @Inject
     private VMInstanceDao vmDao;
     @Inject
-    private CapacityManager capacityManager;
-    @Inject
     private VolumeApiService volumeApiService;
     @Inject
     private DeploymentPlanningManager deploymentPlanningManager;
@@ -206,11 +255,29 @@
     @Inject
     private GuestOSHypervisorDao guestOSHypervisorDao;
     @Inject
-    private VMSnapshotDao vmSnapshotDao;
-    @Inject
     private SnapshotDao snapshotDao;
     @Inject
     private UserVmDao userVmDao;
+    @Inject
+    private NetworkOfferingDao networkOfferingDao;
+    @Inject
+    EntityManager entityMgr;
+    @Inject
+    private NetworkOrchestrationService networkMgr;
+    @Inject
+    private PhysicalNetworkDao physicalNetworkDao;
+    @Inject
+    private IpAddressManager ipAddressManager;
+    @Inject
+    private StoragePoolHostDao storagePoolHostDao;
+    @Inject
+    private HypervisorGuruManager hypervisorGuruManager;
+    @Inject
+    private VmwareDatacenterDao vmwareDatacenterDao;
+    @Inject
+    private ImageStoreDao imageStoreDao;
+    @Inject
+    private DataStoreManager dataStoreManager;
 
     protected Gson gson;
 
@@ -218,10 +285,11 @@
         gson = GsonHelper.getGsonLogger();
     }
 
-    private VMTemplateVO createDefaultDummyVmImportTemplate() {
+    private VMTemplateVO createDefaultDummyVmImportTemplate(boolean isKVM) {
+        String templateName = (isKVM) ? KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME : VM_IMPORT_DEFAULT_TEMPLATE_NAME;
         VMTemplateVO template = null;
         try {
-            template = VMTemplateVO.createSystemIso(templateDao.getNextInSequence(Long.class, "id"), VM_IMPORT_DEFAULT_TEMPLATE_NAME, VM_IMPORT_DEFAULT_TEMPLATE_NAME, true,
+            template = VMTemplateVO.createSystemIso(templateDao.getNextInSequence(Long.class, "id"), templateName, templateName, true,
                     "", true, 64, Account.ACCOUNT_ID_SYSTEM, "",
                     "VM Import Default Template", false, 1);
             template.setState(VirtualMachineTemplate.State.Inactive);
@@ -230,7 +298,7 @@
                 return null;
             }
             templateDao.remove(template.getId());
-            template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
+            template = templateDao.findByName(templateName);
         } catch (Exception e) {
             LOGGER.error("Unable to create default dummy template for VM import", e);
         }
@@ -240,6 +308,7 @@
     private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) {
         UnmanagedInstanceResponse response = new UnmanagedInstanceResponse();
         response.setName(instance.getName());
+
         if (cluster != null) {
             response.setClusterId(cluster.getUuid());
         }
@@ -294,6 +363,7 @@
                 response.addNic(nicResponse);
             }
         }
+
         return response;
     }
 
@@ -335,7 +405,6 @@
                         String[] split = path.split(" ");
                         path = split[split.length - 1];
                         split = path.split("/");
-                        ;
                         path = split[split.length - 1];
                         split = path.split("\\.");
                         path = split[0];
@@ -387,29 +456,32 @@
         return volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOffering.getTags());
     }
 
-    private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedInstanceTO instance, ServiceOfferingVO serviceOffering, final Account owner, final DataCenter zone, final Map<String, String> details)
+    private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedInstanceTO instance, ServiceOfferingVO serviceOffering, final Account owner, final DataCenter zone, final Map<String, String> details, Hypervisor.HypervisorType hypervisorType)
             throws ServerApiException, PermissionDeniedException, ResourceAllocationException {
         if (instance == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM is not valid"));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Cannot find VM to import.");
         }
         if (serviceOffering == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering is not valid"));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Cannot find service offering used to import VM [%s].", instance.getName()));
         }
         accountService.checkAccess(owner, serviceOffering, zone);
         final Integer cpu = instance.getCpuCores();
         final Integer memory = instance.getMemory();
         Integer cpuSpeed = instance.getCpuSpeed() == null ? 0 : instance.getCpuSpeed();
+
         if (cpu == null || cpu == 0) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("CPU cores for VM (%s) not valid", instance.getName()));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("CPU cores [%s] is not valid for importing VM [%s].", cpu, instance.getName()));
         }
         if (memory == null || memory == 0) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Memory for VM (%s) not valid", instance.getName()));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Memory [%s] is not valid for importing VM [%s].", memory, instance.getName()));
         }
+
         if (serviceOffering.isDynamic()) {
             if (details.containsKey(VmDetailConstants.CPU_SPEED)) {
                 try {
                     cpuSpeed = Integer.parseInt(details.get(VmDetailConstants.CPU_SPEED));
                 } catch (Exception e) {
+                    LOGGER.error(String.format("Failed to get CPU speed for importing VM [%s] due to [%s].", instance.getName(), e.getMessage()), e);
                 }
             }
             Map<String, String> parameters = new HashMap<>();
@@ -428,12 +500,12 @@
             if (!memory.equals(serviceOffering.getRamSize()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMB memory does not match VM memory %dMB and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getRamSize(), memory, instance.getPowerState()));
             }
-            if (cpuSpeed != null && cpuSpeed > 0 && !cpuSpeed.equals(serviceOffering.getSpeed()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) {
+            if (hypervisorType == Hypervisor.HypervisorType.VMware && cpuSpeed != null && cpuSpeed > 0 && !cpuSpeed.equals(serviceOffering.getSpeed()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMHz CPU speed does not match VM CPU speed %dMHz and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getSpeed(), cpuSpeed, instance.getPowerState()));
             }
         }
-        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.cpu, new Long(serviceOffering.getCpu()));
-        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.memory, new Long(serviceOffering.getRamSize()));
+        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.cpu, Long.valueOf(serviceOffering.getCpu()));
+        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.memory, Long.valueOf(serviceOffering.getRamSize()));
         return serviceOffering;
     }
 
@@ -482,7 +554,8 @@
             List<StoragePoolVO> pools = primaryDataStoreDao.listPoolsByCluster(cluster.getId());
             pools.addAll(primaryDataStoreDao.listByDataCenterId(zone.getId()));
             for (StoragePool pool : pools) {
-                if (pool.getPath().endsWith(dsName)) {
+                String searchPoolParam = StringUtils.isNotBlank(dsPath) ? dsPath : dsName;
+                if (StringUtils.contains(pool.getPath(), searchPoolParam)) {
                     storagePool = pool;
                     break;
                 }
@@ -494,16 +567,15 @@
         return storagePool;
     }
 
-    private Pair<UnmanagedInstanceTO.Disk, List<UnmanagedInstanceTO.Disk>> getRootAndDataDisks(List<UnmanagedInstanceTO.Disk> disks, final Map<String, Long> dataDiskOfferingMap) {
+    private Pair<UnmanagedInstanceTO.Disk, List<UnmanagedInstanceTO.Disk>> getRootAndDataDisks(
+            List<UnmanagedInstanceTO.Disk> disks,
+            final Map<String, Long> dataDiskOfferingMap) {
         UnmanagedInstanceTO.Disk rootDisk = null;
         List<UnmanagedInstanceTO.Disk> dataDisks = new ArrayList<>();
-        if (disks.size() == 1) {
-            rootDisk = disks.get(0);
-            return new Pair<>(rootDisk, dataDisks);
-        }
+
         Set<String> callerDiskIds = dataDiskOfferingMap.keySet();
         if (callerDiskIds.size() != disks.size() - 1) {
-            String msg = String.format("VM has total %d disks for which %d disk offering mappings provided. %d disks need a disk offering for import", disks.size(), callerDiskIds.size(), disks.size()-1);
+            String msg = String.format("VM has total %d disks for which %d disk offering mappings provided. %d disks need a disk offering for import", disks.size(), callerDiskIds.size(), disks.size() - 1);
             LOGGER.error(String.format("%s. %s parameter can be used to provide disk offerings for the disks", msg, ApiConstants.DATADISK_OFFERING_LIST));
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, msg);
         }
@@ -515,18 +587,23 @@
                 rootDisk = disk;
             } else {
                 dataDisks.add(disk);
+                DiskOffering diskOffering = diskOfferingDao.findById(dataDiskOfferingMap.getOrDefault(disk.getDiskId(), null));
+                if ((disk.getCapacity() == null || disk.getCapacity() <= 0) && diskOffering != null) {
+                    disk.setCapacity(diskOffering.getDiskSize());
+                }
             }
         }
-        if (diskIdsWithoutOffering.size() > 1) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM has total %d disks, disk offering mapping not provided for %d disks. Disk IDs that may need a disk offering - %s", disks.size(), diskIdsWithoutOffering.size()-1, String.join(", ", diskIdsWithoutOffering)));
+        if (diskIdsWithoutOffering.size() > 1 || rootDisk == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM has total %d disks, disk offering mapping not provided for %d disks. Disk IDs that may need a disk offering - %s", disks.size(), diskIdsWithoutOffering.size() - 1, String.join(", ", diskIdsWithoutOffering)));
         }
+
         return new Pair<>(rootDisk, dataDisks);
     }
 
-    private void checkUnmanagedDiskAndOfferingForImport(UnmanagedInstanceTO.Disk disk, DiskOffering diskOffering, ServiceOffering serviceOffering, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed)
+    private void checkUnmanagedDiskAndOfferingForImport(String instanceName, UnmanagedInstanceTO.Disk disk, DiskOffering diskOffering, ServiceOffering serviceOffering, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed)
             throws ServerApiException, PermissionDeniedException, ResourceAllocationException {
         if (serviceOffering == null && diskOffering == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID: %s not found during VM import", disk.getDiskId()));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID [%s] not found during VM [%s] import.", disk.getDiskId(), instanceName));
         }
         if (diskOffering != null) {
             accountService.checkAccess(owner, diskOffering, zone);
@@ -547,15 +624,15 @@
         }
     }
 
-    private void checkUnmanagedDiskAndOfferingForImport(List<UnmanagedInstanceTO.Disk> disks, final Map<String, Long> diskOfferingMap, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed)
+    private void checkUnmanagedDiskAndOfferingForImport(String intanceName, List<UnmanagedInstanceTO.Disk> disks, final Map<String, Long> diskOfferingMap, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed)
             throws ServerApiException, PermissionDeniedException, ResourceAllocationException {
         String diskController = null;
         for (UnmanagedInstanceTO.Disk disk : disks) {
             if (disk == null) {
-                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve disk details for VM"));
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve disk details for VM [%s].", intanceName));
             }
             if (!diskOfferingMap.containsKey(disk.getDiskId())) {
-                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID: %s not found during VM import", disk.getDiskId()));
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID [%s] not found during VM import.", disk.getDiskId()));
             }
             if (StringUtils.isEmpty(diskController)) {
                 diskController = disk.getController();
@@ -564,17 +641,12 @@
                     throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Multiple data disk controllers of different type (%s, %s) are not supported for import. Please make sure that all data disk controllers are of the same type", diskController, disk.getController()));
                 }
             }
-            checkUnmanagedDiskAndOfferingForImport(disk, diskOfferingDao.findById(diskOfferingMap.get(disk.getDiskId())), null, owner, zone, cluster, migrateAllowed);
+            checkUnmanagedDiskAndOfferingForImport(intanceName, disk, diskOfferingDao.findById(diskOfferingMap.get(disk.getDiskId())), null, owner, zone, cluster, migrateAllowed);
         }
     }
 
-    private void checkUnmanagedNicAndNetworkForImport(UnmanagedInstanceTO.Nic nic, Network network, final DataCenter zone, final Account owner, final boolean autoAssign) throws ServerApiException {
-        if (nic == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve NIC details during VM import"));
-        }
-        if (network == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import", nic.getNicId()));
-        }
+    private void checkUnmanagedNicAndNetworkForImport(String instanceName, UnmanagedInstanceTO.Nic nic, Network network, final DataCenter zone, final Account owner, final boolean autoAssign, Hypervisor.HypervisorType hypervisorType) throws ServerApiException {
+        basicNetworkChecks(instanceName, nic, network);
         if (network.getDataCenterId() != zone.getId()) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network(ID: %s) for nic(ID: %s) belongs to a different zone than VM to be imported", network.getUuid(), nic.getNicId()));
         }
@@ -582,43 +654,45 @@
         if (!autoAssign && network.getGuestType().equals(Network.GuestType.Isolated)) {
             return;
         }
+        checksOnlyNeededForVmware(nic, network, hypervisorType);
+    }
 
-        String networkBroadcastUri = network.getBroadcastUri() == null ? null : network.getBroadcastUri().toString();
-        if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() == null &&
-                (StringUtils.isEmpty(networkBroadcastUri) ||
-                        !networkBroadcastUri.equals(String.format("vlan://%d", nic.getVlan())))) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) vlan://%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan()));
-        }
-        String pvLanType = nic.getPvlanType() == null ? "" : nic.getPvlanType().toLowerCase().substring(0, 1);
-        if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() != null && nic.getPvlan() != 0 &&
-                (StringUtils.isEmpty(network.getBroadcastUri().toString()) ||
-                        !networkBroadcastUri.equals(String.format("pvlan://%d-%s%d", nic.getVlan(), pvLanType, nic.getPvlan())))) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("PVLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) pvlan://%d-%s%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan(), pvLanType, nic.getPvlan()));
+    private void checksOnlyNeededForVmware(UnmanagedInstanceTO.Nic nic, Network network, final Hypervisor.HypervisorType hypervisorType) {
+        if (hypervisorType == Hypervisor.HypervisorType.VMware) {
+            String networkBroadcastUri = network.getBroadcastUri() == null ? null : network.getBroadcastUri().toString();
+            if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() == null &&
+                    (StringUtils.isEmpty(networkBroadcastUri) ||
+                            !networkBroadcastUri.equals(String.format("vlan://%d", nic.getVlan())))) {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) vlan://%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan()));
+            }
+            String pvLanType = nic.getPvlanType() == null ? "" : nic.getPvlanType().toLowerCase().substring(0, 1);
+            if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() != null && nic.getPvlan() != 0 &&
+                    (StringUtils.isEmpty(networkBroadcastUri) || !String.format("pvlan://%d-%s%d", nic.getVlan(), pvLanType, nic.getPvlan()).equals(networkBroadcastUri))) {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("PVLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) pvlan://%d-%s%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan(), pvLanType, nic.getPvlan()));
+            }
         }
     }
 
-    private void checkUnmanagedNicAndNetworkHostnameForImport(UnmanagedInstanceTO.Nic nic, Network network, final String hostName) throws ServerApiException {
+    private void basicNetworkChecks(String instanceName, UnmanagedInstanceTO.Nic nic, Network network) {
         if (nic == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve NIC details during VM import"));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve the NIC details used by VM [%s] from VMware. Please check if this VM have NICs in VMWare.", instanceName));
         }
         if (network == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import", nic.getNicId()));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import.", nic.getNicId()));
         }
+    }
+
+    private void checkUnmanagedNicAndNetworkHostnameForImport(String instanceName, UnmanagedInstanceTO.Nic nic, Network network, final String hostName) throws ServerApiException {
+        basicNetworkChecks(instanceName, nic, network);
         // Check for duplicate hostname in network, get all vms hostNames in the network
         List<String> hostNames = vmDao.listDistinctHostNames(network.getId());
         if (CollectionUtils.isNotEmpty(hostNames) && hostNames.contains(hostName)) {
-            throw new InvalidParameterValueException("The vm with hostName " + hostName + " already exists in the network domain: " + network.getNetworkDomain() + "; network="
-                    + network);
+            throw new InvalidParameterValueException(String.format("VM with Name [%s] already exists in the network [%s] domain [%s]. Cannot import another VM with the same name. Pleasy try again with a different name.", hostName, network, network.getNetworkDomain()));
         }
     }
 
-    private void checkUnmanagedNicIpAndNetworkForImport(UnmanagedInstanceTO.Nic nic, Network network, final Network.IpAddresses ipAddresses) throws ServerApiException {
-        if (nic == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve NIC details during VM import"));
-        }
-        if (network == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import", nic.getNicId()));
-        }
+    private void checkUnmanagedNicIpAndNetworkForImport(String instanceName, UnmanagedInstanceTO.Nic nic, Network network, final Network.IpAddresses ipAddresses) throws ServerApiException {
+        basicNetworkChecks(instanceName, nic, network);
         // Check IP is assigned for non L2 networks
         if (!network.getGuestType().equals(Network.GuestType.L2) && (ipAddresses == null || StringUtils.isEmpty(ipAddresses.getIp4Address()))) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC(ID: %s) needs a valid IP address for it to be associated with network(ID: %s). %s parameter of API can be used for this", nic.getNicId(), network.getUuid(), ApiConstants.NIC_IP_ADDRESS_LIST));
@@ -632,10 +706,13 @@
         }
     }
 
-    private Map<String, Long> getUnmanagedNicNetworkMap(List<UnmanagedInstanceTO.Nic> nics, final Map<String, Long> callerNicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap, final DataCenter zone, final String hostName, final Account owner) throws ServerApiException {
+    private Map<String, Long> getUnmanagedNicNetworkMap(String instanceName, List<UnmanagedInstanceTO.Nic> nics, final Map<String, Long> callerNicNetworkMap,
+                                                        final Map<String, Network.IpAddresses> callerNicIpAddressMap, final DataCenter zone, final String hostName,
+                                                        final Account owner, Hypervisor.HypervisorType hypervisorType) throws ServerApiException {
         Map<String, Long> nicNetworkMap = new HashMap<>();
         String nicAdapter = null;
-        for (UnmanagedInstanceTO.Nic nic : nics) {
+        for (int i = 0; i < nics.size(); i++) {
+            UnmanagedInstanceTO.Nic nic = nics.get(i);
             if (StringUtils.isEmpty(nicAdapter)) {
                 nicAdapter = nic.getAdapterType();
             } else {
@@ -657,22 +734,27 @@
                             continue;
                         }
                         try {
-                            checkUnmanagedNicAndNetworkForImport(nic, networkVO, zone, owner, true);
+                            checkUnmanagedNicAndNetworkForImport(instanceName, nic, networkVO, zone, owner, true, hypervisorType);
                             network = networkVO;
                         } catch (Exception e) {
+                            LOGGER.error(String.format("Error when checking NIC [%s] of unmanaged instance to import due to [%s].", nic.getNicId(), e.getMessage()), e);
                         }
                         if (network != null) {
-                            checkUnmanagedNicAndNetworkHostnameForImport(nic, network, hostName);
-                            checkUnmanagedNicIpAndNetworkForImport(nic, network, ipAddresses);
+                            checkUnmanagedNicAndNetworkHostnameForImport(instanceName, nic, network, hostName);
+                            checkUnmanagedNicIpAndNetworkForImport(instanceName, nic, network, ipAddresses);
                             break;
                         }
                     }
                 }
             } else {
                 network = networkDao.findById(callerNicNetworkMap.get(nic.getNicId()));
-                checkUnmanagedNicAndNetworkForImport(nic, network, zone, owner, false);
-                checkUnmanagedNicAndNetworkHostnameForImport(nic, network, hostName);
-                checkUnmanagedNicIpAndNetworkForImport(nic, network, ipAddresses);
+                boolean autoImport = false;
+                if (hypervisorType == Hypervisor.HypervisorType.KVM) {
+                    autoImport = ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equalsIgnoreCase("auto");
+                }
+                checkUnmanagedNicAndNetworkForImport(instanceName, nic, network, zone, owner, autoImport, hypervisorType);
+                checkUnmanagedNicAndNetworkHostnameForImport(instanceName, nic, network, hostName);
+                checkUnmanagedNicIpAndNetworkForImport(instanceName, nic, network, ipAddresses);
             }
             if (network == null) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Suitable network for nic(ID: %s) not found during VM import", nic.getNicId()));
@@ -682,13 +764,88 @@
         return nicNetworkMap;
     }
 
+    private Pair<DiskProfile, StoragePool> importExternalDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, DeployDestination dest, DiskOffering diskOffering,
+                                                      Volume.Type type, VirtualMachineTemplate template,Long deviceId, String remoteUrl, String username, String password,
+                                                      String tmpPath, DiskProfile diskProfile) {
+        final String path = StringUtils.isEmpty(disk.getDatastorePath()) ? disk.getImagePath() : disk.getDatastorePath();
+        String chainInfo = disk.getChainInfo();
+        if (StringUtils.isEmpty(chainInfo)) {
+            VirtualMachineDiskInfo diskInfo = new VirtualMachineDiskInfo();
+            diskInfo.setDiskDeviceBusName(String.format("%s%d:%d", disk.getController(), disk.getControllerUnit(), disk.getPosition()));
+            diskInfo.setDiskChain(new String[]{disk.getImagePath()});
+            chainInfo = gson.toJson(diskInfo);
+        }
+        Map<Volume, StoragePool> storage = dest.getStorageForDisks();
+        Volume volume = volumeDao.findById(diskProfile.getVolumeId());
+        StoragePool storagePool = storage.get(volume);
+        CopyRemoteVolumeCommand copyRemoteVolumeCommand = new CopyRemoteVolumeCommand();
+        copyRemoteVolumeCommand.setRemoteIp(remoteUrl);
+        copyRemoteVolumeCommand.setUsername(username);
+        copyRemoteVolumeCommand.setPassword(password);
+        copyRemoteVolumeCommand.setSrcFile(path);
+        StorageFilerTO storageTO = new StorageFilerTO(storagePool);
+        copyRemoteVolumeCommand.setStorageFilerTO(storageTO);
+        if(tmpPath == null || tmpPath.length() < 1) {
+            tmpPath = "/tmp/";
+        } else {
+            // Add / if path doesn't end with /
+            if(tmpPath.charAt(tmpPath.length() - 1) != '/') {
+                tmpPath += "/";
+            }
+        }
+        copyRemoteVolumeCommand.setTempPath(tmpPath);
+        Answer answer = agentManager.easySend(dest.getHost().getId(), copyRemoteVolumeCommand);
+        if (!(answer instanceof CopyRemoteVolumeAnswer)) {
+            throw new CloudRuntimeException("Error while copying volume");
+        }
+        CopyRemoteVolumeAnswer copyRemoteVolumeAnswer = (CopyRemoteVolumeAnswer) answer;
+        if(!copyRemoteVolumeAnswer.getResult()) {
+            throw new CloudRuntimeException("Error while copying volume");
+        }
+        diskProfile.setSize(copyRemoteVolumeAnswer.getSize());
+        DiskProfile profile = volumeManager.updateImportedVolume(type, diskOffering, vm, template, deviceId,
+                storagePool.getId(), copyRemoteVolumeAnswer.getFilename(), chainInfo, diskProfile);
+
+        return new Pair<>(profile, storagePool);
+    }
+
+    private Pair<DiskProfile, StoragePool> importKVMLocalDisk(VirtualMachine vm, DiskOffering diskOffering,
+                                                              Volume.Type type, VirtualMachineTemplate template,
+                                                              Long deviceId, Long hostId, String diskPath, DiskProfile diskProfile) {
+        List<StoragePoolVO> storagePools = primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(hostId, null);
+
+        if(storagePools.size() < 1) {
+            throw new CloudRuntimeException("Local Storage not found for host");
+        }
+
+        StoragePool storagePool = storagePools.get(0);
+
+        DiskProfile profile = volumeManager.updateImportedVolume(type, diskOffering, vm, template, deviceId,
+                storagePool.getId(), diskPath, null, diskProfile);
+
+        return new Pair<>(profile, storagePool);
+    }
+
+
+    private Pair<DiskProfile, StoragePool> importKVMSharedDisk(VirtualMachine vm, DiskOffering diskOffering,
+                                                              Volume.Type type, VirtualMachineTemplate template,
+                                                              Long deviceId, Long poolId, String diskPath, DiskProfile diskProfile) {
+        StoragePool storagePool = primaryDataStoreDao.findById(poolId);
+
+        DiskProfile profile = volumeManager.updateImportedVolume(type, diskOffering, vm, template, deviceId,
+                poolId, diskPath, null, diskProfile);
+
+        return new Pair<>(profile, storagePool);
+    }
+
+
     private Pair<DiskProfile, StoragePool> importDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering,
                                                       Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template,
                                                       Account owner, Long deviceId) {
         final DataCenter zone = dataCenterDao.findById(vm.getDataCenterId());
         final String path = StringUtils.isEmpty(disk.getFileBaseName()) ? disk.getImagePath() : disk.getFileBaseName();
         String chainInfo = disk.getChainInfo();
-        if (StringUtils.isEmpty(chainInfo)) {
+        if (vm.getHypervisorType() == Hypervisor.HypervisorType.VMware && StringUtils.isEmpty(chainInfo)) {
             VirtualMachineDiskInfo diskInfo = new VirtualMachineDiskInfo();
             diskInfo.setDiskDeviceBusName(String.format("%s%d:%d", disk.getController(), disk.getControllerUnit(), disk.getPosition()));
             diskInfo.setDiskChain(new String[]{disk.getImagePath()});
@@ -702,7 +859,8 @@
     }
 
     private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, int deviceId, boolean isDefaultNic, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
-        Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, forced);
+        DataCenterVO dataCenterVO = dataCenterDao.findById(network.getDataCenterId());
+        Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, dataCenterVO, forced);
         if (result == null) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC ID: %s import failed", nic.getNicId()));
         }
@@ -741,6 +899,7 @@
         if (!hostSupportsServiceOffering(sourceHost, serviceOffering)) {
             LOGGER.debug(String.format("VM %s needs to be migrated", vm.getUuid()));
             final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, template, serviceOffering, owner, null);
+            profile.setServiceOffering(serviceOfferingDao.findById(vm.getId(), serviceOffering.getId()));
             DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList();
             excludeList.addHost(sourceHost.getId());
             final DataCenterDeployment plan = new DataCenterDeployment(sourceHost.getDataCenterId(), sourceHost.getPodId(), sourceHost.getClusterId(), null, null, null);
@@ -748,14 +907,10 @@
             try {
                 dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null);
             } catch (Exception e) {
-                LOGGER.warn(String.format("VM import failed for unmanaged vm: %s during vm migration, finding deployment destination", vm.getInstanceName()), e);
+                String errorMsg = String.format("VM import failed for Unmanaged VM [%s] during VM migration, cannot find deployment destination due to [%s].", vm.getInstanceName(), e.getMessage());
+                LOGGER.warn(errorMsg, e);
                 cleanupFailedImportVM(vm);
-                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during vm migration, finding deployment destination", vm.getInstanceName()));
-            }
-            if (dest != null) {
-                if (LOGGER.isDebugEnabled()) {
-                    LOGGER.debug(" Found " + dest + " for migrating the vm to");
-                }
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg);
             }
             if (dest == null) {
                 cleanupFailedImportVM(vm);
@@ -772,9 +927,10 @@
                 }
                 vm = userVmManager.getUserVm(vm.getId());
             } catch (Exception e) {
-                LOGGER.error(String.format("VM import failed for unmanaged vm: %s during vm migration", vm.getInstanceName()), e);
+                String errorMsg = String.format("VM import failed for Unmanaged VM [%s] during VM migration due to [%s].", vm.getInstanceName(), e.getMessage());
+                LOGGER.error(errorMsg, e);
                 cleanupFailedImportVM(vm);
-                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during vm migration. %s", userVm.getInstanceName(), e.getMessage()));
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg);
             }
         }
         for (Pair<DiskProfile, StoragePool> diskProfileStoragePool : diskProfileStoragePoolList) {
@@ -808,7 +964,7 @@
                 for (StoragePool pool : storagePools) {
                     if (diskProfileStoragePool.second().getId() != pool.getId() &&
                             storagePoolSupportsDiskOffering(pool, dOffering)
-                            ) {
+                    ) {
                         storagePool = pool;
                         break;
                     }
@@ -820,7 +976,7 @@
                 for (StoragePool pool : storagePools) {
                     if (diskProfileStoragePool.second().getId() != pool.getId() &&
                             storagePoolSupportsDiskOffering(pool, dOffering)
-                            ) {
+                    ) {
                         storagePool = pool;
                         break;
                     }
@@ -860,9 +1016,9 @@
 
     private void publishVMUsageUpdateResourceCount(final UserVm userVm, ServiceOfferingVO serviceOfferingVO) {
         if (userVm == null || serviceOfferingVO == null) {
-            LOGGER.error("Failed to publish usage records during VM import");
+            LOGGER.error(String.format("Failed to publish usage records during VM import because VM [%s] or ServiceOffering [%s] is null.", userVm, serviceOfferingVO));
             cleanupFailedImportVM(userVm);
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm during publishing usage records"));
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "VM import failed for Unmanaged VM during publishing Usage Records.");
         }
         try {
             if (!serviceOfferingVO.isDynamic()) {
@@ -877,13 +1033,13 @@
                         userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplayVm());
             }
         } catch (Exception e) {
-            LOGGER.error(String.format("Failed to publish usage records during VM import for unmanaged vm %s", userVm.getInstanceName()), e);
+            LOGGER.error(String.format("Failed to publish usage records during VM import for unmanaged VM [%s] due to [%s].", userVm.getInstanceName(), e.getMessage()), e);
             cleanupFailedImportVM(userVm);
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm %s during publishing usage records", userVm.getInstanceName()));
         }
         resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.user_vm, userVm.isDisplayVm());
-        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.cpu, userVm.isDisplayVm(), new Long(serviceOfferingVO.getCpu()));
-        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.memory, userVm.isDisplayVm(), new Long(serviceOfferingVO.getRamSize()));
+        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.cpu, userVm.isDisplayVm(), Long.valueOf(serviceOfferingVO.getCpu()));
+        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.memory, userVm.isDisplayVm(), Long.valueOf(serviceOfferingVO.getRamSize()));
         // Save usage event and update resource count for user vm volumes
         List<VolumeVO> volumes = volumeDao.findByInstance(userVm.getId());
         for (VolumeVO volume : volumes) {
@@ -913,19 +1069,21 @@
                                                 final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
                                                 final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap,
                                                 final Map<String, Long> nicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap,
-                                                final Map<String, String> details, final boolean migrateAllowed, final boolean forced) {
+                                                final Map<String, String> details, final boolean migrateAllowed, final boolean forced, final boolean isImportUnmanagedFromSameHypervisor) {
+        LOGGER.debug(LogUtils.logGsonWithoutException("Trying to import VM [%s] with name [%s], in zone [%s], cluster [%s], and host [%s], using template [%s], service offering [%s], disks map [%s], NICs map [%s] and details [%s].",
+                unmanagedInstance, instanceName, zone, cluster, host, template, serviceOffering, dataDiskOfferingMap, nicNetworkMap, details));
         UserVm userVm = null;
-
         ServiceOfferingVO validatedServiceOffering = null;
         try {
-            validatedServiceOffering = getUnmanagedInstanceServiceOffering(unmanagedInstance, serviceOffering, owner, zone, details);
+            validatedServiceOffering = getUnmanagedInstanceServiceOffering(unmanagedInstance, serviceOffering, owner, zone, details, cluster.getHypervisorType());
         } catch (Exception e) {
-            LOGGER.error("Service offering for VM import not compatible", e);
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import VM: %s. %s", unmanagedInstance.getName(), StringUtils.defaultString(e.getMessage())));
+            String errorMsg = String.format("Failed to import Unmanaged VM [%s] because the service offering [%s] is not compatible due to [%s].", unmanagedInstance.getName(), serviceOffering.getUuid(), StringUtils.defaultIfEmpty(e.getMessage(), ""));
+            LOGGER.error(errorMsg, e);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMsg);
         }
 
         String internalCSName = unmanagedInstance.getInternalCSName();
-        if(StringUtils.isEmpty(internalCSName)){
+        if (StringUtils.isEmpty(internalCSName)) {
             internalCSName = instanceName;
         }
         Map<String, String> allDetails = new HashMap<>(details);
@@ -937,7 +1095,7 @@
             }
         }
 
-        if (!migrateAllowed && !hostSupportsServiceOffering(host, validatedServiceOffering)) {
+        if (!migrateAllowed && host != null && !hostSupportsServiceOffering(host, validatedServiceOffering)) {
             throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with host: %s of unmanaged VM: %s", serviceOffering.getUuid(), host.getUuid(), instanceName));
         }
         // Check disks and supplied disk offerings
@@ -951,11 +1109,23 @@
         if (rootDisk == null || StringUtils.isEmpty(rootDisk.getController())) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed. Unable to retrieve root disk details for VM: %s ", instanceName));
         }
+        if (cluster.getHypervisorType() == Hypervisor.HypervisorType.KVM) {
+            Long rootDiskOfferingId = validatedServiceOffering.getDiskOfferingId();
+            DiskOffering rootDiskOffering = diskOfferingDao.findById(rootDiskOfferingId);
+            if ((rootDisk.getCapacity() == null || rootDisk.getCapacity() <= 0) && rootDiskOffering != null) {
+                rootDisk.setCapacity(rootDiskOffering.getDiskSize());
+            }
+        }
         allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController());
+        if (cluster.getHypervisorType() == Hypervisor.HypervisorType.KVM && isImportUnmanagedFromSameHypervisor) {
+            long size = Double.valueOf(Math.ceil((double)rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB)).longValue();
+            allDetails.put(VmDetailConstants.ROOT_DISK_SIZE, String.valueOf(size));
+        }
+
         try {
-            checkUnmanagedDiskAndOfferingForImport(rootDisk, null, validatedServiceOffering, owner, zone, cluster, migrateAllowed);
+            checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), rootDisk, null, validatedServiceOffering, owner, zone, cluster, migrateAllowed);
             if (CollectionUtils.isNotEmpty(dataDisks)) { // Data disk(s) present
-                checkUnmanagedDiskAndOfferingForImport(dataDisks, dataDiskOfferingMap, owner, zone, cluster, migrateAllowed);
+                checkUnmanagedDiskAndOfferingForImport(unmanagedInstance.getName(), dataDisks, dataDiskOfferingMap, owner, zone, cluster, migrateAllowed);
                 allDetails.put(VmDetailConstants.DATA_DISK_CONTROLLER, dataDisks.get(0).getController());
             }
             resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.volume, unmanagedInstanceDisks.size());
@@ -965,23 +1135,31 @@
         }
         // Check NICs and supplied networks
         Map<String, Network.IpAddresses> nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap);
-        Map<String, Long> allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner);
+        Map<String, Long> allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, cluster.getHypervisorType());
         if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) {
             allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType());
         }
+
+        if (StringUtils.isNotEmpty(unmanagedInstance.getVncPassword())) {
+            allDetails.put(VmDetailConstants.KVM_VNC_PASSWORD, unmanagedInstance.getVncPassword());
+        }
+
         VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff;
         if (unmanagedInstance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOn)) {
             powerState = VirtualMachine.PowerState.PowerOn;
         }
+
         try {
             userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner,
                     null, caller, true, null, owner.getAccountId(), userId,
                     validatedServiceOffering, null, hostName,
-                    cluster.getHypervisorType(), allDetails, powerState);
+                    cluster.getHypervisorType(), allDetails, powerState, null);
         } catch (InsufficientCapacityException ice) {
-            LOGGER.error(String.format("Failed to import vm name: %s", instanceName), ice);
-            throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ice.getMessage());
+            String errorMsg = String.format("Failed to import VM [%s] due to [%s].", instanceName, ice.getMessage());
+            LOGGER.error(errorMsg, ice);
+            throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, errorMsg);
         }
+
         if (userVm == null) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName));
         }
@@ -991,17 +1169,16 @@
                 throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId()));
             }
             Long minIops = null;
-            if (details.containsKey("minIops")) {
-                minIops = Long.parseLong(details.get("minIops"));
+            if (details.containsKey(MIN_IOPS)) {
+                minIops = Long.parseLong(details.get(MIN_IOPS));
             }
             Long maxIops = null;
-            if (details.containsKey("maxIops")) {
-                maxIops = Long.parseLong(details.get("maxIops"));
+            if (details.containsKey(MAX_IOPS)) {
+                maxIops = Long.parseLong(details.get(MAX_IOPS));
             }
             DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId());
             diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()),
-                    (rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB), minIops, maxIops,
-                    template, owner, null));
+                    rootDisk.getCapacity(), minIops, maxIops, template, owner, null));
             long deviceId = 1L;
             for (UnmanagedInstanceTO.Disk disk : dataDisks) {
                 if (disk.getCapacity() == null || disk.getCapacity() == 0) {
@@ -1009,7 +1186,7 @@
                 }
                 DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId()));
                 diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()),
-                        (disk.getCapacity() / Resource.ResourceType.bytesToGiB), offering.getMinIops(), offering.getMaxIops(),
+                        disk.getCapacity(), offering.getMinIops(), offering.getMaxIops(),
                         template, owner, deviceId));
                 deviceId++;
             }
@@ -1023,7 +1200,7 @@
             for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) {
                 Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId()));
                 Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId());
-                importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, forced);
+                importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced);
                 nicIndex++;
             }
         } catch (Exception e) {
@@ -1048,7 +1225,7 @@
         command.setInstanceName(instanceName);
         command.setManagedInstancesNames(managedVms);
         Answer answer = agentManager.easySend(host.getId(), command);
-        if (!(answer instanceof GetUnmanagedInstancesAnswer)) {
+        if (!(answer instanceof GetUnmanagedInstancesAnswer) || !answer.getResult()) {
             return unmanagedInstances;
         }
         GetUnmanagedInstancesAnswer unmanagedInstancesAnswer = (GetUnmanagedInstancesAnswer) answer;
@@ -1056,23 +1233,29 @@
         return unmanagedInstances;
     }
 
-    @Override
-    public ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd) {
+    protected Cluster basicAccessChecks(Long clusterId) {
         final Account caller = CallContext.current().getCallingAccount();
         if (caller.getType() != Account.Type.ADMIN) {
-            throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid()));
+            throw new PermissionDeniedException(String.format("Cannot perform this operation, caller account [%s] is not ROOT Admin.", caller.getUuid()));
         }
-        final Long clusterId = cmd.getClusterId();
         if (clusterId == null) {
-            throw new InvalidParameterValueException(String.format("Cluster ID cannot be null"));
+            throw new InvalidParameterValueException("Cluster ID cannot be null.");
         }
         final Cluster cluster = clusterDao.findById(clusterId);
         if (cluster == null) {
-            throw new InvalidParameterValueException(String.format("Cluster ID: %d cannot be found", clusterId));
+            throw new InvalidParameterValueException(String.format("Cluster with ID [%d] cannot be found.", clusterId));
         }
-        if (cluster.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
-            throw new InvalidParameterValueException(String.format("VM ingestion is currently not supported for hypervisor: %s", cluster.getHypervisorType().toString()));
+
+        if (!importUnmanagedInstancesSupportedHypervisors.contains(cluster.getHypervisorType())) {
+            throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor [%s].", cluster.getHypervisorType().toString()));
         }
+        return cluster;
+    }
+
+    @Override
+    public ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd) {
+        Long clusterId = cmd.getClusterId();
+        Cluster cluster = basicAccessChecks(clusterId);
         String keyword = cmd.getKeyword();
         if (StringUtils.isNotEmpty(keyword)) {
             keyword = keyword.toLowerCase();
@@ -1091,7 +1274,7 @@
                         !instance.getName().toLowerCase().contains(keyword)) {
                     continue;
                 }
-                responses.add(createUnmanagedInstanceResponse(instance, cluster, host));
+                responses.add(responseGenerator.createUnmanagedInstanceResponse(instance, cluster, host));
             }
         }
         ListResponse<UnmanagedInstanceResponse> listResponses = new ListResponse<>();
@@ -1101,84 +1284,36 @@
 
     @Override
     public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) {
-        final Account caller = CallContext.current().getCallingAccount();
-        if (caller.getType() != Account.Type.ADMIN) {
-            throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid()));
-        }
-        final Long clusterId = cmd.getClusterId();
-        final Cluster cluster = clusterDao.findById(clusterId);
-        if (cluster == null) {
-            throw new InvalidParameterValueException(String.format("Cluster ID: %d cannot be found", clusterId));
-        }
-        if (cluster.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
-            throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor: %s", cluster.getHypervisorType().toString()));
-        }
-        final DataCenter zone = dataCenterDao.findById(cluster.getDataCenterId());
+        return baseImportInstance(cmd);
+    }
+
+    /**
+     * Base logic for import virtual machines (unmanaged, external) into CloudStack
+     * @param cmd importVM or importUnmanagedInstance command
+     * @return imported user vm
+     */
+    private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) {
+        basicParametersCheckForImportInstance(cmd.getName(), cmd.getDomainId(), cmd.getAccountName());
+
         final String instanceName = cmd.getName();
-        if (StringUtils.isEmpty(instanceName)) {
-            throw new InvalidParameterValueException("Instance name cannot be empty");
-        }
-        if (cmd.getDomainId() != null && StringUtils.isEmpty(cmd.getAccountName())) {
-            throw new InvalidParameterValueException(String.format("%s parameter must be specified with %s parameter", ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT));
-        }
+        Long clusterId = cmd.getClusterId();
+        Cluster cluster = basicAccessChecks(clusterId);
+
+        final Account caller = CallContext.current().getCallingAccount();
+        final DataCenter zone = dataCenterDao.findById(cluster.getDataCenterId());
         final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
-        long userId = CallContext.current().getCallingUserId();
-        List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
-        if (CollectionUtils.isNotEmpty(userVOs)) {
-            userId = userVOs.get(0).getId();
-        }
-        VMTemplateVO template;
-        final Long templateId = cmd.getTemplateId();
-        if (templateId == null) {
-            template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
-            if (template == null) {
-                template = createDefaultDummyVmImportTemplate();
-                if (template == null) {
-                    throw new InvalidParameterValueException(String.format("Default VM import template with unique name: %s for hypervisor: %s cannot be created. Please use templateid parameter for import", VM_IMPORT_DEFAULT_TEMPLATE_NAME, cluster.getHypervisorType().toString()));
-                }
-            }
-        } else {
-            template = templateDao.findById(templateId);
-        }
-        if (template == null) {
-            throw new InvalidParameterValueException(String.format("Template ID: %d cannot be found", templateId));
-        }
-        final Long serviceOfferingId = cmd.getServiceOfferingId();
-        final ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
-        if (serviceOffering == null) {
-            throw new InvalidParameterValueException(String.format("Service offering ID: %d cannot be found", serviceOfferingId));
-        }
-        accountService.checkAccess(owner, serviceOffering, zone);
-        try {
-            resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1);
-        } catch (ResourceAllocationException e) {
-            LOGGER.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e);
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage())));
-        }
-        String displayName = cmd.getDisplayName();
-        if (StringUtils.isEmpty(displayName)) {
-            displayName = instanceName;
-        }
-        String hostName = cmd.getHostName();
-        if (StringUtils.isEmpty(hostName)) {
-            if (!NetUtils.verifyDomainNameLabel(instanceName, true)) {
-                throw new InvalidParameterValueException("Please provide hostname for the VM. VM name contains unsupported characters for it to be used as hostname");
-            }
-            hostName = instanceName;
-        }
-        if (!NetUtils.verifyDomainNameLabel(hostName, true)) {
-            throw new InvalidParameterValueException("Invalid VM hostname. VM hostname can contain ASCII letters 'a' through 'z', the digits '0' through '9', "
-                    + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit");
-        }
-        if (cluster.getHypervisorType().equals(Hypervisor.HypervisorType.VMware) &&
-                Boolean.parseBoolean(configurationDao.getValue(Config.SetVmInternalNameUsingDisplayName.key()))) {
-            // If global config vm.instancename.flag is set to true, then CS will set guest VM's name as it appears on the hypervisor, to its hostname.
-            // In case of VMware since VM name must be unique within a DC, check if VM with the same hostname already exists in the zone.
-            VMInstanceVO vmByHostName = vmDao.findVMByHostNameInZone(hostName, zone.getId());
-            if (vmByHostName != null && vmByHostName.getState() != VirtualMachine.State.Expunging) {
-                throw new InvalidParameterValueException(String.format("Failed to import VM: %s. There already exists a VM by the hostname: %s in zone: %s", instanceName, hostName, zone.getUuid()));
-            }
-        }
+        long userId = getUserIdForImportInstance(owner);
+
+        VMTemplateVO template = getTemplateForImportInstance(cmd.getTemplateId(), cluster.getHypervisorType());
+        ServiceOfferingVO serviceOffering = getServiceOfferingForImportInstance(cmd.getServiceOfferingId(), owner, zone);
+
+        checkResourceLimitForImportInstance(owner);
+
+        String displayName = getDisplayNameForImportInstance(cmd.getDisplayName(), instanceName);
+        String hostName = getHostNameForImportInstance(cmd.getHostName(), cluster.getHypervisorType(), instanceName, displayName);
+
+        checkVmwareInstanceNameForImportInstance(cluster.getHypervisorType(), instanceName, hostName, zone);
+
         final Map<String, Long> nicNetworkMap = cmd.getNicNetworkList();
         final Map<String, Network.IpAddresses> nicIpAddressMap = cmd.getNicIpAddressList();
         final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
@@ -1189,8 +1324,155 @@
         List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
         List<String> managedVms = new ArrayList<>(additionalNameFilters);
         managedVms.addAll(getHostsManagedVms(hosts));
+
+        ActionEventUtils.onStartedActionEvent(userId, owner.getId(), EventTypes.EVENT_VM_IMPORT,
+                cmd.getEventDescription(), null, null, true, 0);
+
+        if (cmd instanceof ImportVmCmd) {
+            ImportVmCmd importVmCmd = (ImportVmCmd) cmd;
+            if (StringUtils.isBlank(importVmCmd.getImportSource())) {
+                throw new CloudRuntimeException("Please provide an import source for importing the VM");
+            }
+            String source = importVmCmd.getImportSource().toUpperCase();
+            ImportSource importSource = Enum.valueOf(ImportSource.class, source);
+            if (ImportSource.VMWARE == importSource) {
+                userVm = importUnmanagedInstanceFromVmwareToKvm(zone, cluster,
+                        template, instanceName, displayName, hostName, caller, owner, userId,
+                        serviceOffering, dataDiskOfferingMap,
+                        nicNetworkMap, nicIpAddressMap,
+                        details, importVmCmd, forced);
+            }
+        } else {
+            if (List.of(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM).contains(cluster.getHypervisorType())) {
+                userVm = importUnmanagedInstanceFromHypervisor(zone, cluster, hosts, additionalNameFilters,
+                        template, instanceName, displayName, hostName, caller, owner, userId,
+                        serviceOffering, dataDiskOfferingMap,
+                        nicNetworkMap, nicIpAddressMap,
+                        details, cmd.getMigrateAllowed(), managedVms, forced);
+            }
+        }
+
+        if (userVm == null) {
+            ActionEventUtils.onCompletedActionEvent(userId, owner.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_IMPORT,
+                    cmd.getEventDescription(), null, null, 0);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to find unmanaged vm with name: %s in cluster: %s", instanceName, cluster.getUuid()));
+        }
+        ActionEventUtils.onCompletedActionEvent(userId, owner.getId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_IMPORT,
+                cmd.getEventDescription(), userVm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0);
+        return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0);
+    }
+
+    private long getUserIdForImportInstance(Account owner) {
+        long userId = CallContext.current().getCallingUserId();
+        List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
+        if (CollectionUtils.isNotEmpty(userVOs)) {
+            userId = userVOs.get(0).getId();
+        }
+        return userId;
+    }
+
+    protected void basicParametersCheckForImportInstance(String name, Long domainId, String accountName) {
+        if (StringUtils.isEmpty(name)) {
+            throw new InvalidParameterValueException("Instance name cannot be empty");
+        }
+        if (domainId != null && StringUtils.isEmpty(accountName)) {
+            throw new InvalidParameterValueException(String.format("%s parameter must be specified with %s parameter", ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT));
+        }
+    }
+
+    private void checkVmwareInstanceNameForImportInstance(Hypervisor.HypervisorType hypervisorType, String instanceName, String hostName, DataCenter zone) {
+        if (hypervisorType.equals(Hypervisor.HypervisorType.VMware) &&
+                Boolean.parseBoolean(configurationDao.getValue(Config.SetVmInternalNameUsingDisplayName.key()))) {
+            // If global config vm.instancename.flag is set to true, then CS will set guest VM's name as it appears on the hypervisor, to its hostname.
+            // In case of VMware since VM name must be unique within a DC, check if VM with the same hostname already exists in the zone.
+            VMInstanceVO vmByHostName = vmDao.findVMByHostNameInZone(hostName, zone.getId());
+            if (vmByHostName != null && vmByHostName.getState() != VirtualMachine.State.Expunging) {
+                throw new InvalidParameterValueException(String.format("Failed to import VM: %s. There already exists a VM by the hostname: %s in zone: %s", instanceName, hostName, zone.getUuid()));
+            }
+        }
+    }
+
+    private String getHostNameForImportInstance(String hostName, Hypervisor.HypervisorType hypervisorType,
+                                                String instanceName, String displayName) {
+        if (StringUtils.isEmpty(hostName)) {
+            hostName = hypervisorType == Hypervisor.HypervisorType.VMware ? instanceName : displayName;
+            if (!NetUtils.verifyDomainNameLabel(hostName, true)) {
+                throw new InvalidParameterValueException("Please provide a valid hostname for the VM. VM name contains unsupported characters that cannot be used as hostname.");
+            }
+        }
+        if (!NetUtils.verifyDomainNameLabel(hostName, true)) {
+            throw new InvalidParameterValueException("Invalid VM hostname. VM hostname can contain ASCII letters 'a' through 'z', the digits '0' through '9', "
+                    + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit");
+        }
+        return hostName;
+    }
+
+    private String getDisplayNameForImportInstance(String displayName, String instanceName) {
+        return StringUtils.isEmpty(displayName) ? instanceName : displayName;
+    }
+
+    private void checkResourceLimitForImportInstance(Account owner) {
+        try {
+            resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1);
+        } catch (ResourceAllocationException e) {
+            LOGGER.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage())));
+        }
+    }
+
+    private ServiceOfferingVO getServiceOfferingForImportInstance(Long serviceOfferingId, Account owner, DataCenter zone) {
+        if (serviceOfferingId == null) {
+            throw new InvalidParameterValueException("Service offering ID cannot be null");
+        }
+        final ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
+        if (serviceOffering == null) {
+            throw new InvalidParameterValueException(String.format("Service offering ID: %d cannot be found", serviceOfferingId));
+        }
+        accountService.checkAccess(owner, serviceOffering, zone);
+        return serviceOffering;
+    }
+
+    protected VMTemplateVO getTemplateForImportInstance(Long templateId, Hypervisor.HypervisorType hypervisorType) {
+        VMTemplateVO template;
+        if (templateId == null) {
+            template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
+            if (template == null) {
+                template = createDefaultDummyVmImportTemplate(false);
+                if (template == null) {
+                    throw new InvalidParameterValueException(String.format("Default VM import template with unique name: %s for hypervisor: %s cannot be created. Please use templateid parameter for import", VM_IMPORT_DEFAULT_TEMPLATE_NAME, hypervisorType.toString()));
+                }
+            }
+        } else {
+            template = templateDao.findById(templateId);
+        }
+        if (template == null) {
+            throw new InvalidParameterValueException(String.format("Template ID: %d cannot be found", templateId));
+        }
+        return template;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VM_IMPORT, eventDescription = "importing VM", async = true)
+    public UserVmResponse importVm(ImportVmCmd cmd) {
+        String source = cmd.getImportSource().toUpperCase();
+        ImportSource importSource = Enum.valueOf(ImportSource.class, source);
+        if (ImportSource.VMWARE == importSource  || ImportSource.UNMANAGED == importSource) {
+            return baseImportInstance(cmd);
+        } else {
+            return importKvmInstance(cmd);
+        }
+    }
+
+    private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cluster,
+                                                         List<HostVO> hosts, List<String> additionalNameFilters,
+                                                         VMTemplateVO template, String instanceName, String displayName,
+                                                         String hostName, Account caller, Account owner, long userId,
+                                                         ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
+                                                         Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
+                                                         Map<String, String> details, Boolean migrateAllowed, List<String> managedVms, boolean forced) {
+        UserVm userVm = null;
         for (HostVO host : hosts) {
-            HashMap<String, UnmanagedInstanceTO> unmanagedInstances = getUnmanagedInstancesForHost(host, cmd.getName(), managedVms);
+            HashMap<String, UnmanagedInstanceTO> unmanagedInstances = getUnmanagedInstancesForHost(host, instanceName, managedVms);
             if (MapUtils.isEmpty(unmanagedInstances)) {
                 continue;
             }
@@ -1203,12 +1485,18 @@
                 if (unmanagedInstance == null) {
                     throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve details for unmanaged VM: %s", name));
                 }
+
+                if (template.getName().equals(VM_IMPORT_DEFAULT_TEMPLATE_NAME) && cluster.getHypervisorType().equals(Hypervisor.HypervisorType.KVM)) {
+                    throw new InvalidParameterValueException("Template is needed and unable to use default template for hypervisor " + host.getHypervisorType().toString());
+                }
+
                 if (template.getName().equals(VM_IMPORT_DEFAULT_TEMPLATE_NAME)) {
                     String osName = unmanagedInstance.getOperatingSystem();
                     GuestOS guestOS = null;
                     if (StringUtils.isNotEmpty(osName)) {
                         guestOS = guestOSDao.findOneByDisplayName(osName);
                     }
+
                     GuestOSHypervisor guestOSHypervisor = null;
                     if (guestOS != null) {
                         guestOSHypervisor = guestOSHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), host.getHypervisorType().toString(), host.getHypervisorVersion());
@@ -1222,23 +1510,336 @@
                         }
                         throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve guest OS details for unmanaged VM: %s with OS name: %s, OS ID: %s for hypervisor: %s version: %s. templateid parameter can be used to assign template for VM", name, osName, unmanagedInstance.getOperatingSystemId(), host.getHypervisorType().toString(), host.getHypervisorVersion()));
                     }
+
                     template.setGuestOSId(guestOSHypervisor.getGuestOsId());
                 }
                 userVm = importVirtualMachineInternal(unmanagedInstance, instanceName, zone, cluster, host,
-                        template, displayName, hostName, caller, owner, userId,
+                        template, displayName, hostName, CallContext.current().getCallingAccount(), owner, userId,
                         serviceOffering, dataDiskOfferingMap,
                         nicNetworkMap, nicIpAddressMap,
-                        details, cmd.getMigrateAllowed(), forced);
+                        details, migrateAllowed, forced, true);
                 break;
             }
             if (userVm != null) {
                 break;
             }
         }
-        if (userVm == null) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to find unmanaged vm with name: %s in cluster: %s", instanceName, cluster.getUuid()));
+        return userVm;
+    }
+
+    private UnmanagedInstanceTO cloneSourceVmwareUnmanagedInstance(String vcenter, String datacenterName, String username, String password, String clusterName, String sourceHostName, String sourceVM) {
+        HypervisorGuru vmwareGuru = hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware);
+
+        Map<String, String> params = createParamsForTemplateFromVmwareVmMigration(vcenter, datacenterName,
+                username, password, clusterName, sourceHostName, sourceVM);
+
+        return vmwareGuru.cloneHypervisorVMOutOfBand(sourceHostName, sourceVM, params);
+    }
+
+    protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster destinationCluster, VMTemplateVO template,
+                                                          String sourceVM, String displayName, String hostName,
+                                                          Account caller, Account owner, long userId,
+                                                          ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
+                                                          Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
+                                                          Map<String, String> details, ImportVmCmd cmd, boolean forced) {
+        Long existingVcenterId = cmd.getExistingVcenterId();
+        String vcenter = cmd.getVcenter();
+        String datacenterName = cmd.getDatacenterName();
+        String username = cmd.getUsername();
+        String password = cmd.getPassword();
+        String clusterName = cmd.getClusterName();
+        String sourceHostName = cmd.getHostIp();
+        Long convertInstanceHostId = cmd.getConvertInstanceHostId();
+        Long convertStoragePoolId = cmd.getConvertStoragePoolId();
+
+        if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    "Please provide an existing vCenter ID or a vCenter IP/Name, parameters are mutually exclusive");
         }
-        return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0);
+        if (existingVcenterId == null && StringUtils.isAnyBlank(vcenter, datacenterName, username, password)) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    "Please set all the information for a vCenter IP/Name, datacenter, username and password");
+        }
+
+        if (existingVcenterId != null) {
+            VmwareDatacenterVO existingDC = vmwareDatacenterDao.findById(existingVcenterId);
+            if (existingDC == null) {
+                String err = String.format("Cannot find any existing Vmware DC with ID %s", existingVcenterId);
+                LOGGER.error(err);
+                throw new CloudRuntimeException(err);
+            }
+            vcenter = existingDC.getVcenterHost();
+            datacenterName = existingDC.getVmwareDatacenterName();
+            username = existingDC.getUser();
+            password = existingDC.getPassword();
+        }
+
+        UnmanagedInstanceTO clonedInstance = null;
+        try {
+            String instanceName = getGeneratedInstanceName(owner);
+            clonedInstance = cloneSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password,
+                    clusterName, sourceHostName, sourceVM);
+            checkNetworkingBeforeConvertingVmwareInstance(zone, owner, instanceName, hostName, clonedInstance, nicNetworkMap, nicIpAddressMap, forced);
+            UnmanagedInstanceTO convertedInstance = convertVmwareInstanceToKVM(vcenter, datacenterName, clusterName, username, password,
+                    sourceHostName, clonedInstance, destinationCluster, convertInstanceHostId, convertStoragePoolId);
+            sanitizeConvertedInstance(convertedInstance, clonedInstance);
+            UserVm userVm = importVirtualMachineInternal(convertedInstance, instanceName, zone, destinationCluster, null,
+                    template, displayName, hostName, caller, owner, userId,
+                    serviceOffering, dataDiskOfferingMap,
+                    nicNetworkMap, nicIpAddressMap,
+                    details, false, forced, false);
+            LOGGER.debug(String.format("VM %s imported successfully", sourceVM));
+            return userVm;
+        } catch (CloudRuntimeException e) {
+            LOGGER.error(String.format("Error importing VM: %s", e.getMessage()), e);
+            ActionEventUtils.onCompletedActionEvent(userId, owner.getId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_IMPORT,
+                    cmd.getEventDescription(), null, null, 0);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
+        } finally {
+            removeClonedInstance(vcenter, datacenterName, username, password, sourceHostName, clonedInstance.getName(), sourceVM);
+        }
+    }
+
+    private void checkNetworkingBeforeConvertingVmwareInstance(DataCenter zone, Account owner, String instanceName,
+                                                               String hostName, UnmanagedInstanceTO clonedInstance,
+                                                               Map<String, Long> nicNetworkMap,
+                                                               Map<String, Network.IpAddresses> nicIpAddressMap,
+                                                               boolean forced) {
+        List<UnmanagedInstanceTO.Nic> nics = clonedInstance.getNics();
+        List<Long> networkIds = new ArrayList<>(nicNetworkMap.values());
+        if (nics.size() != networkIds.size()) {
+            String msg = String.format("Different number of nics found on instance %s: %s vs %s nics provided",
+                    clonedInstance.getName(), nics.size(), networkIds.size());
+            LOGGER.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+
+        for (UnmanagedInstanceTO.Nic nic : nics) {
+            Long networkId = nicNetworkMap.get(nic.getNicId());
+            NetworkVO network = networkDao.findById(networkId);
+            if (network == null) {
+                String err = String.format("Cannot find a network with id = %s", networkId);
+                LOGGER.error(err);
+                throw new CloudRuntimeException(err);
+            }
+            Network.IpAddresses ipAddresses = null;
+            if (MapUtils.isNotEmpty(nicIpAddressMap) && nicIpAddressMap.containsKey(nic.getNicId())) {
+                ipAddresses = nicIpAddressMap.get(nic.getNicId());
+            }
+            boolean autoImport = ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equalsIgnoreCase("auto");
+            checkUnmanagedNicAndNetworkMacAddressForImport(network, nic, forced);
+            checkUnmanagedNicAndNetworkForImport(instanceName, nic, network, zone, owner, autoImport, Hypervisor.HypervisorType.KVM);
+            checkUnmanagedNicAndNetworkHostnameForImport(instanceName, nic, network, hostName);
+            checkUnmanagedNicIpAndNetworkForImport(instanceName, nic, network, ipAddresses);
+        }
+    }
+
+    private void checkUnmanagedNicAndNetworkMacAddressForImport(NetworkVO network, UnmanagedInstanceTO.Nic nic, boolean forced) {
+        NicVO existingNic = nicDao.findByNetworkIdAndMacAddress(network.getId(), nic.getMacAddress());
+        if (existingNic != null && !forced) {
+            String err = String.format("NIC with MAC address = %s exists on network with ID = %s and forced flag is disabled",
+                    nic.getMacAddress(), network.getId());
+            LOGGER.error(err);
+            throw new CloudRuntimeException(err);
+        }
+    }
+
+    private String getGeneratedInstanceName(Account owner) {
+        long id = vmDao.getNextInSequence(Long.class, "id");
+        String instanceSuffix = configurationDao.getValue(Config.InstanceName.key());
+        if (instanceSuffix == null) {
+            instanceSuffix = "DEFAULT";
+        }
+        return VirtualMachineName.getVmName(id, owner.getId(), instanceSuffix);
+    }
+
+    private void sanitizeConvertedInstance(UnmanagedInstanceTO convertedInstance, UnmanagedInstanceTO clonedInstance) {
+        convertedInstance.setCpuCores(clonedInstance.getCpuCores());
+        convertedInstance.setCpuSpeed(clonedInstance.getCpuSpeed());
+        convertedInstance.setCpuCoresPerSocket(clonedInstance.getCpuCoresPerSocket());
+        convertedInstance.setMemory(clonedInstance.getMemory());
+        convertedInstance.setPowerState(UnmanagedInstanceTO.PowerState.PowerOff);
+        List<UnmanagedInstanceTO.Disk> convertedInstanceDisks = convertedInstance.getDisks();
+        List<UnmanagedInstanceTO.Disk> clonedInstanceDisks = clonedInstance.getDisks();
+        for (int i = 0; i < convertedInstanceDisks.size(); i++) {
+            UnmanagedInstanceTO.Disk disk = convertedInstanceDisks.get(i);
+            disk.setDiskId(clonedInstanceDisks.get(i).getDiskId());
+        }
+        List<UnmanagedInstanceTO.Nic> convertedInstanceNics = convertedInstance.getNics();
+        List<UnmanagedInstanceTO.Nic> clonedInstanceNics = clonedInstance.getNics();
+        if (CollectionUtils.isEmpty(convertedInstanceNics) && CollectionUtils.isNotEmpty(clonedInstanceNics)) {
+            for (UnmanagedInstanceTO.Nic nic : clonedInstanceNics) {
+                // In case the NICs information is not parsed from the converted XML domain, use the cloned instance NICs with virtio adapter
+                nic.setAdapterType("virtio");
+            }
+            convertedInstance.setNics(clonedInstanceNics);
+        } else {
+            for (int i = 0; i < convertedInstanceNics.size(); i++) {
+                UnmanagedInstanceTO.Nic nic = convertedInstanceNics.get(i);
+                nic.setNicId(clonedInstanceNics.get(i).getNicId());
+            }
+        }
+    }
+
+    private void removeClonedInstance(String vcenter, String datacenterName,
+                                      String username, String password,
+                                      String sourceHostName, String clonedInstanceName,
+                                      String sourceVM) {
+        HypervisorGuru vmwareGuru = hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware);
+        Map<String, String> params = createParamsForRemoveClonedInstance(vcenter, datacenterName, username, password, sourceVM);
+        boolean result = vmwareGuru.removeClonedHypervisorVMOutOfBand(sourceHostName, clonedInstanceName, params);
+        if (!result) {
+            String msg = String.format("Could not properly remove the cloned instance %s from VMware datacenter %s:%s",
+                    clonedInstanceName, vcenter, datacenterName);
+            LOGGER.warn(msg);
+            return;
+        }
+        LOGGER.debug(String.format("Removed the cloned instance %s from VMWare datacenter %s:%s",
+                clonedInstanceName, vcenter, datacenterName));
+    }
+
+    private Map<String, String> createParamsForRemoveClonedInstance(String vcenter, String datacenterName, String username,
+                                                                    String password, String sourceVM) {
+        Map<String, String> params = new HashMap<>();
+        params.put(VmDetailConstants.VMWARE_VCENTER_HOST, vcenter);
+        params.put(VmDetailConstants.VMWARE_DATACENTER_NAME, datacenterName);
+        params.put(VmDetailConstants.VMWARE_VCENTER_USERNAME, username);
+        params.put(VmDetailConstants.VMWARE_VCENTER_PASSWORD, password);
+        return params;
+    }
+
+    private HostVO selectInstanceConvertionKVMHostInCluster(Cluster destinationCluster, Long convertInstanceHostId) {
+        if (convertInstanceHostId != null) {
+            HostVO selectedHost = hostDao.findById(convertInstanceHostId);
+            if (selectedHost == null) {
+                String msg = String.format("Cannot find host with ID %s", convertInstanceHostId);
+                LOGGER.error(msg);
+                throw new CloudRuntimeException(msg);
+            }
+            if (selectedHost.getResourceState() != ResourceState.Enabled ||
+                    selectedHost.getStatus() != Status.Up || selectedHost.getType() != Host.Type.Routing ||
+                    selectedHost.getClusterId() != destinationCluster.getId()) {
+                String msg = String.format("Cannot perform the conversion on the host %s as it is not a running and Enabled host", selectedHost.getName());
+                LOGGER.error(msg);
+                throw new CloudRuntimeException(msg);
+            }
+            return selectedHost;
+        }
+        List<HostVO> hosts = hostDao.listByClusterAndHypervisorType(destinationCluster.getId(), destinationCluster.getHypervisorType());
+        if (CollectionUtils.isEmpty(hosts)) {
+            String err = String.format("Could not find any running %s host in cluster %s",
+                    destinationCluster.getHypervisorType(), destinationCluster.getName());
+            LOGGER.error(err);
+            throw new CloudRuntimeException(err);
+        }
+        List<HostVO> filteredHosts = hosts.stream()
+                .filter(x -> x.getResourceState() == ResourceState.Enabled)
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(filteredHosts)) {
+            String err = String.format("Could not find a %s host in cluster %s to perform the instance conversion",
+                    destinationCluster.getHypervisorType(), destinationCluster.getName());
+            LOGGER.error(err);
+            throw new CloudRuntimeException(err);
+        }
+        return filteredHosts.get(new Random().nextInt(filteredHosts.size()));
+    }
+
+    private UnmanagedInstanceTO convertVmwareInstanceToKVM(String vcenter, String datacenterName, String clusterName,
+                                                           String username, String password, String hostName,
+                                                           UnmanagedInstanceTO clonedInstance, Cluster destinationCluster,
+                                                           Long convertInstanceHostId, Long convertStoragePoolId) {
+        HostVO convertHost = selectInstanceConvertionKVMHostInCluster(destinationCluster, convertInstanceHostId);
+        String vmName = clonedInstance.getName();
+        LOGGER.debug(String.format("The host %s (%s) is selected to execute the conversion of the instance %s" +
+                " from VMware to KVM ", convertHost.getId(), convertHost.getName(), vmName));
+
+        RemoteInstanceTO remoteInstanceTO = new RemoteInstanceTO(hostName, vmName,
+                vcenter, datacenterName, clusterName, username, password);
+        DataStoreTO temporaryConvertLocation = selectInstanceConversionTemporaryLocation(destinationCluster, convertStoragePoolId, convertHost);
+        List<String> destinationStoragePools = selectInstanceConvertionStoragePools(destinationCluster, clonedInstance.getDisks());
+        ConvertInstanceCommand cmd = new ConvertInstanceCommand(remoteInstanceTO,
+                Hypervisor.HypervisorType.KVM, destinationStoragePools, temporaryConvertLocation);
+        int timeoutSeconds = StorageManager.ConvertVmwareInstanceToKvmTimeout.value() * 60 * 60;
+        cmd.setWait(timeoutSeconds);
+
+        Answer convertAnswer;
+        try {
+             convertAnswer = agentManager.send(convertHost.getId(), cmd);
+        } catch (AgentUnavailableException | OperationTimedoutException e) {
+            String err = String.format("Could not send the convert instance command to host %s (%s) due to: %s",
+                    convertHost.getId(), convertHost.getName(), e.getMessage());
+            LOGGER.error(err, e);
+            throw new CloudRuntimeException(err);
+        }
+
+        if (!convertAnswer.getResult()) {
+            String err = String.format("The convert process failed for instance %s from Vmware to KVM on host %s: %s",
+                    vmName, convertHost.getName(), convertAnswer.getDetails());
+            LOGGER.error(err);
+            throw new CloudRuntimeException(err);
+        }
+        return ((ConvertInstanceAnswer) convertAnswer).getConvertedInstance();
+    }
+
+    private List<String> selectInstanceConvertionStoragePools(Cluster destinationCluster, List<UnmanagedInstanceTO.Disk> disks) {
+        List<String> storagePools = new ArrayList<>(disks.size());
+        List<StoragePoolVO> pools = primaryDataStoreDao.listPoolsByCluster(destinationCluster.getId());
+        //TODO: Choose pools by capacity
+        for (UnmanagedInstanceTO.Disk disk : disks) {
+            Long capacity = disk.getCapacity();
+            storagePools.add(pools.get(0).getUuid());
+        }
+        return storagePools;
+    }
+
+    private void logFailureAndThrowException(String msg) {
+        LOGGER.error(msg);
+        throw new CloudRuntimeException(msg);
+    }
+
+    protected DataStoreTO selectInstanceConversionTemporaryLocation(Cluster destinationCluster, Long convertStoragePoolId, HostVO convertHost) {
+        if (convertStoragePoolId != null) {
+            StoragePoolVO selectedStoragePool = primaryDataStoreDao.findById(convertStoragePoolId);
+            if (selectedStoragePool == null) {
+                logFailureAndThrowException(String.format("Cannot find a storage pool with ID %s", convertStoragePoolId));
+            }
+            if ((selectedStoragePool.getScope() == ScopeType.CLUSTER && selectedStoragePool.getClusterId() != destinationCluster.getId()) ||
+                    (selectedStoragePool.getScope() == ScopeType.ZONE && selectedStoragePool.getDataCenterId() != destinationCluster.getDataCenterId())) {
+                logFailureAndThrowException(String.format("Cannot use the storage pool %s for the instance conversion as " +
+                        "it is not in the scope of the cluster %s", selectedStoragePool.getName(), destinationCluster.getName()));
+            }
+            if (selectedStoragePool.getScope() == ScopeType.HOST &&
+                    storagePoolHostDao.findByPoolHost(selectedStoragePool.getId(), convertHost.getId()) == null) {
+                logFailureAndThrowException(String.format("The storage pool %s is not a local storage pool for the host %s", selectedStoragePool.getName(), convertHost.getName()));
+            } else if (selectedStoragePool.getPoolType() != Storage.StoragePoolType.NetworkFilesystem) {
+                logFailureAndThrowException(String.format("The storage pool %s is not supported for temporary conversion location, supported pools are NFS storage pools", selectedStoragePool.getName()));
+            }
+            return dataStoreManager.getPrimaryDataStore(convertStoragePoolId).getTO();
+        } else {
+            long zoneId = destinationCluster.getDataCenterId();
+            ImageStoreVO imageStore = imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs");
+            if (imageStore == null) {
+                logFailureAndThrowException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " +
+                        "for instance conversion", zoneId));
+            }
+            DataStore dataStore = dataStoreManager.getDataStore(imageStore.getId(), DataStoreRole.Image);
+            return dataStore.getTO();
+        }
+    }
+
+    protected Map<String, String> createParamsForTemplateFromVmwareVmMigration(String vcenterHost, String datacenterName,
+                                                                               String username, String password,
+                                                                               String clusterName, String sourceHostName,
+                                                                               String sourceVMName) {
+        Map<String, String> params = new HashMap<>();
+        params.put(VmDetailConstants.VMWARE_VCENTER_HOST, vcenterHost);
+        params.put(VmDetailConstants.VMWARE_DATACENTER_NAME, datacenterName);
+        params.put(VmDetailConstants.VMWARE_VCENTER_USERNAME, username);
+        params.put(VmDetailConstants.VMWARE_VCENTER_PASSWORD, password);
+        params.put(VmDetailConstants.VMWARE_CLUSTER_NAME, clusterName);
+        params.put(VmDetailConstants.VMWARE_HOST_NAME, sourceHostName);
+        params.put(VmDetailConstants.VMWARE_VM_NAME, sourceVMName);
+        return params;
     }
 
     @Override
@@ -1247,6 +1848,8 @@
         cmdList.add(ListUnmanagedInstancesCmd.class);
         cmdList.add(ImportUnmanagedInstanceCmd.class);
         cmdList.add(UnmanageVMInstanceCmd.class);
+        cmdList.add(ListVmsForImportCmd.class);
+        cmdList.add(ImportVmCmd.class);
         return cmdList;
     }
 
@@ -1308,8 +1911,7 @@
         }
 
         if (hostId == null) {
-            throw new CloudRuntimeException("Cannot find a host to verify if the VM to unmanage " +
-                    "with id = " + vmVO.getUuid() + " exists.");
+            throw new CloudRuntimeException(String.format("Cannot find a host to verify if the VM [%s] exists. Thus we are unable to unmanage it.", vmVO.getUuid()));
         }
         return hostId;
     }
@@ -1322,8 +1924,9 @@
             throw new InvalidParameterValueException("Could not find VM to unmanage, it is either removed or not existing VM");
         } else if (vmVO.getState() != VirtualMachine.State.Running && vmVO.getState() != VirtualMachine.State.Stopped) {
             throw new InvalidParameterValueException("VM with id = " + vmVO.getUuid() + " must be running or stopped to be unmanaged");
-        } else if (vmVO.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
-            throw new UnsupportedServiceException("Unmanage VM is currently allowed for VMware VMs only");
+        } else if (!UnmanagedVMsManager.isSupported(vmVO.getHypervisorType())) {
+            throw new UnsupportedServiceException("Unmanage VM is currently not allowed for hypervisor " +
+                    vmVO.getHypervisorType().toString());
         } else if (vmVO.getType() != VirtualMachine.Type.User) {
             throw new UnsupportedServiceException("Unmanage VM is currently allowed for guest VMs only");
         }
@@ -1357,6 +1960,546 @@
         return answer.getResult();
     }
 
+    private UserVmResponse importKvmInstance(ImportVmCmd cmd) {
+        final Account caller = CallContext.current().getCallingAccount();
+        if (caller.getType() != Account.Type.ADMIN) {
+            throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid()));
+        }
+        final Long zoneId = cmd.getZoneId();
+        final DataCenterVO zone = dataCenterDao.findById(zoneId);
+        if (zone == null) {
+            throw new InvalidParameterValueException("Please specify a valid zone.");
+        }
+        final String hypervisorType = cmd.getHypervisor();
+        if (!Hypervisor.HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) {
+            throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor: %s", hypervisorType));
+        }
+
+        final String instanceName = cmd.getName();
+        if (StringUtils.isEmpty(instanceName)) {
+            throw new InvalidParameterValueException(String.format("Instance name cannot be empty"));
+        }
+        if (cmd.getDomainId() != null && StringUtils.isEmpty(cmd.getAccountName())) {
+            throw new InvalidParameterValueException("domainid parameter must be specified with account parameter");
+        }
+        final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
+        long userId = CallContext.current().getCallingUserId();
+        List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
+        if (CollectionUtils.isNotEmpty(userVOs)) {
+            userId = userVOs.get(0).getId();
+        }
+        VMTemplateVO template = templateDao.findByName(KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME);
+        if (template == null) {
+            template = createDefaultDummyVmImportTemplate(true);
+            if (template == null) {
+                throw new InvalidParameterValueException("Error while creating default Import Vm Template");
+            }
+        }
+
+        final Long serviceOfferingId = cmd.getServiceOfferingId();
+        if (serviceOfferingId == null) {
+            throw new InvalidParameterValueException(String.format("Service offering ID cannot be null"));
+        }
+        final ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
+        if (serviceOffering == null) {
+            throw new InvalidParameterValueException(String.format("Service offering ID: %d cannot be found", serviceOfferingId));
+        }
+        accountService.checkAccess(owner, serviceOffering, zone);
+        try {
+            resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1);
+        } catch (ResourceAllocationException e) {
+            LOGGER.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), StringUtils.defaultString(e.getMessage())));
+        }
+        String displayName = cmd.getDisplayName();
+        if (StringUtils.isEmpty(displayName)) {
+            displayName = instanceName;
+        }
+        String hostName = cmd.getHostName();
+        if (StringUtils.isEmpty(hostName)) {
+            if (!NetUtils.verifyDomainNameLabel(instanceName, true)) {
+                throw new InvalidParameterValueException(String.format("Please provide hostname for the VM. VM name contains unsupported characters for it to be used as hostname"));
+            }
+            hostName = instanceName;
+        }
+        if (!NetUtils.verifyDomainNameLabel(hostName, true)) {
+            throw new InvalidParameterValueException("Invalid VM hostname. VM hostname can contain ASCII letters 'a' through 'z', the digits '0' through '9', "
+                    + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit");
+        }
+
+        final Map<String, Long> nicNetworkMap = cmd.getNicNetworkList();
+        final Map<String, Network.IpAddresses> nicIpAddressMap = cmd.getNicIpAddressList();
+        final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
+        final Map<String, String> details = cmd.getDetails();
+
+        String remoteUrl = cmd.getHost();
+        String source = cmd.getImportSource().toUpperCase();
+        String diskPath = cmd.getDiskPath();
+        ImportSource importSource = Enum.valueOf(ImportSource.class, source);
+        Long hostId = cmd.getHostId();
+        Long poolId = cmd.getStoragePoolId();
+        Long networkId = cmd.getNetworkId();
+
+        UnmanagedInstanceTO unmanagedInstanceTO = null;
+        if (ImportSource.EXTERNAL == importSource) {
+            if (StringUtils.isBlank(cmd.getUsername())) {
+                throw new InvalidParameterValueException("Username need to be provided.");
+            }
+
+            HashMap<String, UnmanagedInstanceTO> instancesMap = getRemoteVms(zoneId, remoteUrl, cmd.getUsername(), cmd.getPassword());
+            unmanagedInstanceTO = instancesMap.get(cmd.getName());
+            if (unmanagedInstanceTO == null) {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Vm with name: %s not found on remote host", instanceName));
+            }
+        }
+
+        if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) {
+            if (diskPath == null) {
+                throw new InvalidParameterValueException("Disk Path is required for Import from shared/local storage");
+            }
+
+            if (networkId == null) {
+                throw new InvalidParameterValueException("Network is required for Import from shared/local storage");
+            }
+
+            if (poolId == null) {
+                throw new InvalidParameterValueException("Storage Pool is required for Import from shared/local storage");
+            }
+
+            StoragePool storagePool = primaryDataStoreDao.findById(poolId);
+            if (storagePool == null) {
+                throw new InvalidParameterValueException("Storage Pool not found");
+            }
+
+            if (volumeDao.findByPoolIdAndPath(poolId, diskPath) != null) {
+                throw new InvalidParameterValueException("Disk image is already in use");
+            }
+
+            DiskOffering diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId());
+
+            if (diskOffering != null && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) {
+                throw new InvalidParameterValueException(String.format("Service offering: %s storage tags are not compatible with selected storage pool: %s", serviceOffering.getUuid(), storagePool.getUuid()));
+            }
+        }
+
+        if (ImportSource.LOCAL == importSource) {
+            if (hostId == null) {
+                throw new InvalidParameterValueException("Host is required for Import from local storage");
+            }
+
+            if (hostDao.findById(hostId) == null) {
+                throw new InvalidParameterValueException("Host not found");
+            }
+
+            if(storagePoolHostDao.findByPoolHost(poolId, hostId) == null) {
+                throw new InvalidParameterValueException("Specified Local Storage Pool not found on Host");
+            }
+        }
+
+        UserVm userVm = null;
+
+        if (ImportSource.EXTERNAL == importSource) {
+            String username = cmd.getUsername();
+            String password = cmd.getPassword();
+            String tmpPath = cmd.getTmpPath();
+            userVm = importExternalKvmVirtualMachine(unmanagedInstanceTO, instanceName, zone,
+                    template, displayName, hostName, caller, owner, userId,
+                    serviceOffering, dataDiskOfferingMap,
+                    nicNetworkMap, nicIpAddressMap, remoteUrl, username, password, tmpPath, details);
+        } else if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) {
+            try {
+                userVm = importKvmVirtualMachineFromDisk(importSource, instanceName, zone,
+                        template, displayName, hostName, caller, owner, userId,
+                        serviceOffering, dataDiskOfferingMap, networkId, hostId, poolId, diskPath,
+                        details);
+            } catch (InsufficientCapacityException e) {
+                throw new RuntimeException(e);
+            } catch (ResourceAllocationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        if (userVm == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import Vm with name: %s ", instanceName));
+        }
+
+        CallContext.current().setEventResourceId(userVm.getId());
+        CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
+        return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0);
+    }
+
+    private UserVm importExternalKvmVirtualMachine(final UnmanagedInstanceTO unmanagedInstance, final String instanceName, final DataCenter zone,
+                                                final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
+                                                final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap,
+                                                final Map<String, Long> nicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap,
+                                                final String remoteUrl, String username, String password, String tmpPath, final Map<String, String> details) {
+        UserVm userVm = null;
+
+        Map<String, String> allDetails = new HashMap<>(details);
+        // Check disks and supplied disk offerings
+        List<UnmanagedInstanceTO.Disk> unmanagedInstanceDisks = unmanagedInstance.getDisks();
+
+        if (CollectionUtils.isEmpty(unmanagedInstanceDisks)) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("No attached disks found for the unmanaged VM: %s", instanceName));
+        }
+
+        Pair<UnmanagedInstanceTO.Disk, List<UnmanagedInstanceTO.Disk>> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap);
+        final UnmanagedInstanceTO.Disk rootDisk = rootAndDataDisksPair.first();
+        final List<UnmanagedInstanceTO.Disk> dataDisks = rootAndDataDisksPair.second();
+        if (rootDisk == null || StringUtils.isEmpty(rootDisk.getController())) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed. Unable to retrieve root disk details for VM: %s ", instanceName));
+        }
+        allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController());
+
+        // Check NICs and supplied networks
+        Map<String, Network.IpAddresses> nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap);
+        Map<String, Long> allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getName(), unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner, Hypervisor.HypervisorType.KVM);
+        if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) {
+            allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType());
+        }
+        VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff;
+
+        String internalName = getInternalName(owner.getAccountId());
+
+        try {
+            userVm = userVmManager.importVM(zone, null, template, internalName, displayName, owner,
+                    null, caller, true, null, owner.getAccountId(), userId,
+                    serviceOffering, null, hostName,
+                    Hypervisor.HypervisorType.KVM, allDetails, powerState, null);
+        } catch (InsufficientCapacityException ice) {
+            LOGGER.error(String.format("Failed to import vm name: %s", instanceName), ice);
+            throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ice.getMessage());
+        }
+        if (userVm == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName));
+        }
+        DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId());
+        String rootVolumeName = String.format("ROOT-%s", userVm.getId());
+        DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, null, null, null, userVm, template, owner, null);
+
+        DiskProfile[] dataDiskProfiles = new DiskProfile[dataDisks.size()];
+        int diskSeq = 0;
+        for (UnmanagedInstanceTO.Disk disk : dataDisks) {
+            if (disk.getCapacity() == null || disk.getCapacity() == 0) {
+                throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", disk.getDiskId()));
+            }
+            DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId()));
+            DiskProfile dataDiskProfile = volumeManager.allocateRawVolume(Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()), offering, null, null, null, userVm, template, owner, null);
+            dataDiskProfiles[diskSeq++] = dataDiskProfile;
+        }
+
+        final VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm, template, serviceOffering, owner, null);
+        ServiceOfferingVO dummyOffering = serviceOfferingDao.findById(userVm.getId(), serviceOffering.getId());
+        profile.setServiceOffering(dummyOffering);
+        DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList();
+        final DataCenterDeployment plan = new DataCenterDeployment(zone.getId(), null, null, null, null, null);
+        DeployDestination dest = null;
+        try {
+            dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null);
+        } catch (Exception e) {
+            LOGGER.warn(String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName()), e);
+            cleanupFailedImportVM(userVm);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName()));
+        }
+        if(dest == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName()));
+        }
+
+        List<Pair<DiskProfile, StoragePool>> diskProfileStoragePoolList = new ArrayList<>();
+        try {
+            if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) {
+                throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId()));
+            }
+
+            diskProfileStoragePoolList.add(importExternalDisk(rootDisk, userVm, dest, diskOffering, Volume.Type.ROOT,
+                    template, null, remoteUrl, username, password, tmpPath, diskProfile));
+
+            long deviceId = 1L;
+            diskSeq = 0;
+            for (UnmanagedInstanceTO.Disk disk : dataDisks) {
+                DiskProfile dataDiskProfile = dataDiskProfiles[diskSeq++];
+                DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId()));
+
+                diskProfileStoragePoolList.add(importExternalDisk(disk, userVm, dest, offering, Volume.Type.DATADISK,
+                        template, deviceId, remoteUrl, username, password, tmpPath, dataDiskProfile));
+                deviceId++;
+            }
+        } catch (Exception e) {
+            LOGGER.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e);
+            cleanupFailedImportVM(userVm);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage())));
+        }
+        try {
+            int nicIndex = 0;
+            for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) {
+                Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId()));
+                Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId());
+                importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true);
+                nicIndex++;
+            }
+        } catch (Exception e) {
+            LOGGER.error(String.format("Failed to import NICs while importing vm: %s", instanceName), e);
+            cleanupFailedImportVM(userVm);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage())));
+        }
+        publishVMUsageUpdateResourceCount(userVm, dummyOffering);
+        return userVm;
+    }
+
+    private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource, final String instanceName, final DataCenter zone,
+                                                   final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
+                                                   final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap, final Long networkId,
+                                                   final Long hostId, final Long poolId, final String diskPath, final Map<String, String> details) throws InsufficientCapacityException, ResourceAllocationException {
+
+        UserVm userVm = null;
+
+        Map<String, String> allDetails = new HashMap<>(details);
+
+        VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff;
+
+        NetworkVO network = networkDao.findById(networkId);
+        if (network == null) {
+            throw new InvalidParameterValueException("Unable to find network by id " + networkId);
+        }
+
+        networkModel.checkNetworkPermissions(owner, network);
+
+        // don't allow to use system networks
+        NetworkOffering networkOffering = entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId());
+        if (networkOffering.isSystemOnly()) {
+            throw new InvalidParameterValueException("Network id=" + networkId + " is system only and can't be used for vm deployment");
+        }
+
+        LinkedHashMap<String, List<NicProfile>> networkNicMap = new LinkedHashMap<>();
+
+        if ((network.getDataCenterId() != zone.getId())) {
+            if (!network.isStrechedL2Network()) {
+                throw new InvalidParameterValueException("Network id=" + network.getId() +
+                        " doesn't belong to zone " + zone.getId());
+            }
+        }
+
+        String macAddress = networkModel.getNextAvailableMacAddressInNetwork(networkId);
+        String ipAddress = null;
+        if (network.getGuestType() != Network.GuestType.L2) {
+            ipAddress = ipAddressManager.acquireGuestIpAddress(network, null);
+        }
+
+        Network.IpAddresses requestedIpPair = new Network.IpAddresses(ipAddress, null, macAddress);
+
+        NicProfile nicProfile = new NicProfile(requestedIpPair.getIp4Address(), requestedIpPair.getIp6Address(), requestedIpPair.getMacAddress());
+        nicProfile.setOrderIndex(0);
+
+        boolean securityGroupEnabled = false;
+        if (networkModel.isSecurityGroupSupportedInNetwork(network)) {
+            securityGroupEnabled = true;
+        }
+        List<NicProfile> profiles = networkNicMap.get(network.getUuid());
+        if (CollectionUtils.isEmpty(profiles)) {
+            profiles = new ArrayList<>();
+        }
+        profiles.add(nicProfile);
+        networkNicMap.put(network.getUuid(), profiles);
+
+        String internalName = getInternalName(owner.getAccountId());
+
+        try {
+            userVm = userVmManager.importVM(zone, null, template, internalName, displayName, owner,
+                    null, caller, true, null, owner.getAccountId(), userId,
+                    serviceOffering, null, hostName,
+                    Hypervisor.HypervisorType.KVM, allDetails, powerState, networkNicMap);
+        } catch (InsufficientCapacityException ice) {
+            LOGGER.error(String.format("Failed to import vm name: %s", instanceName), ice);
+            throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ice.getMessage());
+        }
+        if (userVm == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName));
+        }
+        DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId());
+        String rootVolumeName = String.format("ROOT-%s", userVm.getId());
+        DiskProfile diskProfile = volumeManager.allocateRawVolume(Volume.Type.ROOT, rootVolumeName, diskOffering, null, null, null, userVm, template, owner, null);
+
+        final VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm, template, serviceOffering, owner, null);
+        ServiceOfferingVO dummyOffering = serviceOfferingDao.findById(userVm.getId(), serviceOffering.getId());
+        profile.setServiceOffering(dummyOffering);
+        DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList();
+        final DataCenterDeployment plan = new DataCenterDeployment(zone.getId(), null, null, hostId, poolId, null);
+        DeployDestination dest = null;
+        try {
+            dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null);
+        } catch (Exception e) {
+            LOGGER.warn(String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName()), e);
+            cleanupFailedImportVM(userVm);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s while finding deployment destination", userVm.getInstanceName()));
+        }
+        if(dest == null) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Import failed for Vm: %s. Suitable deployment destination not found", userVm.getInstanceName()));
+        }
+
+
+        Map<Volume, StoragePool> storage = dest.getStorageForDisks();
+        Volume volume = volumeDao.findById(diskProfile.getVolumeId());
+        StoragePool storagePool = storage.get(volume);
+        CheckVolumeCommand checkVolumeCommand = new CheckVolumeCommand();
+        checkVolumeCommand.setSrcFile(diskPath);
+        StorageFilerTO storageTO = new StorageFilerTO(storagePool);
+        checkVolumeCommand.setStorageFilerTO(storageTO);
+        Answer answer = agentManager.easySend(dest.getHost().getId(), checkVolumeCommand);
+        if (!(answer instanceof CheckVolumeAnswer)) {
+            cleanupFailedImportVM(userVm);
+            throw new CloudRuntimeException("Disk not found or is invalid");
+        }
+        CheckVolumeAnswer checkVolumeAnswer = (CheckVolumeAnswer) answer;
+        if(!checkVolumeAnswer.getResult()) {
+            cleanupFailedImportVM(userVm);
+            throw new CloudRuntimeException("Disk not found or is invalid");
+        }
+        diskProfile.setSize(checkVolumeAnswer.getSize());
+
+
+        List<Pair<DiskProfile, StoragePool>> diskProfileStoragePoolList = new ArrayList<>();
+        try {
+            long deviceId = 1L;
+            if(ImportSource.SHARED == importSource) {
+                diskProfileStoragePoolList.add(importKVMSharedDisk(userVm, diskOffering, Volume.Type.ROOT,
+                        template, deviceId, poolId, diskPath, diskProfile));
+            } else if(ImportSource.LOCAL == importSource) {
+                diskProfileStoragePoolList.add(importKVMLocalDisk(userVm, diskOffering, Volume.Type.ROOT,
+                        template, deviceId, hostId, diskPath, diskProfile));
+            }
+        } catch (Exception e) {
+            LOGGER.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e);
+            cleanupFailedImportVM(userVm);
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage())));
+        }
+        networkOrchestrationService.importNic(macAddress,0,network, true, userVm, requestedIpPair, zone, true);
+        publishVMUsageUpdateResourceCount(userVm, dummyOffering);
+        return userVm;
+    }
+
+
+    private NetworkVO getDefaultNetwork(DataCenter zone, Account owner, boolean selectAny) throws InsufficientCapacityException, ResourceAllocationException {
+        NetworkVO defaultNetwork = null;
+
+        // if no network is passed in
+        // Check if default virtual network offering has
+        // Availability=Required. If it's true, search for corresponding
+        // network
+        // * if network is found, use it. If more than 1 virtual network is
+        // found, throw an error
+        // * if network is not found, create a new one and use it
+
+        List<NetworkOfferingVO> requiredOfferings = networkOfferingDao.listByAvailability(NetworkOffering.Availability.Required, false);
+        if (requiredOfferings.size() < 1) {
+            throw new InvalidParameterValueException("Unable to find network offering with availability=" + NetworkOffering.Availability.Required
+                    + " to automatically create the network as a part of vm creation");
+        }
+
+        if (requiredOfferings.get(0).getState() == NetworkOffering.State.Enabled) {
+            // get Virtual networks
+            List<? extends Network> virtualNetworks = networkModel.listNetworksForAccount(owner.getId(), zone.getId(), Network.GuestType.Isolated);
+            if (virtualNetworks == null) {
+                throw new InvalidParameterValueException("No (virtual) networks are found for account " + owner);
+            }
+            if (virtualNetworks.isEmpty()) {
+                defaultNetwork = createDefaultNetworkForAccount(zone, owner, requiredOfferings);
+            } else if (virtualNetworks.size() > 1 && !selectAny) {
+                throw new InvalidParameterValueException("More than 1 default Isolated networks are found for account " + owner + "; please specify networkIds");
+            } else {
+                defaultNetwork = networkDao.findById(virtualNetworks.get(0).getId());
+            }
+        } else {
+            throw new InvalidParameterValueException("Required network offering id=" + requiredOfferings.get(0).getId() + " is not in " + NetworkOffering.State.Enabled);
+        }
+
+        return defaultNetwork;
+    }
+
+    private NetworkVO createDefaultNetworkForAccount(DataCenter zone, Account owner, List<NetworkOfferingVO> requiredOfferings)
+            throws InsufficientCapacityException, ResourceAllocationException {
+        NetworkVO defaultNetwork = null;
+        long physicalNetworkId = networkModel.findPhysicalNetworkId(zone.getId(), requiredOfferings.get(0).getTags(), requiredOfferings.get(0).getTrafficType());
+        // Validate physical network
+        PhysicalNetwork physicalNetwork = physicalNetworkDao.findById(physicalNetworkId);
+        if (physicalNetwork == null) {
+            throw new InvalidParameterValueException("Unable to find physical network with id: " + physicalNetworkId + " and tag: "
+                    + requiredOfferings.get(0).getTags());
+        }
+        LOGGER.debug("Creating network for account " + owner + " from the network offering id=" + requiredOfferings.get(0).getId() + " as a part of deployVM process");
+        Network newNetwork = networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(), owner.getAccountName() + "-network", owner.getAccountName() + "-network",
+                null, null, null, false, null, owner, null, physicalNetwork, zone.getId(), ControlledEntity.ACLType.Account, null, null, null, null, true, null, null,
+                null, null, null, null, null, null, null, null);
+        if (newNetwork != null) {
+            defaultNetwork = networkDao.findById(newNetwork.getId());
+        }
+        return defaultNetwork;
+    }
+
+    //generate unit test
+    public ListResponse<UnmanagedInstanceResponse> listVmsForImport(ListVmsForImportCmd cmd) {
+        final Account caller = CallContext.current().getCallingAccount();
+        if (caller.getType() != Account.Type.ADMIN) {
+            throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid()));
+        }
+        final Long zoneId = cmd.getZoneId();
+        final DataCenterVO zone = dataCenterDao.findById(zoneId);
+        if (zone == null) {
+            throw new InvalidParameterValueException("Please specify a valid zone.");
+        }
+        final String hypervisorType = cmd.getHypervisor();
+        if (Hypervisor.HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) {
+            if (StringUtils.isBlank(cmd.getUsername())) {
+                throw new InvalidParameterValueException("Username need to be provided.");
+            }
+        } else {
+            throw new InvalidParameterValueException(String.format("VM Import is currently not supported for hypervisor: %s", hypervisorType));
+        }
+
+        String keyword = cmd.getKeyword();
+        if (StringUtils.isNotEmpty(keyword)) {
+            keyword = keyword.toLowerCase();
+        }
+
+        List<UnmanagedInstanceResponse> responses = new ArrayList<>();
+        HashMap<String, UnmanagedInstanceTO> vmMap = getRemoteVms(zoneId, cmd.getHost(), cmd.getUsername(), cmd.getPassword());
+        for (String key : vmMap.keySet()) {
+            UnmanagedInstanceTO instance = vmMap.get(key);
+            if (StringUtils.isNotEmpty(keyword) &&
+                    !instance.getName().toLowerCase().contains(keyword)) {
+                continue;
+            }
+            responses.add(createUnmanagedInstanceResponse(instance, null, null));
+        }
+
+        ListResponse<UnmanagedInstanceResponse> listResponses = new ListResponse<>();
+        listResponses.setResponses(responses, responses.size());
+        return listResponses;
+    }
+
+    private HashMap<String, UnmanagedInstanceTO> getRemoteVms(long zoneId, String remoteUrl, String username, String password) {
+        //ToDo: add option to list one Vm by name
+        List<HostVO> hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.KVM, zoneId);
+        if(hosts.size() < 1) {
+            throw new CloudRuntimeException("No hosts available for Vm Import");
+        }
+        HostVO host = hosts.get(0);
+        GetRemoteVmsCommand getRemoteVmsCommand = new GetRemoteVmsCommand(remoteUrl, username, password);
+        Answer answer = agentManager.easySend(host.getId(), getRemoteVmsCommand);
+        if (!(answer instanceof GetRemoteVmsAnswer)) {
+            throw new CloudRuntimeException("Error while listing remote Vms");
+        }
+        GetRemoteVmsAnswer getRemoteVmsAnswer = (GetRemoteVmsAnswer) answer;
+        return getRemoteVmsAnswer.getUnmanagedInstances();
+    }
+
+    private String getInternalName(long accounId) {
+        String instanceSuffix = configurationDao.getValue(Config.InstanceName.key());
+        if (instanceSuffix == null) {
+            instanceSuffix = "DEFAULT";
+        }
+        long vmId = userVmDao.getNextInSequence(Long.class, "id");
+        return VirtualMachineName.getVmName(vmId, accounId, instanceSuffix);
+    }
+
     @Override
     public String getConfigComponentName() {
         return UnmanagedVMsManagerImpl.class.getSimpleName();
@@ -1364,6 +2507,6 @@
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] { UnmanageVMPreserveNic };
+        return new ConfigKey<?>[]{UnmanageVMPreserveNic};
     }
 }
diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImpl.java
new file mode 100644
index 0000000..0bb2f94
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImpl.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 com.cloud.api.query.MutualExclusiveIdsManagerBase;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.user.AccountManager;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.UserVmManager;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+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;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.springframework.scheduling.support.CronExpression;
+
+import javax.inject.Inject;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.TimeZone;
+
+public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase implements VMScheduleManager, PluggableService {
+
+    private static Logger LOGGER = Logger.getLogger(VMScheduleManagerImpl.class);
+
+    @Inject
+    private VMScheduleDao vmScheduleDao;
+    @Inject
+    private UserVmManager userVmManager;
+    @Inject
+    private VMScheduler vmScheduler;
+    @Inject
+    private AccountManager accountManager;
+
+    @Override
+    public List<Class<?>> getCommands() {
+        final List<Class<?>> cmdList = new ArrayList<>();
+        cmdList.add(CreateVMScheduleCmd.class);
+        cmdList.add(ListVMScheduleCmd.class);
+        cmdList.add(UpdateVMScheduleCmd.class);
+        cmdList.add(DeleteVMScheduleCmd.class);
+        return cmdList;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_CREATE, eventDescription = "Creating VM Schedule", create = true)
+    public VMScheduleResponse createSchedule(CreateVMScheduleCmd cmd) {
+        VirtualMachine vm = userVmManager.getUserVm(cmd.getVmId());
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
+        if (vm == null) {
+            throw new InvalidParameterValueException(String.format("Invalid value for vmId: %s", cmd.getVmId()));
+        }
+
+        VMSchedule.Action action = null;
+        if (cmd.getAction() != null) {
+            try {
+                action = VMSchedule.Action.valueOf(cmd.getAction().toUpperCase());
+            } catch (IllegalArgumentException exception) {
+                throw new InvalidParameterValueException(String.format("Invalid value for action: %s", cmd.getAction()));
+            }
+        }
+
+        Date cmdStartDate = cmd.getStartDate();
+        Date cmdEndDate = cmd.getEndDate();
+        String cmdTimeZone = cmd.getTimeZone();
+        TimeZone timeZone = TimeZone.getTimeZone(cmdTimeZone);
+        String timeZoneId = timeZone.getID();
+        Date startDate = DateUtils.addMinutes(new Date(), 1);
+        if (cmdStartDate != null) {
+            startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
+        }
+        Date endDate = null;
+        if (cmdEndDate != null) {
+            endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
+        }
+
+        CronExpression cronExpression = DateUtil.parseSchedule(cmd.getSchedule());
+
+        validateStartDateEndDate(startDate, endDate, timeZone);
+
+        String description = null;
+        if (StringUtils.isBlank(cmd.getDescription())) {
+            description = String.format("%s - %s", action, DateUtil.getHumanReadableSchedule(cronExpression));
+        } else description = cmd.getDescription();
+
+        LOGGER.warn(String.format("Using timezone [%s] for running the schedule for VM [%s], as an equivalent of [%s].", timeZoneId, vm.getUuid(), cmdTimeZone));
+
+        String finalDescription = description;
+        VMSchedule.Action finalAction = action;
+        Date finalStartDate = startDate;
+        Date finalEndDate = endDate;
+
+        return Transaction.execute((TransactionCallback<VMScheduleResponse>) status -> {
+            VMScheduleVO vmSchedule = vmScheduleDao.persist(new VMScheduleVO(cmd.getVmId(), finalDescription, cronExpression.toString(), timeZoneId, finalAction, finalStartDate, finalEndDate, cmd.getEnabled()));
+            vmScheduler.scheduleNextJob(vmSchedule, new Date());
+            CallContext.current().setEventResourceId(vm.getId());
+            CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
+            return createResponse(vmSchedule);
+        });
+    }
+
+    @Override
+    public VMScheduleResponse createResponse(VMSchedule vmSchedule) {
+        VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
+        VMScheduleResponse response = new VMScheduleResponse();
+
+        response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
+        response.setId(vmSchedule.getUuid());
+        response.setVmId(vm.getUuid());
+        response.setDescription(vmSchedule.getDescription());
+        response.setSchedule(vmSchedule.getSchedule());
+        response.setTimeZone(vmSchedule.getTimeZone());
+        response.setAction(vmSchedule.getAction());
+        response.setEnabled(vmSchedule.getEnabled());
+        response.setStartDate(vmSchedule.getStartDate());
+        response.setEndDate(vmSchedule.getEndDate());
+        response.setCreated(vmSchedule.getCreated());
+        return response;
+    }
+
+    @Override
+    public ListResponse<VMScheduleResponse> listSchedule(ListVMScheduleCmd cmd) {
+        Long id = cmd.getId();
+        Boolean enabled = cmd.getEnabled();
+        Long vmId = cmd.getVmId();
+
+        VirtualMachine vm = userVmManager.getUserVm(vmId);
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
+
+        VMSchedule.Action action = null;
+        if (cmd.getAction() != null) {
+            try {
+                action = VMSchedule.Action.valueOf(cmd.getAction());
+            } catch (IllegalArgumentException exception) {
+                throw new InvalidParameterValueException("Invalid value for action: " + cmd.getAction());
+            }
+        }
+
+        Pair<List<VMScheduleVO>, Integer> result = vmScheduleDao.searchAndCount(id, vmId, action, enabled, cmd.getStartIndex(), cmd.getPageSizeVal());
+
+        ListResponse<VMScheduleResponse> response = new ListResponse<>();
+        List<VMScheduleResponse> responsesList = new ArrayList<>();
+        for (VMSchedule vmSchedule : result.first()) {
+            responsesList.add(createResponse(vmSchedule));
+        }
+        response.setResponses(responsesList, result.second());
+        return response;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_UPDATE, eventDescription = "Updating VM Schedule")
+    public VMScheduleResponse updateSchedule(UpdateVMScheduleCmd cmd) {
+        Long id = cmd.getId();
+        VMScheduleVO vmSchedule = vmScheduleDao.findById(id);
+
+        if (vmSchedule == null) {
+            throw new CloudRuntimeException("VM schedule doesn't exist");
+        }
+
+        VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
+
+        CronExpression cronExpression = Objects.requireNonNullElse(
+                DateUtil.parseSchedule(cmd.getSchedule()),
+                DateUtil.parseSchedule(vmSchedule.getSchedule())
+        );
+
+        String description = cmd.getDescription();
+        if (description == null && vmSchedule.getDescription() == null) {
+            description = String.format("%s - %s", vmSchedule.getAction(), DateUtil.getHumanReadableSchedule(cronExpression));
+        }
+        String cmdTimeZone = cmd.getTimeZone();
+        Date cmdStartDate = cmd.getStartDate();
+        Date cmdEndDate = cmd.getEndDate();
+        Boolean enabled = cmd.getEnabled();
+
+        TimeZone timeZone;
+        String timeZoneId;
+        if (cmdTimeZone != null) {
+            timeZone = TimeZone.getTimeZone(cmdTimeZone);
+            timeZoneId = timeZone.getID();
+            if (!timeZoneId.equals(cmdTimeZone)) {
+                LOGGER.warn(String.format("Using timezone [%s] for running the schedule [%s] for VM %s, as an equivalent of [%s].",
+                        timeZoneId, vmSchedule.getSchedule(), vmSchedule.getVmId(), cmdTimeZone));
+            }
+            vmSchedule.setTimeZone(timeZoneId);
+        } else {
+            timeZoneId = vmSchedule.getTimeZone();
+            timeZone = TimeZone.getTimeZone(timeZoneId);
+        }
+
+        Date startDate = vmSchedule.getStartDate().before(DateUtils.addMinutes(new Date(), 1)) ? DateUtils.addMinutes(new Date(), 1) : vmSchedule.getStartDate();
+        Date endDate = vmSchedule.getEndDate();
+        if (cmdEndDate != null) {
+            endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
+        }
+
+        if (cmdStartDate != null) {
+            startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
+        }
+
+        validateStartDateEndDate(Objects.requireNonNullElse(startDate, DateUtils.addMinutes(new Date(), 1)), endDate, timeZone);
+
+        if (enabled != null) {
+            vmSchedule.setEnabled(enabled);
+        }
+        if (description != null) {
+            vmSchedule.setDescription(description);
+        }
+        if (cmdEndDate != null) {
+            vmSchedule.setEndDate(endDate);
+        }
+        if (cmdStartDate != null) {
+            vmSchedule.setStartDate(startDate);
+        }
+        vmSchedule.setSchedule(cronExpression.toString());
+
+        return Transaction.execute((TransactionCallback<VMScheduleResponse>) status -> {
+            vmScheduleDao.update(cmd.getId(), vmSchedule);
+            vmScheduler.updateScheduledJob(vmSchedule);
+            CallContext.current().setEventResourceId(vm.getId());
+            CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
+            return createResponse(vmSchedule);
+        });
+    }
+
+    void validateStartDateEndDate(Date startDate, Date endDate, TimeZone tz) {
+        ZonedDateTime now = ZonedDateTime.now(tz.toZoneId());
+        ZonedDateTime zonedStartDate = ZonedDateTime.ofInstant(startDate.toInstant(), tz.toZoneId());
+
+        if (zonedStartDate.isBefore(now)) {
+            throw new InvalidParameterValueException(String.format("Invalid value for start date. Start date [%s] can't be before current time [%s].", zonedStartDate, now));
+        }
+
+        if (endDate != null) {
+            ZonedDateTime zonedEndDate = ZonedDateTime.ofInstant(endDate.toInstant(), tz.toZoneId());
+            if (zonedEndDate.isBefore(now)) {
+                throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before current time [%s].", zonedEndDate, now));
+            }
+            if (zonedEndDate.isBefore(zonedStartDate)) {
+                throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before start date [%s].", zonedEndDate, zonedStartDate));
+            }
+        }
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_DELETE, eventDescription = "Deleting VM Schedule for VM")
+    public long removeScheduleByVmId(long vmId, boolean expunge) {
+        SearchCriteria<VMScheduleVO> sc = vmScheduleDao.getSearchCriteriaForVMId(vmId);
+        List<VMScheduleVO> vmSchedules = vmScheduleDao.search(sc, null);
+        List<Long> ids = new ArrayList<>();
+        for (final VMScheduleVO vmSchedule : vmSchedules) {
+            ids.add(vmSchedule.getId());
+        }
+        vmScheduler.removeScheduledJobs(ids);
+        if (expunge) {
+            return vmScheduleDao.expunge(sc);
+        }
+        CallContext.current().setEventResourceId(vmId);
+        CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
+        return vmScheduleDao.remove(sc);
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_DELETE, eventDescription = "Deleting VM Schedule")
+    public Long removeSchedule(DeleteVMScheduleCmd cmd) {
+        VirtualMachine vm = userVmManager.getUserVm(cmd.getVmId());
+        accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
+
+        List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
+
+        if (ids.isEmpty()) {
+            throw new InvalidParameterValueException("Either id or ids parameter must be specified");
+        }
+        return Transaction.execute((TransactionCallback<Long>) status -> {
+            vmScheduler.removeScheduledJobs(ids);
+            CallContext.current().setEventResourceId(vm.getId());
+            CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
+            return vmScheduleDao.removeSchedulesForVmIdAndIds(vm.getId(), ids);
+        });
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java
new file mode 100644
index 0000000..fa96a1c
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.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.vm.schedule;
+
+import com.cloud.utils.component.Manager;
+import com.cloud.utils.concurrency.Scheduler;
+import org.apache.cloudstack.framework.config.ConfigKey;
+
+import java.util.Date;
+import java.util.List;
+
+public interface VMScheduler extends Manager, Scheduler {
+    ConfigKey<Integer> VMScheduledJobExpireInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "vmscheduler.jobs.expire.interval", "30", "VM Scheduler expire interval in days", true);
+
+    void removeScheduledJobs(List<Long> vmScheduleIds);
+
+    void updateScheduledJob(VMScheduleVO vmSchedule);
+
+    Date scheduleNextJob(VMScheduleVO vmSchedule, Date timestamp);
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedulerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedulerImpl.java
new file mode 100644
index 0000000..5d25f36
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedulerImpl.java
@@ -0,0 +1,412 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 com.cloud.api.ApiGsonHelper;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.vm.UserVmManager;
+import com.cloud.vm.VirtualMachine;
+import com.google.common.primitives.Longs;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
+import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
+import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
+import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
+import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDao;
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.log4j.Logger;
+import org.springframework.scheduling.support.CronExpression;
+
+import javax.inject.Inject;
+import javax.persistence.EntityExistsException;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class VMSchedulerImpl extends ManagerBase implements VMScheduler, Configurable {
+    private static Logger LOGGER = Logger.getLogger(VMSchedulerImpl.class);
+    @Inject
+    private VMScheduledJobDao vmScheduledJobDao;
+    @Inject
+    private VMScheduleDao vmScheduleDao;
+    @Inject
+    private UserVmManager userVmManager;
+    @Inject
+    private AsyncJobManager asyncJobManager;
+    private AsyncJobDispatcher asyncJobDispatcher;
+    private Timer vmSchedulerTimer;
+    private Date currentTimestamp;
+
+    private EnumMap<VMSchedule.Action, String> actionEventMap = new EnumMap<>(VMSchedule.Action.class);
+
+    public AsyncJobDispatcher getAsyncJobDispatcher() {
+        return asyncJobDispatcher;
+    }
+
+    public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
+        asyncJobDispatcher = dispatcher;
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[]{VMScheduledJobExpireInterval};
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return VMScheduler.class.getSimpleName();
+    }
+
+    @Override
+    public void removeScheduledJobs(List<Long> vmScheduleIds) {
+        if (vmScheduleIds == null || vmScheduleIds.isEmpty()) {
+            LOGGER.debug("Removed 0 scheduled jobs");
+            return;
+        }
+        Date now = new Date();
+        int rowsRemoved = vmScheduledJobDao.expungeJobsForSchedules(vmScheduleIds, now);
+        LOGGER.debug(String.format("Removed %s VM scheduled jobs", rowsRemoved));
+    }
+
+    @Override
+    public void updateScheduledJob(VMScheduleVO vmSchedule) {
+        removeScheduledJobs(Longs.asList(vmSchedule.getId()));
+        scheduleNextJob(vmSchedule, new Date());
+    }
+
+    @Override
+    public Date scheduleNextJob(VMScheduleVO vmSchedule, Date timestamp) {
+        if (!vmSchedule.getEnabled()) {
+            LOGGER.debug(String.format("VM Schedule [id=%s] for VM [id=%s] is disabled. Not scheduling next job.", vmSchedule.getUuid(), vmSchedule.getVmId()));
+            return null;
+        }
+
+        CronExpression cron = DateUtil.parseSchedule(vmSchedule.getSchedule());
+        Date startDate = vmSchedule.getStartDate();
+        Date endDate = vmSchedule.getEndDate();
+        VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
+
+        if (vm == null) {
+            LOGGER.info(String.format("VM [id=%s] is removed. Disabling VM schedule [id=%s].", vmSchedule.getVmId(), vmSchedule.getUuid()));
+            vmSchedule.setEnabled(false);
+            vmScheduleDao.persist(vmSchedule);
+            return null;
+        }
+
+        ZonedDateTime now;
+        if (timestamp != null) {
+            now = ZonedDateTime.ofInstant(timestamp.toInstant(), vmSchedule.getTimeZoneId());
+        } else {
+            now = ZonedDateTime.now(vmSchedule.getTimeZoneId());
+        }
+        ZonedDateTime zonedStartDate = ZonedDateTime.ofInstant(startDate.toInstant(), vmSchedule.getTimeZoneId());
+        ZonedDateTime zonedEndDate = null;
+        if (endDate != null) {
+            zonedEndDate = ZonedDateTime.ofInstant(endDate.toInstant(), vmSchedule.getTimeZoneId());
+        }
+        if (zonedEndDate != null && now.isAfter(zonedEndDate)) {
+            LOGGER.info(String.format("End time is less than current time. Disabling VM schedule [id=%s] for VM [id=%s].", vmSchedule.getUuid(), vmSchedule.getVmId()));
+            vmSchedule.setEnabled(false);
+            vmScheduleDao.persist(vmSchedule);
+            return null;
+        }
+
+        ZonedDateTime ts = null;
+        if (zonedStartDate.isAfter(now)) {
+            ts = cron.next(zonedStartDate);
+        } else {
+            ts = cron.next(now);
+        }
+
+        if (ts == null) {
+            LOGGER.info(String.format("No next schedule found. Disabling VM schedule [id=%s] for VM [id=%s].", vmSchedule.getUuid(), vmSchedule.getVmId()));
+            vmSchedule.setEnabled(false);
+            vmScheduleDao.persist(vmSchedule);
+            return null;
+        }
+
+        Date scheduledDateTime = Date.from(ts.toInstant());
+        VMScheduledJobVO scheduledJob = new VMScheduledJobVO(vmSchedule.getVmId(), vmSchedule.getId(), vmSchedule.getAction(), scheduledDateTime);
+        try {
+            vmScheduledJobDao.persist(scheduledJob);
+            ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(), actionEventMap.get(vmSchedule.getAction()),
+                    String.format("Scheduled action (%s) [vmId: %s scheduleId: %s]  at %s", vmSchedule.getAction(), vm.getUuid(), vmSchedule.getUuid(), scheduledDateTime),
+                    vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), true, 0);
+        } catch (EntityExistsException exception) {
+            LOGGER.debug("Job is already scheduled.");
+        }
+        return scheduledDateTime;
+    }
+
+    @Override
+    public boolean start() {
+        actionEventMap.put(VMSchedule.Action.START, EventTypes.EVENT_VM_SCHEDULE_START);
+        actionEventMap.put(VMSchedule.Action.STOP, EventTypes.EVENT_VM_SCHEDULE_STOP);
+        actionEventMap.put(VMSchedule.Action.REBOOT, EventTypes.EVENT_VM_SCHEDULE_REBOOT);
+        actionEventMap.put(VMSchedule.Action.FORCE_STOP, EventTypes.EVENT_VM_SCHEDULE_FORCE_STOP);
+        actionEventMap.put(VMSchedule.Action.FORCE_REBOOT, EventTypes.EVENT_VM_SCHEDULE_FORCE_REBOOT);
+
+        // Adding 1 minute to currentTimestamp to ensure that
+        // jobs which were to be run at current time, doesn't cause issues
+        currentTimestamp = DateUtils.addMinutes(new Date(), 1);
+        scheduleNextJobs(currentTimestamp);
+
+        final TimerTask schedulerPollTask = new ManagedContextTimerTask() {
+            @Override
+            protected void runInContext() {
+                try {
+                    poll(new Date());
+                } catch (final Throwable t) {
+                    LOGGER.warn("Catch throwable in VM scheduler ", t);
+                }
+            }
+        };
+
+        vmSchedulerTimer = new Timer("VMSchedulerPollTask");
+        vmSchedulerTimer.scheduleAtFixedRate(schedulerPollTask, 5000L, 60 * 1000L);
+        return true;
+    }
+
+    @Override
+    public void poll(Date timestamp) {
+        currentTimestamp = DateUtils.round(timestamp, Calendar.MINUTE);
+        String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+        LOGGER.debug(String.format("VM scheduler.poll is being called at %s", displayTime));
+
+        GlobalLock scanLock = GlobalLock.getInternLock("vmScheduler.poll");
+        try {
+            if (scanLock.lock(30)) {
+                try {
+                    scheduleNextJobs(currentTimestamp);
+                } finally {
+                    scanLock.unlock();
+                }
+            }
+        } finally {
+            scanLock.releaseRef();
+        }
+
+        scanLock = GlobalLock.getInternLock("vmScheduler.poll");
+        try {
+            if (scanLock.lock(30)) {
+                try {
+                    startJobs(); // Create async job and update scheduled job
+                } finally {
+                    scanLock.unlock();
+                }
+            }
+        } finally {
+            scanLock.releaseRef();
+        }
+
+        try {
+            cleanupVMScheduledJobs();
+        } catch (Exception e) {
+            LOGGER.warn("Error in cleaning up vm scheduled jobs", e);
+        }
+    }
+
+    private void scheduleNextJobs(Date timestamp) {
+        for (final VMScheduleVO schedule : vmScheduleDao.listAllActiveSchedules()) {
+            try {
+                scheduleNextJob(schedule, timestamp);
+            } catch (Exception e) {
+                LOGGER.warn("Error in scheduling next job for schedule " + schedule.getUuid(), e);
+            }
+        }
+    }
+
+    /**
+     * Delete scheduled jobs before vm.scheduler.expire.interval days
+     */
+    private void cleanupVMScheduledJobs() {
+        Date deleteBeforeDate = DateUtils.addDays(currentTimestamp, -1 * VMScheduledJobExpireInterval.value());
+        int rowsRemoved = vmScheduledJobDao.expungeJobsBefore(deleteBeforeDate);
+        LOGGER.info(String.format("Cleaned up %d VM scheduled job entries", rowsRemoved));
+    }
+
+    void executeJobs(Map<Long, VMScheduledJob> jobsToExecute) {
+        String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+
+        for (Map.Entry<Long, VMScheduledJob> entry : jobsToExecute.entrySet()) {
+            VMScheduledJob vmScheduledJob = entry.getValue();
+            VirtualMachine vm = userVmManager.getUserVm(vmScheduledJob.getVmId());
+
+            VMScheduledJobVO tmpVMScheduleJob = null;
+            try {
+                if (LOGGER.isDebugEnabled()) {
+                    final Date scheduledTimestamp = vmScheduledJob.getScheduledTime();
+                    displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
+                    LOGGER.debug(String.format("Executing %s for VM id %d for schedule id: %d at %s", vmScheduledJob.getAction(), vmScheduledJob.getVmId(), vmScheduledJob.getVmScheduleId(), displayTime));
+                }
+
+                tmpVMScheduleJob = vmScheduledJobDao.acquireInLockTable(vmScheduledJob.getId());
+                Long jobId = processJob(vmScheduledJob, vm);
+                if (jobId != null) {
+                    tmpVMScheduleJob.setAsyncJobId(jobId);
+                    vmScheduledJobDao.update(vmScheduledJob.getId(), tmpVMScheduleJob);
+                }
+            } catch (final Exception e) {
+                LOGGER.warn(String.format("Executing scheduled job id: %s failed due to %s", vmScheduledJob.getId(), e));
+            } finally {
+                if (tmpVMScheduleJob != null) {
+                    vmScheduledJobDao.releaseFromLockTable(vmScheduledJob.getId());
+                }
+            }
+        }
+    }
+
+    Long processJob(VMScheduledJob vmScheduledJob, VirtualMachine vm) {
+        if (!Arrays.asList(VirtualMachine.State.Running, VirtualMachine.State.Stopped).contains(vm.getState())) {
+            LOGGER.info(String.format("Skipping action (%s) for [vmId:%s scheduleId: %s] because VM is invalid state: %s", vmScheduledJob.getAction(), vm.getUuid(), vmScheduledJob.getVmScheduleId(), vm.getState()));
+            return null;
+        }
+
+        final Long eventId = ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), null,
+                actionEventMap.get(vmScheduledJob.getAction()), true,
+                String.format("Executing action (%s) for VM Id:%s", vmScheduledJob.getAction(), vm.getUuid()),
+                vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0);
+
+        if (vm.getState() == VirtualMachine.State.Running) {
+            switch (vmScheduledJob.getAction()) {
+                case STOP:
+                    return executeStopVMJob(vm, false, eventId);
+                case FORCE_STOP:
+                    return executeStopVMJob(vm, true, eventId);
+                case REBOOT:
+                    return executeRebootVMJob(vm, false, eventId);
+                case FORCE_REBOOT:
+                    return executeRebootVMJob(vm, true, eventId);
+            }
+        } else if (vm.getState() == VirtualMachine.State.Stopped && vmScheduledJob.getAction() == VMSchedule.Action.START) {
+            return executeStartVMJob(vm, eventId);
+        }
+
+        LOGGER.warn(String.format("Skipping action (%s) for [vmId:%s scheduleId: %s] because VM is in state: %s",
+                vmScheduledJob.getAction(), vm.getUuid(), vmScheduledJob.getVmScheduleId(), vm.getState()));
+        return null;
+    }
+
+    private void skipJobs(Map<Long, VMScheduledJob> jobsToExecute, Map<Long, List<VMScheduledJob>> jobsNotToExecute) {
+        for (Map.Entry<Long, List<VMScheduledJob>> entry : jobsNotToExecute.entrySet()) {
+            Long vmId = entry.getKey();
+            List<VMScheduledJob> skippedVmScheduledJobVOS = entry.getValue();
+            VirtualMachine vm = userVmManager.getUserVm(vmId);
+            for (final VMScheduledJob skippedVmScheduledJobVO : skippedVmScheduledJobVOS) {
+                VMScheduledJob scheduledJob = jobsToExecute.get(vmId);
+                LOGGER.info(String.format("Skipping scheduled job [id: %s, vmId: %s] because of conflict with another scheduled job [id: %s]", skippedVmScheduledJobVO.getUuid(), vm.getUuid(), scheduledJob.getUuid()));
+            }
+        }
+    }
+
+    /**
+     * Create async jobs for VM scheduled jobs
+     */
+    private void startJobs() {
+        String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+
+        final List<VMScheduledJobVO> vmScheduledJobs = vmScheduledJobDao.listJobsToStart(currentTimestamp);
+        LOGGER.debug(String.format("Got %d scheduled jobs to be executed at %s", vmScheduledJobs.size(), displayTime));
+
+        Map<Long, VMScheduledJob> jobsToExecute = new HashMap<>();
+        Map<Long, List<VMScheduledJob>> jobsNotToExecute = new HashMap<>();
+        for (final VMScheduledJobVO vmScheduledJobVO : vmScheduledJobs) {
+            long vmId = vmScheduledJobVO.getVmId();
+            if (jobsToExecute.get(vmId) == null) {
+                jobsToExecute.put(vmId, vmScheduledJobVO);
+            } else {
+                jobsNotToExecute.computeIfAbsent(vmId, k -> new ArrayList<>()).add(vmScheduledJobVO);
+            }
+        }
+
+        executeJobs(jobsToExecute);
+        skipJobs(jobsToExecute, jobsNotToExecute);
+    }
+
+    long executeStartVMJob(VirtualMachine vm, long eventId) {
+        final Map<String, String> params = new HashMap<>();
+        params.put(ApiConstants.ID, String.valueOf(vm.getId()));
+        params.put("ctxUserId", "1");
+        params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
+        params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
+
+        final StartVMCmd cmd = new StartVMCmd();
+        ComponentContext.inject(cmd);
+
+        AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StartVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
+        job.setDispatcher(asyncJobDispatcher.getName());
+
+        return asyncJobManager.submitAsyncJob(job);
+    }
+
+    long executeStopVMJob(VirtualMachine vm, boolean isForced, long eventId) {
+        final Map<String, String> params = new HashMap<>();
+        params.put(ApiConstants.ID, String.valueOf(vm.getId()));
+        params.put("ctxUserId", "1");
+        params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
+        params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
+        params.put(ApiConstants.FORCED, String.valueOf(isForced));
+
+        final StopVMCmd cmd = new StopVMCmd();
+        ComponentContext.inject(cmd);
+
+        AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StopVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
+        job.setDispatcher(asyncJobDispatcher.getName());
+
+        return asyncJobManager.submitAsyncJob(job);
+    }
+
+    long executeRebootVMJob(VirtualMachine vm, boolean isForced, long eventId) {
+        final Map<String, String> params = new HashMap<>();
+        params.put(ApiConstants.ID, String.valueOf(vm.getId()));
+        params.put("ctxUserId", "1");
+        params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
+        params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
+        params.put(ApiConstants.FORCED, String.valueOf(isForced));
+
+        final RebootVMCmd cmd = new RebootVMCmd();
+        ComponentContext.inject(cmd);
+
+        AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), RebootVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
+        job.setDispatcher(asyncJobDispatcher.getName());
+
+        return asyncJobManager.submitAsyncJob(job);
+    }
+}
diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
index a9db159..7227264 100644
--- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
@@ -21,10 +21,10 @@
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:util="http://www.springframework.org/schema/util"
-       
+
        xsi:schemaLocation="http://www.springframework.org/schema/beans
                       http://www.springframework.org/schema/beans/spring-beans.xsd
-                      http://www.springframework.org/schema/aop 
+                      http://www.springframework.org/schema/aop
                       http://www.springframework.org/schema/aop/spring-aop.xsd
                       http://www.springframework.org/schema/context
                       http://www.springframework.org/schema/context/spring-context.xsd
@@ -129,7 +129,7 @@
 
     <bean id="capacityManagerImpl" class="com.cloud.capacity.CapacityManagerImpl" />
 
-    <bean id="configurationManagerImpl" class="com.cloud.configuration.ConfigurationManagerImpl" >  
+    <bean id="configurationManagerImpl" class="com.cloud.configuration.ConfigurationManagerImpl" >
         <property name="secChecker" value="#{securityCheckersRegistry.registered}" />
     </bean>
 
@@ -211,45 +211,45 @@
 
     <bean id="uploadMonitorImpl" class="com.cloud.storage.upload.UploadMonitorImpl" />
     <bean id="usageServiceImpl" class="com.cloud.usage.UsageServiceImpl" />
-    
+
     <bean id="virtualNetworkApplianceManagerImpl"
         class="com.cloud.network.router.VirtualNetworkApplianceManagerImpl" />
-        
+
     <bean id="vpcManagerImpl" class="com.cloud.network.vpc.VpcManagerImpl" >
         <property name="vpcElements" value="#{vpcProvidersRegistry.registered}"></property>
     </bean>
-    
+
     <bean id="vpcTxCallable" class="com.cloud.network.vpc.VpcPrivateGatewayTransactionCallable" />
-    
+
     <bean id="vpcVirtualNetworkApplianceManagerImpl"
         class="com.cloud.network.router.VpcVirtualNetworkApplianceManagerImpl" />
-    
+
     <bean id="virtualNetworkApplianceFactory"
         class="com.cloud.network.rules.VirtualNetworkApplianceFactory" />
-    
+
     <bean id="topologyContext" class="org.apache.cloudstack.network.topology.NetworkTopologyContext" init-method="init" />
-    
+
     <bean id="basicNetworkTopology" class="org.apache.cloudstack.network.topology.BasicNetworkTopology" />
     <bean id="advancedNetworkTopology" class="org.apache.cloudstack.network.topology.AdvancedNetworkTopology" />
-    
+
     <bean id="basicNetworkVisitor" class="org.apache.cloudstack.network.topology.BasicNetworkVisitor" />
     <bean id="advancedNetworkVisitor" class="org.apache.cloudstack.network.topology.AdvancedNetworkVisitor" />
-    
+
     <bean id="commandSetupHelper"
         class="com.cloud.network.router.CommandSetupHelper" />
-    
+
     <bean id="routerControlHelper"
         class="com.cloud.network.router.RouterControlHelper" />
-        
+
     <bean id="networkHelper"
         class="com.cloud.network.router.NetworkHelperImpl" />
-        
+
     <bean id="vpcNetworkHelper"
         class="com.cloud.network.router.VpcNetworkHelperImpl" />
-        
+
     <bean id="nicProfileHelper"
         class="com.cloud.network.router.NicProfileHelperImpl" />
-        
+
     <bean id="routerDeploymentDefinitionBuilder"
         class="org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinitionBuilder" />
 
@@ -257,6 +257,9 @@
         <property name="name" value="ApiAsyncJobDispatcher" />
     </bean>
 
+    <bean id="shutdownManager" class="org.apache.cloudstack.shutdown.ShutdownManagerImpl" >
+        <property name="name" value="shutdownManager" />
+    </bean>
 
     <bean id="statsCollector" class="com.cloud.server.StatsCollector" />
 
@@ -265,14 +268,14 @@
     <bean id="domainManagerImpl" class="com.cloud.user.DomainManagerImpl" />
 
     <bean id="downloadMonitorImpl" class="com.cloud.storage.download.DownloadMonitorImpl" />
-  
+
     <bean id="lBHealthCheckManagerImpl" class="com.cloud.network.lb.LBHealthCheckManagerImpl" />
 
     <bean id="volumeApiServiceImpl" class="com.cloud.storage.VolumeApiServiceImpl">
         <property name="storagePoolAllocators"
             value="#{storagePoolAllocatorsRegistry.registered}" />
     </bean>
-    
+
     <bean id="ApplicationLoadBalancerService" class="org.apache.cloudstack.network.lb.ApplicationLoadBalancerManagerImpl" />
 
     <bean id="vMSnapshotManagerImpl" class="com.cloud.vm.snapshot.VMSnapshotManagerImpl" />
@@ -334,14 +337,27 @@
         <constructor-arg name="timeout" value="10000" />
     </bean>
 
+    <bean id="storageStorageBrowserImpl" class="org.apache.cloudstack.storage.browser.StorageBrowserImpl" />
+
     <bean id="rollingMaintenanceManager" class="com.cloud.resource.RollingMaintenanceManagerImpl">
         <property name="affinityGroupProcessors"
                   value="#{affinityProcessorsRegistry.registered}" />
     </bean>
 
+    <bean id="clusterDrsService" class="org.apache.cloudstack.cluster.ClusterDrsServiceImpl">
+        <property name="drsAlgorithms" value="#{clusterDrsAlgorithmRegistry.registered}"/>
+        <property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
+    </bean>
     <bean id="resourceManagerUtilImpl"
           class="com.cloud.tags.ResourceManagerUtilImpl"/>
 
     <bean id="resourceIconManager" class="com.cloud.resourceicon.ResourceIconManagerImpl" />
+    <bean id="bucketApiServiceImpl" class="org.apache.cloudstack.storage.object.BucketApiServiceImpl" />
 
+    <bean id="VMScheduleManagerImpl" class="org.apache.cloudstack.vm.schedule.VMScheduleManagerImpl" />
+    <bean id="VMSchedulerImpl" class="org.apache.cloudstack.vm.schedule.VMSchedulerImpl">
+        <property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
+    </bean>
+
+    <bean id="vnfTemplateManager" class="org.apache.cloudstack.storage.template.VnfTemplateManagerImpl" />
 </beans>
diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml
index 1900717..244c2d9 100644
--- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml
@@ -81,4 +81,4 @@
 
     <bean id="DPDKHelper" class="com.cloud.hypervisor.kvm.dpdk.DpdkHelperImpl" />
     
-</beans>
\ No newline at end of file
+</beans>
diff --git a/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-backend/module.properties b/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-backend/module.properties
index 120c91d..765be37 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-backend/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-backend/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-alert-adapter-backend
-parent=backend
\ No newline at end of file
+parent=backend
diff --git a/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-compute/module.properties b/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-compute/module.properties
index 12213f4..9cd4878 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-compute/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-alert-adapter-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-storage/module.properties b/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-storage/module.properties
index c156009..2716ac7 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-storage/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-storage/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-alert-adapter-storage
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/server/src/main/resources/META-INF/cloudstack/server-allocator/module.properties b/server/src/main/resources/META-INF/cloudstack/server-allocator/module.properties
index f69c483..f0075d0 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-allocator/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-allocator/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-allocator
-parent=allocator
\ No newline at end of file
+parent=allocator
diff --git a/server/src/main/resources/META-INF/cloudstack/server-api/module.properties b/server/src/main/resources/META-INF/cloudstack/server-api/module.properties
index 74a9c50..034e81a 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-api/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-api/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-api
-parent=api
\ No newline at end of file
+parent=api
diff --git a/server/src/main/resources/META-INF/cloudstack/server-api/spring-server-api-context.xml b/server/src/main/resources/META-INF/cloudstack/server-api/spring-server-api-context.xml
index 30e7647..1f1b24e 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-api/spring-server-api-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/server-api/spring-server-api-context.xml
@@ -30,4 +30,4 @@
     <bean id="domainChecker" class="com.cloud.acl.DomainChecker" />
     <bean id="affinityGroupAccessChecker" class="com.cloud.acl.AffinityGroupAccessChecker" />
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/server/src/main/resources/META-INF/cloudstack/server-compute/module.properties b/server/src/main/resources/META-INF/cloudstack/server-compute/module.properties
index 7b42a91..6766e6d 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-compute/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-compute/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-compute
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/server/src/main/resources/META-INF/cloudstack/server-discoverer/module.properties b/server/src/main/resources/META-INF/cloudstack/server-discoverer/module.properties
index 0c4f5e1..9d49120 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-discoverer/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml b/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
index 3a7e0ff..98abb08 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml
@@ -38,6 +38,11 @@
         <property name="name" value="Lxc Discover" />
     </bean>
 
+    <bean id="CustomServerDiscoverer"
+          class="com.cloud.hypervisor.discoverer.CustomServerDiscoverer">
+        <property name="name" value="CustomHW Agent" />
+    </bean>
+
     <bean id="dummyHostDiscoverer" class="com.cloud.resource.DummyHostDiscoverer">
         <property name="name" value="dummyHostDiscoverer" />
     </bean>
diff --git a/server/src/main/resources/META-INF/cloudstack/server-fencer/module.properties b/server/src/main/resources/META-INF/cloudstack/server-fencer/module.properties
index b4a8684..b045d11 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-fencer/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-fencer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-fencer
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/server/src/main/resources/META-INF/cloudstack/server-investigator/module.properties b/server/src/main/resources/META-INF/cloudstack/server-investigator/module.properties
index 85e6882..dffdb2f 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-investigator/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-investigator/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-investigator
-parent=compute
\ No newline at end of file
+parent=compute
diff --git a/server/src/main/resources/META-INF/cloudstack/server-network/module.properties b/server/src/main/resources/META-INF/cloudstack/server-network/module.properties
index 95a7d1b..5d99a31 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-network/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-network/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-network
-parent=network
\ No newline at end of file
+parent=network
diff --git a/server/src/main/resources/META-INF/cloudstack/server-planner/module.properties b/server/src/main/resources/META-INF/cloudstack/server-planner/module.properties
index 541b769..023afd7 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-planner/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-planner/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-planner
-parent=planner
\ No newline at end of file
+parent=planner
diff --git a/server/src/main/resources/META-INF/cloudstack/server-storage/module.properties b/server/src/main/resources/META-INF/cloudstack/server-storage/module.properties
index 9eaf2d0..5a427b2 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-storage/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-storage/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-storage
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/server/src/main/resources/META-INF/cloudstack/server-template-adapter/module.properties b/server/src/main/resources/META-INF/cloudstack/server-template-adapter/module.properties
index 85ba317..66721d3 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-template-adapter/module.properties
+++ b/server/src/main/resources/META-INF/cloudstack/server-template-adapter/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=server-template-adapter
-parent=storage
\ No newline at end of file
+parent=storage
diff --git a/server/src/main/resources/META-INF/cloudstack/system/spring-server-system-context.xml b/server/src/main/resources/META-INF/cloudstack/system/spring-server-system-context.xml
index 9eb0a4c..d742c63 100644
--- a/server/src/main/resources/META-INF/cloudstack/system/spring-server-system-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/system/spring-server-system-context.xml
@@ -33,4 +33,4 @@
         </constructor-arg>
     </bean>
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/server/src/test/java/com/cloud/alert/AlertControlsUnitTest.java b/server/src/test/java/com/cloud/alert/AlertControlsUnitTest.java
index b21dfe7..5bbf2db 100644
--- a/server/src/test/java/com/cloud/alert/AlertControlsUnitTest.java
+++ b/server/src/test/java/com/cloud/alert/AlertControlsUnitTest.java
@@ -16,17 +16,11 @@
 // under the License.
 package com.cloud.alert;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.when;
-
-import java.util.Date;
-
+import com.cloud.alert.dao.AlertDao;
+import com.cloud.server.ManagementServerImpl;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
 import junit.framework.TestCase;
-
 import org.apache.log4j.Logger;
 import org.junit.After;
 import org.junit.Before;
@@ -35,10 +29,14 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
-import com.cloud.alert.dao.AlertDao;
-import com.cloud.server.ManagementServerImpl;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
+import java.util.Date;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
 
 public class AlertControlsUnitTest extends TestCase {
     private static final Logger s_logger = Logger.getLogger(AlertControlsUnitTest.class);
diff --git a/server/src/test/java/com/cloud/alert/AlertManagerImplTest.java b/server/src/test/java/com/cloud/alert/AlertManagerImplTest.java
index 0ac2d00..dc787d7 100644
--- a/server/src/test/java/com/cloud/alert/AlertManagerImplTest.java
+++ b/server/src/test/java/com/cloud/alert/AlertManagerImplTest.java
@@ -16,10 +16,7 @@
 // under the License.
 package com.cloud.alert;
 
-import java.io.UnsupportedEncodingException;
-
-import javax.mail.MessagingException;
-
+import com.cloud.alert.dao.AlertDao;
 import org.apache.cloudstack.utils.mailing.SMTPMailSender;
 import org.apache.log4j.Logger;
 import org.junit.Assert;
@@ -31,7 +28,8 @@
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.alert.dao.AlertDao;
+import javax.mail.MessagingException;
+import java.io.UnsupportedEncodingException;
 
 @RunWith(MockitoJUnitRunner.class)
 public class AlertManagerImplTest {
diff --git a/server/src/test/java/com/cloud/alert/MockAlertManagerImpl.java b/server/src/test/java/com/cloud/alert/MockAlertManagerImpl.java
index d0b4ac2..3f7a332 100644
--- a/server/src/test/java/com/cloud/alert/MockAlertManagerImpl.java
+++ b/server/src/test/java/com/cloud/alert/MockAlertManagerImpl.java
@@ -17,11 +17,10 @@
 
 package com.cloud.alert;
 
-import java.util.Map;
+import com.cloud.utils.component.ManagerBase;
 
 import javax.naming.ConfigurationException;
-
-import com.cloud.utils.component.ManagerBase;
+import java.util.Map;
 
 public class MockAlertManagerImpl extends ManagerBase implements AlertManager {
 
diff --git a/server/src/test/java/com/cloud/api/APITest.java b/server/src/test/java/com/cloud/api/APITest.java
index 12c3b5a..e76b7a7 100644
--- a/server/src/test/java/com/cloud/api/APITest.java
+++ b/server/src/test/java/com/cloud/api/APITest.java
@@ -16,6 +16,10 @@
 // under the License.
 package com.cloud.api;
 
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.gson.Gson;
+import org.apache.cloudstack.api.response.SuccessResponse;
+
 import java.io.BufferedReader;
 import java.io.EOFException;
 import java.io.InputStreamReader;
@@ -28,12 +32,6 @@
 import java.util.HashMap;
 import java.util.Iterator;
 
-import com.google.gson.Gson;
-
-import org.apache.cloudstack.api.response.SuccessResponse;
-
-import com.cloud.utils.exception.CloudRuntimeException;
-
 /**
  * Base class for API Test
  *
diff --git a/server/src/test/java/com/cloud/api/ApiDispatcherTest.java b/server/src/test/java/com/cloud/api/ApiDispatcherTest.java
index 26080da..d7975b9 100644
--- a/server/src/test/java/com/cloud/api/ApiDispatcherTest.java
+++ b/server/src/test/java/com/cloud/api/ApiDispatcherTest.java
@@ -17,8 +17,13 @@
 
 package com.cloud.api;
 
-import java.util.HashMap;
-
+import com.cloud.api.dispatch.DispatchChain;
+import com.cloud.api.dispatch.DispatchTask;
+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.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.ServerApiException;
@@ -29,13 +34,7 @@
 import org.mockito.Mockito;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import com.cloud.api.dispatch.DispatchChain;
-import com.cloud.api.dispatch.DispatchTask;
-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 java.util.HashMap;
 
 public class ApiDispatcherTest {
     protected static final Long resourceId = 1L;
diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
index fff6fb2..1bea3ac 100644
--- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
+++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
@@ -16,45 +16,13 @@
 // under the License.
 package com.cloud.api;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.TimeZone;
-import java.util.UUID;
-
-import org.apache.cloudstack.annotation.dao.AnnotationDao;
-import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
-import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
-import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
-import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
-import org.apache.cloudstack.api.response.UsageRecordResponse;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.usage.UsageService;
-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.Mockito;
-import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.cloud.domain.DomainVO;
+import com.cloud.network.PublicIpQuarantine;
 import com.cloud.network.as.AutoScaleVmGroup;
 import com.cloud.network.as.AutoScaleVmGroupVO;
 import com.cloud.network.as.AutoScaleVmProfileVO;
 import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
+import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.IPAddressVO;
 import com.cloud.network.dao.LoadBalancerVO;
 import com.cloud.network.dao.NetworkServiceMapDao;
@@ -71,8 +39,45 @@
 import com.cloud.user.dao.UserDataDao;
 import com.cloud.utils.net.Ip;
 import com.cloud.vm.NicSecondaryIp;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
+import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
+import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
+import org.apache.cloudstack.api.response.IpQuarantineResponse;
+import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
+import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
+import org.apache.cloudstack.api.response.UsageRecordResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.usage.UsageService;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+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.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
+import java.lang.reflect.Field;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class ApiResponseHelperTest {
 
     @Mock
@@ -95,6 +100,9 @@
     @Mock
     UserDataDao userDataDaoMock;
 
+    @Mock
+    IPAddressDao ipAddressDaoMock;
+
     @Spy
     @InjectMocks
     ApiResponseHelper apiResponseHelper = new ApiResponseHelper();
@@ -155,7 +163,6 @@
     }
 
     @Test
-    @PrepareForTest(ApiDBUtils.class)
     public void testUsageRecordResponse(){
         //Creating the usageVO object to be passed to the createUsageResponse.
         Long zoneId = null;
@@ -180,12 +187,13 @@
 
         AccountVO account = new AccountVO();
 
-        PowerMockito.mockStatic(ApiDBUtils.class);
-        when(ApiDBUtils.findAccountById(anyLong())).thenReturn(account);
-        when(ApiDBUtils.findDomainById(anyLong())).thenReturn(domain);
+        try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
+            when(ApiDBUtils.findAccountById(anyLong())).thenReturn(account);
+            when(ApiDBUtils.findDomainById(anyLong())).thenReturn(domain);
 
-        UsageRecordResponse MockResponse = helper.createUsageResponse(usage);
-        assertEquals("DomainName",MockResponse.getDomainName());
+            UsageRecordResponse MockResponse = helper.createUsageResponse(usage);
+            assertEquals("DomainName", MockResponse.getDomainName());
+        }
     }
 
     @Test
@@ -259,34 +267,33 @@
     }
 
     @Test
-    @PrepareForTest(ApiDBUtils.class)
     public void testAutoScaleVmGroupResponse() {
         AutoScaleVmGroupVO vmGroup = new AutoScaleVmGroupVO(1L, 2L, 3L, 4L, "test", 5, 6, 7, 8, new Date(), 9L, AutoScaleVmGroup.State.ENABLED);
 
-        PowerMockito.mockStatic(ApiDBUtils.class);
-        when(ApiDBUtils.findAutoScaleVmProfileById(anyLong())).thenReturn(null);
-        when(ApiDBUtils.findLoadBalancerById(anyLong())).thenReturn(null);
-        when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
-        when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
-        when(ApiDBUtils.countAvailableVmsByGroupId(anyLong())).thenReturn(9);
+        try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
+            when(ApiDBUtils.findAutoScaleVmProfileById(anyLong())).thenReturn(null);
+            when(ApiDBUtils.findLoadBalancerById(anyLong())).thenReturn(null);
+            when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
+            when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
+            when(ApiDBUtils.countAvailableVmsByGroupId(anyLong())).thenReturn(9);
 
-        AutoScaleVmGroupResponse response = apiResponseHelper.createAutoScaleVmGroupResponse(vmGroup);
-        assertEquals("test", response.getName());
-        assertEquals(5, response.getMinMembers());
-        assertEquals(6, response.getMaxMembers());
-        assertEquals(8, response.getInterval());
-        assertEquals(9, response.getAvailableVirtualMachineCount());
-        assertEquals(AutoScaleVmGroup.State.ENABLED.toString(), response.getState());
+            AutoScaleVmGroupResponse response = apiResponseHelper.createAutoScaleVmGroupResponse(vmGroup);
+            assertEquals("test", response.getName());
+            assertEquals(5, response.getMinMembers());
+            assertEquals(6, response.getMaxMembers());
+            assertEquals(8, response.getInterval());
+            assertEquals(9, response.getAvailableVirtualMachineCount());
+            assertEquals(AutoScaleVmGroup.State.ENABLED.toString(), response.getState());
 
-        assertNull(response.getNetworkName());
-        assertNull(response.getLbProvider());
-        assertNull(response.getPublicIp());
-        assertNull(response.getPublicPort());
-        assertNull(response.getPrivatePort());
+            assertNull(response.getNetworkName());
+            assertNull(response.getLbProvider());
+            assertNull(response.getPublicIp());
+            assertNull(response.getPublicPort());
+            assertNull(response.getPrivatePort());
+        }
     }
 
     @Test
-    @PrepareForTest(ApiDBUtils.class)
     public void testAutoScaleVmGroupResponseWithNetwork() {
         AutoScaleVmGroupVO vmGroup = new AutoScaleVmGroupVO(1L, 2L, 3L, 4L, "test", 5, 6, 7, 8, new Date(), 9L, AutoScaleVmGroup.State.ENABLED);
 
@@ -295,78 +302,154 @@
                 "testnetwork", "displaytext", "networkdomain", null, 1L, null, null, false, null, false);
         IPAddressVO ipAddressVO = new IPAddressVO(new Ip("10.10.10.10"), 1L, 1L, 1L,false);
 
-        PowerMockito.mockStatic(ApiDBUtils.class);
-        when(ApiDBUtils.findAutoScaleVmProfileById(anyLong())).thenReturn(null);
-        when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
-        when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
-        when(ApiDBUtils.findLoadBalancerById(anyLong())).thenReturn(lb);
+        try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
+            when(ApiDBUtils.findAutoScaleVmProfileById(anyLong())).thenReturn(null);
+            when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
+            when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
+            when(ApiDBUtils.findLoadBalancerById(anyLong())).thenReturn(lb);
 
-        when(ApiDBUtils.findNetworkById(anyLong())).thenReturn(network);
-        when(ntwkSrvcDaoMock.getProviderForServiceInNetwork(anyLong(), any())).thenReturn("VirtualRouter");
-        when(ApiDBUtils.findIpAddressById(anyLong())).thenReturn(ipAddressVO);
+            when(ApiDBUtils.findNetworkById(anyLong())).thenReturn(network);
+            when(ntwkSrvcDaoMock.getProviderForServiceInNetwork(anyLong(), any())).thenReturn("VirtualRouter");
+            when(ApiDBUtils.findIpAddressById(anyLong())).thenReturn(ipAddressVO);
 
-        AutoScaleVmGroupResponse response = apiResponseHelper.createAutoScaleVmGroupResponse(vmGroup);
-        assertEquals("test", response.getName());
-        assertEquals(5, response.getMinMembers());
-        assertEquals(6, response.getMaxMembers());
-        assertEquals(8, response.getInterval());
-        assertEquals(AutoScaleVmGroup.State.ENABLED.toString(), response.getState());
+            AutoScaleVmGroupResponse response = apiResponseHelper.createAutoScaleVmGroupResponse(vmGroup);
+            assertEquals("test", response.getName());
+            assertEquals(5, response.getMinMembers());
+            assertEquals(6, response.getMaxMembers());
+            assertEquals(8, response.getInterval());
+            assertEquals(AutoScaleVmGroup.State.ENABLED.toString(), response.getState());
 
-        assertEquals("testnetwork", response.getNetworkName());
-        assertEquals("VirtualRouter", response.getLbProvider());
-        assertEquals("10.10.10.10", response.getPublicIp());
-        assertEquals("8080", response.getPublicPort());
-        assertEquals("8081", response.getPrivatePort());
+            assertEquals("testnetwork", response.getNetworkName());
+            assertEquals("VirtualRouter", response.getLbProvider());
+            assertEquals("10.10.10.10", response.getPublicIp());
+            assertEquals("8080", response.getPublicPort());
+            assertEquals("8081", response.getPrivatePort());
+        }
     }
 
     @Test
-    @PrepareForTest(ApiDBUtils.class)
     public void testAutoScaleVmProfileResponse() {
         AutoScaleVmProfileVO vmProfile = new AutoScaleVmProfileVO(zoneId, domainId, accountId, serviceOfferingId, templateId, null, null, userdata, null, autoScaleUserId);
         vmProfile.setUserDataId(userdataId);
         vmProfile.setUserDataDetails(userdataDetails);
 
-        PowerMockito.mockStatic(ApiDBUtils.class);
-        when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
-        when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
+        try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
+            when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
+            when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
 
-        UserData.UserDataOverridePolicy templatePolicy = UserData.UserDataOverridePolicy.APPEND;
-        VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
-        when(ApiDBUtils.findTemplateById(anyLong())).thenReturn(templateVO);
-        when(templateVO.getUserDataOverridePolicy()).thenReturn(templatePolicy);
+            UserData.UserDataOverridePolicy templatePolicy = UserData.UserDataOverridePolicy.APPEND;
+            VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
+            when(ApiDBUtils.findTemplateById(anyLong())).thenReturn(templateVO);
+            when(templateVO.getUserDataOverridePolicy()).thenReturn(templatePolicy);
 
-        UserDataVO userDataVO =  Mockito.mock(UserDataVO.class);
-        String userDataUuid = "userDataUuid";
-        String userDataName = "userDataName";
-        when(userDataDaoMock.findById(anyLong())).thenReturn(userDataVO);
-        when(userDataVO.getUuid()).thenReturn(userDataUuid);
-        when(userDataVO.getName()).thenReturn(userDataName);
+            UserDataVO userDataVO = Mockito.mock(UserDataVO.class);
+            String userDataUuid = "userDataUuid";
+            String userDataName = "userDataName";
+            when(userDataDaoMock.findById(anyLong())).thenReturn(userDataVO);
+            when(userDataVO.getUuid()).thenReturn(userDataUuid);
+            when(userDataVO.getName()).thenReturn(userDataName);
 
-        AutoScaleVmProfileResponse response = apiResponseHelper.createAutoScaleVmProfileResponse(vmProfile);
-        assertEquals(templatePolicy.toString(), response.getUserDataPolicy());
-        assertEquals(userdata, response.getUserData());
-        assertEquals(userDataUuid, response.getUserDataId());
-        assertEquals(userDataName, response.getUserDataName());
-        assertEquals(userdataDetails, response.getUserDataDetails());
+            AutoScaleVmProfileResponse response = apiResponseHelper.createAutoScaleVmProfileResponse(vmProfile);
+            assertEquals(templatePolicy.toString(), response.getUserDataPolicy());
+            assertEquals(userdata, response.getUserData());
+            assertEquals(userDataUuid, response.getUserDataId());
+            assertEquals(userDataName, response.getUserDataName());
+            assertEquals(userdataDetails, response.getUserDataDetails());
+        }
     }
 
     @Test
-    @PrepareForTest(ApiDBUtils.class)
     public void testAutoScaleVmProfileResponseWithoutUserData() {
         AutoScaleVmProfileVO vmProfile = new AutoScaleVmProfileVO(zoneId, domainId, accountId, serviceOfferingId, templateId, null, null, null, null, autoScaleUserId);
 
-        PowerMockito.mockStatic(ApiDBUtils.class);
-        when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
-        when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
+        try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
+            when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new AccountVO());
+            when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
 
-        VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
-        when(ApiDBUtils.findTemplateById(anyLong())).thenReturn(templateVO);
+            VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
+            when(ApiDBUtils.findTemplateById(anyLong())).thenReturn(templateVO);
 
-        AutoScaleVmProfileResponse response = apiResponseHelper.createAutoScaleVmProfileResponse(vmProfile);
-        assertNull(response.getUserDataPolicy());
-        assertNull(response.getUserData());
-        assertNull(response.getUserDataId());
-        assertNull(response.getUserDataName());
-        assertNull(response.getUserDataDetails());
+            AutoScaleVmProfileResponse response = apiResponseHelper.createAutoScaleVmProfileResponse(vmProfile);
+            assertNull(response.getUserDataPolicy());
+            assertNull(response.getUserData());
+            assertNull(response.getUserDataId());
+            assertNull(response.getUserDataName());
+            assertNull(response.getUserDataDetails());
+        }
+    }
+
+    private UnmanagedInstanceTO getUnmanagedInstaceForTests() {
+        UnmanagedInstanceTO instance = Mockito.mock(UnmanagedInstanceTO.class);
+        Mockito.when(instance.getPowerState()).thenReturn(UnmanagedInstanceTO.PowerState.PowerOff);
+        Mockito.when(instance.getClusterName()).thenReturn("CL1");
+        UnmanagedInstanceTO.Disk disk = Mockito.mock(UnmanagedInstanceTO.Disk.class);
+        Mockito.when(disk.getDiskId()).thenReturn("0");
+        Mockito.when(disk.getLabel()).thenReturn("Hard disk 1");
+        Mockito.when(disk.getCapacity()).thenReturn(17179869184L);
+        Mockito.when(disk.getPosition()).thenReturn(0);
+        Mockito.when(instance.getDisks()).thenReturn(List.of(disk));
+        UnmanagedInstanceTO.Nic nic = Mockito.mock(UnmanagedInstanceTO.Nic.class);
+        Mockito.when(nic.getNicId()).thenReturn("Network adapter 1");
+        Mockito.when(nic.getMacAddress()).thenReturn("aa:bb:cc:dd:ee:ff");
+        Mockito.when(instance.getNics()).thenReturn(List.of(nic));
+        return instance;
+    }
+
+    @Test
+    public void testCreateUnmanagedInstanceResponseVmwareDcVms() {
+        UnmanagedInstanceTO instance = getUnmanagedInstaceForTests();
+        UnmanagedInstanceResponse response = apiResponseHelper.createUnmanagedInstanceResponse(instance, null, null);
+        Assert.assertEquals(1, response.getDisks().size());
+        Assert.assertEquals(1, response.getNics().size());
+    }
+
+    @Test
+    public void createQuarantinedIpsResponseTestReturnsObject() {
+        String quarantinedIpUuid = "quarantined_ip_uuid";
+        Long previousOwnerId = 300L;
+        String previousOwnerUuid = "previous_owner_uuid";
+        String previousOwnerName = "previous_owner_name";
+        Long removerAccountId = 400L;
+        String removerAccountUuid = "remover_account_uuid";
+        Long publicIpAddressId = 500L;
+        String publicIpAddress = "1.2.3.4";
+        Date created = new Date(599L);
+        Date removed = new Date(600L);
+        Date endDate = new Date(601L);
+        String removalReason = "removalReason";
+
+        PublicIpQuarantine quarantinedIpMock = Mockito.mock(PublicIpQuarantine.class);
+        IPAddressVO ipAddressVoMock = Mockito.mock(IPAddressVO.class);
+        Account previousOwner = Mockito.mock(Account.class);
+        Account removerAccount = Mockito.mock(Account.class);
+
+        Mockito.when(quarantinedIpMock.getUuid()).thenReturn(quarantinedIpUuid);
+        Mockito.when(quarantinedIpMock.getPreviousOwnerId()).thenReturn(previousOwnerId);
+        Mockito.when(quarantinedIpMock.getPublicIpAddressId()).thenReturn(publicIpAddressId);
+        Mockito.doReturn(ipAddressVoMock).when(ipAddressDaoMock).findById(publicIpAddressId);
+        Mockito.when(ipAddressVoMock.getAddress()).thenReturn(new Ip(publicIpAddress));
+        Mockito.doReturn(previousOwner).when(accountManagerMock).getAccount(previousOwnerId);
+        Mockito.when(previousOwner.getUuid()).thenReturn(previousOwnerUuid);
+        Mockito.when(previousOwner.getName()).thenReturn(previousOwnerName);
+        Mockito.when(quarantinedIpMock.getCreated()).thenReturn(created);
+        Mockito.when(quarantinedIpMock.getRemoved()).thenReturn(removed);
+        Mockito.when(quarantinedIpMock.getEndDate()).thenReturn(endDate);
+        Mockito.when(quarantinedIpMock.getRemovalReason()).thenReturn(removalReason);
+        Mockito.when(quarantinedIpMock.getRemoverAccountId()).thenReturn(removerAccountId);
+        Mockito.when(removerAccount.getUuid()).thenReturn(removerAccountUuid);
+        Mockito.doReturn(removerAccount).when(accountManagerMock).getAccount(removerAccountId);
+
+        IpQuarantineResponse result = apiResponseHelper.createQuarantinedIpsResponse(quarantinedIpMock);
+
+        Assert.assertEquals(quarantinedIpUuid, result.getId());
+        Assert.assertEquals(publicIpAddress, result.getPublicIpAddress());
+        Assert.assertEquals(previousOwnerUuid, result.getPreviousOwnerId());
+        Assert.assertEquals(previousOwnerName, result.getPreviousOwnerName());
+        Assert.assertEquals(created, result.getCreated());
+        Assert.assertEquals(removed, result.getRemoved());
+        Assert.assertEquals(endDate, result.getEndDate());
+        Assert.assertEquals(removalReason, result.getRemovalReason());
+        Assert.assertEquals(removerAccountUuid, result.getRemoverAccountId());
+        Assert.assertEquals("quarantinedip", result.getResponseName());
     }
 }
diff --git a/server/src/test/java/com/cloud/api/ApiServletTest.java b/server/src/test/java/com/cloud/api/ApiServletTest.java
index 250d2b0..4d4f0a1 100644
--- a/server/src/test/java/com/cloud/api/ApiServletTest.java
+++ b/server/src/test/java/com/cloud/api/ApiServletTest.java
@@ -16,23 +16,17 @@
 // under the License.
 package com.cloud.api;
 
-import static org.mockito.ArgumentMatchers.nullable;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
-import java.net.InetAddress;
-import java.net.URLEncoder;
-import java.net.UnknownHostException;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
+import com.cloud.api.auth.ListUserTwoFactorAuthenticatorProvidersCmd;
+import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
+import com.cloud.api.auth.ValidateUserTwoFactorAuthenticationCodeCmd;
+import com.cloud.server.ManagementServer;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManagerImpl;
+import com.cloud.user.AccountService;
+import com.cloud.user.User;
+import com.cloud.user.UserAccount;
+import com.cloud.utils.HttpUtils;
+import com.cloud.vm.UserVmManager;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.auth.APIAuthenticationManager;
 import org.apache.cloudstack.api.auth.APIAuthenticationType;
@@ -46,20 +40,24 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-
-import com.cloud.api.auth.ListUserTwoFactorAuthenticatorProvidersCmd;
-import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
-import com.cloud.api.auth.ValidateUserTwoFactorAuthenticationCodeCmd;
-import com.cloud.server.ManagementServer;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManagerImpl;
-import com.cloud.user.AccountService;
-import com.cloud.user.User;
-import com.cloud.user.UserAccount;
-import com.cloud.utils.HttpUtils;
-import com.cloud.vm.UserVmManager;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.URLEncoder;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.nullable;
+
 @RunWith(MockitoJUnitRunner.class)
 public class ApiServletTest {
 
diff --git a/server/src/test/java/com/cloud/api/ListPerfTest.java b/server/src/test/java/com/cloud/api/ListPerfTest.java
index a376a00..5162a6b 100644
--- a/server/src/test/java/com/cloud/api/ListPerfTest.java
+++ b/server/src/test/java/com/cloud/api/ListPerfTest.java
@@ -16,11 +16,11 @@
 // under the License.
 package com.cloud.api;
 
-import java.util.HashMap;
-
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.HashMap;
+
 /**
  * Test fixture to do performance test for list command
  * Currently we commented out this test suite since it requires a real MS and Db running.
diff --git a/server/src/test/java/com/cloud/api/LoginResponse.java b/server/src/test/java/com/cloud/api/LoginResponse.java
index 81c61d8..5158e2e 100644
--- a/server/src/test/java/com/cloud/api/LoginResponse.java
+++ b/server/src/test/java/com/cloud/api/LoginResponse.java
@@ -16,11 +16,9 @@
 // under the License.
 package com.cloud.api;
 
-import com.google.gson.annotations.SerializedName;
-
-import org.apache.cloudstack.api.BaseResponse;
-
 import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.BaseResponse;
 
 /**
  * Login Response object
diff --git a/server/src/test/java/com/cloud/api/dispatch/CommandCreationWorkerTest.java b/server/src/test/java/com/cloud/api/dispatch/CommandCreationWorkerTest.java
index 90b9bdf..86bf373 100644
--- a/server/src/test/java/com/cloud/api/dispatch/CommandCreationWorkerTest.java
+++ b/server/src/test/java/com/cloud/api/dispatch/CommandCreationWorkerTest.java
@@ -16,25 +16,23 @@
 // under the License.
 package com.cloud.api.dispatch;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
+import com.cloud.exception.ResourceAllocationException;
 import com.cloud.user.Account;
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
+import org.apache.cloudstack.api.BaseAsyncCreateCmd;
 import org.apache.cloudstack.context.CallContext;
 import org.junit.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import org.apache.cloudstack.api.BaseAsyncCreateCmd;
-
-import com.cloud.exception.ResourceAllocationException;
-
 public class CommandCreationWorkerTest {
 
     @Test
diff --git a/server/src/test/java/com/cloud/api/dispatch/DispatchChainFactoryTest.java b/server/src/test/java/com/cloud/api/dispatch/DispatchChainFactoryTest.java
index f54caae..ee5fa3f 100644
--- a/server/src/test/java/com/cloud/api/dispatch/DispatchChainFactoryTest.java
+++ b/server/src/test/java/com/cloud/api/dispatch/DispatchChainFactoryTest.java
@@ -17,6 +17,7 @@
 package com.cloud.api.dispatch;
 
 import org.junit.Test;
+
 import static org.junit.Assert.assertEquals;
 
 public class DispatchChainFactoryTest {
diff --git a/server/src/test/java/com/cloud/api/dispatch/ParamGenericValidationWorkerTest.java b/server/src/test/java/com/cloud/api/dispatch/ParamGenericValidationWorkerTest.java
index 822a2ca..bfb1b29 100644
--- a/server/src/test/java/com/cloud/api/dispatch/ParamGenericValidationWorkerTest.java
+++ b/server/src/test/java/com/cloud/api/dispatch/ParamGenericValidationWorkerTest.java
@@ -16,15 +16,15 @@
 // under the License.
 package com.cloud.api.dispatch;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
+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.user.AccountVO;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
@@ -36,15 +36,14 @@
 import org.apache.log4j.Logger;
 import org.junit.Test;
 
-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.user.AccountVO;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class ParamGenericValidationWorkerTest {
 
diff --git a/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java b/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
index 7ac982d..cf2a43e 100644
--- a/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
+++ b/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
@@ -18,8 +18,18 @@
  */
 package com.cloud.api.dispatch;
 
-import java.util.HashMap;
-
+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.user.AccountManager;
+import com.cloud.user.User;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.context.CallContext;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -29,19 +39,7 @@
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import org.apache.cloudstack.api.BaseCmd;
-import org.apache.cloudstack.api.Parameter;
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.context.CallContext;
-
-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.user.AccountManager;
-import com.cloud.user.User;
+import java.util.HashMap;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ParamProcessWorkerTest {
diff --git a/server/src/test/java/com/cloud/api/dispatch/SpecificCmdValidationWorkerTest.java b/server/src/test/java/com/cloud/api/dispatch/SpecificCmdValidationWorkerTest.java
index 4ae7200..f986a47 100644
--- a/server/src/test/java/com/cloud/api/dispatch/SpecificCmdValidationWorkerTest.java
+++ b/server/src/test/java/com/cloud/api/dispatch/SpecificCmdValidationWorkerTest.java
@@ -16,19 +16,17 @@
 // under the License.
 package com.cloud.api.dispatch;
 
+import com.cloud.exception.ResourceAllocationException;
+import org.apache.cloudstack.api.BaseCmd;
+import org.junit.Test;
+
 import java.util.HashMap;
 import java.util.Map;
 
-import org.junit.Test;
-
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import org.apache.cloudstack.api.BaseCmd;
-
-import com.cloud.exception.ResourceAllocationException;
-
 public class SpecificCmdValidationWorkerTest {
 
     @Test
diff --git a/server/src/test/java/com/cloud/api/query/MutualExclusiveIdsManagerBaseTest.java b/server/src/test/java/com/cloud/api/query/MutualExclusiveIdsManagerBaseTest.java
index a4d9261..8c4c71c 100755
--- a/server/src/test/java/com/cloud/api/query/MutualExclusiveIdsManagerBaseTest.java
+++ b/server/src/test/java/com/cloud/api/query/MutualExclusiveIdsManagerBaseTest.java
@@ -18,13 +18,8 @@
 //
 package com.cloud.api.query;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.never;
-
-import java.util.Arrays;
-import java.util.List;
-
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.db.SearchCriteria;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,8 +27,12 @@
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.utils.db.SearchCriteria;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.never;
 
 @RunWith(MockitoJUnitRunner.class)
 public class MutualExclusiveIdsManagerBaseTest {
diff --git a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
index 5f7231a..be8978e 100644
--- a/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/QueryManagerImplTest.java
@@ -17,62 +17,104 @@
 
 package com.cloud.api.query;
 
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.cloudstack.acl.SecurityChecker;
-import org.apache.cloudstack.api.ApiCommandResourceType;
-import org.apache.cloudstack.api.command.user.event.ListEventsCmd;
-import org.apache.cloudstack.api.response.EventResponse;
-import org.apache.cloudstack.api.response.ListResponse;
-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.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
+import com.cloud.api.query.dao.TemplateJoinDao;
 import com.cloud.api.query.vo.EventJoinVO;
+import com.cloud.api.query.vo.TemplateJoinVO;
+import com.cloud.event.EventVO;
+import com.cloud.event.dao.EventDao;
 import com.cloud.event.dao.EventJoinDao;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.PermissionDeniedException;
 import com.cloud.network.Network;
+import com.cloud.network.VNF;
 import com.cloud.network.dao.NetworkVO;
-import com.cloud.projects.Project;
+import com.cloud.server.ResourceTag;
+import com.cloud.storage.BucketVO;
+import com.cloud.storage.dao.BucketDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
 import com.cloud.utils.Pair;
-import com.cloud.utils.component.ComponentContext;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.db.Filter;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.acl.SecurityChecker;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.command.admin.storage.ListObjectStoragePoolsCmd;
+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.resource.ListDetailOptionsCmd;
+import org.apache.cloudstack.api.response.DetailOptionsResponse;
+import org.apache.cloudstack.api.response.EventResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ObjectStoreResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+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.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ComponentContext.class, ViewResponseHelper.class})
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class QueryManagerImplTest {
     public static final long USER_ID = 1;
     public static final long ACCOUNT_ID = 1;
 
+    @Spy
+    @InjectMocks
+    private QueryManagerImpl queryManagerImplSpy = new QueryManagerImpl();
+
     @Mock
     EntityManager entityManager;
+
     @Mock
     AccountManager accountManager;
+
+    @Mock
+    EventDao eventDao;
+
     @Mock
     EventJoinDao eventJoinDao;
 
+    @Mock
+    Account accountMock;
+
+    @Mock
+    TemplateJoinDao templateJoinDaoMock;
+
+    @Mock
+    SearchCriteria searchCriteriaMock;
+
+    @Mock
+    ObjectStoreDao objectStoreDao;
+
+    @Mock
+    BucketDao bucketDao;
+
     private AccountVO account;
     private UserVO user;
 
@@ -91,19 +133,12 @@
                 UUID.randomUUID().toString(), User.Source.UNKNOWN);
         CallContext.register(user, account);
         Mockito.when(accountManager.isRootAdmin(account.getId())).thenReturn(false);
-        Mockito.doNothing().when(accountManager).buildACLSearchParameters(Mockito.any(Account.class), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyList(),
-                Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
-        Mockito.doNothing().when(accountManager).buildACLSearchBuilder(Mockito.any(SearchBuilder.class), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(),
-                Mockito.any(Project.ListProjectResourcesCriteria.class));
-        Mockito.doNothing().when(accountManager).buildACLViewSearchCriteria(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(),
-                Mockito.any(Project.ListProjectResourcesCriteria.class));
-        final SearchBuilder<EventJoinVO> searchBuilder = Mockito.mock(SearchBuilder.class);
-        final SearchCriteria<EventJoinVO> searchCriteria = Mockito.mock(SearchCriteria.class);
-        final EventJoinVO eventJoinVO = Mockito.mock(EventJoinVO.class);
-        when(searchBuilder.entity()).thenReturn(eventJoinVO);
-        when(searchBuilder.create()).thenReturn(searchCriteria);
-        Mockito.when(eventJoinDao.createSearchBuilder()).thenReturn(searchBuilder);
-        Mockito.when(eventJoinDao.createSearchCriteria()).thenReturn(searchCriteria);
+        final SearchBuilder<EventVO> eventSearchBuilder = Mockito.mock(SearchBuilder.class);
+        final SearchCriteria<EventVO> eventSearchCriteria = Mockito.mock(SearchCriteria.class);
+        final EventVO eventVO = Mockito.mock(EventVO.class);
+        when(eventSearchBuilder.entity()).thenReturn(eventVO);
+        when(eventSearchBuilder.create()).thenReturn(eventSearchCriteria);
+        Mockito.when(eventDao.createSearchBuilder()).thenReturn(eventSearchBuilder);
     }
 
     private ListEventsCmd setupMockListEventsCmd() {
@@ -119,25 +154,33 @@
         String uuid = UUID.randomUUID().toString();
         Mockito.when(cmd.getResourceId()).thenReturn(uuid);
         Mockito.when(cmd.getResourceType()).thenReturn(ApiCommandResourceType.Network.toString());
-        List<EventJoinVO> events = new ArrayList<>();
-        events.add(Mockito.mock(EventJoinVO.class));
-        events.add(Mockito.mock(EventJoinVO.class));
-        events.add(Mockito.mock(EventJoinVO.class));
-        Pair<List<EventJoinVO>, Integer> pair = new Pair<>(events, events.size());
+        List<EventVO> events = new ArrayList<>();
+        events.add(Mockito.mock(EventVO.class));
+        events.add(Mockito.mock(EventVO.class));
+        events.add(Mockito.mock(EventVO.class));
+        Pair<List<EventVO>, Integer> pair = new Pair<>(events, events.size());
+
+        List<EventJoinVO> eventJoins = new ArrayList<>();
+        eventJoins.add(Mockito.mock(EventJoinVO.class));
+        eventJoins.add(Mockito.mock(EventJoinVO.class));
+        eventJoins.add(Mockito.mock(EventJoinVO.class));
+
         NetworkVO network = Mockito.mock(NetworkVO.class);
         Mockito.when(network.getId()).thenReturn(1L);
         Mockito.when(network.getAccountId()).thenReturn(account.getId());
         Mockito.when(entityManager.findByUuidIncludingRemoved(Network.class, uuid)).thenReturn(network);
         Mockito.doNothing().when(accountManager).checkAccess(account, SecurityChecker.AccessType.ListEntry, true, network);
-        Mockito.when(eventJoinDao.searchAndCount(Mockito.any(), Mockito.any(Filter.class))).thenReturn(pair);
+        Mockito.when(eventDao.searchAndCount(Mockito.any(), Mockito.any(Filter.class))).thenReturn(pair);
+        Mockito.when(eventJoinDao.searchByIds(Mockito.any())).thenReturn(eventJoins);
         List<EventResponse> respList = new ArrayList<EventResponse>();
-        for (EventJoinVO vt : events) {
+        for (EventJoinVO vt : eventJoins) {
             respList.add(eventJoinDao.newEventResponse(vt));
         }
-        PowerMockito.mockStatic(ViewResponseHelper.class);
-        Mockito.when(ViewResponseHelper.createEventResponse(Mockito.any())).thenReturn(respList);
-        ListResponse<EventResponse> result = queryManager.searchForEvents(cmd);
-        Assert.assertEquals((int) result.getCount(), events.size());
+        try (MockedStatic<ViewResponseHelper> ignored = Mockito.mockStatic(ViewResponseHelper.class)) {
+            Mockito.when(ViewResponseHelper.createEventResponse(Mockito.any())).thenReturn(respList);
+            ListResponse<EventResponse> result = queryManager.searchForEvents(cmd);
+            Assert.assertEquals((int) result.getCount(), events.size());
+        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -187,4 +230,126 @@
         Mockito.doThrow(new PermissionDeniedException("Denied")).when(accountManager).checkAccess(account, SecurityChecker.AccessType.ListEntry, false, network);
         queryManager.searchForEvents(cmd);
     }
+
+    @Test
+    public void listVnfDetailOptionsCmd() {
+        ListDetailOptionsCmd cmd = Mockito.mock(ListDetailOptionsCmd.class);
+        when(cmd.getResourceType()).thenReturn(ResourceTag.ResourceObjectType.VnfTemplate);
+
+        DetailOptionsResponse response = queryManager.listDetailOptions(cmd);
+        Map<String, List<String>> options = response.getDetails();
+
+        int expectedLength = VNF.AccessDetail.values().length + VNF.VnfDetail.values().length;
+        Assert.assertEquals(expectedLength, options.size());
+        Set<String> keys = options.keySet();
+        for (VNF.AccessDetail detail : VNF.AccessDetail.values()) {
+            Assert.assertTrue(keys.contains(detail.name().toLowerCase()));
+        }
+        for (VNF.VnfDetail detail : VNF.VnfDetail.values()) {
+            Assert.assertTrue(keys.contains(detail.name().toLowerCase()));
+        }
+        List<String> expectedAccessMethods = Arrays.stream(VNF.AccessMethod.values()).map(method -> method.toString()).sorted().collect(Collectors.toList());
+        Assert.assertEquals(expectedAccessMethods, options.get(VNF.AccessDetail.ACCESS_METHODS.name().toLowerCase()));
+
+    }
+
+    @Test
+    public void applyPublicTemplateRestrictionsTestDoesNotApplyRestrictionsWhenCallerIsRootAdmin() {
+        Mockito.when(accountMock.getType()).thenReturn(Account.Type.ADMIN);
+
+        queryManagerImplSpy.applyPublicTemplateSharingRestrictions(searchCriteriaMock, accountMock);
+
+        Mockito.verify(searchCriteriaMock, Mockito.never()).addAnd(Mockito.anyString(), Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void applyPublicTemplateRestrictionsTestAppliesRestrictionsWhenCallerIsNotRootAdmin() {
+        long callerDomainId = 1L;
+        long sharableDomainId = 2L;
+        long unsharableDomainId = 3L;
+
+        Mockito.when(accountMock.getType()).thenReturn(Account.Type.NORMAL);
+
+        Mockito.when(accountMock.getDomainId()).thenReturn(callerDomainId);
+        TemplateJoinVO templateMock1 = Mockito.mock(TemplateJoinVO.class);
+        Mockito.when(templateMock1.getDomainId()).thenReturn(callerDomainId);
+        Mockito.lenient().doReturn(false).when(queryManagerImplSpy).checkIfDomainSharesTemplates(callerDomainId);
+
+        TemplateJoinVO templateMock2 = Mockito.mock(TemplateJoinVO.class);
+        Mockito.when(templateMock2.getDomainId()).thenReturn(sharableDomainId);
+        Mockito.doReturn(true).when(queryManagerImplSpy).checkIfDomainSharesTemplates(sharableDomainId);
+
+        TemplateJoinVO templateMock3 = Mockito.mock(TemplateJoinVO.class);
+        Mockito.when(templateMock3.getDomainId()).thenReturn(unsharableDomainId);
+        Mockito.doReturn(false).when(queryManagerImplSpy).checkIfDomainSharesTemplates(unsharableDomainId);
+
+        List<TemplateJoinVO> publicTemplates = List.of(templateMock1, templateMock2, templateMock3);
+        Mockito.when(templateJoinDaoMock.listPublicTemplates()).thenReturn(publicTemplates);
+
+        queryManagerImplSpy.applyPublicTemplateSharingRestrictions(searchCriteriaMock, accountMock);
+
+        Mockito.verify(searchCriteriaMock).addAnd("domainId", SearchCriteria.Op.NOTIN, unsharableDomainId);
+    }
+
+    @Test
+    public void addDomainIdToSetIfDomainDoesNotShareTemplatesTestDoesNotAddWhenCallerBelongsToDomain() {
+        long domainId = 1L;
+        Set<Long> set = new HashSet<>();
+
+        Mockito.when(accountMock.getDomainId()).thenReturn(domainId);
+
+        queryManagerImplSpy.addDomainIdToSetIfDomainDoesNotShareTemplates(domainId, accountMock, set);
+
+        Assert.assertEquals(0, set.size());
+    }
+
+    @Test
+    public void addDomainIdToSetIfDomainDoesNotShareTemplatesTestAddsWhenDomainDoesNotShareTemplates() {
+        long domainId = 1L;
+        Set<Long> set = new HashSet<>();
+
+        Mockito.when(accountMock.getDomainId()).thenReturn(2L);
+        Mockito.doReturn(false).when(queryManagerImplSpy).checkIfDomainSharesTemplates(domainId);
+
+        queryManagerImplSpy.addDomainIdToSetIfDomainDoesNotShareTemplates(domainId, accountMock, set);
+
+        Assert.assertTrue(set.contains(domainId));
+    }
+
+    @Test
+    public void testSearchForObjectStores() {
+        ListObjectStoragePoolsCmd cmd = new ListObjectStoragePoolsCmd();
+        List<ObjectStoreVO> objectStores = new ArrayList<>();
+        ObjectStoreVO os1 = new ObjectStoreVO();
+        os1.setName("MinIOStore");
+        ObjectStoreVO os2 = new ObjectStoreVO();
+        os1.setName("Simulator");
+        objectStores.add(os1);
+        objectStores.add(os2);
+        SearchBuilder<ObjectStoreVO> sb = Mockito.mock(SearchBuilder.class);
+        ObjectStoreVO objectStoreVO = Mockito.mock(ObjectStoreVO.class);
+        when(sb.entity()).thenReturn(objectStoreVO);
+        when(objectStoreDao.createSearchBuilder()).thenReturn(sb);
+        when(objectStoreDao.searchAndCount(any(), any())).thenReturn(new Pair<>(objectStores, 2));
+        ListResponse<ObjectStoreResponse> result = queryManagerImplSpy.searchForObjectStores(cmd);
+        assertEquals(2, result.getCount().intValue());
+    }
+
+    @Test
+    public void testSearchForBuckets() {
+        ListBucketsCmd listBucketsCmd = new ListBucketsCmd();
+        List<BucketVO> buckets = new ArrayList<>();
+        BucketVO b1 = new BucketVO();
+        b1.setName("test-bucket-1");
+        BucketVO b2 = new BucketVO();
+        b2.setName("test-bucket-1");
+        buckets.add(b1);
+        buckets.add(b2);
+        SearchBuilder<BucketVO> sb = Mockito.mock(SearchBuilder.class);
+        BucketVO bucketVO = Mockito.mock(BucketVO.class);
+        when(sb.entity()).thenReturn(bucketVO);
+        when(bucketDao.createSearchBuilder()).thenReturn(sb);
+        when(bucketDao.searchAndCount(any(), any())).thenReturn(new Pair<>(buckets, 2));
+        queryManagerImplSpy.searchForBuckets(listBucketsCmd);
+    }
 }
diff --git a/server/src/test/java/com/cloud/api/query/dao/GenericDaoBaseWithTagInformationBaseTest.java b/server/src/test/java/com/cloud/api/query/dao/GenericDaoBaseWithTagInformationBaseTest.java
index c7c65f8..a227ae3 100755
--- a/server/src/test/java/com/cloud/api/query/dao/GenericDaoBaseWithTagInformationBaseTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/GenericDaoBaseWithTagInformationBaseTest.java
@@ -16,15 +16,18 @@
 // under the License.
 package com.cloud.api.query.dao;
 
-import static org.junit.Assert.assertEquals;
-
-import org.apache.cloudstack.api.BaseResponseWithTagInformation;
-import org.apache.cloudstack.api.response.ResourceTagResponse;
-import org.powermock.api.mockito.PowerMockito;
-
 import com.cloud.api.ApiDBUtils;
 import com.cloud.api.query.vo.BaseViewWithTagInformationVO;
+import com.cloud.api.query.vo.ResourceTagJoinVO;
 import com.cloud.server.ResourceTag.ResourceObjectType;
+import org.apache.cloudstack.api.BaseResponseWithTagInformation;
+import org.apache.cloudstack.api.response.ResourceTagResponse;
+import org.junit.After;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.assertEquals;
 
 public abstract class GenericDaoBaseWithTagInformationBaseTest<T extends BaseViewWithTagInformationVO,
                                                                 Z extends BaseResponseWithTagInformation> {
@@ -43,10 +46,16 @@
     private final static String TAG_ACCOUNT_NAME = "admin";
 
     private final static String RESPONSE_OBJECT_NAME = "tag";
+    private MockedStatic<ApiDBUtils> apiDBUtilsMocked;
 
     public void prepareSetup(){
-        PowerMockito.spy(ApiDBUtils.class);
-        PowerMockito.stub(PowerMockito.method(ApiDBUtils.class, "newResourceTagResponse")).toReturn(getResourceTagResponse());
+        apiDBUtilsMocked = Mockito.mockStatic(ApiDBUtils.class);
+        apiDBUtilsMocked.when(ReflectionTestUtils.invokeMethod(ApiDBUtils.class, "newResourceTagResponse", Mockito.any(ResourceTagJoinVO.class), Mockito.anyBoolean())).thenReturn(getResourceTagResponse());
+    }
+
+    @After
+    public void tearDown(){
+        apiDBUtilsMocked.close();
     }
 
     private ResourceTagResponse getResourceTagResponse(){
diff --git a/server/src/test/java/com/cloud/api/query/dao/SecurityGroupJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/SecurityGroupJoinDaoImplTest.java
index db2d68f..e848947 100644
--- a/server/src/test/java/com/cloud/api/query/dao/SecurityGroupJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/SecurityGroupJoinDaoImplTest.java
@@ -16,13 +16,13 @@
 // under the License.
 package com.cloud.api.query.dao;
 
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
+import com.cloud.api.query.vo.SecurityGroupJoinVO;
+import com.cloud.network.security.SecurityGroupVMMapVO;
+import com.cloud.network.security.dao.SecurityGroupVMMapDao;
+import com.cloud.user.Account;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.dao.UserVmDao;
+import junit.framework.TestCase;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.junit.Before;
 import org.junit.Test;
@@ -32,14 +32,12 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.api.query.vo.SecurityGroupJoinVO;
-import com.cloud.network.security.SecurityGroupVMMapVO;
-import com.cloud.network.security.dao.SecurityGroupVMMapDao;
-import com.cloud.user.Account;
-import com.cloud.vm.UserVmVO;
-import com.cloud.vm.dao.UserVmDao;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 
-import junit.framework.TestCase;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class SecurityGroupJoinDaoImplTest extends TestCase {
diff --git a/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java
index 40eb210..94d6722 100755
--- a/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java
@@ -16,33 +16,43 @@
 // under the License.
 package com.cloud.api.query.dao;
 
-import java.util.Date;
-import java.util.Map;
-
+import com.cloud.api.query.vo.TemplateJoinVO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.Storage;
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.storage.dao.VnfTemplateDetailsDao;
+import com.cloud.storage.dao.VnfTemplateNicDao;
+import com.cloud.template.TemplateManager;
+import com.cloud.user.Account;
 import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.api.response.VnfTemplateResponse;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import com.cloud.api.ApiDBUtils;
-import com.cloud.api.query.vo.TemplateJoinVO;
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.storage.Storage;
-import com.cloud.template.TemplateManager;
-import com.cloud.user.Account;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ApiDBUtils.class)
+@RunWith(MockitoJUnitRunner.class)
 public class TemplateJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseTest<TemplateJoinVO, TemplateResponse> {
 
     @InjectMocks
     private TemplateJoinDaoImpl _templateJoinDaoImpl;
 
+    @Mock
+    private VnfTemplateNicDao vnfTemplateNicDao;
+
+    @Mock
+    private VnfTemplateDetailsDao vnfTemplateDetailsDao;
+
     private TemplateJoinVO template = new TemplateJoinVO();
     private TemplateResponse templateResponse = new TemplateResponse();
 
@@ -64,6 +74,8 @@
     private String domainName = "ROOT";
     private String detailName = "detail_name1";
     private String detailValue = "detail_val";
+    private Storage.TemplateType templateType = Storage.TemplateType.VNF;
+    private Long templateId = 101L;
 
     @Before
     public void setup() {
@@ -72,7 +84,7 @@
     }
 
     @Test
-    public void testUpdateTemplateTagInfo(){
+    public void testUpdateTemplateTagInfo() {
         testUpdateTagInformation(_templateJoinDaoImpl, template, templateResponse);
     }
 
@@ -93,8 +105,8 @@
         Assert.assertEquals(accountName, ReflectionTestUtils.getField(response, "account"));
         Assert.assertEquals(domainUuid, ReflectionTestUtils.getField(response, "domainId"));
         Assert.assertEquals(domainName, ReflectionTestUtils.getField(response, "domainName"));
-        Assert.assertTrue(((Map)ReflectionTestUtils.getField(response, "details")).containsKey(detailName));
-        Assert.assertEquals(detailValue, ((Map)ReflectionTestUtils.getField(response, "details")).get(detailName));
+        Assert.assertTrue(((Map) ReflectionTestUtils.getField(response, "details")).containsKey(detailName));
+        Assert.assertEquals(detailValue, ((Map) ReflectionTestUtils.getField(response, "details")).get(detailName));
     }
 
     private void populateTemplateJoinVO() {
@@ -115,5 +127,27 @@
         ReflectionTestUtils.setField(template, "domainName", domainName);
         ReflectionTestUtils.setField(template, "detailName", detailName);
         ReflectionTestUtils.setField(template, "detailValue", detailValue);
+        ReflectionTestUtils.setField(template, "templateType", templateType);
+    }
+
+    @Test
+    public void testNewUpdateResponseForVnf() {
+        ReflectionTestUtils.setField(template, "id", templateId);
+        ReflectionTestUtils.setField(template, "templateType", templateType);
+
+        VnfTemplateNicVO vnfNic1 = new VnfTemplateNicVO(templateId, 0L, "eth0", true, true, "first");
+        VnfTemplateNicVO vnfNic2 = new VnfTemplateNicVO(templateId, 1L, "eth1", true, true, "second");
+        Mockito.doReturn(Arrays.asList(vnfNic1, vnfNic2)).when(vnfTemplateNicDao).listByTemplateId(templateId);
+
+        VnfTemplateDetailVO detail1 = new VnfTemplateDetailVO(templateId, "name1", "value1", true);
+        VnfTemplateDetailVO detail2 = new VnfTemplateDetailVO(templateId, "name2", "value2", true);
+        VnfTemplateDetailVO detail3 = new VnfTemplateDetailVO(templateId, "name3", "value3", true);
+        Mockito.doReturn(Arrays.asList(detail1, detail2, detail3)).when(vnfTemplateDetailsDao).listDetails(templateId);
+
+        final TemplateResponse response = _templateJoinDaoImpl.newUpdateResponse(template);
+        Assert.assertTrue(response instanceof VnfTemplateResponse);
+        Assert.assertEquals(2, ((VnfTemplateResponse)response).getVnfNics().size());
+        Assert.assertEquals(3, ((VnfTemplateResponse)response).getVnfDetails().size());
+
     }
 }
diff --git a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
index d44e5b1..320c556 100755
--- a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
@@ -16,35 +16,140 @@
 // under the License.
 package com.cloud.api.query.dao;
 
+import com.cloud.api.query.vo.UserVmJoinVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.storage.dao.VnfTemplateDetailsDao;
+import com.cloud.storage.dao.VnfTemplateNicDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.UserStatisticsVO;
+import com.cloud.user.dao.UserDao;
+import com.cloud.user.dao.UserStatisticsDao;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.vm.dao.UserVmDetailsDao;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.response.UserVmResponse;
+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.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.api.ApiDBUtils;
-import com.cloud.api.query.vo.UserVmJoinVO;
+import java.util.Arrays;
+import java.util.EnumSet;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ApiDBUtils.class)
+import static org.mockito.ArgumentMatchers.nullable;
+
+@RunWith(MockitoJUnitRunner.class)
 public class UserVmJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseTest<UserVmJoinVO, UserVmResponse> {
 
     @InjectMocks
     private UserVmJoinDaoImpl _userVmJoinDaoImpl;
 
+    @Mock
+    private UserDao userDao;
+
+    @Mock
+    private AnnotationDao annotationDao;
+
+    @Mock
+    private AccountManager accountMgr;
+
+    @Mock
+    private UserVmDetailsDao _userVmDetailsDao;
+
+    @Mock
+    private UserStatisticsDao userStatsDao;
+
+    @Mock
+    private VnfTemplateNicDao vnfTemplateNicDao;
+
+    @Mock
+    private VnfTemplateDetailsDao vnfTemplateDetailsDao;
+
     private UserVmJoinVO userVm = new UserVmJoinVO();
     private UserVmResponse userVmResponse = new UserVmResponse();
 
+    @Mock
+    Account caller;
+
+    @Mock
+    UserVmJoinVO userVmMock;
+
+    private Long vmId = 100L;
+
+    private Long templateId = 101L;
+
     @Before
     public void setup() {
         prepareSetup();
     }
 
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+    }
+
     @Test
     public void testUpdateUserVmTagInfo(){
         testUpdateTagInformation(_userVmJoinDaoImpl, userVm, userVmResponse);
     }
 
+    private void prepareNewUserVmResponseForVnfAppliance() {
+        Mockito.when(userVmMock.getId()).thenReturn(vmId);
+        Mockito.when(userVmMock.getTemplateId()).thenReturn(templateId);
+        Mockito.when(userVmMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
+
+        Mockito.when(caller.getId()).thenReturn(2L);
+        Mockito.when(accountMgr.isRootAdmin(nullable(Long.class))).thenReturn(true);
+
+        SearchBuilder<UserStatisticsVO> searchBuilderMock = Mockito.mock(SearchBuilder.class);
+        Mockito.doReturn(searchBuilderMock).when(userStatsDao).createSearchBuilder();
+        UserStatisticsVO userStatisticsVOMock = Mockito.mock(UserStatisticsVO.class);
+        Mockito.when(searchBuilderMock.entity()).thenReturn(userStatisticsVOMock);
+        SearchCriteria<UserStatisticsVO> searchCriteriaMock = Mockito.mock(SearchCriteria.class);
+        Mockito.doReturn(searchCriteriaMock).when(searchBuilderMock).create();
+        Mockito.doReturn(Arrays.asList()).when(userStatsDao).search(searchCriteriaMock, null);
+
+        VnfTemplateNicVO vnfNic1 = new VnfTemplateNicVO(templateId, 0L, "eth0", true, true, "first");
+        VnfTemplateNicVO vnfNic2 = new VnfTemplateNicVO(templateId, 1L, "eth1", true, true, "second");
+        Mockito.doReturn(Arrays.asList(vnfNic1, vnfNic2)).when(vnfTemplateNicDao).listByTemplateId(templateId);
+
+        VnfTemplateDetailVO detail1 = new VnfTemplateDetailVO(templateId, "name1", "value1", true);
+        VnfTemplateDetailVO detail2 = new VnfTemplateDetailVO(templateId, "name2", "value2", true);
+        VnfTemplateDetailVO detail3 = new VnfTemplateDetailVO(templateId, "name3", "value3", true);
+        Mockito.doReturn(Arrays.asList(detail1, detail2, detail3)).when(vnfTemplateDetailsDao).listDetails(templateId);
+    }
+
+    @Test
+    public void testNewUserVmResponseForVnfAppliance() {
+        prepareNewUserVmResponseForVnfAppliance();
+
+        UserVmResponse response = _userVmJoinDaoImpl.newUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock,
+                EnumSet.of(ApiConstants.VMDetails.all), null, null, caller);
+
+        Assert.assertEquals(2, response.getVnfNics().size());
+        Assert.assertEquals(3, response.getVnfDetails().size());
+    }
+
+    @Test
+    public void testNewUserVmResponseForVnfApplianceVnfNics() {
+        prepareNewUserVmResponseForVnfAppliance();
+
+        UserVmResponse response = _userVmJoinDaoImpl.newUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock,
+                EnumSet.of(ApiConstants.VMDetails.vnfnics), null, null, caller);
+
+        Assert.assertEquals(2, response.getVnfNics().size());
+        Assert.assertEquals(3, response.getVnfDetails().size());
+    }
 }
diff --git a/server/src/test/java/com/cloud/api/query/dao/VolumeJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/VolumeJoinDaoImplTest.java
index b0b0ad2..8ac60f4 100755
--- a/server/src/test/java/com/cloud/api/query/dao/VolumeJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/VolumeJoinDaoImplTest.java
@@ -16,19 +16,15 @@
 // under the License.
 package com.cloud.api.query.dao;
 
+import com.cloud.api.query.vo.VolumeJoinVO;
 import org.apache.cloudstack.api.response.VolumeResponse;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.api.ApiDBUtils;
-import com.cloud.api.query.vo.VolumeJoinVO;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ApiDBUtils.class)
+@RunWith(MockitoJUnitRunner.class)
 public class VolumeJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseTest<VolumeJoinVO, VolumeResponse> {
 
     @InjectMocks
diff --git a/server/src/test/java/com/cloud/capacity/CapacityManagerTest.java b/server/src/test/java/com/cloud/capacity/CapacityManagerTest.java
index 686089b..54e1243 100644
--- a/server/src/test/java/com/cloud/capacity/CapacityManagerTest.java
+++ b/server/src/test/java/com/cloud/capacity/CapacityManagerTest.java
@@ -17,21 +17,20 @@
 
 package com.cloud.capacity;
 
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import org.junit.Assert;
-import org.junit.Test;
-
 import com.cloud.capacity.dao.CapacityDao;
 import com.cloud.dc.ClusterDetailsDao;
 import com.cloud.dc.ClusterDetailsVO;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.vm.VirtualMachine;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class CapacityManagerTest {
     CapacityDao CDao = mock(CapacityDao.class);
diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java
index 47dfa4b..958a39b 100644
--- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java
+++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java
@@ -16,31 +16,76 @@
 // under the License.
 package com.cloud.configuration;
 
-import java.util.List;
-
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.storage.StorageManager;
+import com.cloud.utils.net.NetUtils;
 import org.apache.cloudstack.framework.config.ConfigDepot;
 import org.apache.cloudstack.framework.config.ConfigKey;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.domain.Domain;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.offering.DiskOffering;
+import com.cloud.storage.DiskOfferingVO;
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd;
+import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
+import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
 
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.storage.StorageManager;
-import com.cloud.utils.net.NetUtils;
+import java.util.ArrayList;
+import java.util.List;
 
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(NetUtils.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ConfigurationManagerImplTest {
     @Mock
     ConfigDepot configDepot;
+    @InjectMocks
     ConfigurationManagerImpl configurationManagerImplSpy = Mockito.spy(new ConfigurationManagerImpl());
+    @Mock
+    SearchCriteria<DiskOfferingDetailVO> searchCriteriaDiskOfferingDetailMock;
+    @Mock
+    DiskOffering diskOfferingMock;
+    @Mock
+    Account accountMock;
+    @Mock
+    User userMock;
+    @Mock
+    Domain domainMock;
+    @Mock
+    DataCenterDao zoneDaoMock;
+    @Mock
+    DomainDao domainDaoMock;
+    @Mock
+    EntityManager entityManagerMock;
+    @Mock
+    DiskOfferingDetailsDao diskOfferingDetailsDao;
+    @Spy
+    DiskOfferingVO diskOfferingVOSpy;
+    @Mock
+    UpdateDiskOfferingCmd updateDiskOfferingCmdMock;
+
+    Long validId = 1L;
+    Long invalidId = 100L;
+    List<Long> filteredZoneIds = List.of(1L, 2L, 3L);
+    List<Long> existingZoneIds = List.of(1L, 2L, 3L);
+    List<Long> filteredDomainIds = List.of(1L, 2L, 3L);
+    List<Long> existingDomainIds = List.of(1L, 2L, 3L);
+    List<Long> emptyExistingZoneIds = new ArrayList<>();
+    List<Long> emptyExistingDomainIds = new ArrayList<>();
+    List<Long> emptyFilteredDomainIds = new ArrayList<>();
 
     @Before
     public void setUp() throws Exception {
@@ -53,6 +98,7 @@
         Assert.assertNull(testVariable);
     }
 
+
     @Test
     public void validateIfIntValueIsInRangeTestInvalidValueReturnString() {
         String testVariable = configurationManagerImplSpy.validateIfIntValueIsInRange("String name", "9", "1-5");
@@ -61,7 +107,7 @@
 
     @Test
     public void validateIfStringValueIsInRangeTestValidValuesReturnNull() {
-        String testVariable = "";
+        String testVariable;
         List<String> methods = List.of("privateip", "hypervisorList", "instanceName", "domainName", "default");
         Mockito.doReturn(null).when(configurationManagerImplSpy).validateRangePrivateIp(Mockito.anyString(), Mockito.anyString());
         Mockito.doReturn(null).when(configurationManagerImplSpy).validateRangeHypervisorList(Mockito.anyString());
@@ -76,7 +122,7 @@
 
     @Test
     public void validateIfStringValueIsInRangeTestInvalidValuesReturnString() {
-        String testVariable = "";
+        String testVariable;
         List<String> methods = List.of("privateip", "hypervisorList", "instanceName", "domainName", "default");
         Mockito.doReturn("returnMsg").when(configurationManagerImplSpy).validateRangePrivateIp(Mockito.anyString(), Mockito.anyString());
         Mockito.doReturn("returnMsg").when(configurationManagerImplSpy).validateRangeHypervisorList(Mockito.anyString());
@@ -94,7 +140,6 @@
     public void validateIfStringValueIsInRangeTestMultipleRangesValidValueReturnNull() {
         Mockito.doReturn("returnMsg1").when(configurationManagerImplSpy).validateRangePrivateIp(Mockito.anyString(), Mockito.anyString());
         Mockito.doReturn(null).when(configurationManagerImplSpy).validateRangeInstanceName(Mockito.anyString());
-        Mockito.doReturn("returnMsg2").when(configurationManagerImplSpy).validateRangeOther(Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
         String testVariable = configurationManagerImplSpy.validateIfStringValueIsInRange("name", "value", "privateip", "instanceName", "default");
         Assert.assertNull(testVariable);
     }
@@ -111,18 +156,20 @@
 
     @Test
     public void validateRangePrivateIpTestValidValueReturnNull() {
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isSiteLocalAddress(Mockito.anyString())).thenReturn(true);
-        String testVariable = configurationManagerImplSpy.validateRangePrivateIp("name", "value");
-        Assert.assertNull(testVariable);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.isSiteLocalAddress(Mockito.anyString())).thenReturn(true);
+            String testVariable = configurationManagerImplSpy.validateRangePrivateIp("name", "value");
+            Assert.assertNull(testVariable);
+        }
     }
 
     @Test
     public void validateRangePrivateIpTestInvalidValueReturnString() {
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.isSiteLocalAddress(Mockito.anyString())).thenReturn(false);
-        String testVariable = configurationManagerImplSpy.validateRangePrivateIp("name", "value");
-        Assert.assertEquals("a valid site local IP address", testVariable);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.isSiteLocalAddress(Mockito.anyString())).thenReturn(false);
+            String testVariable = configurationManagerImplSpy.validateRangePrivateIp("name", "value");
+            Assert.assertEquals("a valid site local IP address", testVariable);
+        }
     }
 
     @Test
@@ -139,18 +186,20 @@
 
     @Test
     public void validateRangeInstanceNameTestValidValueReturnNull() {
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.verifyInstanceName(Mockito.anyString())).thenReturn(true);
-        String testVariable = configurationManagerImplSpy.validateRangeInstanceName("ThisStringShouldBeValid");
-        Assert.assertNull(testVariable);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.verifyInstanceName(Mockito.anyString())).thenReturn(true);
+            String testVariable = configurationManagerImplSpy.validateRangeInstanceName("ThisStringShouldBeValid");
+            Assert.assertNull(testVariable);
+        }
     }
 
     @Test
     public void validateRangeInstanceNameTestInvalidValueReturnString() {
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.verifyInstanceName(Mockito.anyString())).thenReturn(false);
-        String testVariable = configurationManagerImplSpy.validateRangeInstanceName("This string should not be valid.");
-        Assert.assertEquals("a valid instance name (instance names cannot contain hyphens, spaces or plus signs)", testVariable);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.verifyInstanceName(Mockito.anyString())).thenReturn(false);
+            String testVariable = configurationManagerImplSpy.validateRangeInstanceName("This string should not be valid.");
+            Assert.assertEquals("a valid instance name (instance names cannot contain hyphens, spaces or plus signs)", testVariable);
+        }
     }
 
     @Test
@@ -213,8 +262,6 @@
         configurationManagerImplSpy.validateIpAddressRelatedConfigValues("config.ip", "");
         Mockito.when(configurationManagerImplSpy._configDepot.get("config.ip")).thenReturn(null);
         configurationManagerImplSpy.validateIpAddressRelatedConfigValues("config.ip", "something");
-        ConfigKey<?> key = StorageManager.MountDisabledStoragePool;
-        Mockito.doReturn(key).when(configurationManagerImplSpy._configDepot).get(StorageManager.MountDisabledStoragePool.key());
         configurationManagerImplSpy.validateIpAddressRelatedConfigValues(StorageManager.MountDisabledStoragePool.key(), "false");
     }
 
@@ -252,4 +299,112 @@
         Mockito.doReturn(key).when(configurationManagerImplSpy._configDepot).get("config.iprange");
         configurationManagerImplSpy.validateIpAddressRelatedConfigValues("config.iprange", "192.168.1.1-192.168.1.100");
     }
+
+    @Test
+    public void validateDomainTestInvalidIdThrowException() {
+        Mockito.doReturn(null).when(domainDaoMock).findById(invalidId);
+        Assert.assertThrows(InvalidParameterValueException.class, () -> configurationManagerImplSpy.validateDomain(List.of(invalidId)));
+    }
+
+    @Test
+    public void validateZoneTestInvalidIdThrowException() {
+        Mockito.doReturn(null).when(zoneDaoMock).findById(invalidId);
+        Assert.assertThrows(InvalidParameterValueException.class, () -> configurationManagerImplSpy.validateZone(List.of(invalidId)));
+    }
+
+    @Test
+    public void updateDiskOfferingIfCmdAttributeNotNullTestNotNullValueUpdateOfferingAttribute() {
+        Mockito.doReturn("DiskOfferingName").when(updateDiskOfferingCmdMock).getDiskOfferingName();
+        Mockito.doReturn("DisplayText").when(updateDiskOfferingCmdMock).getDisplayText();
+        Mockito.doReturn(1).when(updateDiskOfferingCmdMock).getSortKey();
+        Mockito.doReturn(false).when(updateDiskOfferingCmdMock).getDisplayOffering();
+
+        configurationManagerImplSpy.updateDiskOfferingIfCmdAttributeNotNull(diskOfferingVOSpy, updateDiskOfferingCmdMock);
+
+        Assert.assertEquals(updateDiskOfferingCmdMock.getDiskOfferingName(), diskOfferingVOSpy.getName());
+        Assert.assertEquals(updateDiskOfferingCmdMock.getDisplayText(), diskOfferingVOSpy.getDisplayText());
+        Assert.assertEquals(updateDiskOfferingCmdMock.getSortKey(), (Integer) diskOfferingVOSpy.getSortKey());
+        Assert.assertEquals(updateDiskOfferingCmdMock.getDisplayOffering(), diskOfferingVOSpy.getDisplayOffering());
+    }
+
+    @Test
+    public void updateDiskOfferingIfCmdAttributeNotNullTestNullValueDoesntUpdateOfferingAttribute() {
+        Mockito.doReturn("Name").when(diskOfferingVOSpy).getName();
+        Mockito.doReturn("DisplayText").when(diskOfferingVOSpy).getDisplayText();
+        Mockito.doReturn(1).when(diskOfferingVOSpy).getSortKey();
+        Mockito.doReturn(true).when(diskOfferingVOSpy).getDisplayOffering();
+
+        configurationManagerImplSpy.updateDiskOfferingIfCmdAttributeNotNull(diskOfferingVOSpy, updateDiskOfferingCmdMock);
+
+        Assert.assertNotEquals(updateDiskOfferingCmdMock.getDiskOfferingName(), diskOfferingVOSpy.getName());
+        Assert.assertNotEquals(updateDiskOfferingCmdMock.getDisplayText(), diskOfferingVOSpy.getDisplayText());
+        Assert.assertNotEquals(updateDiskOfferingCmdMock.getSortKey(), (Integer) diskOfferingVOSpy.getSortKey());
+        Assert.assertNotEquals(updateDiskOfferingCmdMock.getDisplayOffering(), diskOfferingVOSpy.getDisplayOffering());
+    }
+
+    @Test
+    public void updateDiskOfferingDetailsDomainIdsTestDifferentDomainIdsDiskOfferingDetailsAddDomainIds() {
+        List<DiskOfferingDetailVO> detailsVO = new ArrayList<>();
+        Long diskOfferingId = validId;
+
+        configurationManagerImplSpy.updateDiskOfferingDetailsDomainIds(detailsVO, searchCriteriaDiskOfferingDetailMock, diskOfferingId, filteredDomainIds, existingDomainIds);
+
+        for (int i = 0; i < detailsVO.size(); i++) {
+            Assert.assertEquals(filteredDomainIds.get(i), (Long) Long.parseLong(detailsVO.get(i).getValue()));
+        }
+    }
+
+    @Test
+    public void checkDomainAdminUpdateOfferingRestrictionsTestDifferentZoneIdsThrowException() {
+        Assert.assertThrows(InvalidParameterValueException.class,
+                () -> configurationManagerImplSpy.checkDomainAdminUpdateOfferingRestrictions(diskOfferingMock, userMock, filteredZoneIds, emptyExistingZoneIds, existingDomainIds, filteredDomainIds));
+    }
+
+    @Test
+    public void checkDomainAdminUpdateOfferingRestrictionsTestEmptyExistingDomainIdsThrowException() {
+        Assert.assertThrows(InvalidParameterValueException.class,
+                () -> configurationManagerImplSpy.checkDomainAdminUpdateOfferingRestrictions(diskOfferingMock, userMock, filteredZoneIds, existingZoneIds, emptyExistingDomainIds, filteredDomainIds));
+    }
+
+    @Test
+    public void checkDomainAdminUpdateOfferingRestrictionsTestEmptyFilteredDomainIdsThrowException() {
+        Assert.assertThrows(InvalidParameterValueException.class,
+                () -> configurationManagerImplSpy.checkDomainAdminUpdateOfferingRestrictions(diskOfferingMock, userMock, filteredZoneIds, existingZoneIds, existingDomainIds, emptyFilteredDomainIds));
+    }
+
+    @Test
+    public void getAccountNonChildDomainsTestValidValuesReturnChildDomains() {
+        Mockito.doReturn(null).when(updateDiskOfferingCmdMock).getSortKey();
+        List<Long> nonChildDomains = configurationManagerImplSpy.getAccountNonChildDomains(diskOfferingMock, accountMock, userMock, updateDiskOfferingCmdMock, existingDomainIds);
+
+        for (int i = 0; i < existingDomainIds.size(); i++) {
+            Assert.assertEquals(existingDomainIds.get(i), nonChildDomains.get(i));
+        }
+    }
+
+    @Test
+    public void getAccountNonChildDomainsTestAllDomainsAreChildDomainsReturnEmptyList() {
+        for (Long existingDomainId : existingDomainIds) {
+            Mockito.when(domainDaoMock.isChildDomain(accountMock.getDomainId(), existingDomainId)).thenReturn(true);
+        }
+
+        List<Long> nonChildDomains = configurationManagerImplSpy.getAccountNonChildDomains(diskOfferingMock, accountMock, userMock, updateDiskOfferingCmdMock, existingDomainIds);
+
+        Assert.assertTrue(nonChildDomains.isEmpty());
+    }
+
+    @Test
+    public void getAccountNonChildDomainsTestNotNullCmdAttributeThrowException() {
+        Mockito.doReturn("name").when(updateDiskOfferingCmdMock).getDiskOfferingName();
+
+        Assert.assertThrows(InvalidParameterValueException.class, () -> configurationManagerImplSpy.getAccountNonChildDomains(diskOfferingMock, accountMock, userMock, updateDiskOfferingCmdMock, existingDomainIds));
+    }
+
+    @Test
+    public void checkIfDomainIsChildDomainTestNonChildDomainThrowException() {
+        Mockito.doReturn(false).when(domainDaoMock).isChildDomain(Mockito.anyLong(), Mockito.anyLong());
+        Mockito.doReturn(domainMock).when(entityManagerMock).findById(Domain.class, 1L);
+
+        Assert.assertThrows(InvalidParameterValueException.class, () -> configurationManagerImplSpy.checkIfDomainIsChildDomain(diskOfferingMock, accountMock, userMock, filteredDomainIds));
+    }
 }
diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
index 715924d..4b9441d 100644
--- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
+++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
@@ -17,60 +17,6 @@
 
 package com.cloud.configuration;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.UUID;
-
-import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
-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.ListGuestNetworkIpv6PrefixesCmd;
-import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
-import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
-import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
-import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
-import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
-import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.messagebus.MessageBusBase;
-import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
-import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.apache.log4j.Logger;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.mockito.stubbing.Answer;
-import org.springframework.test.util.ReflectionTestUtils;
-
 import com.cloud.api.query.dao.NetworkOfferingJoinDao;
 import com.cloud.api.query.vo.NetworkOfferingJoinVO;
 import com.cloud.configuration.Resource.ResourceType;
@@ -110,8 +56,10 @@
 import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao;
 import com.cloud.network.dao.PhysicalNetworkDao;
 import com.cloud.network.dao.PhysicalNetworkVO;
+import com.cloud.offering.DiskOffering;
 import com.cloud.projects.ProjectManager;
 import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.StoragePoolTagVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.storage.dao.StoragePoolTagsDao;
@@ -131,6 +79,60 @@
 import com.cloud.utils.net.NetUtils;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
+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.ListGuestNetworkIpv6PrefixesCmd;
+import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
+import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
+import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
+import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
+import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
+import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.messagebus.MessageBusBase;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class ConfigurationManagerTest {
 
@@ -548,15 +550,67 @@
     }
 
     @Test
+    public void validateEmptySourceNatServiceCapablitiesTest() {
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+
+        configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
+    }
+
+    @Test
+    public void validateInvalidSourceNatTypeForSourceNatServiceCapablitiesTest() {
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "perDomain");
+
+        boolean caught = false;
+        try {
+            configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
+        } catch (InvalidParameterValueException e) {
+            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Either peraccount or perzone source NAT type can be specified for SupportedSourceNatTypes"));
+            caught = true;
+        }
+        Assert.assertTrue("should not be accepted", caught);
+    }
+
+    @Test
+    public void validateInvalidBooleanValueForSourceNatServiceCapablitiesTest() {
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "maybe");
+
+        boolean caught = false;
+        try {
+            configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
+        } catch (InvalidParameterValueException e) {
+            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Unknown specified value for RedundantRouter"));
+            caught = true;
+        }
+        Assert.assertTrue("should not be accepted", caught);
+    }
+
+    @Test
+    public void validateInvalidCapabilityForSourceNatServiceCapablitiesTest() {
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.ElasticIp, "perDomain");
+
+        boolean caught = false;
+        try {
+            configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
+        } catch (InvalidParameterValueException e) {
+            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Only SupportedSourceNatTypes, Network.Capability[name=RedundantRouter] capabilities can be specified for source nat service"));
+            caught = true;
+        }
+        Assert.assertTrue("should not be accepted", caught);
+    }
+
+    @Test
     public void validateEmptyStaticNatServiceCapablitiesTest() {
-        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<Capability, String>();
+        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<>();
 
         configurationMgr.validateStaticNatServiceCapablities(staticNatServiceCapabilityMap);
     }
 
     @Test
     public void validateInvalidStaticNatServiceCapablitiesTest() {
-        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<Capability, String>();
+        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<>();
         staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "Frue and Talse");
 
         boolean caught = false;
@@ -570,8 +624,42 @@
     }
 
     @Test
+    public void isRedundantRouter() {
+        Map<Network.Service, Set<Network.Provider>> serviceCapabilityMap = new HashMap<>();
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "peraccount");
+        sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "true");
+        Assert.assertTrue(configurationMgr.isRedundantRouter(serviceCapabilityMap, sourceNatServiceCapabilityMap));
+    }
+
+    @Test
+    public void isSharedSourceNat() {
+        Map<Network.Service, Set<Network.Provider>> serviceCapabilityMap = new HashMap<>();
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "perzone");
+        Assert.assertTrue(configurationMgr.isSharedSourceNat(serviceCapabilityMap, sourceNatServiceCapabilityMap));
+    }
+
+    @Test
+    public void isNotSharedSourceNat() {
+        Map<Network.Service, Set<Network.Provider>> serviceCapabilityMap = new HashMap<>();
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "peraccount");
+        Assert.assertFalse(configurationMgr.isSharedSourceNat(serviceCapabilityMap, sourceNatServiceCapabilityMap));
+    }
+
+    @Test
+    public void sourceNatCapabilitiesContainValidValues() {
+        Map<Capability, String> sourceNatServiceCapabilityMap = new HashMap<>();
+        sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "peraccount");
+        sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "True");
+
+        Assert.assertTrue(configurationMgr.sourceNatCapabilitiesContainValidValues(sourceNatServiceCapabilityMap));
+    }
+
+    @Test
     public void validateTTStaticNatServiceCapablitiesTest() {
-        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<Capability, String>();
+        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<>();
         staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "true and Talse");
         staticNatServiceCapabilityMap.put(Capability.ElasticIp, "True");
 
@@ -580,7 +668,7 @@
 
     @Test
     public void validateFTStaticNatServiceCapablitiesTest() {
-        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<Capability, String>();
+        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<>();
         staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "false");
         staticNatServiceCapabilityMap.put(Capability.ElasticIp, "True");
 
@@ -589,7 +677,7 @@
 
     @Test
     public void validateTFStaticNatServiceCapablitiesTest() {
-        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<Capability, String>();
+        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<>();
         staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "true and Talse");
         staticNatServiceCapabilityMap.put(Capability.ElasticIp, "false");
 
@@ -608,7 +696,7 @@
 
     @Test
     public void validateFFStaticNatServiceCapablitiesTest() {
-        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<Capability, String>();
+        Map<Capability, String> staticNatServiceCapabilityMap = new HashMap<>();
         staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "false");
         staticNatServiceCapabilityMap.put(Capability.ElasticIp, "False");
 
@@ -987,17 +1075,18 @@
 
     @Test
     public void shouldUpdateDiskOfferingTests(){
-        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyString(), Mockito.anyString()));
-        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), nullable(String.class), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class)));
-        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class)));
-        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), Mockito.anyInt(), nullable(Boolean.class), nullable(String.class), nullable(String.class)));
-        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), Mockito.anyBoolean(), nullable(String.class), nullable(String.class)));
-        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), nullable(Boolean.class), Mockito.anyString(), Mockito.anyString()));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyString(), Mockito.anyString(), Mockito.any(DiskOffering.State.class)));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), nullable(String.class), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class)));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class), Mockito.any(DiskOffering.State.class)));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class)));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), Mockito.anyInt(), nullable(Boolean.class), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class)));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), Mockito.anyBoolean(), nullable(String.class), nullable(String.class), nullable(DiskOffering.State.class)));
+        Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), nullable(String.class), nullable(int.class), nullable(Boolean.class), Mockito.anyString(), Mockito.anyString(), nullable(DiskOffering.State.class)));
     }
 
     @Test
     public void shouldUpdateDiskOfferingTestFalse(){
-        Assert.assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null, null, null, null, null));
+        Assert.assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null, null, null, null, null, null));
     }
 
     @Test
@@ -1077,12 +1166,17 @@
     @Test
     public void updateDiskOfferingTagsWithPrimaryStorageWithCorrectTagsTestSuccess(){
         String tags = "tag1,tag2";
-        List<String> storageTagsWithCorrectTags = new ArrayList<>(Arrays.asList("tag1","tag2"));
         List<StoragePoolVO> pools = new ArrayList<>(Arrays.asList(storagePoolVO));
         List<VolumeVO> volumes = new ArrayList<>(Arrays.asList(volumeVO));
 
+        StoragePoolTagVO poolTagMock1 = Mockito.mock(StoragePoolTagVO.class);
+        StoragePoolTagVO poolTagMock2 = Mockito.mock(StoragePoolTagVO.class);
+        List<StoragePoolTagVO> poolTags = List.of(poolTagMock1, poolTagMock2);
+        Mockito.doReturn("tag1").when(poolTagMock1).getTag();
+        Mockito.doReturn("tag2").when(poolTagMock2).getTag();
+
         Mockito.when(primaryDataStoreDao.listStoragePoolsWithActiveVolumesByOfferingId(anyLong())).thenReturn(pools);
-        Mockito.when(storagePoolTagsDao.getStoragePoolTags(anyLong())).thenReturn(storageTagsWithCorrectTags);
+        Mockito.when(storagePoolTagsDao.findStoragePoolTags(anyLong())).thenReturn(poolTags);
         Mockito.when(diskOfferingDao.findById(anyLong())).thenReturn(diskOfferingVOMock);
         Mockito.when(_volumeDao.findByDiskOfferingId(anyLong())).thenReturn(volumes);
 
diff --git a/server/src/test/java/com/cloud/configuration/ValidateIpRangeTest.java b/server/src/test/java/com/cloud/configuration/ValidateIpRangeTest.java
index 4a5da88..d090066 100644
--- a/server/src/test/java/com/cloud/configuration/ValidateIpRangeTest.java
+++ b/server/src/test/java/com/cloud/configuration/ValidateIpRangeTest.java
@@ -16,21 +16,20 @@
 // under the License.
 package com.cloud.configuration;
 
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
+import com.cloud.dc.VlanVO;
+import com.cloud.network.Network;
+import com.cloud.network.NetworkModel;
+import com.cloud.utils.Pair;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import com.cloud.dc.VlanVO;
-import com.cloud.network.Network;
-import com.cloud.network.NetworkModel;
-import com.cloud.utils.Pair;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.when;
 
 public class ValidateIpRangeTest {
     @Mock
diff --git a/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyManagerTest.java b/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyManagerTest.java
index 3a50c5c..428b53a 100644
--- a/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyManagerTest.java
+++ b/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyManagerTest.java
@@ -17,30 +17,9 @@
 
 package com.cloud.consoleproxy;
 
-import static org.mockito.AdditionalMatchers.not;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.log4j.Logger;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.springframework.test.util.ReflectionTestUtils;
-
-import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenter.NetworkType;
+import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.info.ConsoleProxyStatus;
 import com.cloud.network.Networks.TrafficType;
@@ -52,6 +31,26 @@
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonParseException;
+import org.apache.log4j.Logger;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class ConsoleProxyManagerTest {
 
diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
index ea9e2bb..6bfc8fb 100644
--- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
+++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
@@ -16,54 +16,86 @@
 // under the License.
 package com.cloud.deploy;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-
+import com.cloud.agent.AgentManager;
+import com.cloud.capacity.CapacityManager;
+import com.cloud.capacity.dao.CapacityDao;
+import com.cloud.configuration.ConfigurationManagerImpl;
+import com.cloud.dc.ClusterDetailsDao;
 import com.cloud.dc.ClusterDetailsVO;
+import com.cloud.dc.ClusterVO;
 import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.HostPodVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.DedicatedResourceDao;
+import com.cloud.dc.dao.HostPodDao;
+import com.cloud.deploy.DeploymentPlanner.ExcludeList;
+import com.cloud.deploy.DeploymentPlanner.PlannerResourceUsage;
+import com.cloud.deploy.dao.PlannerHostReservationDao;
+import com.cloud.exception.AffinityConflictException;
+import com.cloud.exception.InsufficientServerCapacityException;
 import com.cloud.gpu.GPU;
+import com.cloud.gpu.dao.HostGpuGroupsDao;
 import com.cloud.host.Host;
 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.host.dao.HostTagsDao;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.org.Grouping.AllocationState;
+import com.cloud.resource.ResourceManager;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDetailsDao;
 import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.ScopeType;
 import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StoragePoolStatus;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.GuestOSCategoryDao;
+import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VolumeDao;
 import com.cloud.template.VirtualMachineTemplate;
 import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.Pair;
+import com.cloud.utils.component.ComponentContext;
 import com.cloud.vm.DiskProfile;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachine.Type;
 import com.cloud.vm.VirtualMachineProfile;
 import com.cloud.vm.VirtualMachineProfileImpl;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.dao.UserVmDetailsDao;
+import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.affinity.AffinityGroupProcessor;
+import org.apache.cloudstack.affinity.AffinityGroupService;
+import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
 import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
+import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
+import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMReservationDao;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.test.utils.SpringUtils;
 import org.apache.commons.collections.CollectionUtils;
 import org.junit.Assert;
 import org.junit.Before;
@@ -77,7 +109,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.ComponentScan.Filter;
@@ -90,52 +121,23 @@
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
-import org.apache.cloudstack.affinity.AffinityGroupProcessor;
-import org.apache.cloudstack.affinity.AffinityGroupService;
-import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
-import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
-import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMReservationDao;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.messagebus.MessageBus;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.test.utils.SpringUtils;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-import com.cloud.agent.AgentManager;
-import com.cloud.capacity.CapacityManager;
-import com.cloud.capacity.dao.CapacityDao;
-import com.cloud.dc.ClusterDetailsDao;
-import com.cloud.dc.ClusterVO;
-import com.cloud.dc.DataCenterVO;
-import com.cloud.dc.dao.ClusterDao;
-import com.cloud.dc.dao.DataCenterDao;
-import com.cloud.dc.dao.DedicatedResourceDao;
-import com.cloud.dc.dao.HostPodDao;
-import com.cloud.deploy.DeploymentPlanner.ExcludeList;
-import com.cloud.deploy.DeploymentPlanner.PlannerResourceUsage;
-import com.cloud.deploy.dao.PlannerHostReservationDao;
-import com.cloud.exception.AffinityConflictException;
-import com.cloud.exception.InsufficientServerCapacityException;
-import com.cloud.gpu.dao.HostGpuGroupsDao;
-import com.cloud.host.dao.HostDao;
-import com.cloud.host.dao.HostTagsDao;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.resource.ResourceManager;
-import com.cloud.org.Grouping.AllocationState;
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.service.dao.ServiceOfferingDetailsDao;
-import com.cloud.storage.StorageManager;
-import com.cloud.storage.dao.DiskOfferingDao;
-import com.cloud.storage.dao.GuestOSCategoryDao;
-import com.cloud.storage.dao.GuestOSDao;
-import com.cloud.storage.dao.StoragePoolHostDao;
-import com.cloud.storage.dao.VolumeDao;
-import com.cloud.user.AccountManager;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.vm.dao.UserVmDao;
-import com.cloud.vm.dao.UserVmDetailsDao;
-import com.cloud.vm.dao.VMInstanceDao;
-import com.cloud.host.dao.HostDetailsDao;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@@ -202,6 +204,9 @@
     @Inject
     ClusterDetailsDao clusterDetailsDao;
 
+    @Inject
+    PrimaryDataStoreDao primaryDataStoreDao;
+
     @Mock
     Host host;
 
@@ -275,13 +280,13 @@
     @Test
     public void dataCenterAvoidTest() throws InsufficientServerCapacityException, AffinityConflictException {
         ServiceOfferingVO svcOffering =
-            new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm",
-                    false, VirtualMachine.Type.User, null, "FirstFitPlanner", true, false);
+                new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm",
+                        false, VirtualMachine.Type.User, null, "FirstFitPlanner", true, false);
         Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering);
 
         DataCenterDeployment plan = new DataCenterDeployment(dataCenterId);
 
-        Mockito.when(avoids.shouldAvoid((DataCenterVO)Matchers.anyObject())).thenReturn(true);
+        Mockito.when(avoids.shouldAvoid((DataCenterVO) Matchers.anyObject())).thenReturn(true);
         DeployDestination dest = _dpm.planDeployment(vmProfile, plan, avoids, null);
         assertNull("DataCenter is in avoid set, destination should be null! ", dest);
     }
@@ -289,12 +294,12 @@
     @Test
     public void plannerCannotHandleTest() throws InsufficientServerCapacityException, AffinityConflictException {
         ServiceOfferingVO svcOffering =
-            new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm",
-                    false, VirtualMachine.Type.User, null, "UserDispersingPlanner", true, false);
+                new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm",
+                        false, VirtualMachine.Type.User, null, "UserDispersingPlanner", true, false);
         Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering);
 
         DataCenterDeployment plan = new DataCenterDeployment(dataCenterId);
-        Mockito.when(avoids.shouldAvoid((DataCenterVO)Matchers.anyObject())).thenReturn(false);
+        Mockito.when(avoids.shouldAvoid((DataCenterVO) Matchers.anyObject())).thenReturn(false);
 
         Mockito.when(_planner.canHandle(vmProfile, plan, avoids)).thenReturn(false);
         DeployDestination dest = _dpm.planDeployment(vmProfile, plan, avoids, null);
@@ -304,15 +309,15 @@
     @Test
     public void emptyClusterListTest() throws InsufficientServerCapacityException, AffinityConflictException {
         ServiceOfferingVO svcOffering =
-            new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm",
-                    false, VirtualMachine.Type.User, null, "FirstFitPlanner", true, false);
+                new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm",
+                        false, VirtualMachine.Type.User, null, "FirstFitPlanner", true, false);
         Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering);
 
         DataCenterDeployment plan = new DataCenterDeployment(dataCenterId);
-        Mockito.when(avoids.shouldAvoid((DataCenterVO)Matchers.anyObject())).thenReturn(false);
+        Mockito.when(avoids.shouldAvoid((DataCenterVO) Matchers.anyObject())).thenReturn(false);
         Mockito.when(_planner.canHandle(vmProfile, plan, avoids)).thenReturn(true);
 
-        Mockito.when(((DeploymentClusterPlanner)_planner).orderClusters(vmProfile, plan, avoids)).thenReturn(null);
+        Mockito.when(((DeploymentClusterPlanner) _planner).orderClusters(vmProfile, plan, avoids)).thenReturn(null);
         DeployDestination dest = _dpm.planDeployment(vmProfile, plan, avoids, null);
         assertNull("Planner cannot handle, destination should be null! ", dest);
     }
@@ -388,7 +393,8 @@
         }
     }
 
-    private void prepareAndVerifyAvoidDisabledResourcesTest(int timesRouter, int timesAdminVm, int timesDisabledResource, long roleId, Type vmType, boolean isSystemDepolyable,
+    private void prepareAndVerifyAvoidDisabledResourcesTest(int timesRouter, int timesAdminVm,
+            int timesDisabledResource, long roleId, Type vmType, boolean isSystemDepolyable,
             boolean isAdminVmDeployable) {
         Mockito.doReturn(isSystemDepolyable).when(_dpm).isRouterDeployableInDisabledResources();
         Mockito.doReturn(isAdminVmDeployable).when(_dpm).isAdminVmDeployableInDisabledResources();
@@ -501,9 +507,9 @@
 
     @Test
     public void volumesRequireEncryptionTest() {
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path", Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
-        VolumeVO vol2 = new VolumeVO("vol2", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.DATADISK);
-        VolumeVO vol3 = new VolumeVO("vol3", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.DATADISK);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
+        VolumeVO vol2 = new VolumeVO("vol2", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.DATADISK);
+        VolumeVO vol3 = new VolumeVO("vol3", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.DATADISK);
         vol2.setPassphraseId(1L);
 
         List<VolumeVO> volumes = List.of(vol1, vol2, vol3);
@@ -512,9 +518,9 @@
 
     @Test
     public void volumesDoNotRequireEncryptionTest() {
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
-        VolumeVO vol2 = new VolumeVO("vol2", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.DATADISK);
-        VolumeVO vol3 = new VolumeVO("vol3", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.DATADISK);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
+        VolumeVO vol2 = new VolumeVO("vol2", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.DATADISK);
+        VolumeVO vol3 = new VolumeVO("vol3", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.DATADISK);
 
         List<VolumeVO> volumes = List.of(vol1, vol2, vol3);
         Assert.assertFalse("Volumes do not require encryption, but reporting they do", _dpm.anyVolumeRequiresEncryption(volumes));
@@ -531,7 +537,7 @@
         }};
         host.setDetails(hostDetails);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
         vol1.setPassphraseId(1L);
 
         setupMocksForPlanDeploymentHostTests(host, vol1);
@@ -556,7 +562,7 @@
         }};
         host.setDetails(hostDetails);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
         vol1.setPassphraseId(1L);
 
         setupMocksForPlanDeploymentHostTests(host, vol1);
@@ -581,7 +587,7 @@
         }};
         host.setDetails(hostDetails);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
 
         setupMocksForPlanDeploymentHostTests(host, vol1);
 
@@ -605,7 +611,7 @@
         }};
         host.setDetails(hostDetails);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
 
         setupMocksForPlanDeploymentHostTests(host, vol1);
 
@@ -630,7 +636,7 @@
         host.setDetails(hostDetails);
         Mockito.when(host.getStatus()).thenReturn(Status.Up);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
         vol1.setPassphraseId(1L);
 
         setupMocksForPlanDeploymentHostTests(host, vol1);
@@ -660,7 +666,7 @@
         host.setDetails(hostDetails);
         Mockito.when(host.getStatus()).thenReturn(Status.Up);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
         vol1.setPassphraseId(1L);
 
         setupMocksForPlanDeploymentHostTests(host, vol1);
@@ -686,7 +692,7 @@
         host.setDetails(hostDetails);
         Mockito.when(host.getStatus()).thenReturn(Status.Up);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
         vol1.setPassphraseId(1L);
 
         DeploymentClusterPlanner planner = setupMocksForPlanDeploymentHostTests(host, vol1);
@@ -711,7 +717,7 @@
         host.setDetails(hostDetails);
         Mockito.when(host.getStatus()).thenReturn(Status.Up);
 
-        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId,podId,1L,1L, instanceId,"folder","path",Storage.ProvisioningType.THIN, (long)10<<30, Volume.Type.ROOT);
+        VolumeVO vol1 = new VolumeVO("vol1", dataCenterId, podId, 1L, 1L, instanceId, "folder", "path", Storage.ProvisioningType.THIN, (long) 10 << 30, Volume.Type.ROOT);
         vol1.setPassphraseId(1L);
 
         DeploymentClusterPlanner planner = setupMocksForPlanDeploymentHostTests(host, vol1);
@@ -795,7 +801,6 @@
         Mockito.when(volDao.findByInstanceAndType(1L, Volume.Type.ROOT)).thenReturn(Arrays.asList(vol1));
         Mockito.when(_dataStoreManager.getPrimaryDataStore(vol1.getPoolId())).thenReturn((DataStore) primaryDataStore);
         Mockito.when(avoids.shouldAvoid(storagePool)).thenReturn(Boolean.FALSE);
-        PowerMockito.whenNew(DiskProfile.class).withAnyArguments().thenReturn(diskProfile);
 
         Mockito.doReturn(Arrays.asList(storagePool)).when(allocator).allocateToPool(diskProfile, vmProfile, plan,
                 avoids, 10);
@@ -805,6 +810,7 @@
         assertTrue(vol1.getPoolId() == null);
 
     }
+
     // This is so ugly but everything is so intertwined...
     private DeploymentClusterPlanner setupMocksForPlanDeploymentHostTests(HostVO host, VolumeVO vol1) {
         long diskOfferingId = 345L;
@@ -837,10 +843,10 @@
         Mockito.doNothing().when(hostDao).loadDetails(host);
         Mockito.doReturn(volumeVOs).when(volDao).findByInstance(ArgumentMatchers.anyLong());
         Mockito.doReturn(suitable).when(_dpm).findSuitablePoolsForVolumes(
-            ArgumentMatchers.any(VirtualMachineProfile.class),
-            ArgumentMatchers.any(DataCenterDeployment.class),
-            ArgumentMatchers.any(ExcludeList.class),
-            ArgumentMatchers.anyInt()
+                ArgumentMatchers.any(VirtualMachineProfile.class),
+                ArgumentMatchers.any(DataCenterDeployment.class),
+                ArgumentMatchers.any(ExcludeList.class),
+                ArgumentMatchers.anyInt()
         );
 
         ClusterVO clusterVO = new ClusterVO();
@@ -848,10 +854,10 @@
         Mockito.when(_clusterDao.findById(ArgumentMatchers.anyLong())).thenReturn(clusterVO);
 
         Mockito.doReturn(List.of(host)).when(_dpm).findSuitableHosts(
-            ArgumentMatchers.any(VirtualMachineProfile.class),
-            ArgumentMatchers.any(DeploymentPlan.class),
-            ArgumentMatchers.any(ExcludeList.class),
-            ArgumentMatchers.anyInt()
+                ArgumentMatchers.any(VirtualMachineProfile.class),
+                ArgumentMatchers.any(DeploymentPlan.class),
+                ArgumentMatchers.any(ExcludeList.class),
+                ArgumentMatchers.anyInt()
         );
 
         Map<Volume, StoragePool> suitableVolumeStoragePoolMap = new HashMap<>() {{
@@ -864,13 +870,13 @@
         Mockito.when(capacityMgr.checkIfHostReachMaxGuestLimit(host)).thenReturn(false);
         Mockito.when(capacityMgr.checkIfHostHasCpuCapability(ArgumentMatchers.anyLong(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())).thenReturn(true);
         Mockito.when(capacityMgr.checkIfHostHasCapacity(
-            ArgumentMatchers.anyLong(),
-            ArgumentMatchers.anyInt(),
-            ArgumentMatchers.anyLong(),
-            ArgumentMatchers.anyBoolean(),
-            ArgumentMatchers.anyFloat(),
-            ArgumentMatchers.anyFloat(),
-            ArgumentMatchers.anyBoolean()
+                ArgumentMatchers.anyLong(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyLong(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyFloat(),
+                ArgumentMatchers.anyFloat(),
+                ArgumentMatchers.anyBoolean()
         )).thenReturn(true);
         Mockito.when(serviceOfferingDetailsDao.findDetail(vmProfile.getServiceOfferingId(), GPU.Keys.vgpuType.toString())).thenReturn(null);
 
@@ -881,9 +887,9 @@
         DeploymentClusterPlanner planner = Mockito.spy(new FirstFitPlanner());
         try {
             Mockito.doReturn(List.of(clusterId), List.of()).when(planner).orderClusters(
-                ArgumentMatchers.any(VirtualMachineProfile.class),
-                ArgumentMatchers.any(DeploymentPlan.class),
-                ArgumentMatchers.any(ExcludeList.class)
+                    ArgumentMatchers.any(VirtualMachineProfile.class),
+                    ArgumentMatchers.any(DeploymentPlan.class),
+                    ArgumentMatchers.any(ExcludeList.class)
             );
         } catch (Exception ex) {
             ex.printStackTrace();
@@ -901,7 +907,8 @@
         return dc;
     }
 
-    private void assertAvoidIsEmpty(ExcludeList avoids, boolean isDcEmpty, boolean isPodsEmpty, boolean isClustersEmpty, boolean isHostsEmpty) {
+    private void assertAvoidIsEmpty(ExcludeList avoids, boolean isDcEmpty, boolean isPodsEmpty, boolean isClustersEmpty,
+            boolean isHostsEmpty) {
         Assert.assertEquals(isDcEmpty, CollectionUtils.isEmpty(avoids.getDataCentersToAvoid()));
         Assert.assertEquals(isPodsEmpty, CollectionUtils.isEmpty(avoids.getPodsToAvoid()));
         Assert.assertEquals(isClustersEmpty, CollectionUtils.isEmpty(avoids.getClustersToAvoid()));
@@ -909,8 +916,9 @@
     }
 
     @Configuration
-    @ComponentScan(basePackageClasses = {DeploymentPlanningManagerImpl.class}, includeFilters = {@Filter(value = TestConfiguration.Library.class,
-                                                                                                         type = FilterType.CUSTOM)}, useDefaultFilters = false)
+    @ComponentScan(basePackageClasses = {DeploymentPlanningManagerImpl.class},
+                   includeFilters = {@Filter(value = TestConfiguration.Library.class,
+                                             type = FilterType.CUSTOM)}, useDefaultFilters = false)
     public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration {
 
         @Bean
@@ -1178,4 +1186,85 @@
         Assert.assertEquals(6, hosts.get(6).getId());
         Assert.assertEquals(2, hosts.get(7).getId());
     }
+
+    private List<Long> prepareMockForAvoidOtherClustersForDeploymentIfMigrationDisabled(boolean configValue,
+            boolean mockVolumes, boolean mockClusterStoreVolume) {
+        try {
+            Field f = ConfigKey.class.getDeclaredField("_defaultValue");
+            f.setAccessible(true);
+            f.set(ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS, String.valueOf(configValue));
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        List<Long> allClusters = List.of(101L, 102L, 103L, 104L);
+        Mockito.when(_clusterDao.listAllClusters(Mockito.anyLong())).thenReturn(allClusters);
+        if (mockVolumes) {
+            VolumeVO vol1 = Mockito.mock(VolumeVO.class);
+            Mockito.when(vol1.getPoolId()).thenReturn(1L);
+            VolumeVO vol2 = Mockito.mock(VolumeVO.class);
+            Mockito.when(vol2.getPoolId()).thenReturn(2L);
+            StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class);
+            Mockito.when(pool1.getScope()).thenReturn(ScopeType.ZONE);
+            Mockito.when(primaryDataStoreDao.findById(1L)).thenReturn(pool1);
+            StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class);
+            Mockito.when(pool2.getScope()).thenReturn(mockClusterStoreVolume ? ScopeType.CLUSTER : ScopeType.GLOBAL);
+            Mockito.when(primaryDataStoreDao.findById(2L)).thenReturn(pool2);
+            Mockito.when(volDao.findUsableVolumesForInstance(1L)).thenReturn(List.of(vol1, vol2));
+        } else {
+            Mockito.when(volDao.findUsableVolumesForInstance(1L)).thenReturn(new ArrayList<>());
+        }
+        return allClusters;
+    }
+
+    @Test
+    public void avoidOtherClustersForDeploymentIfMigrationDisabledNonValidHost() {
+        prepareMockForAvoidOtherClustersForDeploymentIfMigrationDisabled(false, false, false);
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        ExcludeList excludeList = new ExcludeList();
+        _dpm.avoidOtherClustersForDeploymentIfMigrationDisabled(vm, null, excludeList);
+        Assert.assertTrue(CollectionUtils.isEmpty(excludeList.getClustersToAvoid()));
+
+        Host lastHost = Mockito.mock(Host.class);
+        Mockito.when(lastHost.getClusterId()).thenReturn(null);
+        _dpm.avoidOtherClustersForDeploymentIfMigrationDisabled(vm, lastHost, excludeList);
+        Assert.assertTrue(CollectionUtils.isEmpty(excludeList.getClustersToAvoid()));
+    }
+
+    private Set<Long> runAvoidOtherClustersForDeploymentIfMigrationDisabledTest() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(vm.getId()).thenReturn(1L);
+        ExcludeList excludeList = new ExcludeList();
+        Host lastHost = Mockito.mock(Host.class);
+        Long sourceClusterId = 101L;
+        Mockito.when(lastHost.getClusterId()).thenReturn(sourceClusterId);
+        _dpm.avoidOtherClustersForDeploymentIfMigrationDisabled(vm, lastHost, excludeList);
+        return excludeList.getClustersToAvoid();
+    }
+
+    @Test
+    public void avoidOtherClustersForDeploymentIfMigrationDisabledConfigAllows() {
+        prepareMockForAvoidOtherClustersForDeploymentIfMigrationDisabled(true, false, false);
+        Assert.assertTrue(CollectionUtils.isEmpty(runAvoidOtherClustersForDeploymentIfMigrationDisabledTest()));
+    }
+
+    @Test
+    public void avoidOtherClustersForDeploymentIfMigrationDisabledNoVmVolumes() {
+        prepareMockForAvoidOtherClustersForDeploymentIfMigrationDisabled(false, false, false);
+        Assert.assertTrue(CollectionUtils.isEmpty(runAvoidOtherClustersForDeploymentIfMigrationDisabledTest()));
+    }
+
+    @Test
+    public void avoidOtherClustersForDeploymentIfMigrationDisabledVmVolumesNonValidScope() {
+        prepareMockForAvoidOtherClustersForDeploymentIfMigrationDisabled(false, true, false);
+        Assert.assertTrue(CollectionUtils.isEmpty(runAvoidOtherClustersForDeploymentIfMigrationDisabledTest()));
+    }
+
+    @Test
+    public void avoidOtherClustersForDeploymentIfMigrationDisabledValid() {
+        List<Long> allClusters = prepareMockForAvoidOtherClustersForDeploymentIfMigrationDisabled(false, true, true);
+        Set<Long> avoidedClusters = runAvoidOtherClustersForDeploymentIfMigrationDisabledTest();
+        Assert.assertTrue(CollectionUtils.isNotEmpty(avoidedClusters));
+        Assert.assertEquals(allClusters.size() - 1, avoidedClusters.size());
+        Assert.assertFalse(avoidedClusters.contains(allClusters.get(0)));
+    }
 }
diff --git a/server/src/test/java/com/cloud/event/ActionEventInterceptorTest.java b/server/src/test/java/com/cloud/event/ActionEventInterceptorTest.java
index 5809de6..9211ce1 100644
--- a/server/src/test/java/com/cloud/event/ActionEventInterceptorTest.java
+++ b/server/src/test/java/com/cloud/event/ActionEventInterceptorTest.java
@@ -17,16 +17,17 @@
 
 package com.cloud.event;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.inject.Inject;
-
+import com.cloud.configuration.Config;
+import com.cloud.event.dao.EventDao;
+import com.cloud.projects.dao.ProjectDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountVO;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.db.EntityManager;
 import org.aopalliance.intercept.MethodInvocation;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.context.CallContext;
@@ -40,27 +41,22 @@
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.cloud.configuration.Config;
-import com.cloud.event.dao.EventDao;
-import com.cloud.projects.dao.ProjectDao;
-import com.cloud.user.Account;
-import com.cloud.user.AccountVO;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.user.dao.UserDao;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.utils.db.EntityManager;
+import javax.inject.Inject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ComponentContext.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ActionEventInterceptorTest {
     //Predictable constants used throughout this test.
     public static final long EVENT_ID = 1;
@@ -108,6 +104,8 @@
     protected static final String eventType = EventTypes.EVENT_VM_START;
     protected static final String eventDescription = "Starting VM";
 
+    private MockedStatic<ComponentContext> componentContextMocked;
+
     /**
      * This setup method injects the mocked beans into the ActionEventUtils class.
      * Because ActionEventUtils has static methods, we must also remember these fields
@@ -152,7 +150,7 @@
     public void setupCommonMocks() throws Exception {
         //Some basic mocks.
         Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true");
-        PowerMockito.mockStatic(ComponentContext.class);
+        componentContextMocked = Mockito.mockStatic(ComponentContext.class);
         Mockito.when(ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus);
 
         //Needed for persist to actually set an ID that can be returned from the ActionEventUtils
@@ -170,22 +168,12 @@
         });
 
         //Needed to record events published on the bus.
-        Mockito.doAnswer(new Answer<Void>() {
-            @Override public Void answer(InvocationOnMock invocation) throws Throwable {
-                Event event = (Event)invocation.getArguments()[0];
-                publishedEvents.add(event);
-                return null;
-            }
-
-        }).when(eventBus).publish(Mockito.any(Event.class));
-
         account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
         account.setId(ACCOUNT_ID);
         user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone",
                 UUID.randomUUID().toString(), User.Source.UNKNOWN);
 
         Mockito.when(accountDao.findById(ACCOUNT_ID)).thenReturn(account);
-        Mockito.when(userDao.findById(USER_ID)).thenReturn(user);
     }
 
     /**
@@ -208,6 +196,8 @@
         }
 
         utils.init();
+
+        componentContextMocked.close();
     }
 
     @Test
diff --git a/server/src/test/java/com/cloud/event/ActionEventUtilsTest.java b/server/src/test/java/com/cloud/event/ActionEventUtilsTest.java
index e541f2a..aed2870 100644
--- a/server/src/test/java/com/cloud/event/ActionEventUtilsTest.java
+++ b/server/src/test/java/com/cloud/event/ActionEventUtilsTest.java
@@ -36,19 +36,17 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.configuration.Config;
 import com.cloud.event.dao.EventDao;
 import com.cloud.network.IpAddress;
 import com.cloud.projects.dao.ProjectDao;
 import com.cloud.storage.Snapshot;
-import com.cloud.storage.Volume;
 import com.cloud.user.Account;
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
@@ -62,8 +60,7 @@
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ComponentContext.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ActionEventUtilsTest {
     //Predictable constants used throughout this test.
     public static final long EVENT_ID = 1;
@@ -105,6 +102,8 @@
     private AccountVO account;
     private UserVO user;
 
+    private MockedStatic<ComponentContext> componentContextMocked;
+
     /**
      * This setup method injects the mocked beans into the ActionEventUtils class.
      * Because ActionEventUtils has static methods, we must also remember these fields
@@ -149,8 +148,8 @@
     public void setupCommonMocks() throws Exception {
         //Some basic mocks.
         Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true");
-        PowerMockito.mockStatic(ComponentContext.class);
-        Mockito.when(ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus);
+        componentContextMocked = Mockito.mockStatic(ComponentContext.class);
+        componentContextMocked.when(() -> ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus);
 
         //Needed for persist to actually set an ID that can be returned from the ActionEventUtils
         //methods.
@@ -205,6 +204,8 @@
         }
 
         utils.init();
+
+        componentContextMocked.close();
     }
 
     @Test
@@ -293,9 +294,6 @@
         final Long resourceId = 1L;
         final String resourceType = ApiCommandResourceType.VirtualMachine.toString();
         final String resourceUuid = UUID.randomUUID().toString();
-        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
-        Mockito.when(vm.getId()).thenReturn(resourceId);
-        Mockito.when(entityMgr.findByUuidIncludingRemoved(VirtualMachine.class, resourceUuid)).thenReturn(vm);
         CallContext.current().putContextParameter(VirtualMachine.class, resourceUuid);
         ActionEventUtils.onScheduledActionEvent(USER_ID, ACCOUNT_ID, EventTypes.EVENT_VM_START, "Test event", resourceId, resourceType, true, 0L);
 
@@ -319,9 +317,6 @@
         final Long resourceId = 1L;
         final String resourceType = ApiCommandResourceType.VirtualMachine.toString();
         final String resourceUuid = UUID.randomUUID().toString();
-        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
-        Mockito.when(vm.getId()).thenReturn(resourceId);
-        Mockito.when(entityMgr.findByUuidIncludingRemoved(VirtualMachine.class, resourceUuid)).thenReturn(vm);
         CallContext.current().putContextParameter(VirtualMachine.class, resourceUuid);
         ActionEventUtils.onCreatedActionEvent(USER_ID, ACCOUNT_ID, EventVO.LEVEL_INFO, EventTypes.EVENT_VM_START, true, "Test event", resourceId, resourceType);
 
@@ -345,9 +340,6 @@
         final Long resourceId = 1L;
         final String resourceType = ApiCommandResourceType.VirtualMachine.toString();
         final String resourceUuid = UUID.randomUUID().toString();
-        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
-        Mockito.when(vm.getId()).thenReturn(resourceId);
-        Mockito.when(entityMgr.findByUuidIncludingRemoved(VirtualMachine.class, resourceUuid)).thenReturn(vm);
         CallContext.current().putContextParameter(VirtualMachine.class, resourceUuid);
         ActionEventUtils.startNestedActionEvent(EventTypes.EVENT_VM_START, "Test event", resourceId, resourceType);
 
@@ -365,21 +357,13 @@
         final Long snapshotResourceId = 100L;
         final String snapshotResourceType = ApiCommandResourceType.Snapshot.toString();
         final String snapshotResourceUuid = UUID.randomUUID().toString();
-        final Long resourceId = 1L;
-        final String resourceType = ApiCommandResourceType.Volume.toString();
-        final String resourceUuid = UUID.randomUUID().toString();
         Snapshot snapshot = Mockito.mock(Snapshot.class);
         Mockito.when(snapshot.getUuid()).thenReturn(snapshotResourceUuid);
-        Mockito.when(snapshot.getVolumeId()).thenReturn(resourceId);
-        Volume volume = Mockito.mock(Volume.class);
-        Mockito.when(volume.getUuid()).thenReturn(resourceUuid);
         Mockito.when(entityMgr.validEntityType(Snapshot.class)).thenReturn(true);
-        Mockito.when(entityMgr.validEntityType(Volume.class)).thenReturn(true);
         Mockito.when(entityMgr.findByIdIncludingRemoved(Snapshot.class, snapshotResourceId)).thenReturn(snapshot);
-        Mockito.when(entityMgr.findByIdIncludingRemoved(Volume.class, resourceId)).thenReturn(volume);
         ActionEventUtils.onActionEvent(USER_ID, ACCOUNT_ID, account.getDomainId(), EventTypes.EVENT_SNAPSHOT_CREATE, "Test event", snapshotResourceId, snapshotResourceType);
 
-        checkEventResourceAndUnregisterContext(resourceId, resourceUuid, resourceType);
+        checkEventResourceAndUnregisterContext(snapshotResourceId, snapshotResourceUuid, snapshotResourceType);
     }
 
     @Test
@@ -398,7 +382,6 @@
         VirtualMachine vm = Mockito.mock(VirtualMachine.class);
         Mockito.when(vm.getUuid()).thenReturn(resourceUuid);
         Mockito.when(entityMgr.validEntityType(VMSnapshot.class)).thenReturn(true);
-        Mockito.when(entityMgr.validEntityType(VirtualMachine.class)).thenReturn(true);
         Mockito.when(entityMgr.findByIdIncludingRemoved(VMSnapshot.class, vmSnapshotResourceId)).thenReturn(vmSnapshot);
         Mockito.when(entityMgr.findByIdIncludingRemoved(VirtualMachine.class, resourceId)).thenReturn(vm);
         ActionEventUtils.onActionEvent(USER_ID, ACCOUNT_ID, account.getDomainId(), EventTypes.EVENT_VM_SNAPSHOT_CREATE, "Test event", vmSnapshotResourceId, vmSnapshotResourceType);
diff --git a/server/src/test/java/com/cloud/event/EventControlsUnitTest.java b/server/src/test/java/com/cloud/event/EventControlsUnitTest.java
index 91dc921..2871de6 100644
--- a/server/src/test/java/com/cloud/event/EventControlsUnitTest.java
+++ b/server/src/test/java/com/cloud/event/EventControlsUnitTest.java
@@ -16,17 +16,13 @@
 // under the License.
 package com.cloud.event;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.when;
-
-import java.util.Date;
-import java.util.List;
-
+import com.cloud.event.dao.EventDao;
+import com.cloud.server.ManagementServerImpl;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
 import junit.framework.TestCase;
-
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.log4j.Logger;
 import org.junit.After;
 import org.junit.Before;
@@ -35,13 +31,14 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
-import org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import java.util.Date;
+import java.util.List;
 
-import com.cloud.event.dao.EventDao;
-import com.cloud.server.ManagementServerImpl;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
 
 public class EventControlsUnitTest extends TestCase {
     private static final Logger s_logger = Logger.getLogger(EventControlsUnitTest.class);
diff --git a/server/src/test/java/com/cloud/event/dao/EventJoinDaoImplTest.java b/server/src/test/java/com/cloud/event/dao/EventJoinDaoImplTest.java
index a83f6f4..8909e40 100644
--- a/server/src/test/java/com/cloud/event/dao/EventJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/event/dao/EventJoinDaoImplTest.java
@@ -17,8 +17,9 @@
 
 package com.cloud.event.dao;
 
-import java.util.UUID;
-
+import com.cloud.api.query.vo.EventJoinVO;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.vm.VirtualMachine;
 import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.response.EventResponse;
 import org.junit.Assert;
@@ -27,16 +28,11 @@
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.api.query.vo.EventJoinVO;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.utils.db.EntityManager;
-import com.cloud.vm.VirtualMachine;
+import java.util.UUID;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ComponentContext.class)
+@RunWith(MockitoJUnitRunner.class)
 public class EventJoinDaoImplTest {
 
     @Mock
diff --git a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java
index 39fa6bb..629fae2 100644
--- a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java
+++ b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java
@@ -31,6 +31,7 @@
 import javax.inject.Inject;
 
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.managed.context.ManagedContext;
 import org.apache.log4j.Logger;
@@ -65,6 +66,7 @@
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.dao.GuestOSCategoryDao;
 import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.VolumeDao;
 import com.cloud.storage.secondary.SecondaryStorageVmManager;
 import com.cloud.user.AccountManager;
 import com.cloud.vm.VMInstanceVO;
@@ -119,6 +121,10 @@
     SecondaryStorageVmManager secondaryStorageVmManager;
     @Mock
     HostVO hostVO;
+    @Mock
+    VolumeDao volumeDao;
+    @Mock
+    DataStoreProviderManager dataStoreProviderMgr;
 
     HighAvailabilityManagerImpl highAvailabilityManager;
     HighAvailabilityManagerImpl highAvailabilityManagerSpy;
diff --git a/server/src/test/java/com/cloud/ha/KVMFencerTest.java b/server/src/test/java/com/cloud/ha/KVMFencerTest.java
index ffbbcd3..617d0bc 100644
--- a/server/src/test/java/com/cloud/ha/KVMFencerTest.java
+++ b/server/src/test/java/com/cloud/ha/KVMFencerTest.java
@@ -18,9 +18,18 @@
  */
 package com.cloud.ha;
 
-import java.util.Arrays;
-import java.util.Collections;
-
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.FenceAnswer;
+import com.cloud.agent.api.FenceCommand;
+import com.cloud.alert.AlertManager;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.host.HostVO;
+import com.cloud.host.Status;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.resource.ResourceManager;
+import com.cloud.vm.VirtualMachine;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -30,18 +39,8 @@
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.agent.AgentManager;
-import com.cloud.alert.AlertManager;
-import com.cloud.agent.api.FenceAnswer;
-import com.cloud.agent.api.FenceCommand;
-import com.cloud.exception.AgentUnavailableException;
-import com.cloud.exception.OperationTimedoutException;
-import com.cloud.host.HostVO;
-import com.cloud.host.Status;
-import com.cloud.host.dao.HostDao;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.resource.ResourceManager;
-import com.cloud.vm.VirtualMachine;
+import java.util.Arrays;
+import java.util.Collections;
 
 @RunWith(MockitoJUnitRunner.class)
 public class KVMFencerTest {
diff --git a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java
index 875ba82..7c5ef92 100644
--- a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java
+++ b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java
@@ -36,6 +36,9 @@
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
 import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,9 +52,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
-import org.junit.Assert;
 
 @RunWith(MockitoJUnitRunner.class)
 public class KVMGuruTest {
diff --git a/server/src/test/java/com/cloud/keystore/KeystoreTest.java b/server/src/test/java/com/cloud/keystore/KeystoreTest.java
index 24cc3a7..2a0b909 100644
--- a/server/src/test/java/com/cloud/keystore/KeystoreTest.java
+++ b/server/src/test/java/com/cloud/keystore/KeystoreTest.java
@@ -16,16 +16,14 @@
 // under the License.
 package com.cloud.keystore;
 
+import com.cloud.api.ApiSerializerHelper;
+import junit.framework.TestCase;
 import org.apache.cloudstack.api.response.AlertResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.log4j.Logger;
 import org.junit.After;
 import org.junit.Before;
 
-import com.cloud.api.ApiSerializerHelper;
-
-import junit.framework.TestCase;
-
 public class KeystoreTest extends TestCase {
     private final static Logger s_logger = Logger.getLogger(KeystoreTest.class);
 
diff --git a/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java b/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java
index ff5ebb3..64b0de2 100644
--- a/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java
+++ b/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java
@@ -17,28 +17,26 @@
 
 package com.cloud.metadata;
 
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
+import com.cloud.exception.ResourceAllocationException;
 import com.cloud.server.ResourceManagerUtil;
+import com.cloud.server.ResourceTag;
+import com.cloud.server.TaggedResourceService;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.vm.dao.NicDetailsDao;
 import org.apache.commons.collections.map.HashedMap;
 import org.junit.Before;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
-import com.cloud.exception.ResourceAllocationException;
-import com.cloud.server.ResourceTag;
-import com.cloud.server.TaggedResourceService;
-import com.cloud.storage.dao.VolumeDetailsDao;
-import com.cloud.vm.dao.NicDetailsDao;
+import javax.naming.ConfigurationException;
+import java.util.Map;
+
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 
 public class ResourceMetaDataManagerTest {
 
diff --git a/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java b/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java
index 0541da6..826653f 100644
--- a/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java
+++ b/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java
@@ -17,28 +17,6 @@
 
 package com.cloud.network;
 
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import com.cloud.utils.Pair;
-import org.apache.cloudstack.acl.ControlledEntity.ACLType;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.log4j.Logger;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
 import com.cloud.dc.DataCenter.NetworkType;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
@@ -62,11 +40,31 @@
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
+import com.cloud.utils.Pair;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.exception.CloudRuntimeException;
-
 import junit.framework.Assert;
+import org.apache.cloudstack.acl.ControlledEntity.ACLType;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.log4j.Logger;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
 
 //@Ignore("Requires database to be set up")
 public class CreatePrivateNetworkTest {
diff --git a/server/src/test/java/com/cloud/network/DedicateGuestVlanRangesTest.java b/server/src/test/java/com/cloud/network/DedicateGuestVlanRangesTest.java
index 726f99e..3f9bda9 100644
--- a/server/src/test/java/com/cloud/network/DedicateGuestVlanRangesTest.java
+++ b/server/src/test/java/com/cloud/network/DedicateGuestVlanRangesTest.java
@@ -17,29 +17,6 @@
 
 package com.cloud.network;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd;
-import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd;
-import org.apache.cloudstack.api.command.admin.network.ReleaseDedicatedGuestVlanRangeCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.log4j.Logger;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
 import com.cloud.dc.DataCenterVnetVO;
 import com.cloud.dc.dao.DataCenterVnetDao;
 import com.cloud.network.dao.AccountGuestVlanMapDao;
@@ -54,8 +31,29 @@
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.db.TransactionLegacy;
-
 import junit.framework.Assert;
+import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd;
+import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd;
+import org.apache.cloudstack.api.command.admin.network.ReleaseDedicatedGuestVlanRangeCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
 
 public class DedicateGuestVlanRangesTest {
 
diff --git a/server/src/test/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImplTest.java b/server/src/test/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImplTest.java
index 50b021a..91f6fc3 100644
--- a/server/src/test/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImplTest.java
@@ -17,25 +17,6 @@
 
 package com.cloud.network;
 
-import java.lang.reflect.Field;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.routing.HealthCheckLBConfigAnswer;
@@ -77,6 +58,23 @@
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.NicDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import javax.inject.Inject;
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ExternalLoadBalancerDeviceManagerImplTest {
diff --git a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
index 9497457..5b8399a 100644
--- a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
+++ b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java
@@ -23,19 +23,27 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.Vector;
 
+import com.cloud.network.dao.PublicIpQuarantineDao;
+import com.cloud.network.vo.PublicIpQuarantineVO;
 import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+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.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
@@ -79,6 +87,34 @@
 
     AccountVO account;
 
+    @Mock
+    PublicIpQuarantineVO publicIpQuarantineVOMock;
+
+    @Mock
+    PublicIpQuarantineDao publicIpQuarantineDaoMock;
+
+    @Mock
+    IpAddress ipAddressMock;
+
+    @Mock
+    AccountVO newOwnerMock;
+
+    @Mock
+    AccountVO previousOwnerMock;
+
+    @Mock
+    AccountManager accountManagerMock;
+
+    final long dummyID = 1L;
+
+    final String UUID = "uuid";
+
+    private static final Date currentDate = new Date(100L);
+
+    private static final Date beforeCurrentDate = new Date(99L);
+
+    private static final Date afterCurrentDate = new Date(101L);
+
     @Before
     public void setup() throws ResourceUnavailableException {
 
@@ -233,6 +269,172 @@
         return network;
     }
 
+    @Test
+    public void isPublicIpAddressStillInQuarantineTestRemovedDateIsNullAndCurrentDateIsEqualToEndDateShouldReturnFalse() {
+        Date endDate = currentDate;
+
+        Mockito.when(publicIpQuarantineVOMock.getRemoved()).thenReturn(null);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(endDate);
+
+        boolean result = ipAddressManager.isPublicIpAddressStillInQuarantine(publicIpQuarantineVOMock, currentDate);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isPublicIpAddressStillInQuarantineTestRemovedDateIsNullAndEndDateIsBeforeCurrentDateShouldReturnFalse() {
+        Date endDate = beforeCurrentDate;
+
+        Mockito.when(publicIpQuarantineVOMock.getRemoved()).thenReturn(null);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(endDate);
+
+        boolean result = ipAddressManager.isPublicIpAddressStillInQuarantine(publicIpQuarantineVOMock, currentDate);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isPublicIpAddressStillInQuarantineTestRemovedDateIsNullAndEndDateIsAfterCurrentDateShouldReturnTrue() {
+        Date endDate = afterCurrentDate;
+
+        Mockito.when(publicIpQuarantineVOMock.getRemoved()).thenReturn(null);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(endDate);
+
+        boolean result = ipAddressManager.isPublicIpAddressStillInQuarantine(publicIpQuarantineVOMock, currentDate);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void isPublicIpAddressStillInQuarantineTestRemovedDateIsEqualCurrentDateShouldReturnFalse() {
+        Date removedDate = currentDate;
+
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(currentDate);
+        Mockito.when(publicIpQuarantineVOMock.getRemoved()).thenReturn(removedDate);
+
+        boolean result = ipAddressManager.isPublicIpAddressStillInQuarantine(publicIpQuarantineVOMock, currentDate);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isPublicIpAddressStillInQuarantineTestRemovedDateIsBeforeCurrentDateShouldReturnFalse() {
+        Date removedDate = beforeCurrentDate;
+
+        Mockito.when(publicIpQuarantineVOMock.getRemoved()).thenReturn(removedDate);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(null);
+
+        boolean result = ipAddressManager.isPublicIpAddressStillInQuarantine(publicIpQuarantineVOMock, currentDate);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isPublicIpAddressStillInQuarantineTestRemovedDateIsAfterCurrentDateShouldReturnTrue() {
+        Date removedDate = afterCurrentDate;
+
+        Mockito.when(publicIpQuarantineVOMock.getRemoved()).thenReturn(removedDate);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(null);
+
+        boolean result = ipAddressManager.isPublicIpAddressStillInQuarantine(publicIpQuarantineVOMock, currentDate);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void checkIfPublicIpAddressIsNotInQuarantineAndCanBeAllocatedTestIpIsNotInQuarantineShouldReturnTrue() {
+        Mockito.when(ipAddressMock.getId()).thenReturn(dummyID);
+        Mockito.when(publicIpQuarantineDaoMock.findByPublicIpAddressId(Mockito.anyLong())).thenReturn(null);
+
+        boolean result = ipAddressManager.canPublicIpAddressBeAllocated(ipAddressMock, account);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void checkIfPublicIpAddressIsNotInQuarantineAndCanBeAllocatedTestIpIsNoLongerInQuarantineShouldReturnTrue() {
+        Mockito.when(ipAddressMock.getId()).thenReturn(dummyID);
+        Mockito.when(publicIpQuarantineDaoMock.findByPublicIpAddressId(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+        Mockito.doReturn(false).when(ipAddressManager).isPublicIpAddressStillInQuarantine(Mockito.any(PublicIpQuarantineVO.class), Mockito.any(Date.class));
+
+        boolean result = ipAddressManager.canPublicIpAddressBeAllocated(ipAddressMock, newOwnerMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void checkIfPublicIpAddressIsNotInQuarantineAndCanBeAllocatedTestIpIsInQuarantineAndThePreviousOwnerIsTheSameAsTheNewOwnerShouldReturnTrue() {
+        Mockito.when(ipAddressMock.getId()).thenReturn(dummyID);
+        Mockito.when(publicIpQuarantineDaoMock.findByPublicIpAddressId(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+
+        Mockito.doReturn(true).when(ipAddressManager).isPublicIpAddressStillInQuarantine(Mockito.any(PublicIpQuarantineVO.class), Mockito.any(Date.class));
+        Mockito.doNothing().when(ipAddressManager).removePublicIpAddressFromQuarantine(Mockito.anyLong(), Mockito.anyString());
+
+        Mockito.when(publicIpQuarantineVOMock.getPreviousOwnerId()).thenReturn(dummyID);
+        Mockito.when(accountManagerMock.getAccount(Mockito.anyLong())).thenReturn(previousOwnerMock);
+        Mockito.when(previousOwnerMock.getUuid()).thenReturn(UUID);
+        Mockito.when(newOwnerMock.getUuid()).thenReturn(UUID);
+
+        boolean result = ipAddressManager.canPublicIpAddressBeAllocated(ipAddressMock, newOwnerMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void checkIfPublicIpAddressIsNotInQuarantineAndCanBeAllocatedTestIpIsInQuarantineAndThePreviousOwnerIsDifferentFromTheNewOwnerShouldReturnFalse() {
+        final String UUID_2 = "uuid_2";
+
+        Mockito.when(ipAddressMock.getId()).thenReturn(dummyID);
+        Mockito.when(publicIpQuarantineDaoMock.findByPublicIpAddressId(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+
+        Mockito.doReturn(true).when(ipAddressManager).isPublicIpAddressStillInQuarantine(Mockito.any(PublicIpQuarantineVO.class), Mockito.any(Date.class));
+
+        Mockito.when(publicIpQuarantineVOMock.getPreviousOwnerId()).thenReturn(dummyID);
+        Mockito.when(accountManagerMock.getAccount(Mockito.anyLong())).thenReturn(previousOwnerMock);
+        Mockito.when(previousOwnerMock.getUuid()).thenReturn(UUID);
+        Mockito.when(newOwnerMock.getUuid()).thenReturn(UUID_2);
+
+        boolean result = ipAddressManager.canPublicIpAddressBeAllocated(ipAddressMock, newOwnerMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void removePublicIpAddressFromQuarantineTestPersistsObject() {
+        Long quarantineProcessId = 100L;
+        Long publicAddressId = 200L;
+        Long callingAccountId = 300L;
+        String removalReason = "removalReason";
+
+        try (MockedStatic<CallContext> callContextMockedStatic = Mockito.mockStatic(CallContext.class)) {
+            Mockito.doReturn(publicIpQuarantineVOMock).when(publicIpQuarantineDaoMock).findById(quarantineProcessId);
+            Mockito.when(publicIpQuarantineVOMock.getPublicIpAddressId()).thenReturn(publicAddressId);
+            Mockito.doReturn(ipAddressVO).when(ipAddressDao).findById(publicAddressId);
+
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callingAccountId);
+            callContextMockedStatic.when(CallContext::current).thenReturn(callContextMock);
+
+            ipAddressManager.removePublicIpAddressFromQuarantine(quarantineProcessId, removalReason);
+
+            Mockito.verify(publicIpQuarantineVOMock).setRemoved(Mockito.any());
+            Mockito.verify(publicIpQuarantineVOMock).setRemovalReason(removalReason);
+            Mockito.verify(publicIpQuarantineVOMock).setRemoverAccountId(callingAccountId);
+            Mockito.verify(publicIpQuarantineDaoMock).persist(publicIpQuarantineVOMock);
+        }
+    }
+
+    @Test
+    public void updateSourceNatIpAddress() throws Exception {
+        IPAddressVO requestedIp = Mockito.mock(IPAddressVO.class);
+        IPAddressVO oldIp = Mockito.mock(IPAddressVO.class);
+        List<IPAddressVO> userIps = new Vector<>();
+        userIps.add(oldIp);
+        ipAddressManager.updateSourceNatIpAddress(requestedIp, userIps);
+        verify(requestedIp).setSourceNat(true);
+        verify(oldIp).setSourceNat(false);
+    }
+
     private void prepareForCheckIfIpResourceCountShouldBeUpdatedTests() {
         Mockito.when(ipAddressVoMock.getAssociatedWithNetworkId()).thenReturn(1L);
         Mockito.when(ipAddressVoMock.getVpcId()).thenReturn(1L);
diff --git a/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java b/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java
index 1767695..476a73e 100644
--- a/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java
+++ b/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java
@@ -17,15 +17,6 @@
 
 package com.cloud.network;
 
-import static org.mockito.Mockito.mock;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
 import com.cloud.dc.DataCenter;
 import com.cloud.exception.InsufficientAddressCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
@@ -40,6 +31,14 @@
 import com.cloud.vm.NicProfile;
 import com.cloud.vm.dao.NicSecondaryIpDaoImpl;
 import com.cloud.vm.dao.NicSecondaryIpVO;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Mockito.mock;
 
 public class Ipv6AddressManagerTest {
 
diff --git a/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java b/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java
index b1883a7..e0c1da8 100644
--- a/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java
+++ b/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java
@@ -16,39 +16,6 @@
 // under the License.
 package com.cloud.network;
 
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
-
-import javax.management.InstanceAlreadyExistsException;
-import javax.management.MBeanRegistrationException;
-import javax.management.MalformedObjectNameException;
-import javax.management.NotCompliantMBeanException;
-
-import org.apache.cloudstack.api.command.user.ipv6.CreateIpv6FirewallRuleCmd;
-import org.apache.cloudstack.api.command.user.ipv6.UpdateIpv6FirewallRuleCmd;
-import org.apache.cloudstack.api.response.Ipv6RouteResponse;
-import org.apache.cloudstack.api.response.VpcResponse;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.commons.collections.CollectionUtils;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.cloud.api.ApiDBUtils;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterGuestIpv6PrefixVO;
@@ -94,10 +61,38 @@
 import com.cloud.vm.dao.NicDao;
 import com.googlecode.ipv6.IPv6Network;
 import com.googlecode.ipv6.IPv6NetworkMask;
+import org.apache.cloudstack.api.command.user.ipv6.CreateIpv6FirewallRuleCmd;
+import org.apache.cloudstack.api.command.user.ipv6.UpdateIpv6FirewallRuleCmd;
+import org.apache.cloudstack.api.response.Ipv6RouteResponse;
+import org.apache.cloudstack.api.response.VpcResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.commons.collections.CollectionUtils;
+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.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
 
-@PowerMockIgnore("javax.management.*")
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ApiDBUtils.class, ActionEventUtils.class, UsageEventUtils.class})
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+@RunWith(MockitoJUnitRunner.class)
 public class Ipv6ServiceImplTest {
 
     @Mock
@@ -149,6 +144,11 @@
     private AccountVO account;
     private UserVO user;
 
+    private MockedStatic<ApiDBUtils> apiDBUtilsMocked;
+    private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
+
+    private MockedStatic<UsageEventUtils> usageEventUtilsMocked;
+
     @Before
     public void setup() {
         updatedPrefixSubnetMap = new ArrayList<>();
@@ -165,8 +165,17 @@
             persistedPrefixSubnetMap.add(map);
             return map;
         });
-        PowerMockito.mockStatic(ApiDBUtils.class);
-        Mockito.when(ApiDBUtils.findZoneById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class));
+        apiDBUtilsMocked = Mockito.mockStatic(ApiDBUtils.class);
+        apiDBUtilsMocked.when(() -> ApiDBUtils.findZoneById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class));
+        actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
+        usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        apiDBUtilsMocked.close();
+        actionEventUtilsMocked.close();
+        usageEventUtilsMocked.close();
     }
 
     private DataCenterGuestIpv6PrefixVO prepareMocksForIpv6Subnet() {
@@ -396,7 +405,6 @@
         Nic nic = Mockito.mock(Nic.class);
         Mockito.when(nic.getIPv6Address()).thenReturn(null);
         Mockito.when(nic.getBroadcastUri()).thenReturn(URI.create(vlan));
-        Mockito.when(vlanDao.listIpv6RangeByZoneIdAndVlanId(1L, "vlan")).thenReturn(new ArrayList<>());
         try (TransactionLegacy txn = TransactionLegacy.open("testNewErrorAssignPublicIpv6ToNetwork")) {
            ipv6Service.assignPublicIpv6ToNetwork(Mockito.mock(Network.class), nic);
         }
@@ -417,7 +425,6 @@
         VlanVO vlanVO = Mockito.mock(VlanVO.class);
         Mockito.when(vlanVO.getIp6Cidr()).thenReturn(cidr);
         Mockito.when(vlanVO.getIp6Gateway()).thenReturn(gateway);
-        Mockito.when(vlanVO.getVlanType()).thenReturn(Vlan.VlanType.VirtualNetwork);
         List<VlanVO> vlans = new ArrayList<>();
         vlans.add(vlanVO);
         Mockito.when(vlanDao.listIpv6RangeByZoneIdAndVlanId(Mockito.anyLong(), Mockito.anyString())).thenReturn(vlans);
@@ -427,9 +434,7 @@
         }
         Mockito.when(nicDao.listPlaceholderNicsByNetworkIdAndVmType(networkId, VirtualMachine.Type.DomainRouter)).thenReturn(placeholderNics);
         Mockito.when(nicDao.createForUpdate(nicId)).thenReturn(new NicVO(publicReserver, 100L, 1L, VirtualMachine.Type.DomainRouter));
-        PowerMockito.mockStatic(ActionEventUtils.class);
         Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L);
-        PowerMockito.mockStatic(UsageEventUtils.class);
     }
 
     @Test
@@ -765,15 +770,12 @@
             removedNics.add((Long)invocation.getArguments()[0]);
             return true;
         });
-        PowerMockito.mockStatic(ActionEventUtils.class);
-        Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L);
-        PowerMockito.mockStatic(UsageEventUtils.class);
+        actionEventUtilsMocked.when(() -> ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L);
         ipv6Service.removePublicIpv6PlaceholderNics(network);
         Assert.assertEquals(1, removedNics.size());
         Assert.assertEquals(nicId, removedNics.get(0));
         removedNics.clear();
         NicVO nic1 = Mockito.mock(NicVO.class);
-        Mockito.when(nic1.getId()).thenReturn(nicId);
         Mockito.when(nic1.getIPv6Address()).thenReturn(null);
         Mockito.when(nicDao.listPlaceholderNicsByNetworkId(Mockito.anyLong())).thenReturn(List.of(nic1));
         ipv6Service.removePublicIpv6PlaceholderNics(network);
diff --git a/server/src/test/java/com/cloud/network/MockFirewallManagerImpl.java b/server/src/test/java/com/cloud/network/MockFirewallManagerImpl.java
index 4c98798..cfdb857 100644
--- a/server/src/test/java/com/cloud/network/MockFirewallManagerImpl.java
+++ b/server/src/test/java/com/cloud/network/MockFirewallManagerImpl.java
@@ -16,13 +16,6 @@
 // under the License.
 package com.cloud.network;
 
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
-import org.apache.cloudstack.api.command.user.firewall.IListFirewallRulesCmd;
-
 import com.cloud.exception.NetworkRuleConflictException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.network.dao.IPAddressVO;
@@ -36,6 +29,11 @@
 import com.cloud.user.Account;
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.ManagerBase;
+import org.apache.cloudstack.api.command.user.firewall.IListFirewallRulesCmd;
+
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
 
 public class MockFirewallManagerImpl extends ManagerBase implements FirewallManager, FirewallService {
 
diff --git a/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java b/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java
index cc919e0..3df12a4 100644
--- a/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java
+++ b/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java
@@ -17,13 +17,6 @@
 
 package com.cloud.network;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.ConfigurationException;
-
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.Vlan;
 import com.cloud.exception.InsufficientAddressCapacityException;
@@ -51,6 +44,12 @@
 import com.cloud.vm.NicProfile;
 import com.cloud.vm.VirtualMachine;
 
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 public class MockNetworkModelImpl extends ManagerBase implements NetworkModel {
 
     /* (non-Javadoc)
diff --git a/server/src/test/java/com/cloud/network/NetworkModelImplTest.java b/server/src/test/java/com/cloud/network/NetworkModelImplTest.java
index e3f689d..0bbead6 100644
--- a/server/src/test/java/com/cloud/network/NetworkModelImplTest.java
+++ b/server/src/test/java/com/cloud/network/NetworkModelImplTest.java
@@ -16,15 +16,14 @@
 // under the License.
 package com.cloud.network;
 
+import com.cloud.dc.DataCenter;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.Pair;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mockito;
 
-import com.cloud.dc.DataCenter;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.utils.Pair;
-
 public class NetworkModelImplTest {
     final String[] ip4Dns1 = {"5.5.5.5", "6.6.6.6"};
     final String[] ip4Dns2 = {"7.7.7.7", "8.8.8.8"};
diff --git a/server/src/test/java/com/cloud/network/NetworkModelTest.java b/server/src/test/java/com/cloud/network/NetworkModelTest.java
index dd4de3b..13f38de 100644
--- a/server/src/test/java/com/cloud/network/NetworkModelTest.java
+++ b/server/src/test/java/com/cloud/network/NetworkModelTest.java
@@ -17,32 +17,6 @@
 
 package com.cloud.network;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-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;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import com.cloud.user.AccountManager;
-import org.apache.cloudstack.network.NetworkPermissionVO;
-import org.apache.cloudstack.network.dao.NetworkPermissionDao;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.VlanVO;
@@ -65,6 +39,7 @@
 import com.cloud.network.dao.PhysicalNetworkVO;
 import com.cloud.projects.dao.ProjectDao;
 import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
 import com.cloud.user.DomainManager;
 import com.cloud.user.dao.AccountDao;
@@ -73,8 +48,31 @@
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.Ip;
-
 import junit.framework.Assert;
+import org.apache.cloudstack.network.NetworkPermissionVO;
+import org.apache.cloudstack.network.dao.NetworkPermissionDao;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+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;
 
 public class NetworkModelTest {
 
diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
index fcf7ad3..c1e9587 100644
--- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
+++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
@@ -17,8 +17,10 @@
 package com.cloud.network;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
@@ -27,14 +29,24 @@
 import static org.mockito.Mockito.doReturn;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import com.cloud.domain.Domain;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.network.dao.PublicIpQuarantineDao;
+import com.cloud.network.vo.PublicIpQuarantineVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.net.Ip;
+import com.cloud.exception.InsufficientAddressCapacityException;
 import org.apache.cloudstack.alert.AlertService;
+import org.apache.cloudstack.api.command.user.address.UpdateQuarantinedIpCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd;
 import org.apache.cloudstack.api.command.user.network.UpdateNetworkCmd;
 import org.apache.cloudstack.context.CallContext;
@@ -42,6 +54,7 @@
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
@@ -49,10 +62,6 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import com.cloud.agent.api.to.IpAddressTO;
@@ -62,6 +71,7 @@
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.IPAddressVO;
@@ -75,20 +85,21 @@
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.offering.NetworkOffering;
+import com.cloud.offering.ServiceOffering;
 import com.cloud.offerings.NetworkOfferingVO;
 import com.cloud.offerings.dao.NetworkOfferingDao;
 import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
 import com.cloud.org.Grouping;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
-import com.cloud.user.AccountManagerImpl;
 import com.cloud.user.AccountService;
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.UserDao;
 import com.cloud.utils.Pair;
-import com.cloud.utils.component.ComponentContext;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.DomainRouterVO;
@@ -96,14 +107,11 @@
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.NicDao;
-import com.cloud.offering.ServiceOffering;
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.service.dao.ServiceOfferingDao;
-import com.cloud.exception.InvalidParameterValueException;
+import org.junit.After;
+import org.mockito.MockedStatic;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@PowerMockIgnore("javax.management.*")
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ComponentContext.class, CallContext.class})
+@RunWith(MockitoJUnitRunner.class)
 public class NetworkServiceImplTest {
     @Mock
     AccountManager accountManager;
@@ -122,8 +130,6 @@
     @Mock
     NetworkOfferingServiceMapDao networkOfferingServiceMapDao;
     @Mock
-    AccountManager accountMgr;
-    @Mock
     EntityManager entityMgr;
     @Mock
     NetworkService networkService;
@@ -162,8 +168,6 @@
     @Mock
     ServiceOfferingVO serviceOfferingVoMock;
 
-    @InjectMocks
-    AccountManagerImpl accountManagerImpl;
     @Mock
     ConfigKey<Integer> privateMtuKey;
     @Mock
@@ -177,9 +181,46 @@
     CommandSetupHelper commandSetupHelper;
     @Mock
     private Account accountMock;
+
+    @Mock
+    private AccountVO accountVOMock;
+    @Mock
+    private DomainVO domainVOMock;
     @InjectMocks
     NetworkServiceImpl service = new NetworkServiceImpl();
 
+    @Mock
+    DomainDao domainDaoMock;
+
+    @Mock
+    AccountDao accountDaoMock;
+
+    @Mock
+    UpdateQuarantinedIpCmd updateQuarantinedIpCmdMock;
+
+    @Mock
+    PublicIpQuarantineDao publicIpQuarantineDaoMock;
+
+    @Mock
+    private PublicIpQuarantineVO publicIpQuarantineVOMock;
+
+    @Mock
+    private IPAddressVO ipAddressVOMock;
+
+    @Mock
+    private IpAddressManager ipAddressManagerMock;
+
+    @Mock
+    private Ip ipMock;
+
+    private static Date beforeDate;
+
+    private static Date afterDate;
+
+    private final Long publicIpId = 1L;
+
+    private final String dummyIpAddress = "192.168.0.1";
+
     private static final String VLAN_ID_900 = "900";
     private static final String VLAN_ID_901 = "901";
     private static final String VLAN_ID_902 = "902";
@@ -200,6 +241,24 @@
     private  PhysicalNetworkVO phyNet;
     private VpcVO vpc;
 
+    private MockedStatic<CallContext> callContextMocked;
+
+    private AutoCloseable closeable;
+
+    @BeforeClass
+    public static void setUpBeforeClass() {
+        Date date = new Date();
+        Calendar calendar = Calendar.getInstance();
+
+        calendar.setTime(date);
+        calendar.add(Calendar.DATE, -1);
+        beforeDate = calendar.getTime();
+
+        calendar.setTime(date);
+        calendar.add(Calendar.DATE, 1);
+        afterDate = calendar.getTime();
+    }
+
     private void registerCallContext() {
         account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
         account.setId(ACCOUNT_ID);
@@ -212,9 +271,8 @@
 
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        replaceUserChangeMtuField();
-        Mockito.when(userChangeMtuKey.valueIn(anyLong())).thenReturn(Boolean.TRUE);
+        closeable = MockitoAnnotations.openMocks(this);
+        overrideDefaultConfigValue(NetworkService.AllowUsersToSpecifyVRMtu, "_defaultValue", "true");
         offering = Mockito.mock(NetworkOfferingVO.class);
         network = Mockito.mock(Network.class);
         dc = Mockito.mock(DataCenterVO.class);
@@ -223,7 +281,7 @@
         service._networkOfferingDao = networkOfferingDao;
         service._physicalNetworkDao = physicalNetworkDao;
         service._dcDao = dcDao;
-        service._accountMgr = accountMgr;
+        service._accountMgr = accountManager;
         service._networkMgr = networkManager;
         service.alertManager = alertManager;
         service._configMgr = configMgr;
@@ -236,20 +294,32 @@
         service.routerDao = routerDao;
         service.commandSetupHelper = commandSetupHelper;
         service.networkHelper = networkHelper;
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        accountMock = PowerMockito.mock(Account.class);
+        service._ipAddrMgr = ipAddressManagerMock;
+        callContextMocked = Mockito.mockStatic(CallContext.class);
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+        accountMock = Mockito.mock(Account.class);
         Mockito.when(service._accountMgr.finalizeOwner(any(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(accountMock);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
+        Mockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
         NetworkOffering networkOffering = Mockito.mock(NetworkOffering.class);
         Mockito.when(entityMgr.findById(NetworkOffering.class, 1L)).thenReturn(networkOffering);
-        Mockito.when(networkService.findPhysicalNetworkId(Mockito.anyLong(), ArgumentMatchers.nullable(String.class), Mockito.any(Networks.TrafficType.class))).thenReturn(1L);
         Mockito.when(networkOfferingDao.findById(1L)).thenReturn(offering);
         Mockito.when(physicalNetworkDao.findById(Mockito.anyLong())).thenReturn(phyNet);
         Mockito.when(dcDao.findById(Mockito.anyLong())).thenReturn(dc);
-        Mockito.lenient().doNothing().when(accountMgr).checkAccess(accountMock, networkOffering, dc);
-        Mockito.when(accountMgr.isRootAdmin(accountMock.getId())).thenReturn(true);
+        Mockito.lenient().doNothing().when(accountManager).checkAccess(accountMock, networkOffering, dc);
+        Mockito.when(accountManager.isRootAdmin(accountMock.getId())).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        callContextMocked.close();
+        closeable.close();
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
     }
 
     @Test
@@ -352,6 +422,7 @@
         ReflectionTestUtils.setField(createNetworkCmd, "privateMtu", privateMtu);
         ReflectionTestUtils.setField(createNetworkCmd, "physicalNetworkId", null);
         Mockito.when(offering.isSystemOnly()).thenReturn(false);
+        Mockito.when(dc.getId()).thenReturn(1L);
         Mockito.when(dc.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
         Map<String, String> networkProvidersMap = new HashMap<String, String>();
         Mockito.when(networkManager.finalizeServicesAndProvidersForNetwork(ArgumentMatchers.any(NetworkOffering.class), anyLong())).thenReturn(networkProvidersMap);
@@ -371,8 +442,6 @@
         Integer publicMtu = 2450;
         Integer privateMtu = 1500;
         Long zoneId = 1L;
-        when(publicMtuKey.valueIn(zoneId)).thenReturn(NetworkService.DEFAULT_MTU);
-        when(privateMtuKey.valueIn(zoneId)).thenReturn(NetworkService.DEFAULT_MTU);
         Pair<Integer, Integer> interfaceMtus = service.validateMtuConfig(publicMtu, privateMtu, zoneId);
         Assert.assertNotNull(interfaceMtus);
         Assert.assertEquals(NetworkService.DEFAULT_MTU, interfaceMtus.first());
@@ -386,8 +455,6 @@
         Integer publicMtu = 1500;
         Integer privateMtu = 2500;
         Long zoneId = 1L;
-        when(publicMtuKey.valueIn(zoneId)).thenReturn(NetworkService.DEFAULT_MTU);
-        when(privateMtuKey.valueIn(zoneId)).thenReturn(NetworkService.DEFAULT_MTU);
         Pair<Integer, Integer> interfaceMtus = service.validateMtuConfig(publicMtu, privateMtu, zoneId);
         Assert.assertNotNull(interfaceMtus);
         Assert.assertEquals(NetworkService.DEFAULT_MTU, interfaceMtus.first());
@@ -409,6 +476,7 @@
         ReflectionTestUtils.setField(createNetworkCmd, "privateMtu", privateMtu);
         ReflectionTestUtils.setField(createNetworkCmd, "physicalNetworkId", null);
         ReflectionTestUtils.setField(createNetworkCmd, "vpcId", 1L);
+        Mockito.when(dc.getId()).thenReturn(1L);
         Mockito.when(configMgr.isOfferingForVpc(offering)).thenReturn(true);
         Mockito.when(vpcDao.findById(anyLong())).thenReturn(vpc);
 
@@ -442,20 +510,8 @@
         routers.add(routerPrimary);
 
         CallContext.register(callingUser, callingAccount);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Mockito.doReturn(1L).when(callContextMock).getCallingUserId();
-        Mockito.when(userDao.findById(anyLong())).thenReturn(userVO);
-        Mockito.when(accountService.getActiveUser(1L)).thenReturn(callingUser);
-        Mockito.when(callingUser.getAccountId()).thenReturn(1L);
-        Mockito.when(accountService.getActiveAccountById(anyLong())).thenReturn(callingAccount);
-        Mockito.when(networkDao.findById(anyLong())).thenReturn(networkVO);
-        Mockito.when(networkOfferingDao.findByIdIncludingRemoved(anyLong())).thenReturn(offering);
-        Mockito.when(offering.isSystemOnly()).thenReturn(false);
-        Mockito.when(networkVO.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
-        Mockito.when(networkVO.getGuestType()).thenReturn(Network.GuestType.Shared);
-        Mockito.when(nicDao.listByNetworkIdAndType(anyLong(), any(VirtualMachine.Type.class))).thenReturn(List.of(Mockito.mock(NicVO.class)));
+        Mockito.when(CallContext.current()).thenReturn(callContextMock);
         Mockito.when(networkVO.getVpcId()).thenReturn(null);
-        Mockito.when(ipAddressDao.listByNetworkId(anyLong())).thenReturn(addresses);
 
         Pair<Integer, Integer> updatedMtus = service.validateMtuOnUpdate(networkVO, zoneId, publicMtu, privateMtu);
         Assert.assertEquals(publicMtu, updatedMtus.first());
@@ -488,36 +544,19 @@
         Assert.assertEquals(privateMtu, updatedMtus.second());
     }
 
-    private void replaceUserChangeMtuField() throws Exception {
-        Field field = NetworkService.class.getDeclaredField("AllowUsersToSpecifyVRMtu");
-        field.setAccessible(true);
-        // remove final modifier from field
-        Field modifiersField = Field.class.getDeclaredField("modifiers");
-        modifiersField.setAccessible(true);
-        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
-        field.set(null, userChangeMtuKey);
-    }
-
     private void prepareCreateNetworkDnsMocks(CreateNetworkCmd cmd, Network.GuestType guestType, boolean ipv6, boolean isVpc, boolean dnsServiceSupported) {
         long networkOfferingId = 1L;
         Mockito.when(cmd.getNetworkOfferingId()).thenReturn(networkOfferingId);
         NetworkOfferingVO networkOfferingVO = Mockito.mock(NetworkOfferingVO.class);
         Mockito.when(networkOfferingVO.getId()).thenReturn(networkOfferingId);
         Mockito.when(networkOfferingVO.getGuestType()).thenReturn(guestType);
-        Mockito.when(networkOfferingVO.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
-        Mockito.when(networkOfferingVO.isSpecifyIpRanges()).thenReturn(true);
         Mockito.when(networkOfferingDao.findById(networkOfferingId)).thenReturn(networkOfferingVO);
         if (Network.GuestType.Shared.equals(guestType)) {
             Mockito.when(networkModel.isProviderForNetworkOffering(Mockito.any(), Mockito.anyLong())).thenReturn(true);
             Mockito.when(cmd.getGateway()).thenReturn(IP4_GATEWAY);
             Mockito.when(cmd.getNetmask()).thenReturn(IP4_NETMASK);
         }
-        Mockito.when(accountManager.isNormalUser(Mockito.anyLong())).thenReturn(true);
         Mockito.when(physicalNetworkDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(PhysicalNetworkVO.class));
-        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
-        Mockito.when(zone.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
-        Mockito.when(dataCenterDao.findById(Mockito.anyLong())).thenReturn(zone);
-        Mockito.when(networkOrchestrationService.finalizeServicesAndProvidersForNetwork(Mockito.any(), Mockito.anyLong())).thenReturn(new HashMap<>());
         Mockito.when(networkOfferingServiceMapDao.areServicesSupportedByNetworkOffering(networkOfferingId, Network.Service.Dns)).thenReturn(dnsServiceSupported);
         if(ipv6 && Network.GuestType.Isolated.equals(guestType)) {
             Mockito.when(networkOfferingDao.isIpv6Supported(networkOfferingId)).thenReturn(true);
@@ -562,7 +601,7 @@
         }
     }
 
-    @Test(expected = InvalidParameterValueException.class)
+    @Test(expected = CloudRuntimeException.class)
     public void testCreateNetworkDnsOfferingServiceFailure() {
         registerCallContext();
         CreateNetworkCmd cmd = Mockito.mock(CreateNetworkCmd.class);
@@ -577,7 +616,7 @@
         }
     }
 
-    @Test(expected = InvalidParameterValueException.class)
+    @Test(expected = CloudRuntimeException.class)
     public void testCreateIp4NetworkIp6DnsFailure() {
         registerCallContext();
         CreateNetworkCmd cmd = Mockito.mock(CreateNetworkCmd.class);
@@ -713,10 +752,7 @@
             Assert.fail(String.format("Unable to set network guestType, %s", e.getMessage()));
         }
         networkVO.setIp6Cidr("cidr");
-        Long offeringId = 1L;
         NetworkOffering offering = Mockito.mock(NetworkOffering.class);
-        Mockito.when(offering.getId()).thenReturn(offeringId);
-        Mockito.when(networkOfferingServiceMapDao.areServicesSupportedByNetworkOffering(offeringId, Network.Service.Dns)).thenReturn(true);
         boolean updated = service.checkAndUpdateNetworkDns(networkVO, offering, "", null, "", null);
         Assert.assertTrue(updated);
         Assert.assertNull(networkVO.getDns1());
@@ -753,10 +789,10 @@
     public void validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouterTestMustThrowInvalidParameterValueExceptionWhenSystemVmTypeIsNotDomainRouter() {
         doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(anyLong());
         doReturn(ServiceOffering.State.Active).when(serviceOfferingVoMock).getState();
-        doReturn(VirtualMachine.Type.ElasticLoadBalancerVm.toString()).when(serviceOfferingVoMock).getSystemVmType();
+        doReturn(VirtualMachine.Type.ElasticLoadBalancerVm.toString()).when(serviceOfferingVoMock).getVmType();
 
         String expectedMessage = String.format("The specified service offering [%s] is of type [%s]. Virtual routers can only be created with service offering of type [%s].",
-                serviceOfferingVoMock, serviceOfferingVoMock.getSystemVmType(), VirtualMachine.Type.DomainRouter.toString().toLowerCase());
+                serviceOfferingVoMock, serviceOfferingVoMock.getVmType(), VirtualMachine.Type.DomainRouter.toString().toLowerCase());
         InvalidParameterValueException assertThrows = Assert.assertThrows(expectedException, () -> {
             service.validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(1l);
         });
@@ -770,4 +806,250 @@
 
         networkServiceImplMock.validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(1l);
     }
+
+    @Test
+    public void updatePublicIpAddressInQuarantineTestQuarantineIsAlreadyExpiredShouldThrowCloudRuntimeException() {
+        Mockito.when(updateQuarantinedIpCmdMock.getId()).thenReturn(publicIpId);
+        Mockito.when(updateQuarantinedIpCmdMock.getEndDate()).thenReturn(afterDate);
+        Mockito.when(publicIpQuarantineDaoMock.findById(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+        Mockito.when(accountDaoMock.findById(Mockito.anyLong())).thenReturn(accountVOMock);
+        Mockito.when(domainDaoMock.findById(Mockito.anyLong())).thenReturn(domainVOMock);
+        Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
+        Mockito.when(ipAddressDao.findById(Mockito.anyLong())).thenReturn(ipAddressVOMock);
+        Mockito.when(ipAddressVOMock.getAddress()).thenReturn(ipMock);
+        Mockito.when(ipMock.toString()).thenReturn(dummyIpAddress);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(beforeDate);
+        String expectedMessage = String.format("The quarantine for the public IP address [%s] is no longer active; thus, it cannot be updated.", dummyIpAddress);
+        CloudRuntimeException assertThrows = Assert.assertThrows(CloudRuntimeException.class,
+                () -> service.updatePublicIpAddressInQuarantine(updateQuarantinedIpCmdMock));
+
+        Assert.assertEquals(expectedMessage, assertThrows.getMessage());
+    }
+
+    @Test
+    public void updatePublicIpAddressInQuarantineTestGivenEndDateIsBeforeCurrentDateShouldThrowInvalidParameterValueException() {
+        Mockito.when(updateQuarantinedIpCmdMock.getId()).thenReturn(publicIpId);
+        Mockito.when(updateQuarantinedIpCmdMock.getEndDate()).thenReturn(beforeDate);
+
+        String expectedMessage = String.format("The given end date [%s] is invalid as it is before the current date.", beforeDate);
+        InvalidParameterValueException assertThrows = Assert.assertThrows(InvalidParameterValueException.class,
+                () -> service.updatePublicIpAddressInQuarantine(updateQuarantinedIpCmdMock));
+
+        Assert.assertEquals(expectedMessage, assertThrows.getMessage());
+    }
+
+    @Test
+    public void updatePublicIpAddressInQuarantineTestQuarantineIsStillValidAndGivenEndDateIsAfterCurrentDateShouldWork() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(afterDate);
+        calendar.add(Calendar.DATE, 5);
+        Date expectedNewEndDate = calendar.getTime();
+
+        Mockito.when(updateQuarantinedIpCmdMock.getId()).thenReturn(publicIpId);
+        Mockito.when(updateQuarantinedIpCmdMock.getEndDate()).thenReturn(expectedNewEndDate);
+        Mockito.when(publicIpQuarantineDaoMock.findById(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+        Mockito.when(accountDaoMock.findById(Mockito.anyLong())).thenReturn(accountVOMock);
+        Mockito.when(domainDaoMock.findById(Mockito.anyLong())).thenReturn(domainVOMock);
+        Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
+        Mockito.when(ipAddressDao.findById(Mockito.anyLong())).thenReturn(ipAddressVOMock);
+        Mockito.when(ipAddressDao.findById(Mockito.anyLong())).thenReturn(ipAddressVOMock);
+        Mockito.when(ipAddressVOMock.getAddress()).thenReturn(ipMock);
+        Mockito.when(ipMock.toString()).thenReturn(dummyIpAddress);
+        Mockito.when(publicIpQuarantineVOMock.getEndDate()).thenReturn(afterDate);
+        Mockito.when(ipAddressManagerMock.updatePublicIpAddressInQuarantine(anyLong(), Mockito.any(Date.class))).thenReturn(publicIpQuarantineVOMock);
+
+        PublicIpQuarantine actualPublicIpQuarantine = service.updatePublicIpAddressInQuarantine(updateQuarantinedIpCmdMock);
+        Mockito.when(actualPublicIpQuarantine.getEndDate()).thenReturn(expectedNewEndDate);
+
+        Assert.assertEquals(expectedNewEndDate , actualPublicIpQuarantine.getEndDate());
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void retrievePublicIpQuarantineTestIpIdNullAndIpAddressNullShouldThrowException() {
+        service.retrievePublicIpQuarantine(null, null);
+    }
+
+    @Test
+    public void retrievePublicIpQuarantineTestValidIpIdShouldReturnPublicQuarantine() {
+        Mockito.when(publicIpQuarantineDaoMock.findById(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+
+        service.retrievePublicIpQuarantine(1L, null);
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(1)).findById(Mockito.anyLong());
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(0)).findByIpAddress(Mockito.anyString());
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void retrievePublicIpQuarantineTestInvalidIpIdShouldThrowException() {
+        Mockito.when(publicIpQuarantineDaoMock.findById(Mockito.anyLong())).thenReturn(null);
+
+        service.retrievePublicIpQuarantine(1L, null);
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(1)).findById(Mockito.anyLong());
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(0)).findByIpAddress(Mockito.anyString());
+    }
+
+    @Test
+    public void retrievePublicIpQuarantineTestValidIpAddressShouldReturnPublicQuarantine() {
+        Mockito.when(publicIpQuarantineDaoMock.findByIpAddress(Mockito.anyString())).thenReturn(publicIpQuarantineVOMock);
+
+        service.retrievePublicIpQuarantine(null, "10.1.1.1");
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(0)).findById(Mockito.anyLong());
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(1)).findByIpAddress(Mockito.anyString());
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void retrievePublicIpQuarantineTestInvalidIpAddressShouldThrowException() {
+        Mockito.when(publicIpQuarantineDaoMock.findByIpAddress(Mockito.anyString())).thenReturn(null);
+
+        service.retrievePublicIpQuarantine(null, "10.1.1.1");
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(0)).findById(Mockito.anyLong());
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(1)).findByIpAddress(Mockito.anyString());
+    }
+
+    @Test
+    public void retrievePublicIpQuarantineTestIpIdAndAddressInformedShouldUseId() {
+        Mockito.when(publicIpQuarantineDaoMock.findById(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock);
+
+        service.retrievePublicIpQuarantine(1L, "10.1.1.1");
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(1)).findById(Mockito.anyLong());
+        Mockito.verify(publicIpQuarantineDaoMock, Mockito.times(0)).findByIpAddress(Mockito.anyString());
+    }
+
+    @Test
+    public void validateNotSharedNetworkRouterIPv4() {
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.L2);
+        service.validateSharedNetworkRouterIPs(null, null, null, null, null, null, null, null, null, ntwkOff);
+    }
+
+    @Test
+    public void validateSharedNetworkRouterIPs() {
+        String startIP = "10.0.16.2";
+        String endIP = "10.0.16.100";
+        String routerIPv4 = "10.0.16.100";
+        String routerPv6 = "fd17:ac56:1234:2000::fb";
+        String startIPv6 = "fd17:ac56:1234:2000::1";
+        String endIPv6 = "fd17:ac56:1234:2000::fc";
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared);
+        service.validateSharedNetworkRouterIPs(IP4_GATEWAY, startIP, endIP, IP4_NETMASK, routerIPv4, routerPv6, startIPv6, endIPv6, IP6_CIDR, ntwkOff);
+    }
+
+    @Test
+    public void validateSharedNetworkWrongRouterIPv4() {
+        String startIP = "10.0.16.2";
+        String endIP = "10.0.16.100";
+        String routerIPv4 = "10.0.16.101";
+        String routerPv6 = "fd17:ac56:1234:2000::fb";
+        String startIPv6 = "fd17:ac56:1234:2000::1";
+        String endIPv6 = "fd17:ac56:1234:2000::fc";
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared);
+        boolean passing = false;
+        try {
+            service.validateSharedNetworkRouterIPs(IP4_GATEWAY, startIP, endIP, IP4_NETMASK, routerIPv4, routerPv6, startIPv6, endIPv6, IP6_CIDR, ntwkOff);
+        } catch (CloudRuntimeException e) {
+            Assert.assertTrue(e.getMessage().contains("Router IPv4 IP provided is not within the specified range: "));
+            passing = true;
+        }
+        Assert.assertTrue(passing);
+    }
+
+    @Test
+    public void validateSharedNetworkNoEndOfIPv6Range() {
+        String startIP = null;
+        String endIP = null;
+        String routerIPv4 = null;
+        String routerPv6 = "fd17:ac56:1234:2000::1";
+        String startIPv6 = "fd17:ac56:1234:2000::1";
+        String endIPv6 = null;
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared);
+        service.validateSharedNetworkRouterIPs(IP4_GATEWAY, startIP, endIP, IP4_NETMASK, routerIPv4, routerPv6, startIPv6, endIPv6, IP6_CIDR, ntwkOff);
+    }
+
+    @Test
+    public void validateSharedNetworkIPv6RouterNotInRange() {
+        String routerIPv4 = null;
+        String routerIPv6 = "fd17:ac56:1234:2001::1";
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared);
+        boolean passing = true;
+        try {
+            service.validateSharedNetworkRouterIPs(IP4_GATEWAY, null, null, IP4_NETMASK, routerIPv4, routerIPv6, null, null, IP6_CIDR, ntwkOff);
+            passing = false;
+        } catch (CloudRuntimeException e) {
+            Assert.assertTrue(e.getMessage().contains("Router IPv6 address provided is not with the network range"));
+        }
+        Assert.assertTrue(passing);
+    }
+
+    @Test
+    public void invalidateSharedNetworkIPv6RouterAddress() {
+        String routerIPv6 = "fd17:ac56:1234:2000::fg";
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared);
+        boolean passing = false;
+        try {
+            service.validateSharedNetworkRouterIPs(IP4_GATEWAY, null, null, IP4_NETMASK, null, routerIPv6, null, null, IP6_CIDR, ntwkOff);
+        } catch (CloudRuntimeException e) {
+            Assert.assertTrue(e.getMessage().contains("Router IPv6 address provided is of incorrect format"));
+            passing = true;
+        }
+        Assert.assertTrue(passing);
+    }
+
+    @Test
+    public void invalidateSharedNetworkIPv4RouterAddress() {
+        String routerIPv4 = "10.100.1000.1";
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared);
+        boolean passing = false;
+        try {
+            service.validateSharedNetworkRouterIPs(IP4_GATEWAY, null, null, IP4_NETMASK, routerIPv4, null, null, null, IP6_CIDR, ntwkOff);
+        } catch (CloudRuntimeException e) {
+            Assert.assertTrue(e.getMessage().contains("Router IPv4 IP provided is of incorrect format"));
+            passing = true;
+        }
+        Assert.assertTrue(passing);
+    }
+
+    @Test
+    public void checkAndDontSetSourceNatIp() {
+        CreateNetworkCmd cmd = new CreateNetworkCmd();
+        try {
+            service.checkAndSetRouterSourceNatIp(account, cmd, null);
+        } catch (InsufficientAddressCapacityException | ResourceAllocationException e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test
+    public void checkAndSetSourceNatIp() {
+        String srcNatIp = "10.100.1000.10000";
+        Long networkOfferingId = 2l;
+        Long zoneId = 3l;
+        registerCallContext();
+        ReflectionTestUtils.setField(createNetworkCmd, "networkOfferingId", networkOfferingId);
+        ReflectionTestUtils.setField(createNetworkCmd, "sourceNatIP", srcNatIp);
+        ReflectionTestUtils.setField(createNetworkCmd, "zoneId", zoneId);
+        ReflectionTestUtils.setField(createNetworkCmd, "physicalNetworkId", null);
+        NetworkVO networkVO = Mockito.spy(NetworkVO.class);
+        IpAddress ipAddress = Mockito.mock(IPAddressVO.class);
+        NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class);
+        Long networkId = 7l;
+        when(networkVO.getId()).thenReturn(networkId);
+        when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated);
+        when(networkDao.findById(networkId)).thenReturn(networkVO);
+        when(entityMgr.findById(NetworkOffering.class, networkOfferingId)).thenReturn(ntwkOff);
+        when(entityMgr.findById(eq(DataCenter.class), anyLong())).thenReturn(dc);
+        when(ipAddress.getId()).thenReturn(5l);
+        when(networkVO.getId()).thenReturn(networkId);
+        when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated);
+        try {
+            when(ipAddressManagerMock.allocateIp(any(), anyBoolean(), any(), anyLong(), any(), any(), eq(srcNatIp))).thenReturn(ipAddress);
+            service.checkAndSetRouterSourceNatIp(account, createNetworkCmd, networkVO);
+        } catch (InsufficientAddressCapacityException | ResourceAllocationException e) {
+            Assert.fail(e.getMessage());
+        }
+    }
 }
diff --git a/server/src/test/java/com/cloud/network/UpdatePhysicalNetworkTest.java b/server/src/test/java/com/cloud/network/UpdatePhysicalNetworkTest.java
index 4a7c237..eb654c0 100644
--- a/server/src/test/java/com/cloud/network/UpdatePhysicalNetworkTest.java
+++ b/server/src/test/java/com/cloud/network/UpdatePhysicalNetworkTest.java
@@ -16,25 +16,24 @@
 // under the License.
 package com.cloud.network;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.dc.dao.DataCenterVnetDao;
 import com.cloud.network.dao.PhysicalNetworkDao;
 import com.cloud.network.dao.PhysicalNetworkVO;
 import com.cloud.utils.db.TransactionLegacy;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class UpdatePhysicalNetworkTest {
     private PhysicalNetworkDao _physicalNetworkDao = mock(PhysicalNetworkDao.class);
diff --git a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
index 08070fa..aaf0f25 100644
--- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
@@ -16,66 +16,6 @@
 // under the License.
 package com.cloud.network.as;
 
-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.ArgumentMatchers.matches;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-
-import org.apache.cloudstack.affinity.AffinityGroupVO;
-import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
-import org.apache.cloudstack.annotation.AnnotationService;
-import org.apache.cloudstack.annotation.dao.AnnotationDao;
-import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.BaseCmd;
-import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd;
-import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScalePolicyCmd;
-import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmGroupCmd;
-import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmProfileCmd;
-import org.apache.cloudstack.api.command.user.autoscale.CreateConditionCmd;
-import org.apache.cloudstack.api.command.user.autoscale.ListCountersCmd;
-import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
-import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmProfileCmd;
-import org.apache.cloudstack.api.command.user.autoscale.UpdateConditionCmd;
-import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
-import org.apache.cloudstack.config.ApiServiceConfiguration;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.framework.config.ConfigKey;
-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.Mockito;
-import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.springframework.test.util.ReflectionTestUtils;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.PerformanceMonitorAnswer;
 import com.cloud.agent.api.PerformanceMonitorCommand;
@@ -125,7 +65,6 @@
 import com.cloud.network.router.VirtualRouterAutoScale;
 import com.cloud.network.router.VirtualRouterAutoScale.AutoScaleValueType;
 import com.cloud.network.router.VirtualRouterAutoScale.VirtualRouterAutoScaleCounter;
-import com.cloud.network.rules.LoadBalancer;
 import com.cloud.offering.DiskOffering;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.offerings.NetworkOfferingVO;
@@ -160,14 +99,69 @@
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineManager;
-import com.cloud.vm.VirtualMachineProfile;
 import com.cloud.vm.VmStats;
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.affinity.AffinityGroupVO;
+import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd;
+import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScalePolicyCmd;
+import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmGroupCmd;
+import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmProfileCmd;
+import org.apache.cloudstack.api.command.user.autoscale.CreateConditionCmd;
+import org.apache.cloudstack.api.command.user.autoscale.ListCountersCmd;
+import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
+import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmProfileCmd;
+import org.apache.cloudstack.api.command.user.autoscale.UpdateConditionCmd;
+import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
+import org.apache.cloudstack.config.ApiServiceConfiguration;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+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.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore("javax.management.*")
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+
+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.ArgumentMatchers.matches;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class AutoScaleManagerImplTest {
 
     @Spy
@@ -733,7 +727,6 @@
     }
 
     @Test
-    @PrepareForTest(ComponentContext.class)
     public void testCreateAutoScaleVmProfile() {
         when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(zoneMock);
         when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOfferingMock);
@@ -744,73 +737,70 @@
         DispatchChain dispatchChainMock = Mockito.mock(DispatchChain.class);
         when(dispatchChainFactory.getStandardDispatchChain()).thenReturn(dispatchChainMock);
         Mockito.doNothing().when(dispatchChainMock).dispatch(any());
-        PowerMockito.mockStatic(ComponentContext.class);
-        when(ComponentContext.inject(DeployVMCmd.class)).thenReturn(Mockito.mock(DeployVMCmd.class));
+        try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
+            when(ComponentContext.inject(DeployVMCmd.class)).thenReturn(Mockito.mock(DeployVMCmd.class));
 
-        when(autoScaleVmProfileDao.persist(any())).thenReturn(asVmProfileMock);
-        CreateAutoScaleVmProfileCmd cmd = new CreateAutoScaleVmProfileCmd();
+            when(autoScaleVmProfileDao.persist(any())).thenReturn(asVmProfileMock);
+            CreateAutoScaleVmProfileCmd cmd = new CreateAutoScaleVmProfileCmd();
 
-        ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
-        ReflectionTestUtils.setField(cmd, "serviceOfferingId", serviceOfferingId);
-        ReflectionTestUtils.setField(cmd, "templateId", templateId);
-        ReflectionTestUtils.setField(cmd, "expungeVmGracePeriod", expungeVmGracePeriod);
-        ReflectionTestUtils.setField(cmd, "otherDeployParams", otherDeployParams);
-        ReflectionTestUtils.setField(cmd, "counterParamList", counterParamList);
+            ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
+            ReflectionTestUtils.setField(cmd, "serviceOfferingId", serviceOfferingId);
+            ReflectionTestUtils.setField(cmd, "templateId", templateId);
+            ReflectionTestUtils.setField(cmd, "expungeVmGracePeriod", expungeVmGracePeriod);
+            ReflectionTestUtils.setField(cmd, "otherDeployParams", otherDeployParams);
+            ReflectionTestUtils.setField(cmd, "counterParamList", counterParamList);
 
-        ReflectionTestUtils.setField(cmd, "userData", userData);
-        ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
-        ReflectionTestUtils.setField(cmd, "userDataDetails", userDataDetails);
+            ReflectionTestUtils.setField(cmd, "userData", userData);
+            ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
+            ReflectionTestUtils.setField(cmd, "userDataDetails", userDataDetails);
 
-        AutoScaleVmProfile vmProfile = autoScaleManagerImplSpy.createAutoScaleVmProfile(cmd);
+            AutoScaleVmProfile vmProfile = autoScaleManagerImplSpy.createAutoScaleVmProfile(cmd);
 
-        Assert.assertEquals(asVmProfileMock, vmProfile);
-        Mockito.verify(autoScaleVmProfileDao).persist(Mockito.any());
+            Assert.assertEquals(asVmProfileMock, vmProfile);
+            Mockito.verify(autoScaleVmProfileDao).persist(Mockito.any());
 
-        Mockito.verify(userVmMgr).finalizeUserData(any(), any(), any());
-        Mockito.verify(userVmMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
+            Mockito.verify(userVmMgr).finalizeUserData(any(), any(), any());
+            Mockito.verify(userVmMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));
+        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
-    @PrepareForTest(ComponentContext.class)
     public void testCreateAutoScaleVmProfileFail() {
         when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(zoneMock);
         when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOfferingMock);
-        when(entityManager.findByIdIncludingRemoved(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOfferingMock);
         when(entityManager.findById(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock);
-        when(serviceOfferingMock.isDynamic()).thenReturn(false);
         Mockito.doThrow(InvalidParameterValueException.class).when(userVmMgr).finalizeUserData(any(), any(), any());
 
         DispatchChain dispatchChainMock = Mockito.mock(DispatchChain.class);
         when(dispatchChainFactory.getStandardDispatchChain()).thenReturn(dispatchChainMock);
         Mockito.doNothing().when(dispatchChainMock).dispatch(any());
-        PowerMockito.mockStatic(ComponentContext.class);
-        when(ComponentContext.inject(DeployVMCmd.class)).thenReturn(Mockito.mock(DeployVMCmd.class));
+        try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
+            when(ComponentContext.inject(DeployVMCmd.class)).thenReturn(Mockito.mock(DeployVMCmd.class));
 
-        CreateAutoScaleVmProfileCmd cmd = new CreateAutoScaleVmProfileCmd();
+            CreateAutoScaleVmProfileCmd cmd = new CreateAutoScaleVmProfileCmd();
 
-        ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
-        ReflectionTestUtils.setField(cmd, "serviceOfferingId", serviceOfferingId);
-        ReflectionTestUtils.setField(cmd, "templateId", templateId);
-        ReflectionTestUtils.setField(cmd, "expungeVmGracePeriod", expungeVmGracePeriod);
-        ReflectionTestUtils.setField(cmd, "otherDeployParams", otherDeployParams);
-        ReflectionTestUtils.setField(cmd, "counterParamList", counterParamList);
+            ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
+            ReflectionTestUtils.setField(cmd, "serviceOfferingId", serviceOfferingId);
+            ReflectionTestUtils.setField(cmd, "templateId", templateId);
+            ReflectionTestUtils.setField(cmd, "expungeVmGracePeriod", expungeVmGracePeriod);
+            ReflectionTestUtils.setField(cmd, "otherDeployParams", otherDeployParams);
+            ReflectionTestUtils.setField(cmd, "counterParamList", counterParamList);
 
-        ReflectionTestUtils.setField(cmd, "userData", userData);
-        ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
+            ReflectionTestUtils.setField(cmd, "userData", userData);
+            ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
 
-        AutoScaleVmProfile vmProfile = autoScaleManagerImplSpy.createAutoScaleVmProfile(cmd);
+            AutoScaleVmProfile vmProfile = autoScaleManagerImplSpy.createAutoScaleVmProfile(cmd);
+        }
     }
 
     @Test
     public void testUpdateAutoScaleVmProfile() {
         when(autoScaleVmProfileDao.findById(vmProfileId)).thenReturn(asVmProfileMock);
         when(autoScaleVmGroupDao.listByAll(null, vmProfileId)).thenReturn(new ArrayList<>());
-        when(autoScaleVmGroupDao.listByProfile(vmProfileId)).thenReturn(new ArrayList<>());
         when(autoScaleVmProfileDao.persist(any())).thenReturn(asVmProfileMock);
 
         when(asVmProfileMock.getServiceOfferingId()).thenReturn(serviceOfferingId);
         when(asVmProfileMock.getTemplateId()).thenReturn(templateId);
-        when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOfferingMock);
         when(entityManager.findByIdIncludingRemoved(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOfferingMock);
         when(entityManager.findById(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock);
         when(serviceOfferingMock.isDynamic()).thenReturn(false);
@@ -939,7 +929,6 @@
         ReflectionTestUtils.setField(cmd, "scaleDownPolicyIds", Arrays.asList(scaleDownPolicyId));
         ReflectionTestUtils.setField(cmd, "profileId", vmProfileId);
 
-        when(entityManager.findById(LoadBalancer.class, loadBalancerId)).thenReturn(loadBalancerMock);
         when(loadBalancerMock.getAccountId()).thenReturn(accountId);
         when(loadBalancerMock.getDomainId()).thenReturn(domainId);
         when(loadBalancerMock.getDefaultPortStart()).thenReturn(memberPort);
@@ -955,10 +944,8 @@
         SearchBuilder<AutoScalePolicyVO> searchBuilderMock = Mockito.mock(SearchBuilder.class);
         SearchCriteria<AutoScalePolicyVO> searchCriteriaMock = Mockito.mock(SearchCriteria.class);
         when(asScaleUpPolicyMock.getDuration()).thenReturn(scaleUpPolicyDuration);
-        when(asScaleUpPolicyMock.getQuietTime()).thenReturn(scaleUpPolicyQuietTime);
         when(asScaleUpPolicyMock.getAction()).thenReturn(AutoScalePolicy.Action.SCALEUP);
         when(asScaleDownPolicyMock.getDuration()).thenReturn(scaleDownPolicyDuration);
-        when(asScaleDownPolicyMock.getQuietTime()).thenReturn(scaleDownPolicyQuietTime);
         when(asScaleDownPolicyMock.getAction()).thenReturn(AutoScalePolicy.Action.SCALEDOWN);
 
         Mockito.doReturn(searchBuilderMock).when(asPolicyDao).createSearchBuilder();
@@ -967,8 +954,8 @@
         when(asPolicyDao.search(searchCriteriaMock, null)).thenReturn(Arrays.asList(asScaleUpPolicyMock)).thenReturn(Arrays.asList(asScaleDownPolicyMock));
 
         when(autoScaleVmProfileDao.findById(vmProfileId)).thenReturn(asVmProfileMock);
-        PowerMockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).validateAutoScaleCounters(anyLong(), any(), any());
+        Mockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).validateAutoScaleCounters(anyLong(), any(), any());
         when(loadBalancerMock.getNetworkId()).thenReturn(networkId);
         when(networkDao.findById(networkId)).thenReturn(networkMock);
         when(networkMock.getNetworkOfferingId()).thenReturn(networkOfferingId);
@@ -977,7 +964,7 @@
 
         when(autoScaleVmGroupDao.persist(any())).thenReturn(asVmGroupMock);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).scheduleMonitorTask(anyLong());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).scheduleMonitorTask(anyLong());
 
         AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.createAutoScaleVmGroup(cmd);
 
@@ -998,7 +985,7 @@
         List<AutoScaleCounter> supportedAutoScaleCounters = Arrays.asList(new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.Cpu),
                 new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.Memory),
                 new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.VirtualRouter));
-        PowerMockito.doReturn(supportedAutoScaleCounters).when(autoScaleManagerImplSpy).getSupportedAutoScaleCounters(networkId);
+        Mockito.doReturn(supportedAutoScaleCounters).when(autoScaleManagerImplSpy).getSupportedAutoScaleCounters(networkId);
 
         autoScaleManagerImplSpy.validateAutoScaleCounters(networkId, counters, new ArrayList<>());
 
@@ -1016,7 +1003,7 @@
 
         List<AutoScaleCounter> supportedAutoScaleCounters = Arrays.asList(new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.Cpu),
                 new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.VirtualRouter));
-        PowerMockito.doReturn(supportedAutoScaleCounters).when(autoScaleManagerImplSpy).getSupportedAutoScaleCounters(networkId);
+        Mockito.doReturn(supportedAutoScaleCounters).when(autoScaleManagerImplSpy).getSupportedAutoScaleCounters(networkId);
 
         autoScaleManagerImplSpy.validateAutoScaleCounters(networkId, counters, new ArrayList<>());
 
@@ -1045,10 +1032,8 @@
         SearchBuilder<AutoScalePolicyVO> searchBuilderMock = Mockito.mock(SearchBuilder.class);
         SearchCriteria<AutoScalePolicyVO> searchCriteriaMock = Mockito.mock(SearchCriteria.class);
         when(asScaleUpPolicyMock.getDuration()).thenReturn(scaleUpPolicyDuration);
-        when(asScaleUpPolicyMock.getQuietTime()).thenReturn(scaleUpPolicyQuietTime);
         when(asScaleUpPolicyMock.getAction()).thenReturn(AutoScalePolicy.Action.SCALEUP);
         when(asScaleDownPolicyMock.getDuration()).thenReturn(scaleDownPolicyDuration);
-        when(asScaleDownPolicyMock.getQuietTime()).thenReturn(scaleDownPolicyQuietTime);
         when(asScaleDownPolicyMock.getAction()).thenReturn(AutoScalePolicy.Action.SCALEDOWN);
 
         Mockito.doReturn(searchBuilderMock).when(asPolicyDao).createSearchBuilder();
@@ -1058,8 +1043,8 @@
 
         when(lbDao.findById(loadBalancerId)).thenReturn(loadBalancerMock);
         when(autoScaleVmProfileDao.findById(vmProfileId)).thenReturn(asVmProfileMock);
-        PowerMockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).validateAutoScaleCounters(anyLong(), any(), any());
+        Mockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).validateAutoScaleCounters(anyLong(), any(), any());
         when(loadBalancerMock.getNetworkId()).thenReturn(networkId);
         when(networkDao.findById(networkId)).thenReturn(networkMock);
         when(networkMock.getNetworkOfferingId()).thenReturn(networkOfferingId);
@@ -1068,8 +1053,6 @@
 
         when(autoScaleVmGroupDao.persist(any())).thenReturn(asVmGroupMock);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).scheduleMonitorTask(anyLong());
-
         AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.updateAutoScaleVmGroup(cmd);
 
         Assert.assertEquals(asVmGroupMock, vmGroup);
@@ -1092,10 +1075,7 @@
 
         when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
         when(asVmGroupMock.getInterval()).thenReturn(interval);
-        when(asVmGroupMock.getMaxMembers()).thenReturn(maxMembers);
-        when(asVmGroupMock.getMinMembers()).thenReturn(minMembers);
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
-        when(asVmGroupMock.getProfileId()).thenReturn(vmProfileId);
 
         AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.updateAutoScaleVmGroup(cmd);
     }
@@ -1144,8 +1124,8 @@
         when(asVmGroupMock.getId()).thenReturn(vmGroupId);
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.DISABLED);
         when(autoScaleVmGroupDao.persist(any())).thenReturn(asVmGroupMock);
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.DISABLED);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).scheduleMonitorTask(anyLong());
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.DISABLED);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).scheduleMonitorTask(anyLong());
 
         AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.enableAutoScaleVmGroup(vmGroupId);
 
@@ -1179,8 +1159,7 @@
         when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
         when(autoScaleVmGroupDao.persist(any())).thenReturn(asVmGroupMock);
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.ENABLED);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).scheduleMonitorTask(anyLong());
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.ENABLED);
 
         AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.disableAutoScaleVmGroup(vmGroupId);
 
@@ -1207,10 +1186,10 @@
         when(autoScaleVmGroupVmMapDao.countByGroup(vmGroupId)).thenReturn(1);
         when(autoScaleVmGroupVmMapDao.listByGroup(vmGroupId)).thenReturn(Arrays.asList(autoScaleVmGroupVmMapVOMock));
         when(autoScaleVmGroupVmMapVOMock.getInstanceId()).thenReturn(virtualMachineId);
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).destroyVm(virtualMachineId);
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.ENABLED);
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).destroyVm(virtualMachineId);
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.ENABLED);
         when(autoScaleVmGroupDao.remove(vmGroupId)).thenReturn(true);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).cancelMonitorTask(vmGroupId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).cancelMonitorTask(vmGroupId);
         when(autoScaleVmGroupPolicyMapDao.removeByGroupId(vmGroupId)).thenReturn(true);
         when(autoScaleVmGroupVmMapDao.removeByGroup(vmGroupId)).thenReturn(true);
         when(asGroupStatisticsDao.removeByGroupId(vmGroupId)).thenReturn(true);
@@ -1275,7 +1254,7 @@
         when(zoneMock.isLocalStorageEnabled()).thenReturn(false);
         when(diskOfferingMock.isUseLocalStorage()).thenReturn(false);
 
-        PowerMockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
+        Mockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
         when(networkMock.getId()).thenReturn(networkId);
 
         when(userVmMock.getId()).thenReturn(virtualMachineId);
@@ -1323,7 +1302,7 @@
         when(zoneMock.isLocalStorageEnabled()).thenReturn(false);
         when(diskOfferingMock.isUseLocalStorage()).thenReturn(false);
 
-        PowerMockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
+        Mockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
         when(networkMock.getId()).thenReturn(networkId);
 
         when(userVmMock.getId()).thenReturn(virtualMachineId);
@@ -1372,7 +1351,7 @@
         when(zoneMock.isLocalStorageEnabled()).thenReturn(false);
         when(diskOfferingMock.isUseLocalStorage()).thenReturn(false);
 
-        PowerMockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
+        Mockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
         when(networkMock.getId()).thenReturn(networkId);
 
         when(userVmMock.getId()).thenReturn(virtualMachineId);
@@ -1504,62 +1483,59 @@
     }
 
     @Test
-    @PrepareForTest(ActionEventUtils.class)
     public void testDoScaleUp() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
-        PowerMockito.mockStatic(ActionEventUtils.class);
+        try (MockedStatic<ActionEventUtils> ignored = Mockito.mockStatic(ActionEventUtils.class)) {
+            when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
+            when(asVmGroupMock.getId()).thenReturn(vmGroupId);
+            when(asVmGroupMock.getMaxMembers()).thenReturn(maxMembers);
+            when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(maxMembers - 1);
+            when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
 
-        when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
-        when(asVmGroupMock.getId()).thenReturn(vmGroupId);
-        when(asVmGroupMock.getMaxMembers()).thenReturn(maxMembers);
-        when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(maxMembers - 1);
-        when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
+            when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.ENABLED, AutoScaleVmGroup.State.SCALING)).thenReturn(true);
+            when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.SCALING, AutoScaleVmGroup.State.ENABLED)).thenReturn(true);
+            Mockito.doReturn(virtualMachineId).when(autoScaleManagerImplSpy).createNewVM(asVmGroupMock);
 
-        when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.ENABLED, AutoScaleVmGroup.State.SCALING)).thenReturn(true);
-        when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.SCALING, AutoScaleVmGroup.State.ENABLED)).thenReturn(true);
-        PowerMockito.doReturn(virtualMachineId).when(autoScaleManagerImplSpy).createNewVM(asVmGroupMock);
-        Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVm = Mockito.mock(Pair.class);
-        when(userVmMgr.startVirtualMachine(virtualMachineId, null, null, null)).thenReturn(startVm);
+            when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
+            when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock));
+            when(loadBalancerVMMapMock.getInstanceId()).thenReturn(virtualMachineId + 1);
 
-        when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
-        when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock));
-        when(loadBalancerVMMapMock.getInstanceId()).thenReturn(virtualMachineId + 1);
+            when(loadBalancingRulesService.assignToLoadBalancer(anyLong(), any(), any(), eq(true))).thenReturn(true);
 
-        when(loadBalancingRulesService.assignToLoadBalancer(anyLong(), any(), any(), eq(true))).thenReturn(true);
+            autoScaleManagerImplSpy.doScaleUp(vmGroupId, 1);
 
-        autoScaleManagerImplSpy.doScaleUp(vmGroupId, 1);
-
-        Mockito.verify(autoScaleManagerImplSpy).createNewVM(asVmGroupMock);
-        Mockito.verify(loadBalancingRulesService).assignToLoadBalancer(anyLong(), any(), any(), eq(true));
+            Mockito.verify(autoScaleManagerImplSpy).createNewVM(asVmGroupMock);
+            Mockito.verify(loadBalancingRulesService).assignToLoadBalancer(anyLong(), any(), any(), eq(true));
+        }
     }
 
     @Test
-    @PrepareForTest(ActionEventUtils.class)
     public void testDoScaleDown() {
-        PowerMockito.mockStatic(ActionEventUtils.class);
+        try (MockedStatic<ActionEventUtils> ignored = Mockito.mockStatic(ActionEventUtils.class)) {
 
-        when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
-        when(asVmGroupMock.getId()).thenReturn(vmGroupId);
+            when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
+            when(asVmGroupMock.getId()).thenReturn(vmGroupId);
 
-        when(asVmGroupMock.getMinMembers()).thenReturn(minMembers);
-        when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(minMembers + 1);
-        when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
+            when(asVmGroupMock.getMinMembers()).thenReturn(minMembers);
+            when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(minMembers + 1);
+            when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
 
-        when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.ENABLED, AutoScaleVmGroup.State.SCALING)).thenReturn(true);
-        when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.SCALING, AutoScaleVmGroup.State.ENABLED)).thenReturn(true);
+            when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.ENABLED, AutoScaleVmGroup.State.SCALING)).thenReturn(true);
+            when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.SCALING, AutoScaleVmGroup.State.ENABLED)).thenReturn(true);
 
-        when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
-        when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock));
-        when(loadBalancerVMMapMock.getInstanceId()).thenReturn(virtualMachineId);
+            when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
+            when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock));
+            when(loadBalancerVMMapMock.getInstanceId()).thenReturn(virtualMachineId);
 
-        when(loadBalancingRulesService.removeFromLoadBalancer(anyLong(), any(), any(), eq(true))).thenReturn(true);
+            when(loadBalancingRulesService.removeFromLoadBalancer(anyLong(), any(), any(), eq(true))).thenReturn(true);
 
-        when(asVmGroupMock.getProfileId()).thenReturn(vmProfileId);
-        when(autoScaleVmProfileDao.findById(vmProfileId)).thenReturn(asVmProfileMock);
-        when(asVmProfileMock.getExpungeVmGracePeriod()).thenReturn(expungeVmGracePeriod);
+            when(asVmGroupMock.getProfileId()).thenReturn(vmProfileId);
+            when(autoScaleVmProfileDao.findById(vmProfileId)).thenReturn(asVmProfileMock);
+            when(asVmProfileMock.getExpungeVmGracePeriod()).thenReturn(expungeVmGracePeriod);
 
-        autoScaleManagerImplSpy.doScaleDown(vmGroupId);
+            autoScaleManagerImplSpy.doScaleDown(vmGroupId);
 
-        Mockito.verify(loadBalancingRulesService).removeFromLoadBalancer(anyLong(), any(), any(), eq(true));
+            Mockito.verify(loadBalancingRulesService).removeFromLoadBalancer(anyLong(), any(), any(), eq(true));
+        }
     }
 
     @Test
@@ -1585,9 +1561,8 @@
     public void checkAutoScaleVmGroup1() {
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
         when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
-        PowerMockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).checkNetScalerAsGroup(asVmGroupMock);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).checkVirtualRouterAsGroup(asVmGroupMock);
+        Mockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).checkVirtualRouterAsGroup(asVmGroupMock);
 
         autoScaleManagerImplSpy.checkAutoScaleVmGroup(asVmGroupMock);
 
@@ -1599,9 +1574,8 @@
     public void checkAutoScaleVmGroup2() {
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
         when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
-        PowerMockito.doReturn(Network.Provider.VPCVirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).checkNetScalerAsGroup(asVmGroupMock);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).checkVirtualRouterAsGroup(asVmGroupMock);
+        Mockito.doReturn(Network.Provider.VPCVirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).checkVirtualRouterAsGroup(asVmGroupMock);
 
         autoScaleManagerImplSpy.checkAutoScaleVmGroup(asVmGroupMock);
 
@@ -1613,9 +1587,8 @@
     public void checkAutoScaleVmGroup3() {
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
         when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
-        PowerMockito.doReturn(Network.Provider.Netscaler).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).checkNetScalerAsGroup(asVmGroupMock);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).checkVirtualRouterAsGroup(asVmGroupMock);
+        Mockito.doReturn(Network.Provider.Netscaler).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).checkNetScalerAsGroup(asVmGroupMock);
 
         autoScaleManagerImplSpy.checkAutoScaleVmGroup(asVmGroupMock);
 
@@ -1698,7 +1671,6 @@
         when(autoScaleVmGroupVmMapDao.listByGroup(vmGroupId)).thenReturn(Arrays.asList(autoScaleVmGroupVmMapVOMock));
         when(autoScaleVmGroupVmMapVOMock.getInstanceId()).thenReturn(virtualMachineId);
         when(userVmDao.findById(virtualMachineId)).thenReturn(userVmMock);
-        when(userVmMock.getHostId()).thenReturn(null);
 
         Map<Long, List<Long>> result = autoScaleManagerImplSpy.getHostAndVmIdsMap(groupTO);
 
@@ -1743,9 +1715,9 @@
         Map<String, Integer> countersNumberMap = new HashMap<>();
         when(groupTO.getId()).thenReturn(vmGroupId);
         when(groupTO.getLoadBalancerId()).thenReturn(loadBalancerId);
-        PowerMockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).isQuitTimePassForPolicy(policyTO);
-        PowerMockito.doReturn(AutoScalePolicy.Action.SCALEUP).when(autoScaleManagerImplSpy).checkConditionsForPolicy(countersMap, countersNumberMap, policyTO, Network.Provider.VirtualRouter);
+        Mockito.doReturn(Network.Provider.VirtualRouter).when(autoScaleManagerImplSpy).getLoadBalancerServiceProvider(loadBalancerId);
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).isQuitTimePassForPolicy(policyTO);
+        Mockito.doReturn(AutoScalePolicy.Action.SCALEUP).when(autoScaleManagerImplSpy).checkConditionsForPolicy(countersMap, countersNumberMap, policyTO, Network.Provider.VirtualRouter);
 
         AutoScalePolicy.Action result = autoScaleManagerImplSpy.getAutoscaleAction(countersMap, countersNumberMap, groupTO);
 
@@ -1815,10 +1787,8 @@
         ConditionTO conditionTO2 = Mockito.mock(ConditionTO.class);
         CounterTO counterTO2 = Mockito.mock(CounterTO.class);
         when(conditionTO1.getCounter()).thenReturn(counterTO1);
-        when(conditionTO2.getCounter()).thenReturn(counterTO2);
         when(policyTO.getConditions()).thenReturn(Arrays.asList(conditionTO1, conditionTO2));
         when(policyTO.getId()).thenReturn(scaleUpPolicyId);
-        when(policyTO.getAction()).thenReturn(AutoScalePolicy.Action.SCALEUP);
 
         Long counterId2 = counterId + 1;
         Long conditionId2 = conditionId + 1;
@@ -1839,9 +1809,7 @@
         countersNumberMap.put(key2, 1);
 
         when(conditionTO1.getRelationalOperator()).thenReturn(Condition.Operator.LT);
-        when(conditionTO2.getRelationalOperator()).thenReturn(Condition.Operator.LE);
         when(conditionTO1.getThreshold()).thenReturn(40L);
-        when(conditionTO2.getThreshold()).thenReturn(60L);
 
         AutoScalePolicy.Action result = autoScaleManagerImplSpy.checkConditionsForPolicy(countersMap, countersNumberMap,
                 policyTO, Network.Provider.VirtualRouter);
@@ -1946,7 +1914,7 @@
         when(asVmGroupMock.getId()).thenReturn(vmGroupId);
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
         when(lbRulesMgr.toAutoScaleVmGroupTO(asVmGroupMock)).thenReturn(groupTO);
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).isNative(groupTO);
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).isNative(groupTO);
 
         when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(minMembers);
         when(asVmGroupMock.getMinMembers()).thenReturn(minMembers);
@@ -1958,16 +1926,15 @@
         when(userVmMock.getHostId()).thenReturn(hostId);
         when(userVmMock.getInstanceName()).thenReturn(vmName);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).setPerformanceMonitorCommandParams(any(), any());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).setPerformanceMonitorCommandParams(any(), any());
         PerformanceMonitorCommand command = Mockito.mock(PerformanceMonitorCommand.class);
         PerformanceMonitorAnswer answer = new PerformanceMonitorAnswer(command, true, "result");
         when(agentMgr.send(eq(hostId), any(PerformanceMonitorCommand.class))).thenReturn(answer);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).processPerformanceMonitorAnswer(any(), any(), any(), any(), any());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).processPerformanceMonitorAnswer(any(), any(), any(), any(), any());
 
-        PowerMockito.doReturn(AutoScalePolicy.Action.SCALEUP).when(autoScaleManagerImplSpy).getAutoscaleAction(any(), any(), any());
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).doScaleUp(vmGroupId, 1);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).cleanupAsVmGroupStatistics(groupTO);
+        Mockito.doReturn(AutoScalePolicy.Action.SCALEUP).when(autoScaleManagerImplSpy).getAutoscaleAction(any(), any(), any());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).doScaleUp(vmGroupId, 1);
 
         autoScaleManagerImplSpy.checkNetScalerAsGroup(asVmGroupMock);
 
@@ -2002,10 +1969,10 @@
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
         when(lbRulesMgr.toAutoScaleVmGroupTO(asVmGroupMock)).thenReturn(groupTO);
 
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).updateCountersMap(any(), any(), any());
-        PowerMockito.doReturn(AutoScalePolicy.Action.SCALEUP).when(autoScaleManagerImplSpy).getAutoscaleAction(any(), any(), any());
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).doScaleUp(vmGroupId, 1);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).cleanupAsVmGroupStatistics(groupTO);
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).updateCountersMap(any(), any(), any());
+        Mockito.doReturn(AutoScalePolicy.Action.SCALEUP).when(autoScaleManagerImplSpy).getAutoscaleAction(any(), any(), any());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).doScaleUp(vmGroupId, 1);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).cleanupAsVmGroupStatistics(groupTO);
 
         autoScaleManagerImplSpy.checkVirtualRouterAsGroup(asVmGroupMock);
 
@@ -2018,10 +1985,10 @@
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
         when(lbRulesMgr.toAutoScaleVmGroupTO(asVmGroupMock)).thenReturn(groupTO);
 
-        PowerMockito.doReturn(true).when(autoScaleManagerImplSpy).updateCountersMap(any(), any(), any());
-        PowerMockito.doReturn(AutoScalePolicy.Action.SCALEDOWN).when(autoScaleManagerImplSpy).getAutoscaleAction(any(), any(), any());
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).doScaleDown(vmGroupId);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).cleanupAsVmGroupStatistics(groupTO);
+        Mockito.doReturn(true).when(autoScaleManagerImplSpy).updateCountersMap(any(), any(), any());
+        Mockito.doReturn(AutoScalePolicy.Action.SCALEDOWN).when(autoScaleManagerImplSpy).getAutoscaleAction(any(), any(), any());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).doScaleDown(vmGroupId);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).cleanupAsVmGroupStatistics(groupTO);
 
         autoScaleManagerImplSpy.checkVirtualRouterAsGroup(asVmGroupMock);
 
@@ -2042,7 +2009,6 @@
         when(scaleUpConditionTO.getId()).thenReturn(scaleUpConditionId);
         when(scaleUpConditionTO.getCounter()).thenReturn(scaleUpCounterTO);
         when(scaleUpCounterTO.getId()).thenReturn(scaleUpCounterId);
-        when(scaleUpCounterTO.getSource()).thenReturn(Counter.Source.CPU);
 
         AutoScalePolicyTO scaleDownPolicyTO = Mockito.mock(AutoScalePolicyTO.class);
         when(scaleDownPolicyTO.getId()).thenReturn(scaleDownPolicyId);
@@ -2053,7 +2019,6 @@
         when(scaleDownConditionTO.getId()).thenReturn(scaleDownConditionId);
         when(scaleDownConditionTO.getCounter()).thenReturn(scaleDownCounterTO);
         when(scaleDownCounterTO.getId()).thenReturn(scaleDownCounterId);
-        when(scaleDownCounterTO.getSource()).thenReturn(Counter.Source.VIRTUALROUTER);
 
         when(groupTO.getPolicies()).thenReturn(Arrays.asList(scaleUpPolicyTO, scaleDownPolicyTO));
 
@@ -2073,7 +2038,7 @@
         when(asGroupStatisticsDao.listByVmGroupAndPolicyAndCounter(eq(vmGroupId), eq(scaleUpPolicyId), eq(scaleUpCounterId), any())).thenReturn(stats);
         when(asGroupStatisticsDao.listByVmGroupAndPolicyAndCounter(eq(vmGroupId), eq(scaleDownPolicyId), eq(scaleDownCounterId), any())).thenReturn(stats);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).updateCountersMapWithInstantData(any(), any(), any(),
+        Mockito.doNothing().when(autoScaleManagerImplSpy).updateCountersMapWithInstantData(any(), any(), any(),
                 any(), any(), any(), any(), any());
 
         Map<String, Double> countersMap = new HashMap<>();
@@ -2099,7 +2064,6 @@
         when(scaleUpConditionTO.getId()).thenReturn(scaleUpConditionId);
         when(scaleUpConditionTO.getCounter()).thenReturn(scaleUpCounterTO);
         when(scaleUpCounterTO.getId()).thenReturn(scaleUpCounterId);
-        when(scaleUpCounterTO.getSource()).thenReturn(Counter.Source.VIRTUALROUTER);
 
         when(groupTO.getPolicies()).thenReturn(Arrays.asList(scaleUpPolicyTO));
 
@@ -2108,8 +2072,6 @@
                 .thenReturn(new ArrayList<>());
         when(asGroupStatisticsDao.listInactiveByVmGroupAndPolicy(eq(vmGroupId), eq(scaleUpPolicyId), any()))
                 .thenReturn(new ArrayList<>());
-        when(asGroupStatisticsDao.listInactiveByVmGroupAndPolicy(eq(vmGroupId), eq(scaleDownPolicyId), any()))
-                .thenReturn(new ArrayList<>());
 
         List<AutoScaleVmGroupStatisticsVO> stats = new ArrayList<>();
         Date timestamp = new Date();
@@ -2123,7 +2085,7 @@
         when(asGroupStatisticsDao.listByVmGroupAndPolicyAndCounter(eq(vmGroupId), eq(scaleUpPolicyId), eq(scaleUpCounterId), any())).thenReturn(stats);
         when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(1);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).updateCountersMapWithProcessedData(any(), any(), any(), any());
+        Mockito.doNothing().when(autoScaleManagerImplSpy).updateCountersMapWithProcessedData(any(), any(), any(), any());
 
         Map<String, Double> countersMap = new HashMap<>();
         Map<String, Integer> countersNumberMap = new HashMap<>();
@@ -2236,8 +2198,8 @@
         when(autoScaleVmGroupVmMapDao.countAvailableVmsByGroup(vmGroupId)).thenReturn(minMembers);
         when(lbRulesMgr.toAutoScaleVmGroupTO(asVmGroupMock)).thenReturn(groupTO);
 
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).getVmStatsFromHosts(groupTO);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).getNetworkStatsFromVirtualRouter(groupTO);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).getVmStatsFromHosts(groupTO);
+        Mockito.doNothing().when(autoScaleManagerImplSpy).getNetworkStatsFromVirtualRouter(groupTO);
 
         autoScaleManagerImplSpy.monitorVirtualRouterAsGroup(asVmGroupMock);
 
@@ -2258,12 +2220,12 @@
         policyCountersMap.put(scaleDownPolicyId, Arrays.asList(scaleDownCounter));
 
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
-        PowerMockito.doReturn(hostAndVmIdsMap).when(autoScaleManagerImplSpy).getHostAndVmIdsMap(groupTO);
-        PowerMockito.doReturn(policyCountersMap).when(autoScaleManagerImplSpy).getPolicyCounters(groupTO);
+        Mockito.doReturn(hostAndVmIdsMap).when(autoScaleManagerImplSpy).getHostAndVmIdsMap(groupTO);
+        Mockito.doReturn(policyCountersMap).when(autoScaleManagerImplSpy).getPolicyCounters(groupTO);
 
         Map<Long, VmStatsEntry> vmStatsById = new HashMap<>();
-        PowerMockito.doReturn(vmStatsById).doReturn(vmStatsById).when(autoScaleManagerImplSpy).getVmStatsByIdFromHost(anyLong(), any());
-        PowerMockito.doNothing().doNothing().when(autoScaleManagerImplSpy).processVmStatsByIdFromHost(any(), any(), any(), any());
+        Mockito.doReturn(vmStatsById).doReturn(vmStatsById).when(autoScaleManagerImplSpy).getVmStatsByIdFromHost(anyLong(), any());
+        Mockito.doNothing().doNothing().when(autoScaleManagerImplSpy).processVmStatsByIdFromHost(any(), any(), any(), any());
 
         autoScaleManagerImplSpy.getVmStatsFromHosts(groupTO);
 
@@ -2274,8 +2236,6 @@
     @Test
     public void getVmStatsByIdFromHost() {
         List<Long> vmIds = Mockito.mock(ArrayList.class);
-        Map<Long, VmStatsEntry> vmStatsById = Mockito.mock(HashMap.class);
-        Mockito.doReturn(vmStatsById).when(virtualMachineManager).getVirtualMachineStatistics(anyLong(), anyString(), anyList());
 
         Map<Long, ? extends VmStats> result = autoScaleManagerImplSpy.getVmStatsByIdFromHost(-1L, vmIds);
 
@@ -2332,12 +2292,12 @@
     public void getNetworkStatsFromVirtualRouterWithoutRouter() {
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
         when(groupTO.getLoadBalancerId()).thenReturn(loadBalancerId);
-        PowerMockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
+        Mockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
         when(networkMock.getId()).thenReturn(networkId);
         Pair<String, Integer> publicIpAddr = new Pair<>(ipAddress, memberPort);
-        PowerMockito.doReturn(publicIpAddr).when(autoScaleManagerImplSpy).getPublicIpAndPort(loadBalancerId);
+        Mockito.doReturn(publicIpAddr).when(autoScaleManagerImplSpy).getPublicIpAndPort(loadBalancerId);
         when(routerDao.listByNetworkAndRole(networkId, VirtualRouter.Role.VIRTUAL_ROUTER)).thenReturn(new ArrayList<>());
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).processGetAutoScaleMetricsAnswer(any(), any(), nullable(Long.class));
+        Mockito.doNothing().when(autoScaleManagerImplSpy).processGetAutoScaleMetricsAnswer(any(), any(), nullable(Long.class));
 
         autoScaleManagerImplSpy.getNetworkStatsFromVirtualRouter(groupTO);
 
@@ -2348,22 +2308,22 @@
     public void getNetworkStatsFromVirtualRouterWithOneRouter() {
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
         when(groupTO.getLoadBalancerId()).thenReturn(loadBalancerId);
-        PowerMockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
+        Mockito.doReturn(networkMock).when(autoScaleManagerImplSpy).getNetwork(loadBalancerId);
         when(networkMock.getId()).thenReturn(networkId);
         Pair<String, Integer> publicIpAddr = new Pair<>(ipAddress, memberPort);
-        PowerMockito.doReturn(publicIpAddr).when(autoScaleManagerImplSpy).getPublicIpAndPort(loadBalancerId);
+        Mockito.doReturn(publicIpAddr).when(autoScaleManagerImplSpy).getPublicIpAndPort(loadBalancerId);
         when(routerDao.listByNetworkAndRole(networkId, VirtualRouter.Role.VIRTUAL_ROUTER)).thenReturn(Arrays.asList(domainRouterMock));
         when(domainRouterMock.getState()).thenReturn(VirtualMachine.State.Running);
         when(domainRouterMock.getId()).thenReturn(domainRouterId);
         when(domainRouterMock.getHostId()).thenReturn(hostId);
 
         List<VirtualRouterAutoScale.AutoScaleMetrics> metrics = Mockito.mock(ArrayList.class);
-        PowerMockito.doReturn(metrics).when(autoScaleManagerImplSpy).setGetAutoScaleMetricsCommandMetrics(groupTO);
+        Mockito.doReturn(metrics).when(autoScaleManagerImplSpy).setGetAutoScaleMetricsCommandMetrics(groupTO);
         GetAutoScaleMetricsCommand command = Mockito.mock(GetAutoScaleMetricsCommand.class);
         List<VirtualRouterAutoScale.AutoScaleMetricsValue> values = Mockito.mock(ArrayList.class);
         GetAutoScaleMetricsAnswer answer = new GetAutoScaleMetricsAnswer(command, true, values);
         when(agentMgr.easySend(eq(hostId), any(GetAutoScaleMetricsCommand.class))).thenReturn(answer);
-        PowerMockito.doNothing().when(autoScaleManagerImplSpy).processGetAutoScaleMetricsAnswer(any(), any(), nullable(Long.class));
+        Mockito.doNothing().when(autoScaleManagerImplSpy).processGetAutoScaleMetricsAnswer(any(), any(), nullable(Long.class));
 
         autoScaleManagerImplSpy.getNetworkStatsFromVirtualRouter(groupTO);
 
@@ -2381,7 +2341,6 @@
         when(scaleUpPolicyTO.getId()).thenReturn(scaleUpPolicyId);
         when(scaleUpPolicyTO.getConditions()).thenReturn(Arrays.asList(scaleUpConditionTO));
         when(scaleUpConditionTO.getCounter()).thenReturn(scaleUpCounterTO);
-        when(scaleUpCounterTO.getSource()).thenReturn(Counter.Source.CPU);
         when(scaleUpCounterTO.getProvider()).thenReturn(Network.Provider.VirtualRouter.getName());
         when(scaleUpCounterTO.getValue()).thenReturn(VirtualRouterAutoScaleCounter.NETWORK_RECEIVED_AVERAGE_MBPS.toString());
 
@@ -2391,7 +2350,6 @@
         when(scaleDownPolicyTO.getId()).thenReturn(scaleDownPolicyId);
         when(scaleDownPolicyTO.getConditions()).thenReturn(Arrays.asList(scaleDownConditionTO));
         when(scaleDownConditionTO.getCounter()).thenReturn(scaleDownCounterTO);
-        when(scaleDownCounterTO.getSource()).thenReturn(Counter.Source.VIRTUALROUTER);
         when(scaleDownCounterTO.getProvider()).thenReturn(Network.Provider.VirtualRouter.getName());
         when(scaleDownCounterTO.getValue()).thenReturn(VirtualRouterAutoScaleCounter.NETWORK_TRANSMIT_AVERAGE_MBPS.toString());
 
@@ -2413,7 +2371,7 @@
         policyCountersMap.put(scaleDownPolicyId, Arrays.asList(scaleDownCounter));
 
         AutoScaleVmGroupTO groupTO = Mockito.mock(AutoScaleVmGroupTO.class);
-        PowerMockito.doReturn(policyCountersMap).when(autoScaleManagerImplSpy).getPolicyCounters(groupTO);
+        Mockito.doReturn(policyCountersMap).when(autoScaleManagerImplSpy).getPolicyCounters(groupTO);
 
         when(scaleUpCounter.getSource()).thenReturn(Counter.Source.VIRTUALROUTER);
         when(scaleUpCounter.getId()).thenReturn(counterId);
@@ -2449,7 +2407,6 @@
     }
 
     @Test
-    @PrepareForTest(Executors.class)
     public void scheduleMonitorTasks() {
         when(autoScaleVmGroupDao.listAll()).thenReturn(Arrays.asList(asVmGroupMock));
         when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
@@ -2460,13 +2417,14 @@
         ReflectionTestUtils.setField(autoScaleManagerImplSpy, "vmGroupMonitorMaps", vmGroupMonitorMaps);
         when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
         ScheduledExecutorService vmGroupExecutor = Mockito.mock(ScheduledExecutorService.class);
-        PowerMockito.mockStatic(Executors.class);
-        when(Executors.newScheduledThreadPool(eq(1), any())).thenReturn(vmGroupExecutor);
+        try (MockedStatic<Executors> ignored = Mockito.mockStatic(Executors.class)) {
+            when(Executors.newScheduledThreadPool(eq(1), any())).thenReturn(vmGroupExecutor);
 
-        autoScaleManagerImplSpy.scheduleMonitorTasks();
+            autoScaleManagerImplSpy.scheduleMonitorTasks();
 
-        Assert.assertEquals(1, vmGroupMonitorMaps.size());
-        Assert.assertNotNull(vmGroupMonitorMaps.get(vmGroupId));
+            Assert.assertEquals(1, vmGroupMonitorMaps.size());
+            Assert.assertNotNull(vmGroupMonitorMaps.get(vmGroupId));
+        }
     }
 
     @Test
@@ -2474,7 +2432,7 @@
         Map<Long, ScheduledExecutorService> vmGroupMonitorMaps = new HashMap<>();
         ScheduledExecutorService vmGroupExecutor = Mockito.mock(ScheduledExecutorService.class);
         vmGroupMonitorMaps.put(vmGroupId, vmGroupExecutor);
-        PowerMockito.doNothing().when(vmGroupExecutor).shutdown();
+        Mockito.doNothing().when(vmGroupExecutor).shutdown();
 
         ReflectionTestUtils.setField(autoScaleManagerImplSpy, "vmGroupMonitorMaps", vmGroupMonitorMaps);
 
@@ -2508,7 +2466,7 @@
     @Test
     public void destroyVm() {
         when(userVmDao.findById(virtualMachineId)).thenReturn(userVmMock);
-        PowerMockito.doReturn(true).when(userVmMgr).expunge(eq(userVmMock));
+        Mockito.doReturn(true).when(userVmMgr).expunge(eq(userVmMock));
 
         autoScaleManagerImplSpy.destroyVm(virtualMachineId);
 
diff --git a/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java b/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
index e93fcae..52ac5f4 100644
--- a/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
+++ b/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
@@ -16,58 +16,15 @@
 // under the License.
 package com.cloud.network.element;
 
-import static org.hamcrest.Matchers.hasEntry;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import com.cloud.domain.DomainVO;
-import com.cloud.domain.dao.DomainDao;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
-import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
-import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.storage.configdrive.ConfigDrive;
-import org.apache.cloudstack.storage.configdrive.ConfigDriveBuilder;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.reflections.ReflectionUtils;
-
 import com.cloud.agent.AgentManager;
-import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.HandleConfigDriveIsoAnswer;
 import com.cloud.agent.api.HandleConfigDriveIsoCommand;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.deploy.DeployDestination;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.host.HostVO;
@@ -103,9 +60,44 @@
 import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.VMInstanceDao;
 import com.google.common.collect.Maps;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.configdrive.ConfigDrive;
+import org.apache.cloudstack.storage.configdrive.ConfigDriveBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class ConfigDriveNetworkElementTest {
 
     public static final String CLOUD_ID = "xx";
@@ -157,28 +149,30 @@
     @Mock private CallContext callContextMock;
     @Mock private DomainVO domainVO;
 
-    @InjectMocks private final ConfigDriveNetworkElement _configDrivesNetworkElement = new ConfigDriveNetworkElement();
-    @InjectMocks @Spy private NetworkModelImpl _networkModel = new NetworkModelImpl();
+    @Spy @InjectMocks
+    private ConfigDriveNetworkElement _configDrivesNetworkElement = new ConfigDriveNetworkElement();
+    @InjectMocks @Spy private NetworkModelImpl _networkModel;
+
+    private AutoCloseable closeable;
 
     @Before
     public void setUp() throws NoSuchFieldException, IllegalAccessException {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
 
         _configDrivesNetworkElement._networkModel = _networkModel;
+        _configDrivesNetworkElement._networkMgr = _networkModel;
 
         when(_dataStoreMgr.getImageStoreWithFreeCapacity(DATACENTERID)).thenReturn(dataStore);
         when(_ep.select(dataStore)).thenReturn(endpoint);
         when(_vmDao.findById(VMID)).thenReturn(virtualMachine);
         when(_vmInstanceDao.findById(VMID)).thenReturn(virtualMachine);
         when(_dcDao.findById(DATACENTERID)).thenReturn(dataCenterVO);
-        when(_hostDao.findById(HOSTID)).thenReturn(hostVO);
         when(_domainDao.findById(DOMAINID)).thenReturn(domainVO);
         doReturn(nic).when(_networkModel).getDefaultNic(VMID);
         when(_serviceOfferingDao.findByIdIncludingRemoved(VMID, SOID)).thenReturn(serviceOfferingVO);
-        when(_guestOSDao.findById(anyLong())).thenReturn(guestOSVO);
-        when(_guestOSCategoryDao.findById(anyLong())).thenReturn(guestOSCategoryVo);
+        when(_guestOSDao.findById(Mockito.anyLong())).thenReturn(guestOSVO);
+        when(_guestOSCategoryDao.findById(Mockito.anyLong())).thenReturn(guestOSCategoryVo);
         when(_configDao.getValue("cloud.identifier")).thenReturn(CLOUD_ID);
-        when(network.getDataCenterId()).thenReturn(DATACENTERID);
         when(guestOSCategoryVo.getName()).thenReturn("Linux");
         when(dataCenterVO.getName()).thenReturn(ZONENAME);
         when(serviceOfferingVO.getDisplayText()).thenReturn(VMOFFERING);
@@ -197,19 +191,23 @@
         when(deployDestination.getHost()).thenReturn(hostVO);
         when(deployDestination.getDataCenter()).thenReturn(dataCenter);
         when(hostVO.getId()).thenReturn(HOSTID);
-        when(nic.isDefaultNic()).thenReturn(true);
         when(nic.getNetworkId()).thenReturn(NETWORK_ID);
         when(network.getId()).thenReturn(NETWORK_ID);
         when(_networkModel.getNetwork(NETWORK_ID)).thenReturn(network);
         //when(_networkModel.getUserDataUpdateProvider(network)).thenReturn(_configDrivesNetworkElement);
 
-        when(_ntwkSrvcDao.getProviderForServiceInNetwork(NETWORK_ID, Network.Service.UserData)).thenReturn(_configDrivesNetworkElement.getProvider().getName());
+        doReturn(_configDrivesNetworkElement.getProvider().getName()).when(_ntwkSrvcDao).getProviderForServiceInNetwork(NETWORK_ID, Network.Service.UserData);
 
         _networkModel.setNetworkElements(Arrays.asList(_configDrivesNetworkElement));
         _networkModel.start();
 
     }
 
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
     @Test
     public void testCanHandle() throws InsufficientCapacityException, ResourceUnavailableException {
         final NetworkOfferingVO ntwkoffer = mock(NetworkOfferingVO.class);
@@ -221,7 +219,6 @@
     }
 
     @Test
-    @SuppressWarnings("unchecked")
     public void testExpunge() throws NoTransitionException, NoSuchFieldException, IllegalAccessException {
         final StateMachine2<VirtualMachine.State, VirtualMachine.Event, VirtualMachine> stateMachine = VirtualMachine.State.getStateMachine();
 
@@ -238,13 +235,13 @@
         when(_vmInstanceDao.updateState(VirtualMachine.State.Stopped, VirtualMachine.Event.ExpungeOperation, VirtualMachine.State.Expunging, virtualMachine, null)).thenReturn(true);
 
         final HandleConfigDriveIsoAnswer answer = mock(HandleConfigDriveIsoAnswer.class);
-        when(agentManager.easySend(anyLong(), any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
+        when(agentManager.easySend(Mockito.anyLong(), Mockito.any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
         when(answer.getResult()).thenReturn(true);
 
         stateMachine.transitTo(virtualMachine, VirtualMachine.Event.ExpungeOperation, null, _vmInstanceDao);
 
         ArgumentCaptor<HandleConfigDriveIsoCommand> commandCaptor = ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class);
-        verify(agentManager, times(1)).easySend(anyLong(), commandCaptor.capture());
+        verify(agentManager, times(1)).easySend(Mockito.anyLong(), commandCaptor.capture());
         HandleConfigDriveIsoCommand deleteCommand = commandCaptor.getValue();
 
         assertThat(deleteCommand.isCreate(), is(false));
@@ -253,9 +250,6 @@
 
     @Test
     public void testRelease() {
-        final Answer answer = mock(Answer.class);
-        when(agentManager.easySend(anyLong(), any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
-        when(answer.getResult()).thenReturn(true);
         VirtualMachineProfile profile = new VirtualMachineProfileImpl(virtualMachine, null, serviceOfferingVO, null, null);
         assertTrue(_configDrivesNetworkElement.release(network, nicp, profile, null));
     }
@@ -266,46 +260,42 @@
     }
 
     @Test
-    @SuppressWarnings("unchecked")
-    @PrepareForTest({ConfigDriveBuilder.class, CallContext.class})
     public void testAddPasswordAndUserData() throws Exception {
-        PowerMockito.mockStatic(ConfigDriveBuilder.class);
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
-        Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
-        Method method = ReflectionUtils.getMethods(ConfigDriveBuilder.class, ReflectionUtils.withName("buildConfigDrive")).iterator().next();
-        PowerMockito.when(ConfigDriveBuilder.class, method).withArguments(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap()).thenReturn("content");
+        try (MockedStatic<ConfigDriveBuilder> ignored1 = Mockito.mockStatic(ConfigDriveBuilder.class); MockedStatic<CallContext> ignored2 = Mockito.mockStatic(CallContext.class)) {
+            Mockito.when(CallContext.current()).thenReturn(callContextMock);
+            Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
+            Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.anyListOf(String[].class), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())).thenReturn("content");
 
-        final HandleConfigDriveIsoAnswer answer = mock(HandleConfigDriveIsoAnswer.class);
-        final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class);
-        when(agentManager.easySend(anyLong(), any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
-        when(answer.getResult()).thenReturn(true);
-        when(answer.getConfigDriveLocation()).thenReturn(NetworkElement.Location.PRIMARY);
-        when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
-        when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped);
-        when(virtualMachine.getUuid()).thenReturn("vm-uuid");
-        when(userVmDetailVO.getValue()).thenReturn(PUBLIC_KEY);
-        when(nicp.getIPv4Address()).thenReturn("192.168.111.111");
-        when(_userVmDetailsDao.findDetail(anyLong(), anyString())).thenReturn(userVmDetailVO);
-        when(_ipAddressDao.findByAssociatedVmId(VMID)).thenReturn(publicIp);
-        when(publicIp.getAddress()).thenReturn(new Ip("7.7.7.7"));
-        when(_hostDao.findById(virtualMachine.getHostId())).thenReturn(hostVO);
-        when(_hostDao.findById(virtualMachine.getHostId()).getName()).thenReturn("dest-hyp-host-name");
+            final HandleConfigDriveIsoAnswer answer = mock(HandleConfigDriveIsoAnswer.class);
+            final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class);
+            when(agentManager.easySend(Mockito.anyLong(), Mockito.any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
+            when(answer.getResult()).thenReturn(true);
+            when(answer.getConfigDriveLocation()).thenReturn(NetworkElement.Location.PRIMARY);
+            when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
+            when(virtualMachine.getUuid()).thenReturn("vm-uuid");
+            when(userVmDetailVO.getValue()).thenReturn(PUBLIC_KEY);
+            when(nicp.getIPv4Address()).thenReturn("192.168.111.111");
+            when(_userVmDetailsDao.findDetail(Mockito.anyLong(), Mockito.anyString())).thenReturn(userVmDetailVO);
+            when(_ipAddressDao.findByAssociatedVmId(VMID)).thenReturn(publicIp);
+            when(publicIp.getAddress()).thenReturn(new Ip("7.7.7.7"));
+            when(_hostDao.findById(virtualMachine.getHostId())).thenReturn(hostVO);
+            when(_hostDao.findById(virtualMachine.getHostId()).getName()).thenReturn("dest-hyp-host-name");
 
-        Map<VirtualMachineProfile.Param, Object> parms = Maps.newHashMap();
-        parms.put(VirtualMachineProfile.Param.VmPassword, PASSWORD);
-        parms.put(VirtualMachineProfile.Param.VmSshPubKey, PUBLIC_KEY);
-        VirtualMachineProfile profile = new VirtualMachineProfileImpl(virtualMachine, null, serviceOfferingVO, null, parms);
-        profile.setConfigDriveLabel("testlabel");
-        assertTrue(_configDrivesNetworkElement.addPasswordAndUserdata(
-                network, nicp, profile, deployDestination, null));
+            Map<VirtualMachineProfile.Param, Object> parms = Maps.newHashMap();
+            parms.put(VirtualMachineProfile.Param.VmPassword, PASSWORD);
+            parms.put(VirtualMachineProfile.Param.VmSshPubKey, PUBLIC_KEY);
+            VirtualMachineProfile profile = new VirtualMachineProfileImpl(virtualMachine, null, serviceOfferingVO, null, parms);
+            profile.setConfigDriveLabel("testlabel");
+            assertTrue(_configDrivesNetworkElement.addPasswordAndUserdata(
+                    network, nicp, profile, deployDestination, null));
 
-        ArgumentCaptor<HandleConfigDriveIsoCommand> commandCaptor = ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class);
-        verify(agentManager, times(1)).easySend(anyLong(), commandCaptor.capture());
-        HandleConfigDriveIsoCommand createCommand = commandCaptor.getValue();
+            ArgumentCaptor<HandleConfigDriveIsoCommand> commandCaptor = ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class);
+            verify(agentManager, times(1)).easySend(Mockito.anyLong(), commandCaptor.capture());
+            HandleConfigDriveIsoCommand createCommand = commandCaptor.getValue();
 
-        assertTrue(createCommand.isCreate());
-        assertTrue(createCommand.getIsoData().length() > 0);
-        assertTrue(createCommand.getIsoFile().equals(ConfigDrive.createConfigDrivePath(profile.getInstanceName())));
+            assertTrue(createCommand.isCreate());
+            assertTrue(createCommand.getIsoData().length() > 0);
+            assertTrue(createCommand.getIsoFile().equals(ConfigDrive.createConfigDrivePath(profile.getInstanceName())));
+        }
     }
 }
diff --git a/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java b/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java
index 0fb8020..46b8eb9 100644
--- a/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java
+++ b/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java
@@ -16,36 +16,6 @@
 // under the License.
 package com.cloud.network.element;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import com.cloud.network.as.AutoScaleCounter;
-import com.cloud.network.as.AutoScaleCounter.AutoScaleCounterType;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinitionBuilder;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Matchers;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
-
 import com.cloud.cluster.dao.ManagementServerHostDao;
 import com.cloud.configuration.ConfigurationManager;
 import com.cloud.dc.DataCenter;
@@ -69,6 +39,8 @@
 import com.cloud.network.NetworkModelImpl;
 import com.cloud.network.Networks.TrafficType;
 import com.cloud.network.VirtualRouterProvider.Type;
+import com.cloud.network.as.AutoScaleCounter;
+import com.cloud.network.as.AutoScaleCounter.AutoScaleCounterType;
 import com.cloud.network.dao.FirewallRulesDao;
 import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.LoadBalancerDao;
@@ -124,6 +96,33 @@
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinitionBuilder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class VirtualRouterElementTest {
diff --git a/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java b/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java
index d667da4..3511262 100644
--- a/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java
+++ b/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java
@@ -16,27 +16,6 @@
 // under the License.
 package com.cloud.network.element;
 
-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.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.cloudstack.network.topology.AdvancedNetworkTopology;
-import org.apache.cloudstack.network.topology.BasicNetworkTopology;
-import org.apache.cloudstack.network.topology.NetworkTopologyContext;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.exception.ResourceUnavailableException;
@@ -47,6 +26,26 @@
 import com.cloud.utils.db.EntityManager;
 import com.cloud.vm.DomainRouterVO;
 import com.cloud.vm.dao.DomainRouterDao;
+import org.apache.cloudstack.network.topology.AdvancedNetworkTopology;
+import org.apache.cloudstack.network.topology.BasicNetworkTopology;
+import org.apache.cloudstack.network.topology.NetworkTopologyContext;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class VpcVirtualRouterElementTest {
diff --git a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java
index 4a42a54..2200d6b 100644
--- a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java
+++ b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java
@@ -17,25 +17,26 @@
 
 package com.cloud.network.firewall;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.spy;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.IpAddressManager;
+import com.cloud.network.Network;
 import com.cloud.network.NetworkModel;
+import com.cloud.network.NetworkRuleApplier;
 import com.cloud.network.dao.FirewallRulesDao;
+import com.cloud.network.element.FirewallServiceProvider;
+import com.cloud.network.element.VirtualRouterElement;
+import com.cloud.network.element.VpcVirtualRouterElement;
+import com.cloud.network.rules.FirewallManager;
+import com.cloud.network.rules.FirewallRule;
+import com.cloud.network.rules.FirewallRule.Purpose;
+import com.cloud.network.rules.FirewallRuleVO;
 import com.cloud.network.vpc.VpcManager;
 import com.cloud.user.AccountManager;
 import com.cloud.user.DomainManager;
+import com.cloud.utils.component.ComponentContext;
 import junit.framework.Assert;
-
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.log4j.Logger;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -46,20 +47,16 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.network.IpAddressManager;
-import com.cloud.network.Network;
-import com.cloud.network.NetworkRuleApplier;
-import com.cloud.network.element.FirewallServiceProvider;
-import com.cloud.network.element.VirtualRouterElement;
-import com.cloud.network.element.VpcVirtualRouterElement;
-import com.cloud.network.rules.FirewallManager;
-import com.cloud.network.rules.FirewallRule;
-import com.cloud.network.rules.FirewallRule.Purpose;
-import com.cloud.network.rules.FirewallRuleVO;
-import com.cloud.utils.component.ComponentContext;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class FirewallManagerTest {
diff --git a/server/src/test/java/com/cloud/network/guru/DirectNetworkGuruTest.java b/server/src/test/java/com/cloud/network/guru/DirectNetworkGuruTest.java
index ce5aabb..a623be8 100644
--- a/server/src/test/java/com/cloud/network/guru/DirectNetworkGuruTest.java
+++ b/server/src/test/java/com/cloud/network/guru/DirectNetworkGuruTest.java
@@ -16,20 +16,6 @@
 // under the License.
 package com.cloud.network.guru;
 
-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.when;
-
-import java.util.Arrays;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
 import com.cloud.dc.DataCenter.NetworkType;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
@@ -47,6 +33,19 @@
 import com.cloud.user.Account;
 import com.cloud.utils.Pair;
 import com.cloud.vm.NicProfile;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+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.when;
 
 public class DirectNetworkGuruTest {
 
diff --git a/server/src/test/java/com/cloud/network/guru/ExternalGuestNetworkGuruTest.java b/server/src/test/java/com/cloud/network/guru/ExternalGuestNetworkGuruTest.java
index 5a3db48..3286ee5 100644
--- a/server/src/test/java/com/cloud/network/guru/ExternalGuestNetworkGuruTest.java
+++ b/server/src/test/java/com/cloud/network/guru/ExternalGuestNetworkGuruTest.java
@@ -16,18 +16,6 @@
 // under the License.
 package com.cloud.network.guru;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterVO;
 import com.cloud.dc.dao.DataCenterDao;
@@ -41,11 +29,19 @@
 import com.cloud.offering.NetworkOffering;
 import com.cloud.user.Account;
 import com.cloud.utils.Pair;
-import com.cloud.utils.component.ComponentContext;
 import com.cloud.vm.NicProfile;
+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(PowerMockRunner.class)
-@PrepareForTest(ComponentContext.class)
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@RunWith(MockitoJUnitRunner.class)
 public class ExternalGuestNetworkGuruTest {
     @Mock
     NetworkModel networkModel;
@@ -71,7 +67,6 @@
         Mockito.when(zone.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
         Mockito.when(dataCenterDao.findById(Mockito.anyLong())).thenReturn(zone);
         PhysicalNetworkVO physicalNetwork = Mockito.mock(PhysicalNetworkVO.class);
-        Mockito.when(physicalNetwork.getId()).thenReturn(1L);
         Mockito.when(physicalNetworkDao.findById(Mockito.anyLong())).thenReturn(physicalNetwork);
         DeploymentPlan plan = Mockito.mock(DeploymentPlan.class);
         Network network = Mockito.mock(Network.class);
diff --git a/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java b/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java
index 12affd2..37194b6 100644
--- a/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java
+++ b/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java
@@ -17,10 +17,10 @@
 package com.cloud.network.lb;
 
 import com.cloud.domain.DomainVO;
-import com.cloud.exception.ResourceAllocationException;
-import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.network.Network;
 import com.cloud.network.NetworkModelImpl;
@@ -58,11 +58,11 @@
 import org.mockito.Mockito;
 import org.mockito.Spy;
 
-import java.util.UUID;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.List;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyLong;
diff --git a/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java b/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java
index beb88e7..b9928a6 100644
--- a/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java
+++ b/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java
@@ -16,23 +16,6 @@
 // under the License.
 package com.cloud.network.lb;
 
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.UUID;
-
-import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mockito;
-
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceAllocationException;
@@ -55,6 +38,22 @@
 import com.cloud.user.MockAccountManagerImpl;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
+import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
 
 public class UpdateLoadBalancerTest {
 
diff --git a/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java b/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java
index 30e79ed..f03ae9d 100644
--- a/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java
+++ b/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java
@@ -21,12 +21,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CommandSetupHelperTest {
 
     @InjectMocks
diff --git a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java
index 4267b71..24a5105 100644
--- a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java
+++ b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java
@@ -16,6 +16,20 @@
 // under the License.
 package com.cloud.network.router;
 
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.Command;
+import com.cloud.agent.manager.Commands;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.exception.ResourceUnavailableException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
@@ -26,21 +40,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Matchers;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import com.cloud.agent.AgentManager;
-import com.cloud.agent.api.Answer;
-import com.cloud.agent.api.Command;
-import com.cloud.agent.manager.Commands;
-import com.cloud.exception.AgentUnavailableException;
-import com.cloud.exception.OperationTimedoutException;
-import com.cloud.exception.ResourceUnavailableException;
-
 
 @RunWith(MockitoJUnitRunner.class)
 public class NetworkHelperImplTest {
diff --git a/server/src/test/java/com/cloud/network/router/RouterControlHelperTest.java b/server/src/test/java/com/cloud/network/router/RouterControlHelperTest.java
index a3040f1..0b7e325 100644
--- a/server/src/test/java/com/cloud/network/router/RouterControlHelperTest.java
+++ b/server/src/test/java/com/cloud/network/router/RouterControlHelperTest.java
@@ -16,20 +16,6 @@
 // under the License.
 package com.cloud.network.router;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
 import com.cloud.network.Networks.TrafficType;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.dao.NetworkVO;
@@ -37,6 +23,19 @@
 import com.cloud.vm.NicVO;
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.NicDao;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class RouterControlHelperTest {
diff --git a/server/src/test/java/com/cloud/network/router/VirtualNetworkApplianceManagerImplTest.java b/server/src/test/java/com/cloud/network/router/VirtualNetworkApplianceManagerImplTest.java
index 16d8fe8..f25e5ef 100644
--- a/server/src/test/java/com/cloud/network/router/VirtualNetworkApplianceManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/router/VirtualNetworkApplianceManagerImplTest.java
@@ -16,28 +16,6 @@
 // under the License.
 package com.cloud.network.router;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.utils.identity.ManagementServerNode;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.CheckS2SVpnConnectionsAnswer;
 import com.cloud.agent.api.CheckS2SVpnConnectionsCommand;
@@ -90,6 +68,27 @@
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
 
 
 
diff --git a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Test.java b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Test.java
index 205574c..01c4074 100644
--- a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Test.java
+++ b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Test.java
@@ -16,16 +16,10 @@
 // under the License.
 package com.cloud.network.security;
 
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-import javax.sql.DataSource;
-
+import com.cloud.utils.Profiler;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.component.ComponentContext;
+import junit.framework.TestCase;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,11 +28,14 @@
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
-import com.cloud.utils.Profiler;
-import com.cloud.utils.PropertiesUtil;
-import com.cloud.utils.component.ComponentContext;
-
-import junit.framework.TestCase;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = "classpath:/SecurityGroupManagerTestContext.xml")
diff --git a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplTest.java b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplTest.java
index a44f074..d5803a7 100644
--- a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplTest.java
@@ -17,20 +17,17 @@
 
 package com.cloud.network.security;
 
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.inject.Inject;
-
+import com.cloud.network.security.SecurityGroupManagerImpl.CidrComparator;
 import junit.framework.TestCase;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
-import com.cloud.network.security.SecurityGroupManagerImpl.CidrComparator;
+import javax.inject.Inject;
+import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * @author daan
diff --git a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java
index f6136e3..42e5d15 100644
--- a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java
+++ b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java
@@ -17,24 +17,6 @@
 
 package com.cloud.network.security;
 
-import java.io.IOException;
-
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.ComponentScan.Filter;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl;
-import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl;
-import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl;
-import org.apache.cloudstack.test.utils.SpringUtils;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.api.query.dao.SecurityGroupJoinDaoImpl;
 import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl;
@@ -70,6 +52,22 @@
 import com.cloud.vm.dao.UserVmDaoImpl;
 import com.cloud.vm.dao.UserVmDetailsDaoImpl;
 import com.cloud.vm.dao.VMInstanceDaoImpl;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl;
+import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl;
+import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+
+import java.io.IOException;
 
 @Configuration
 @ComponentScan(basePackageClasses = {SecurityGroupRulesDaoImpl.class, UserVmDaoImpl.class, AccountDaoImpl.class, ConfigurationDaoImpl.class, ConfigurationGroupDaoImpl.class,
diff --git a/server/src/test/java/com/cloud/network/security/SecurityGroupQueueTest.java b/server/src/test/java/com/cloud/network/security/SecurityGroupQueueTest.java
index e90cc58..13fd4b5 100644
--- a/server/src/test/java/com/cloud/network/security/SecurityGroupQueueTest.java
+++ b/server/src/test/java/com/cloud/network/security/SecurityGroupQueueTest.java
@@ -16,15 +16,14 @@
 // under the License.
 package com.cloud.network.security;
 
+import com.cloud.utils.Profiler;
+import junit.framework.TestCase;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import junit.framework.TestCase;
-
-import com.cloud.utils.Profiler;
-
 public class SecurityGroupQueueTest extends TestCase {
     public final static SecurityGroupWorkQueue queue = new LocalSecurityGroupWorkQueue();
 
diff --git a/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerImplTest.java
index 1d7cdc1..8e3f7da 100644
--- a/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerImplTest.java
@@ -17,6 +17,9 @@
 
 package com.cloud.network.vpc;
 
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.vpc.NetworkACLItem.State;
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,10 +29,6 @@
 import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.network.vpc.NetworkACLItem.State;
-import com.cloud.utils.exception.CloudRuntimeException;
-
 @RunWith(MockitoJUnitRunner.class)
 public class NetworkACLManagerImplTest {
 
diff --git a/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerTest.java b/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerTest.java
index 4dc75b4..2ed914a 100644
--- a/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerTest.java
+++ b/server/src/test/java/com/cloud/network/vpc/NetworkACLManagerTest.java
@@ -15,22 +15,27 @@
 
 package com.cloud.network.vpc;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.inject.Inject;
-
+import com.cloud.configuration.ConfigurationManager;
+import com.cloud.network.Network;
+import com.cloud.network.NetworkModel;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkServiceMapDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.element.NetworkACLServiceProvider;
+import com.cloud.network.vpc.NetworkACLItem.State;
+import com.cloud.network.vpc.dao.NetworkACLDao;
+import com.cloud.network.vpc.dao.VpcGatewayDao;
+import com.cloud.offerings.dao.NetworkOfferingDao;
 import com.cloud.server.ResourceTag;
+import com.cloud.tags.dao.ResourceTagDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.db.EntityManager;
+import junit.framework.TestCase;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
@@ -52,27 +57,19 @@
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
-import com.cloud.configuration.ConfigurationManager;
-import com.cloud.network.Network;
-import com.cloud.network.NetworkModel;
-import com.cloud.network.dao.NetworkDao;
-import com.cloud.network.dao.NetworkServiceMapDao;
-import com.cloud.network.dao.NetworkVO;
-import com.cloud.network.element.NetworkACLServiceProvider;
-import com.cloud.network.vpc.NetworkACLItem.State;
-import com.cloud.network.vpc.dao.NetworkACLDao;
-import com.cloud.network.vpc.dao.VpcGatewayDao;
-import com.cloud.offerings.dao.NetworkOfferingDao;
-import com.cloud.tags.dao.ResourceTagDao;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.user.AccountVO;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.utils.db.EntityManager;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
 
-import junit.framework.TestCase;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
diff --git a/server/src/test/java/com/cloud/network/vpc/NetworkACLServiceImplTest.java b/server/src/test/java/com/cloud/network/vpc/NetworkACLServiceImplTest.java
index d7333a9..18a0721 100644
--- a/server/src/test/java/com/cloud/network/vpc/NetworkACLServiceImplTest.java
+++ b/server/src/test/java/com/cloud/network/vpc/NetworkACLServiceImplTest.java
@@ -18,7 +18,6 @@
 package com.cloud.network.vpc;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -30,6 +29,8 @@
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.network.vpc.dao.VpcDao;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
@@ -49,9 +50,6 @@
 import org.mockito.Spy;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceUnavailableException;
@@ -66,9 +64,11 @@
 import com.cloud.user.User;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.exception.CloudRuntimeException;
+import org.junit.After;
+import org.mockito.MockedStatic;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
+@RunWith(MockitoJUnitRunner.class)
 public class NetworkACLServiceImplTest {
 
     @Spy
@@ -83,6 +83,8 @@
     @Mock
     private EntityManager entityManagerMock;
     @Mock
+    private VpcDao vpcDaoMock;
+    @Mock
     private AccountManager accountManagerMock;
     @Mock
     private NetworkACLDao networkAclDaoMock;
@@ -102,10 +104,11 @@
     @Mock
     private UpdateNetworkACLListCmd updateNetworkACLListCmdMock;
 
-    private Long networkAclMockId = 1L;
+    private Long networkAclMockId = 5L;
     private Long networkOfferingMockId = 2L;
     private Long networkMockVpcMockId = 3L;
     private long networkAclListId = 1l;
+    private static final String SOME_UUID = "someUuid";
 
     @Mock
     private MoveNetworkAclItemCmd moveNetworkAclItemCmdMock;
@@ -124,10 +127,18 @@
     @Mock
     private CallContext callContextMock;
 
+    @Mock
+    private VpcVO vpcVOMock;
+
+    @Mock
+    private Account accountMock;
+
+    private MockedStatic<CallContext> callContextMocked;
+
     @Before
-    public void befoteTest() {
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
+    public void setup() {
+        callContextMocked = Mockito.mockStatic(CallContext.class);
+        Mockito.when(CallContext.current()).thenReturn(callContextMock);
         Mockito.doReturn(Mockito.mock(User.class)).when(callContextMock).getCallingUser();
         Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
 
@@ -140,15 +151,16 @@
 
         Mockito.when(moveNetworkAclItemCmdMock.getUuidRuleBeingMoved()).thenReturn(uuidAclRuleBeingMoved);
 
-        Mockito.when(aclRuleBeingMovedMock.getUuid()).thenReturn(uuidAclRuleBeingMoved);
         Mockito.when(aclRuleBeingMovedMock.getAclId()).thenReturn(networkAclMockId);
 
-        Mockito.when(previousAclRuleMock.getUuid()).thenReturn(previousAclRuleUuid);
-        Mockito.when(nextAclRuleMock.getUuid()).thenReturn(nextAclRuleUuid);
-
         Mockito.when(networkAclMock.getVpcId()).thenReturn(networkMockVpcMockId);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        callContextMocked.close();
+    }
+
     @Test
     public void createNetworkACLItemTestAclNumberNull() {
         createNetworkACLItemTestForNumberAndExecuteTest(null);
@@ -168,7 +180,6 @@
         Mockito.doNothing().when(networkAclServiceImpl).validateAclRuleNumber(createNetworkAclCmdMock, networkAclMock);
         Mockito.doNothing().when(networkAclServiceImpl).validateNetworkAcl(networkAclMock);
 
-        Mockito.doReturn(Action.Allow).when(networkAclServiceImpl).validateAndCreateNetworkAclRuleAction(anyString());
         Mockito.when(networkAclItemDaoMock.getMaxNumberByACL(networkAclMockId)).thenReturn(5);
 
         Mockito.doNothing().when(networkAclServiceImpl).validateNetworkACLItem(Mockito.any(NetworkACLItemVO.class));
@@ -179,9 +190,9 @@
             }
         });
 
-        NetworkACLItem netowkrAclRuleCreated = networkAclServiceImpl.createNetworkACLItem(createNetworkAclCmdMock);
+        NetworkACLItem networkAclRuleCreated = networkAclServiceImpl.createNetworkACLItem(createNetworkAclCmdMock);
 
-        Assert.assertEquals(number == null ? 6 : number, netowkrAclRuleCreated.getNumber());
+        Assert.assertEquals(number == null ? 6 : number, networkAclRuleCreated.getNumber());
 
         InOrder inOrder = Mockito.inOrder( networkAclServiceImpl, networkAclManagerMock, networkAclItemDaoMock);
         inOrder.verify(networkAclServiceImpl).createAclListIfNeeded(createNetworkAclCmdMock);
@@ -248,15 +259,13 @@
         long networkId = 1L;
         Mockito.when(createNetworkAclCmdMock.getNetworkId()).thenReturn(networkId);
         Network networkMock = Mockito.mock(Network.class);
-        ;
+
         Mockito.when(networkMock.getVpcId()).thenReturn(12L);
         Long expectedAclListId = 15L;
         Mockito.when(networkMock.getNetworkACLId()).thenReturn(expectedAclListId);
 
         Mockito.doReturn(networkMock).when(networkModelMock).getNetwork(networkId);
 
-        Mockito.doReturn(16L).when(networkAclServiceImpl).createAclListForNetworkAndReturnAclListId(createNetworkAclCmdMock, networkMock);
-
         Long aclIdReturned = networkAclServiceImpl.createAclListIfNeeded(createNetworkAclCmdMock);
 
         Assert.assertEquals(expectedAclListId, aclIdReturned);
@@ -370,7 +379,6 @@
     @Test
     public void validateAclRuleNumberTestNumberNull() {
         Mockito.when(createNetworkAclCmdMock.getNumber()).thenReturn(null);
-        Mockito.doReturn(null).when(networkAclItemDaoMock).findByAclAndNumber(Mockito.anyLong(), Mockito.anyInt());
 
         networkAclServiceImpl.validateAclRuleNumber(createNetworkAclCmdMock, networkAclMock);
         Mockito.verify(networkAclItemDaoMock, Mockito.times(0)).findByAclAndNumber(Mockito.anyLong(), Mockito.anyInt());
@@ -396,34 +404,29 @@
     @Test(expected = InvalidParameterValueException.class)
     public void validateNetworkAclTestAclNotDefaulWithoutVpc() {
         Mockito.when(networkAclMock.getId()).thenReturn(3L);
-        Mockito.doReturn(null).when(entityManagerMock).findById(Vpc.class, networkMockVpcMockId);
-        ;
+        Mockito.doReturn(null).when(vpcDaoMock).findById(networkMockVpcMockId);
 
         networkAclServiceImpl.validateNetworkAcl(networkAclMock);
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
-    public void validateNetworkAclTestAclNotDefaulWithVpc() {
+    public void validateNetworkAclTestAclNotDefaultWithVpc() {
         CallContext callContextMock = Mockito.mock(CallContext.class);
         Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
 
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
+        Mockito.when(CallContext.current()).thenReturn(callContextMock);
 
         Mockito.when(networkAclMock.getId()).thenReturn(3L);
         Mockito.when(networkAclMock.getVpcId()).thenReturn(networkMockVpcMockId);
 
-        Mockito.doReturn(Mockito.mock(Vpc.class)).when(entityManagerMock).findById(Vpc.class, networkMockVpcMockId);
+        Mockito.doReturn(vpcVOMock).when(vpcDaoMock).findById(networkMockVpcMockId);
         Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.isNull(AccessType.class), Mockito.eq(true), Mockito.any(Vpc.class));
 
         networkAclServiceImpl.validateNetworkAcl(networkAclMock);
 
-        Mockito.verify(entityManagerMock).findById(Vpc.class, networkMockVpcMockId);
         Mockito.verify(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.isNull(AccessType.class), Mockito.eq(true), Mockito.any(Vpc.class));
 
-        PowerMockito.verifyStatic(CallContext.class);
-        CallContext.current();
+        callContextMocked.verify(() -> CallContext.current());
 
     }
 
@@ -554,7 +557,6 @@
 
     @Test
     public void validateProtocolTestProtocolIsNullOrBlank() {
-        Mockito.doNothing().when(networkAclServiceImpl).validateIcmpTypeAndCode(networkAclItemVoMock);
 
         Mockito.when(networkAclItemVoMock.getProtocol()).thenReturn(null);
         networkAclServiceImpl.validateProtocol(networkAclItemVoMock);
@@ -711,16 +713,22 @@
         Mockito.doReturn(networkAclItemVoMock).when(networkAclServiceImpl).validateNetworkAclRuleIdAndRetrieveIt(updateNetworkACLItemCmdMock);
         Mockito.doReturn(networkAclMock).when(networkAclManagerMock).getNetworkACL(networkAclMockId);
         Mockito.doNothing().when(networkAclServiceImpl).validateNetworkAcl(Mockito.eq(networkAclMock));
+        Mockito.doNothing().when(networkAclServiceImpl).validateGlobalAclPermissionAndAclAssociatedToVpc(Mockito.any(NetworkACL.class), Mockito.any(Account.class), Mockito.anyString());
         Mockito.doNothing().when(networkAclServiceImpl).transferDataToNetworkAclRulePojo(Mockito.eq(updateNetworkACLItemCmdMock), Mockito.eq(networkAclItemVoMock), Mockito.eq(networkAclMock));
         Mockito.doNothing().when(networkAclServiceImpl).validateNetworkACLItem(networkAclItemVoMock);
         Mockito.doReturn(networkAclItemVoMock).when(networkAclManagerMock).updateNetworkACLItem(networkAclItemVoMock);
 
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
+        Mockito.when(CallContext.current()).thenReturn(callContextMock);
+
         networkAclServiceImpl.updateNetworkACLItem(updateNetworkACLItemCmdMock);
 
         InOrder inOrder = Mockito.inOrder(networkAclServiceImpl, networkAclManagerMock);
         inOrder.verify(networkAclServiceImpl).validateNetworkAclRuleIdAndRetrieveIt(updateNetworkACLItemCmdMock);
         inOrder.verify(networkAclManagerMock).getNetworkACL(networkAclMockId);
         inOrder.verify(networkAclServiceImpl).validateNetworkAcl(networkAclMock);
+        inOrder.verify(networkAclServiceImpl).validateGlobalAclPermissionAndAclAssociatedToVpc(networkAclMock, accountMock, "Only Root Admins can update global ACLs.");
         inOrder.verify(networkAclServiceImpl).transferDataToNetworkAclRulePojo(Mockito.eq(updateNetworkACLItemCmdMock), Mockito.eq(networkAclItemVoMock), Mockito.eq(networkAclMock));
         inOrder.verify(networkAclServiceImpl).validateNetworkACLItem(networkAclItemVoMock);
         inOrder.verify(networkAclManagerMock).updateNetworkACLItem(networkAclItemVoMock);
@@ -808,7 +816,6 @@
         Mockito.when(updateNetworkACLItemCmdMock.getReason()).thenReturn(null);
 
         Mockito.when(updateNetworkACLItemCmdMock.isDisplay()).thenReturn(false);
-        Mockito.when(networkAclItemVoMock.isDisplay()).thenReturn(false);
 
         networkAclServiceImpl.transferDataToNetworkAclRulePojo(updateNetworkACLItemCmdMock, networkAclItemVoMock, networkAclMock);
 
@@ -846,7 +853,6 @@
         Mockito.when(updateNetworkACLItemCmdMock.getReason()).thenReturn("reason");
 
         Mockito.when(updateNetworkACLItemCmdMock.isDisplay()).thenReturn(true);
-        Mockito.when(networkAclItemVoMock.isDisplay()).thenReturn(false);
 
         networkAclServiceImpl.transferDataToNetworkAclRulePojo(updateNetworkACLItemCmdMock, networkAclItemVoMock, networkAclMock);
 
@@ -866,7 +872,6 @@
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
     public void updateNetworkACLTestParametersNotNull() {
         String name = "name";
         String description = "desc";
@@ -877,13 +882,18 @@
         Mockito.when(updateNetworkACLListCmdMock.getCustomId()).thenReturn(customId);
         Mockito.when(updateNetworkACLListCmdMock.getId()).thenReturn(networkAclListId);
         Mockito.when(updateNetworkACLListCmdMock.getDisplay()).thenReturn(false);
+        Mockito.when(networkACLVOMock.getVpcId()).thenReturn(networkMockVpcMockId);
+        Mockito.doReturn(vpcVOMock).when(vpcDaoMock).findById(networkMockVpcMockId);
+        Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class),
+        Mockito.isNull(AccessType.class), Mockito.eq(true), Mockito.any(Vpc.class));
+
 
         networkAclServiceImpl.updateNetworkACL(updateNetworkACLListCmdMock);
 
-        InOrder inOrder = Mockito.inOrder(networkAclDaoMock, entityManagerMock, entityManagerMock, accountManagerMock, networkACLVOMock);
+        InOrder inOrder = Mockito.inOrder(networkAclDaoMock, vpcDaoMock, accountManagerMock, networkACLVOMock);
 
         inOrder.verify(networkAclDaoMock).findById(networkAclListId);
-        inOrder.verify(entityManagerMock).findById(Mockito.eq(Vpc.class), Mockito.anyLong());
+        inOrder.verify(vpcDaoMock).findById(Mockito.anyLong());
         inOrder.verify(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.isNull(AccessType.class), Mockito.eq(true), nullable(Vpc.class));
 
 
@@ -1006,7 +1016,6 @@
 
         Mockito.doNothing().when(networkAclServiceImpl).validateAclConsistency(Mockito.any(MoveNetworkAclItemCmd.class), Mockito.any(NetworkACLVO.class), Mockito.anyListOf(NetworkACLItemVO.class));
 
-        Mockito.doReturn(new ArrayList<>()).when(networkAclServiceImpl).getAllAclRulesSortedByNumber(networkAclMockId);
         Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclServiceImpl).moveRuleToTheTop(Mockito.eq(aclRuleBeingMovedMock), Mockito.anyListOf(NetworkACLItemVO.class));
         Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclServiceImpl).moveRuleToTheBottom(Mockito.eq(aclRuleBeingMovedMock), Mockito.anyListOf(NetworkACLItemVO.class));
         Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclServiceImpl).moveRuleBetweenAclRules(Mockito.eq(aclRuleBeingMovedMock), Mockito.anyListOf(NetworkACLItemVO.class),
@@ -1070,20 +1079,23 @@
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
     public void updateNetworkACLTestParametersWithNullValues() {
         Mockito.when(updateNetworkACLListCmdMock.getName()).thenReturn(null);
         Mockito.when(updateNetworkACLListCmdMock.getDescription()).thenReturn(null);
         Mockito.when(updateNetworkACLListCmdMock.getCustomId()).thenReturn(null);
         Mockito.when(updateNetworkACLListCmdMock.getId()).thenReturn(networkAclListId);
         Mockito.when(updateNetworkACLListCmdMock.getDisplay()).thenReturn(null);
+        Mockito.when(networkACLVOMock.getVpcId()).thenReturn(networkMockVpcMockId);
+        Mockito.doReturn(vpcVOMock).when(vpcDaoMock).findById(networkMockVpcMockId);
+        Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class),
+        Mockito.isNull(AccessType.class), Mockito.eq(true), Mockito.any(Vpc.class));
 
         networkAclServiceImpl.updateNetworkACL(updateNetworkACLListCmdMock);
 
-        InOrder inOrder = Mockito.inOrder(networkAclDaoMock, entityManagerMock, accountManagerMock, networkACLVOMock);
+        InOrder inOrder = Mockito.inOrder(networkAclDaoMock, vpcDaoMock, accountManagerMock, networkACLVOMock);
 
         inOrder.verify(networkAclDaoMock).findById(networkAclListId);
-        inOrder.verify(entityManagerMock).findById(eq(Vpc.class), Mockito.anyLong());
+        inOrder.verify(vpcDaoMock).findById(Mockito.anyLong());
         inOrder.verify(accountManagerMock).checkAccess(any(Account.class), isNull(), eq(true), nullable(Vpc.class));
 
         Mockito.verify(networkACLVOMock, Mockito.times(0)).setName(null);
@@ -1100,22 +1112,18 @@
         Mockito.when(nextAclRuleMock.getAclId()).thenReturn(networkAclMockId);
         Mockito.when(previousAclRuleMock.getAclId()).thenReturn(networkAclMockId);
 
-        Mockito.doReturn(networkAclMock).when(networkAclDaoMock).findById(networkAclMockId);
-        Mockito.doReturn(Mockito.mock(Vpc.class)).when(entityManagerMock).findById(Vpc.class, networkMockVpcMockId);
-
         CallContext callContextMock = Mockito.mock(CallContext.class);
         Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
 
-        PowerMockito.mockStatic(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
+        Mockito.doReturn(networkAclMock).when(networkAclDaoMock).findById(networkAclMockId);
+        Mockito.when(CallContext.current()).thenReturn(callContextMock);
 
-        Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.isNull(AccessType.class), Mockito.eq(true), Mockito.any(Vpc.class));
+        Mockito.doNothing().when(networkAclServiceImpl).validateGlobalAclPermissionAndAclAssociatedToVpc(Mockito.any(NetworkACL.class), Mockito.any(Account.class), Mockito.anyString());
 
         networkAclServiceImpl.validateMoveAclRulesData(aclRuleBeingMovedMock, previousAclRuleMock, nextAclRuleMock);
 
         Mockito.verify(networkAclDaoMock).findById(networkAclMockId);
-        Mockito.verify(entityManagerMock).findById(Vpc.class, networkMockVpcMockId);
-        Mockito.verify(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.isNull(AccessType.class), Mockito.eq(true), Mockito.any(Vpc.class));
+        Mockito.verify(networkAclServiceImpl).validateGlobalAclPermissionAndAclAssociatedToVpc(Mockito.any(NetworkACL.class), Mockito.any(Account.class), Mockito.anyString());
     }
 
     @Test
@@ -1185,7 +1193,7 @@
     }
 
     @Test(expected = InvalidParameterValueException.class)
-    public void moveRuleBetweenAclRulesTestThereIsSpaceBetweenPreviousRuleAndNextRuleToAccomodateTheNewRuleWithOtherruleColliding() {
+    public void moveRuleBetweenAclRulesTestThereIsSpaceBetweenPreviousRuleAndNextRuleToAccommodateTheNewRuleWithOtherRuleColliding() {
         Mockito.when(previousAclRuleMock.getNumber()).thenReturn(10);
         Mockito.when(nextAclRuleMock.getNumber()).thenReturn(15);
 
@@ -1201,10 +1209,9 @@
     }
 
     @Test
-    public void moveRuleBetweenAclRulesTestThereIsSpaceBetweenPreviousRuleAndNextRuleToAccomodateTheNewRule() {
+    public void moveRuleBetweenAclRulesTestThereIsSpaceBetweenPreviousRuleAndNextRuleToAccommodateTheNewRule() {
         Mockito.when(previousAclRuleMock.getNumber()).thenReturn(10);
         Mockito.when(nextAclRuleMock.getNumber()).thenReturn(11);
-        Mockito.when(aclRuleBeingMovedMock.getNumber()).thenReturn(50);
         Mockito.when(aclRuleBeingMovedMock.getId()).thenReturn(1l);
 
         ArrayList<NetworkACLItemVO> allAclRules = new ArrayList<>();
@@ -1222,9 +1229,6 @@
         allAclRules.add(networkACLItemVO14);
         allAclRules.add(aclRuleBeingMovedMock);
 
-        Mockito.doNothing().when(networkAclItemDaoMock).updateNumberFieldNetworkItem(Mockito.anyLong(), Mockito.anyInt());
-        Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclItemDaoMock).findById(1l);
-
         Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclServiceImpl).updateAclRuleToNewPositionAndExecuteShiftIfNecessary(Mockito.any(NetworkACLItemVO.class), Mockito.anyInt(),
                 Mockito.anyListOf(NetworkACLItemVO.class), Mockito.anyInt());
 
@@ -1237,7 +1241,7 @@
     }
 
     @Test
-    public void moveRuleBetweenAclRulesTestThereIsNoSpaceBetweenPreviousRuleAndNextRuleToAccomodateTheNewRule() {
+    public void moveRuleBetweenAclRulesTestThereIsNoSpaceBetweenPreviousRuleAndNextRuleToAccommodateTheNewRule() {
         Mockito.when(previousAclRuleMock.getNumber()).thenReturn(10);
         Mockito.when(nextAclRuleMock.getNumber()).thenReturn(15);
         Mockito.when(aclRuleBeingMovedMock.getNumber()).thenReturn(50);
@@ -1252,9 +1256,6 @@
         Mockito.doNothing().when(networkAclItemDaoMock).updateNumberFieldNetworkItem(Mockito.anyLong(), Mockito.anyInt());
         Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclItemDaoMock).findById(1l);
 
-        Mockito.doReturn(aclRuleBeingMovedMock).when(networkAclServiceImpl).updateAclRuleToNewPositionAndExecuteShiftIfNecessary(Mockito.any(NetworkACLItemVO.class), Mockito.anyInt(),
-                Mockito.anyListOf(NetworkACLItemVO.class), Mockito.anyInt());
-
         networkAclServiceImpl.moveRuleBetweenAclRules(aclRuleBeingMovedMock, allAclRules, previousAclRuleMock, nextAclRuleMock);
 
         Mockito.verify(networkAclItemDaoMock).updateNumberFieldNetworkItem(aclRuleBeingMovedMock.getId(), 11);
@@ -1265,9 +1266,6 @@
 
     @Test
     public void updateAclRuleToNewPositionAndExecuteShiftIfNecessaryTest() {
-        Mockito.when(previousAclRuleMock.getNumber()).thenReturn(10);
-        Mockito.when(previousAclRuleMock.getId()).thenReturn(50l);
-
 
         Mockito.when(nextAclRuleMock.getNumber()).thenReturn(11);
         Mockito.when(nextAclRuleMock.getId()).thenReturn(50l);
@@ -1293,8 +1291,6 @@
         allAclRules.add(networkACLItemVO14);
         allAclRules.add(aclRuleBeingMovedMock);
 
-        Mockito.doNothing().when(networkAclItemDaoMock).updateNumberFieldNetworkItem(Mockito.anyLong(), Mockito.anyInt());
-
         Map<Long, NetworkACLItemVO> updatedItems = new HashMap<>();
         Mockito.doAnswer((Answer<Void>) invocation -> {
             Long id = (Long)invocation.getArguments()[0];
@@ -1386,9 +1382,41 @@
         ArrayList<NetworkACLItemVO> allAclRules = new ArrayList<>();
         allAclRules.add(networkAclItemVoMock);
 
-        Mockito.doReturn("someUuid").when(networkAclItemVoMock).getUuid();
+        Mockito.doReturn(SOME_UUID).when(networkAclItemVoMock).getUuid();
         networkAclServiceImpl.validateAclConsistency(moveNetworkAclItemCmdMock, networkACLVOMock, allAclRules);
 
         Mockito.verify(moveNetworkAclItemCmdMock, Mockito.times(1)).getAclConsistencyHash();
     }
+
+    @Test
+    public void checkGlobalAclPermissionTestGlobalAclWithRootAccountShouldWork() {
+        Mockito.doReturn(Account.Type.ADMIN).when(accountMock).getType();
+        Mockito.doReturn(true).when(networkAclServiceImpl).isGlobalAcl(Mockito.anyLong());
+
+        networkAclServiceImpl.checkGlobalAclPermission(networkMockVpcMockId, accountMock, "exception");
+    }
+
+    @Test(expected = PermissionDeniedException.class)
+    public void checkGlobalAclPermissionTestGlobalAclWithNonRootAccountShouldThrow() {
+        Mockito.doReturn(Account.Type.NORMAL).when(accountMock).getType();
+        Mockito.doReturn(true).when(networkAclServiceImpl).isGlobalAcl(Mockito.anyLong());
+
+        networkAclServiceImpl.checkGlobalAclPermission(networkMockVpcMockId, accountMock, "exception");
+    }
+
+    @Test
+    public void validateAclAssociatedToVpcTestNonNullVpcShouldCheckAccess() {
+        Mockito.doReturn(vpcVOMock).when(vpcDaoMock).findById(Mockito.anyLong());
+
+        networkAclServiceImpl.validateAclAssociatedToVpc(networkMockVpcMockId, accountMock, SOME_UUID);
+
+        Mockito.verify(accountManagerMock, Mockito.times(1)).checkAccess(Mockito.any(Account.class), isNull(), Mockito.anyBoolean(), Mockito.any(Vpc.class));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validateAclAssociatedToVpcTestNullVpcShouldThrowInvalidParameterValueException() {
+        Mockito.doReturn(null).when(vpcDaoMock).findById(Mockito.anyLong());
+
+        networkAclServiceImpl.validateAclAssociatedToVpc(networkMockVpcMockId, accountMock, SOME_UUID);
+    }
 }
diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java
index c820026..deffb16 100644
--- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java
@@ -18,53 +18,16 @@
  */
 package com.cloud.network.vpc;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-
-import static org.powermock.api.mockito.PowerMockito.when;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-
-import com.cloud.alert.AlertManager;
-import com.cloud.network.NetworkService;
-import org.apache.cloudstack.acl.SecurityChecker;
-import org.apache.cloudstack.alert.AlertService;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.junit.After;
-import org.junit.Assert;
-import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.powermock.reflect.Whitebox;
-
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.routing.UpdateNetworkCommand;
 import com.cloud.agent.api.to.IpAddressTO;
 import com.cloud.agent.manager.Commands;
-import com.cloud.dc.VlanVO;
-import com.cloud.dc.dao.VlanDao;
+import com.cloud.alert.AlertManager;
 import com.cloud.configuration.Resource;
 import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.VlanVO;
 import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.VlanDao;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceAllocationException;
@@ -74,16 +37,19 @@
 import com.cloud.network.Network.Provider;
 import com.cloud.network.Network.Service;
 import com.cloud.network.NetworkModel;
+import com.cloud.network.NetworkService;
 import com.cloud.network.PhysicalNetwork;
+import com.cloud.network.dao.FirewallRulesDao;
 import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.IPAddressVO;
 import com.cloud.network.dao.NetworkDao;
-import com.cloud.network.dao.NetworkVO;
 import com.cloud.network.element.NetworkElement;
 import com.cloud.network.router.CommandSetupHelper;
 import com.cloud.network.router.NetworkHelper;
 import com.cloud.network.router.VirtualRouter;
+import com.cloud.network.vpc.dao.NetworkACLDao;
 import com.cloud.network.vpc.dao.VpcDao;
+import com.cloud.network.vpc.dao.VpcOfferingDao;
 import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao;
 import com.cloud.offering.NetworkOffering;
 import com.cloud.offerings.NetworkOfferingServiceMapVO;
@@ -91,19 +57,53 @@
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
+import com.cloud.user.ResourceLimitService;
 import com.cloud.user.User;
+import com.cloud.user.UserVO;
 import com.cloud.utils.Pair;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.net.Ip;
+import com.cloud.utils.net.NetUtils;
 import com.cloud.vm.DomainRouterVO;
-import com.cloud.vm.NicVO;
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.NicDao;
-import com.cloud.network.vpc.dao.VpcOfferingDao;
-import com.cloud.user.ResourceLimitService;
-import com.cloud.user.UserVO;
-import com.cloud.utils.net.NetUtils;
+import org.apache.cloudstack.acl.SecurityChecker;
+import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
+import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.junit.After;
+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.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+
+@RunWith(MockitoJUnitRunner.class)
 public class VpcManagerImplTest {
 
     @Mock
@@ -128,6 +128,8 @@
     @Mock
     NetworkDao networkDao;
     @Mock
+    NetworkACLDao networkACLDaoMock;
+    @Mock
     NetworkModel networkModel;
     @Mock
     NetworkOfferingServiceMapDao networkOfferingServiceMapDao;
@@ -149,6 +151,10 @@
     AlertManager alertManager;
     @Mock
     NetworkService networkServiceMock;
+    @Mock
+    FirewallRulesDao firewallDao;
+    @Mock
+    NetworkACLVO networkACLVOMock;
 
     public static final long ACCOUNT_ID = 1;
     private AccountVO account;
@@ -167,6 +173,11 @@
     final Long vpcOwnerId = 1L;
     final String vpcName = "Test-VPC";
     final String vpcDomain = "domain";
+    final Long aclId = 1L;
+    final Long differentVpcAclId = 3L;
+    final Long vpcId = 1L;
+
+    private AutoCloseable closeable;
 
     private void registerCallContext() {
         account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
@@ -177,9 +188,8 @@
     }
 
     @Before
-    public void setup()
-    {
-        MockitoAnnotations.initMocks(this);
+    public void setup() throws NoSuchFieldException, IllegalAccessException {
+        closeable = MockitoAnnotations.openMocks(this);
         manager = new VpcManagerImpl();
         manager._vpcOffSvcMapDao = vpcOfferingServiceMapDao;
         manager.vpcDao = vpcDao;
@@ -200,25 +210,37 @@
         manager._vpcOffDao = vpcOfferingDao;
         manager._dcDao = dataCenterDao;
         manager._ntwkSvc = networkServiceMock;
+        manager._firewallDao = firewallDao;
+        manager._networkAclDao = networkACLDaoMock;
         CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
         registerCallContext();
+        overrideDefaultConfigValue(NetworkService.AllowUsersToSpecifyVRMtu, "_defaultValue", "false");
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         CallContext.unregister();
+        closeable.close();
     }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name,
+            final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
     @Test
     public void getVpcOffSvcProvidersMapForEmptyServiceTest() {
         long vpcOffId = 1L;
         List<VpcOfferingServiceMapVO> list = new ArrayList<VpcOfferingServiceMapVO>();
         list.add(mock(VpcOfferingServiceMapVO.class));
-        when(manager._vpcOffSvcMapDao.listByVpcOffId(vpcOffId)).thenReturn(list);
+        Mockito.when(manager._vpcOffSvcMapDao.listByVpcOffId(vpcOffId)).thenReturn(list);
 
         Map<Service, Set<Provider>> map = manager.getVpcOffSvcProvidersMap(vpcOffId);
 
         assertNotNull(map);
-        assertEquals(map.size(),1);
+        assertEquals(map.size(), 1);
     }
 
     protected Map<String, String> createFakeCapabilityInputMap() {
@@ -244,7 +266,7 @@
 
 
         // Execute
-        boolean result = Whitebox.invokeMethod(this.manager, "isVpcOfferingForRegionLevelVpc",
+        boolean result = ReflectionTestUtils.invokeMethod(this.manager, "isVpcOfferingForRegionLevelVpc",
                 serviceCapabilitystList); //, Network.Capability.RedundantRouter.getName(), Service.SourceNat);
 
         // Assert
@@ -260,7 +282,7 @@
         serviceCapabilitystList.put("", createFakeCapabilityInputMap());
 
         // Execute
-        boolean result = Whitebox.invokeMethod(this.manager, "isVpcOfferingForRegionLevelVpc",
+        boolean result = ReflectionTestUtils.invokeMethod(this.manager, "isVpcOfferingForRegionLevelVpc",
                 serviceCapabilitystList);
 
         // Assert
@@ -300,10 +322,10 @@
         final boolean distributedRouter = true;
         final NetworkElement nwElement1 = mock(NetworkElement.class);
         this.manager._ntwkModel = mock(NetworkModel.class);
-        when(this.manager._ntwkModel.getElementImplementingProvider(Provider.VPCVirtualRouter.getName()))
+        Mockito.when(this.manager._ntwkModel.getElementImplementingProvider(Provider.VPCVirtualRouter.getName()))
                 .thenReturn(nwElement1);
         final Map<Service, Map<Network.Capability, String>> capabilitiesService1 = new HashMap<>();
-        when(nwElement1.getCapabilities()).thenReturn(capabilitiesService1);
+        Mockito.when(nwElement1.getCapabilities()).thenReturn(capabilitiesService1);
         capabilities.put(Capability.RegionLevelVpc, "");
         capabilities.put(Capability.DistributedRouter, "");
         capabilitiesService1.put(service, capabilities);
@@ -324,9 +346,8 @@
         services.add(Service.SourceNat);
         List<NetworkOfferingServiceMapVO> serviceMap = new ArrayList<>();
 
-        Mockito.when(vpcDao.findById(anyLong())).thenReturn(vpcMockVO);
         Mockito.when(manager.getActiveVpc(anyLong())).thenReturn(vpcMock);
-        lenient().doNothing().when(accountManager).checkAccess(any(Account.class), nullable(SecurityChecker.AccessType.class), anyBoolean(), any(Vpc.class));
+        doNothing().when(accountManager).checkAccess(any(Account.class), nullable(SecurityChecker.AccessType.class), anyBoolean(), any(Vpc.class));
         Mockito.when(vpcMock.isRegionLevelVpc()).thenReturn(true);
         Mockito.when(entityMgr.findById(NetworkOffering.class, 1L)).thenReturn(offering);
         Mockito.when(vpcMock.getId()).thenReturn(VPC_ID);
@@ -354,9 +375,10 @@
     }
 
     @Test
-    public void testUpdateVpcNetwork() throws ResourceUnavailableException {
+    public void testUpdateVpcNetwork() throws ResourceUnavailableException, InsufficientCapacityException {
         long vpcId = 1L;
         Integer publicMtu = 1450;
+        String sourceNatIp = "1.2.3.4";
         Account accountMock = Mockito.mock(Account.class);
         VpcVO vpcVO = new VpcVO();
 
@@ -374,7 +396,6 @@
 
         IpAddressTO[] ipsToSend = ips.toArray(new IpAddressTO[0]);
 
-        Mockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock);
         Mockito.when(vpcDao.findById(vpcId)).thenReturn(vpcVO);
         Mockito.when(vpcDao.createForUpdate(anyLong())).thenReturn(vpcVO);
         Mockito.when(ipAddressDao.listByAssociatedVpc(anyLong(), nullable(Boolean.class))).thenReturn(ipAddresses);
@@ -383,37 +404,50 @@
         Mockito.when(vlanVO.getVlanNetmask()).thenReturn("netmask");
         Mockito.when(vlanDao.findById(anyLong())).thenReturn(vlanVO);
         Mockito.doAnswer((org.mockito.stubbing.Answer<Void>) invocation -> {
-            Commands commands = (Commands)invocation.getArguments()[2];
+            Commands commands = (Commands) invocation.getArguments()[2];
             commands.addCommand("updateNetwork", new UpdateNetworkCommand(ipsToSend));
             return null;
         }).when(commandSetupHelper).setupUpdateNetworkCommands(Mockito.any(), Mockito.any(), Mockito.any());
         Mockito.doAnswer((org.mockito.stubbing.Answer<Boolean>) invocation -> {
-            Commands commands = (Commands)invocation.getArguments()[1];
+            Commands commands = (Commands) invocation.getArguments()[1];
             commands.setAnswers(new Answer[]{answer});
             return true;
         }).when(networkHelper).sendCommandsToRouter(Mockito.any(), Mockito.any());
-        Mockito.when(nicDao.findByIpAddressAndVmType(anyString(), any())).thenReturn(Mockito.mock(NicVO.class));
-        Mockito.when(nicDao.update(anyLong(), any())).thenReturn(true);
-        Mockito.when(networkDao.listByVpc(vpcId)).thenReturn(List.of(Mockito.mock(NetworkVO.class)));
-        Mockito.when(networkDao.update(anyLong(), any())).thenReturn(true);
         Mockito.when(vpcDao.update(vpcId, vpcVO)).thenReturn(true);
 
-        manager.updateVpc(vpcId, null, null, null, true, publicMtu);
-        Assert.assertEquals(publicMtu, vpcVO.getPublicMtu());
+        UpdateVPCCmd cmd = Mockito.mock(UpdateVPCCmd.class);
+        Mockito.when(cmd.getId()).thenReturn(vpcId);
+        Mockito.when(cmd.getVpcName()).thenReturn(null);
+        Mockito.when(cmd.getDisplayText()).thenReturn(null);
+        Mockito.when(cmd.getCustomId()).thenReturn(null);
+        Mockito.when(cmd.isDisplayVpc()).thenReturn(true);
+        Mockito.when(cmd.getPublicMtu()).thenReturn(publicMtu);
+        Mockito.when(cmd.getSourceNatIP()).thenReturn(sourceNatIp);
 
+        manager.updateVpc(cmd);
+        Assert.assertEquals(publicMtu, vpcVO.getPublicMtu());
+    }
+
+    @Test
+    public void verifySourceNatIp() {
+        String sourceNatIp = "1.2.3.4";
+        VpcVO vpcVO = Mockito.mock(VpcVO.class); //new VpcVO(1l, "vpc", null, 10l, 1l, 1l, "10.1.0.0/16", null, false, false, false, null, null, null, null);
+        Mockito.when(vpcVO.getId()).thenReturn(1l);
+        IPAddressVO requestedIp = Mockito.mock(IPAddressVO.class);//new IPAddressVO(new Ip(sourceNatIp), 1l, 1l, 1l, true);
+        Mockito.when(ipAddressDao.findByIp(sourceNatIp)).thenReturn(requestedIp);
+        Mockito.when(requestedIp.getVpcId()).thenReturn(1l);
+        Mockito.when(requestedIp.getVpcId()).thenReturn(1l);
+        Assert.assertNull(manager.validateSourceNatip(vpcVO, null));
+        Assert.assertEquals(requestedIp, manager.validateSourceNatip(vpcVO, sourceNatIp));
     }
 
     @Test
     public void testUpdatePublicMtuToGreaterThanThreshold() {
         Integer publicMtu = 2500;
         Integer expectedMtu = 1500;
-        Long vpcId = 1L;
 
         VpcVO vpcVO = new VpcVO();
 
-        Mockito.when(vpcDao.findById(vpcId)).thenReturn(vpcVO);
-        Mockito.when(vpcDao.createForUpdate(anyLong())).thenReturn(vpcVO);
-        lenient().doNothing().when(alertManager).sendAlert(any(AlertService.AlertType.class), anyLong(), anyLong(), anyString(), anyString());
         Integer mtu = manager.validateMtu(vpcVO, publicMtu);
         Assert.assertEquals(expectedMtu, mtu);
     }
@@ -422,7 +456,7 @@
     public void testDisabledConfigCreateIpv6VpcOffering() {
         CreateVPCOfferingCmd cmd = Mockito.mock(CreateVPCOfferingCmd.class);
         Mockito.when(cmd.getInternetProtocol()).thenReturn(NetUtils.InternetProtocol.DualStack.toString());
-        Mockito.doNothing().when(networkServiceMock).validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(Mockito.any());
+        doNothing().when(networkServiceMock).validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(Mockito.any());
         manager.createVpcOffering(cmd);
     }
 
@@ -433,23 +467,17 @@
         Mockito.when(vpcOfferingVO.getState()).thenReturn(VpcOffering.State.Enabled);
         Mockito.when(vpcOfferingDao.findById(vpcOfferingId)).thenReturn(vpcOfferingVO);
         DataCenterVO dataCenterVO = Mockito.mock(DataCenterVO.class);
-        Mockito.when(dataCenterVO.getId()).thenReturn(zoneId);
         Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(dataCenterVO);
-        Mockito.doNothing().when(accountManager).checkAccess(account, vpcOfferingVO, dataCenterVO);
+        doNothing().when(accountManager).checkAccess(account, vpcOfferingVO, dataCenterVO);
         Mockito.when(vpcOfferingServiceMapDao.areServicesSupportedByVpcOffering(vpcOfferingId, new Service[]{Service.Dns})).thenReturn(supportDnsService);
         Mockito.when(vpcOfferingDao.isIpv6Supported(vpcOfferingId)).thenReturn(isIpv6);
-        try {
-            Mockito.doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc);
-        } catch (ResourceAllocationException e) {
-            Assert.fail(String.format("checkResourceLimit failure with exception: %s", e.getMessage()));
-        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
     public void testCreateVpcDnsOfferingServiceFailure() {
         mockVpcDnsResources(false, false);
         try {
-            Mockito.doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc);
+            doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc);
             manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain,
                     ip4Dns[0], null, null, null, true, 1500);
         } catch (ResourceAllocationException e) {
@@ -461,11 +489,25 @@
     public void testCreateVpcDnsIpv6OfferingFailure() {
         mockVpcDnsResources(true, false);
         try {
-            Mockito.doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc);
+            doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc);
             manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain,
                     ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500);
         } catch (ResourceAllocationException e) {
             Assert.fail(String.format("failure with exception: %s", e.getMessage()));
         }
     }
+
+    @Test
+    public void validateVpcPrivateGatewayAclIdTestNullAclVoThrowsInvalidParameterValueException() {
+        Mockito.doReturn(null).when(networkACLDaoMock).findById(aclId);
+        Assert.assertThrows(InvalidParameterValueException.class, () -> manager.validateVpcPrivateGatewayAclId(vpcId, aclId));
+    }
+
+    @Test
+    public void validateVpcPrivateGatewayTestAclFromDifferentVpcThrowsInvalidParameterValueException() {
+        Mockito.doReturn(2L).when(networkACLVOMock).getVpcId();
+        Mockito.doReturn(networkACLVOMock).when(networkACLDaoMock).findById(differentVpcAclId);
+        Assert.assertThrows(InvalidParameterValueException.class, () -> manager.validateVpcPrivateGatewayAclId(vpcId, differentVpcAclId));
+    }
+
 }
diff --git a/server/src/test/java/com/cloud/network/vpn/RemoteAccessVpnManagerImplTest.java b/server/src/test/java/com/cloud/network/vpn/RemoteAccessVpnManagerImplTest.java
index cff95ae..f8b4362 100644
--- a/server/src/test/java/com/cloud/network/vpn/RemoteAccessVpnManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/vpn/RemoteAccessVpnManagerImplTest.java
@@ -17,18 +17,18 @@
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
-import java.lang.reflect.InvocationTargetException;
-import javax.naming.ConfigurationException;
 import junit.framework.TestCase;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(NetUtils.class)
+import javax.naming.ConfigurationException;
+import java.lang.reflect.InvocationTargetException;
+
+@RunWith(MockitoJUnitRunner.class)
 public class RemoteAccessVpnManagerImplTest extends TestCase {
 
     Class<InvalidParameterValueException> expectedException = InvalidParameterValueException.class;
@@ -64,16 +64,16 @@
         String[] range = ipRange.split("-");
         String expectedMessage = String.format("One or both IPs sets in the range [%s] are invalid IPs.", ipRange);
 
-        PowerMockito.mockStatic(NetUtils.class);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.FALSE);
+            Mockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.TRUE);
 
-        PowerMockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.FALSE);
-        PowerMockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.TRUE);
+            InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
+                new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
+            });
 
-        InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
-            new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
-        });
-
-        assertEquals(expectedMessage, assertThrows.getMessage());
+            assertEquals(expectedMessage, assertThrows.getMessage());
+        }
     }
 
     @Test
@@ -82,16 +82,17 @@
         String[] range = ipRange.split("-");
         String expectedMessage = String.format("One or both IPs sets in the range [%s] are invalid IPs.", ipRange);
 
-        PowerMockito.mockStatic(NetUtils.class);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
 
-        PowerMockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.TRUE);
-        PowerMockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.FALSE);
+            Mockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.TRUE);
+            Mockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.FALSE);
 
-        InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
-            new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
-        });
+            InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
+                new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
+            });
 
-        assertEquals(expectedMessage, assertThrows.getMessage());
+            assertEquals(expectedMessage, assertThrows.getMessage());
+        }
     }
 
     @Test
@@ -100,16 +101,17 @@
         String[] range = ipRange.split("-");
         String expectedMessage = String.format("One or both IPs sets in the range [%s] are invalid IPs.", ipRange);
 
-        PowerMockito.mockStatic(NetUtils.class);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
 
-        PowerMockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.FALSE);
-        PowerMockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.FALSE);
+            Mockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.FALSE);
+            Mockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.FALSE);
 
-        InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
-            new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
-        });
+            InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
+                new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
+            });
 
-        assertEquals(expectedMessage, assertThrows.getMessage());
+            assertEquals(expectedMessage, assertThrows.getMessage());
+        }
     }
 
     @Test
@@ -118,17 +120,18 @@
         String[] range = ipRange.split("-");
         String expectedMessage = String.format("Range of IPs [%s] is invalid.", ipRange);
 
-        PowerMockito.mockStatic(NetUtils.class);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
 
-        PowerMockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.TRUE);
-        PowerMockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.TRUE);
-        PowerMockito.when(NetUtils.validIpRange(range[0], range[1])).thenReturn(Boolean.FALSE);
+            Mockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.TRUE);
+            Mockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.TRUE);
+            Mockito.when(NetUtils.validIpRange(range[0], range[1])).thenReturn(Boolean.FALSE);
 
-        InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
-            new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
-        });
+            InvalidParameterValueException assertThrows = Assert.assertThrows(expectedMessage, expectedException, () -> {
+                new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
+            });
 
-        assertEquals(expectedMessage, assertThrows.getMessage());
+            assertEquals(expectedMessage, assertThrows.getMessage());
+        }
     }
 
     @Test
@@ -136,13 +139,13 @@
         String ipRange = "192.168.0.1-192.168.0.255";
         String[] range = ipRange.split("-");
 
-        PowerMockito.mockStatic(NetUtils.class);
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.TRUE);
+            Mockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.TRUE);
+            Mockito.when(NetUtils.validIpRange(range[0], range[1])).thenReturn(Boolean.TRUE);
 
-        PowerMockito.when(NetUtils.isValidIp4(range[0])).thenReturn(Boolean.TRUE);
-        PowerMockito.when(NetUtils.isValidIp4(range[1])).thenReturn(Boolean.TRUE);
-        PowerMockito.when(NetUtils.validIpRange(range[0], range[1])).thenReturn(Boolean.TRUE);
-
-        new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
+            new RemoteAccessVpnManagerImpl().validateIpRange(ipRange, expectedException);
+        }
     }
 
     private <T extends Throwable> void handleExceptionOnValidateIpRangeErrorMustThrowCloudRuntimeException(Class<T> exceptionToCatch){
diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java
index 988d675..182ad83 100644
--- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java
+++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java
@@ -16,11 +16,6 @@
 // under the License.
 package com.cloud.projects;
 
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
@@ -28,6 +23,10 @@
 import com.cloud.user.Account;
 import com.cloud.utils.component.ManagerBase;
 
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
+
 public class MockProjectManagerImpl extends ManagerBase implements ProjectManager {
 
     @Override
@@ -85,12 +84,12 @@
     }
 
     @Override
-    public Project updateProject(long id, String displayText, String newOwnerName) throws ResourceAllocationException {
+    public Project updateProject(long id, String name, String displayText, String newOwnerName) throws ResourceAllocationException {
         return null;
     }
 
     @Override
-    public Project updateProject(long id, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException {
+    public Project updateProject(long id, String name, String displayText, String newOwnerName, Long userId, Role newRole) throws ResourceAllocationException {
         // TODO Auto-generated method stub
         return null;
     }
diff --git a/server/src/test/java/com/cloud/projects/ProjectManagerImplTest.java b/server/src/test/java/com/cloud/projects/ProjectManagerImplTest.java
new file mode 100644
index 0000000..94dffd9
--- /dev/null
+++ b/server/src/test/java/com/cloud/projects/ProjectManagerImplTest.java
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.projects;
+
+import com.cloud.projects.dao.ProjectDao;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProjectManagerImplTest {
+
+    @Spy
+    @InjectMocks
+    ProjectManagerImpl projectManager;
+
+    @Mock
+    ProjectDao projectDao;
+
+    List<ProjectVO> updateProjects;
+
+    @Before
+    public void setUp() throws Exception {
+        updateProjects = new ArrayList<>();
+        Mockito.when(projectDao.update(Mockito.anyLong(), Mockito.any(ProjectVO.class))).thenAnswer((Answer<Boolean>) invocation -> {
+            ProjectVO project = (ProjectVO)invocation.getArguments()[1];
+            updateProjects.add(project);
+            return true;
+        });
+    }
+
+    private void runUpdateProjectNameAndDisplayTextTest(boolean nonNullName, boolean nonNullDisplayText) {
+        ProjectVO projectVO = new ProjectVO();
+        String newName = nonNullName ? "NewName" : null;
+        String newDisplayText = nonNullDisplayText ? "NewDisplayText" : null;
+        projectManager.updateProjectNameAndDisplayText(projectVO, newName, newDisplayText);
+        if (!nonNullName && !nonNullDisplayText) {
+            Assert.assertTrue(updateProjects.isEmpty());
+        } else {
+            Assert.assertFalse(updateProjects.isEmpty());
+            Assert.assertEquals(1, updateProjects.size());
+            ProjectVO updatedProject = updateProjects.get(0);
+            Assert.assertNotNull(updatedProject);
+            if (nonNullName) {
+                Assert.assertEquals(newName, updatedProject.getName());
+            }
+            if (nonNullDisplayText) {
+                Assert.assertEquals(newDisplayText, updatedProject.getDisplayText());
+            }
+        }
+    }
+
+    @Test
+    public void testUpdateProjectNameAndDisplayTextNoUpdate() {
+        runUpdateProjectNameAndDisplayTextTest(false, false);
+    }
+
+    @Test
+    public void testUpdateProjectNameAndDisplayTextUpdateName() {
+        runUpdateProjectNameAndDisplayTextTest(true, false);
+    }
+
+    @Test
+    public void testUpdateProjectNameAndDisplayTextUpdateDisplayText() {
+        runUpdateProjectNameAndDisplayTextTest(false, true);
+    }
+
+    @Test
+    public void testUpdateProjectNameAndDisplayTextUpdateNameDisplayText() {
+        runUpdateProjectNameAndDisplayTextTest(true, true);
+    }
+}
diff --git a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java
index 4d5b5ba..c38cfc3 100755
--- a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java
+++ b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java
@@ -17,27 +17,6 @@
 
 package com.cloud.resource;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
-import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd;
-import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd;
-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.PrepareForMaintenanceCmd;
-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.CancelHostAsDegradedCmd;
-import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd;
-
-import org.apache.cloudstack.framework.config.ConfigKey;
-
 import com.cloud.agent.api.StartupCommand;
 import com.cloud.agent.api.StartupRoutingCommand;
 import com.cloud.agent.api.VgpuTypesInfo;
@@ -61,6 +40,24 @@
 import com.cloud.resource.ResourceState.Event;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.fsm.NoTransitionException;
+import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd;
+import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd;
+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.CancelHostAsDegradedCmd;
+import org.apache.cloudstack.api.command.admin.host.CancelMaintenanceCmd;
+import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd;
+import org.apache.cloudstack.api.command.admin.host.PrepareForMaintenanceCmd;
+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.framework.config.ConfigKey;
+
+import javax.naming.ConfigurationException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class MockResourceManagerImpl extends ManagerBase implements ResourceManager {
 
@@ -73,6 +70,11 @@
         return null;
     }
 
+    @Override
+    public Host autoUpdateHostAllocationState(Long hostId, ResourceState.Event resourceEvent) throws NoTransitionException {
+        return null;
+    }
+
     /* (non-Javadoc)
      * @see com.cloud.resource.ResourceService#cancelMaintenance(com.cloud.api.commands.CancelMaintenanceCmd)
      */
diff --git a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
index a7ddd16..414d411 100644
--- a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
+++ b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
@@ -17,47 +17,6 @@
 
 package com.cloud.resource;
 
-import static com.cloud.resource.ResourceState.Event.ErrorsCorrected;
-import static com.cloud.resource.ResourceState.Event.InternalEnterMaintenance;
-import static com.cloud.resource.ResourceState.Event.UnableToMaintain;
-import static com.cloud.resource.ResourceState.Event.UnableToMigrate;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-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;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-
-import com.cloud.storage.Volume;
-import com.cloud.storage.VolumeVO;
-import com.cloud.storage.dao.VolumeDao;
-import org.apache.cloudstack.api.command.admin.host.CancelHostAsDegradedCmd;
-import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.BDDMockito;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.GetVncPortAnswer;
 import com.cloud.agent.api.GetVncPortCommand;
@@ -71,6 +30,9 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.storage.StorageManager;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
 import com.cloud.utils.Ternary;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
@@ -81,9 +43,45 @@
 import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.VMInstanceDao;
 import com.trilead.ssh2.Connection;
+import org.apache.cloudstack.api.command.admin.host.CancelHostAsDegradedCmd;
+import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.BDDMockito;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ActionEventUtils.class, ResourceManagerImpl.class, SSHCmdHelper.class})
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static com.cloud.resource.ResourceState.Event.ErrorsCorrected;
+import static com.cloud.resource.ResourceState.Event.InternalEnterMaintenance;
+import static com.cloud.resource.ResourceState.Event.UnableToMaintain;
+import static com.cloud.resource.ResourceState.Event.UnableToMigrate;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class ResourceManagerImplTest {
 
     @Mock
@@ -121,11 +119,6 @@
     @Mock
     private GetVncPortAnswer getVncPortAnswerVm2;
     @Mock
-    private GetVncPortCommand getVncPortCommandVm1;
-    @Mock
-    private GetVncPortCommand getVncPortCommandVm2;
-
-    @Mock
     private VolumeVO rootDisk1;
     @Mock
     private VolumeVO rootDisk2;
@@ -154,15 +147,18 @@
     private static long poolId = 1L;
     private List<VolumeVO> rootDisks;
     private List<VolumeVO> dataDisks;
+    private MockedStatic<SSHCmdHelper> sshHelperMocked;
+    private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
+    private MockedConstruction<GetVncPortCommand> getVncPortCommandMockedConstruction;
+    private AutoCloseable closeable;
 
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         when(host.getType()).thenReturn(Host.Type.Routing);
         when(host.getId()).thenReturn(hostId);
         when(host.getResourceState()).thenReturn(ResourceState.Enabled);
         when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
-        when(host.getClusterId()).thenReturn(1L);
         when(hostDao.findById(hostId)).thenReturn(host);
         when(host.getDetail("username")).thenReturn(hostUsername);
         when(host.getDetail("password")).thenReturn(hostPassword);
@@ -176,19 +172,22 @@
         when(vmInstanceDao.listByHostId(hostId)).thenReturn(new ArrayList<>());
         when(vmInstanceDao.listVmsMigratingFromHost(hostId)).thenReturn(new ArrayList<>());
         when(vmInstanceDao.listNonMigratingVmsByHostEqualsLastHost(hostId)).thenReturn(new ArrayList<>());
-        PowerMockito.mockStatic(ActionEventUtils.class);
+        actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
         BDDMockito.given(ActionEventUtils.onCompletedActionEvent(anyLong(), anyLong(), anyString(), anyString(), anyString(), anyLong(), anyString(), anyLong()))
                 .willReturn(1L);
         when(getVncPortAnswerVm1.getAddress()).thenReturn(vm1VncAddress);
         when(getVncPortAnswerVm1.getPort()).thenReturn(vm1VncPort);
         when(getVncPortAnswerVm2.getAddress()).thenReturn(vm2VncAddress);
         when(getVncPortAnswerVm2.getPort()).thenReturn(vm2VncPort);
-        PowerMockito.whenNew(GetVncPortCommand.class).withArguments(vm1Id, vm1InstanceName).thenReturn(getVncPortCommandVm1);
-        PowerMockito.whenNew(GetVncPortCommand.class).withArguments(vm2Id, vm2InstanceName).thenReturn(getVncPortCommandVm2);
-        when(agentManager.easySend(eq(hostId), eq(getVncPortCommandVm1))).thenReturn(getVncPortAnswerVm1);
-        when(agentManager.easySend(eq(hostId), eq(getVncPortCommandVm2))).thenReturn(getVncPortAnswerVm2);
+        getVncPortCommandMockedConstruction = Mockito.mockConstruction(GetVncPortCommand.class, (mock,context) -> {
+            if (context.arguments().get(0).equals(vm1Id) && context.arguments().get(1) == vm1InstanceName) {
+                when(agentManager.easySend(eq(hostId), eq(mock))).thenReturn(getVncPortAnswerVm1);
+            } else if (context.arguments().get(0).equals(vm2Id) && context.arguments().get(1) == vm2InstanceName) {
+                when(agentManager.easySend(eq(hostId), eq(mock))).thenReturn(getVncPortAnswerVm2);
+            }
+        });
 
-        PowerMockito.mockStatic(SSHCmdHelper.class);
+        sshHelperMocked = Mockito.mockStatic(SSHCmdHelper.class);
         BDDMockito.given(SSHCmdHelper.acquireAuthorizedConnection(eq(hostPrivateIp), eq(22),
                 eq(hostUsername), eq(hostPassword), eq(hostPrivateKey))).willReturn(sshConnection);
         BDDMockito.given(SSHCmdHelper.sshExecuteCmdOneShot(eq(sshConnection),
@@ -203,15 +202,23 @@
         when(volumeDao.findByPoolId(poolId, Volume.Type.DATADISK)).thenReturn(dataDisks);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        sshHelperMocked.close();
+        actionEventUtilsMocked.close();
+        getVncPortCommandMockedConstruction.close();
+        closeable.close();
+    }
+
     @Test
     public void testCheckAndMaintainEnterMaintenanceModeNoVms() throws NoTransitionException {
         // Test entering into maintenance with no VMs running on host.
         boolean enterMaintenanceMode = resourceManager.checkAndMaintain(hostId);
         verify(resourceManager).attemptMaintain(host);
         verify(resourceManager).setHostIntoMaintenance(host);
-        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(anyObject());
+        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoErrorInMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(any());
         verify(resourceManager).resourceStateTransitTo(eq(host), eq(InternalEnterMaintenance), anyLong());
 
         Assert.assertTrue(enterMaintenanceMode);
@@ -306,11 +313,11 @@
         verify(agentManager).pullAgentOutMaintenance(hostId);
         verify(resourceManager).setKVMVncAccess(hostId, vms);
         verify(agentManager, times(vms.size())).easySend(eq(hostId), any(GetVncPortCommand.class));
+        verify(agentManager).pullAgentToMaintenance(hostId);
         verify(userVmDetailsDao).addDetail(eq(vm1Id), eq("kvm.vnc.address"), eq(vm1VncAddress), anyBoolean());
         verify(userVmDetailsDao).addDetail(eq(vm1Id), eq("kvm.vnc.port"), eq(String.valueOf(vm1VncPort)), anyBoolean());
         verify(userVmDetailsDao).addDetail(eq(vm2Id), eq("kvm.vnc.address"), eq(vm2VncAddress), anyBoolean());
         verify(userVmDetailsDao).addDetail(eq(vm2Id), eq("kvm.vnc.port"), eq(String.valueOf(vm2VncPort)), anyBoolean());
-        verify(agentManager).pullAgentToMaintenance(hostId);
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -374,7 +381,6 @@
     @Test
     public void testHandleAgentSSHDisabledConnectedAgent() {
         when(host.getStatus()).thenReturn(Status.Up);
-        when(configurationDao.getValue(ResourceManager.KvmSshToAgentEnabled.key())).thenReturn("false");
         resourceManager.handleAgentIfNotConnected(host, false);
         verify(resourceManager, never()).getHostCredentials(eq(host));
         verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword), eq(hostPrivateKey));
@@ -400,7 +406,6 @@
 
     private void setupPendingMigrationRetries() {
         when(haManager.hasPendingMigrationsWork(vm1.getId())).thenReturn(true);
-        when(haManager.hasPendingMigrationsWork(vm2.getId())).thenReturn(false);
     }
 
     private void setupFailedMigrations() {
@@ -418,10 +423,10 @@
     private void verifyErrorInMaintenanceCalls() throws NoTransitionException {
         boolean enterMaintenanceMode = resourceManager.checkAndMaintain(hostId);
         verify(resourceManager).attemptMaintain(host);
-        verify(resourceManager).setHostIntoErrorInMaintenance(eq(host), anyObject());
-        verify(resourceManager, never()).setHostIntoMaintenance(anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(anyObject());
+        verify(resourceManager).setHostIntoErrorInMaintenance(eq(host), any());
+        verify(resourceManager, never()).setHostIntoMaintenance(any());
+        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(any());
         verify(resourceManager).resourceStateTransitTo(eq(host), eq(UnableToMaintain), anyLong());
         Assert.assertFalse(enterMaintenanceMode);
     }
@@ -429,10 +434,10 @@
     private void verifyErrorInPrepareForMaintenanceCalls() throws NoTransitionException {
         boolean enterMaintenanceMode = resourceManager.checkAndMaintain(hostId);
         verify(resourceManager).attemptMaintain(host);
-        verify(resourceManager).setHostIntoErrorInPrepareForMaintenance(eq(host), anyObject());
-        verify(resourceManager, never()).setHostIntoMaintenance(anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(anyObject());
+        verify(resourceManager).setHostIntoErrorInPrepareForMaintenance(eq(host), any());
+        verify(resourceManager, never()).setHostIntoMaintenance(any());
+        verify(resourceManager, never()).setHostIntoErrorInMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(any());
         verify(resourceManager).resourceStateTransitTo(eq(host), eq(UnableToMigrate), anyLong());
         Assert.assertFalse(enterMaintenanceMode);
     }
@@ -442,20 +447,20 @@
         verify(resourceManager).attemptMaintain(host);
         verify(resourceManager).setHostIntoPrepareForMaintenanceAfterErrorsFixed(eq(host));
         verify(resourceManager).resourceStateTransitTo(eq(host), eq(ErrorsCorrected), anyLong());
-        verify(resourceManager, never()).setHostIntoMaintenance(anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInMaintenance(anyObject(), anyObject());
+        verify(resourceManager, never()).setHostIntoMaintenance(any());
+        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoErrorInMaintenance(any(), any());
         Assert.assertFalse(enterMaintenanceMode);
     }
 
     private void verifyNoChangeInMaintenance() throws NoTransitionException {
         boolean enterMaintenanceMode = resourceManager.checkAndMaintain(hostId);
         verify(resourceManager).attemptMaintain(host);
-        verify(resourceManager, never()).setHostIntoMaintenance(anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoErrorInMaintenance(anyObject(), anyObject());
-        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(anyObject());
-        verify(resourceManager, never()).resourceStateTransitTo(anyObject(), any(), anyLong());
+        verify(resourceManager, never()).setHostIntoMaintenance(any());
+        verify(resourceManager, never()).setHostIntoErrorInPrepareForMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoErrorInMaintenance(any(), any());
+        verify(resourceManager, never()).setHostIntoPrepareForMaintenanceAfterErrorsFixed(any());
+        verify(resourceManager, never()).resourceStateTransitTo(any(), any(), anyLong());
         Assert.assertFalse(enterMaintenanceMode);
     }
 
diff --git a/server/src/test/java/com/cloud/resourceicon/ResourceIconManagerImplTest.java b/server/src/test/java/com/cloud/resourceicon/ResourceIconManagerImplTest.java
index 556fae7..8b94585 100644
--- a/server/src/test/java/com/cloud/resourceicon/ResourceIconManagerImplTest.java
+++ b/server/src/test/java/com/cloud/resourceicon/ResourceIconManagerImplTest.java
@@ -16,24 +16,6 @@
 // under the License.
 package com.cloud.resourceicon;
 
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.cloudstack.api.ApiCommandResourceType;
-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.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnitRunner;
-
 import com.cloud.network.dao.NetworkVO;
 import com.cloud.resource.icon.ResourceIconVO;
 import com.cloud.resource.icon.dao.ResourceIconDao;
@@ -49,6 +31,23 @@
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+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.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ResourceIconManagerImplTest {
diff --git a/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java b/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java
index 7de36b2..b8b35e2 100644
--- a/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java
+++ b/server/src/test/java/com/cloud/resourcelimit/CheckedReservationTest.java
@@ -26,20 +26,22 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.reservation.ReservationVO;
 import org.apache.cloudstack.reservation.dao.ReservationDao;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
 
-@PrepareForTest(CheckedReservation.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CheckedReservationTest {
 
     @Mock
@@ -55,26 +57,31 @@
     @Mock
     GlobalLock quotaLimitLock;
 
+    private AutoCloseable closeable;
+
     @Before
     public void setup() {
-        initMocks(this);
-        when(reservation.getId()).thenReturn(1l);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
     public void getId() {
-        when(reservationDao.persist(any())).thenReturn(reservation);
-        when(account.getAccountId()).thenReturn(1l);
         when(account.getDomainId()).thenReturn(4l);
-        when(quotaLimitLock.lock(anyInt())).thenReturn(true);
-        boolean fail = false;
-        try (CheckedReservation cr = new CheckedReservation(account, Resource.ResourceType.user_vm,1l, reservationDao, resourceLimitService); ) {
+        // Some weird behaviour depending on whether the database is up or not.
+        lenient().when(reservationDao.persist(Mockito.any())).thenReturn(reservation);
+        lenient().when(reservation.getId()).thenReturn(1L);
+        try (CheckedReservation cr = new CheckedReservation(account, Resource.ResourceType.user_vm,1l, reservationDao, resourceLimitService) ) {
             long id = cr.getId();
-            assertEquals(1l, id);
+            assertEquals(1L, id);
         } catch (NullPointerException npe) {
             fail("NPE caught");
         } catch (ResourceAllocationException rae) {
-            // this does not work on all plafroms because of the static methods being used in the global lock mechanism
+            // this does not work on all platforms because of the static methods being used in the global lock mechanism
             // normally one would
             // throw new CloudRuntimeException(rae);
             // but we'll ignore this for platforms that can not humour the static bits of the system.
@@ -85,10 +92,7 @@
 
     @Test
     public void getNoAmount() {
-        when(reservationDao.persist(any())).thenReturn(reservation);
-        when(account.getAccountId()).thenReturn(1l);
-        boolean fail = false;
-        try (CheckedReservation cr = new CheckedReservation(account, Resource.ResourceType.cpu,-11l, reservationDao, resourceLimitService); ) {
+        try (CheckedReservation cr = new CheckedReservation(account, Resource.ResourceType.cpu,-11l, reservationDao, resourceLimitService) ) {
             Long amount = cr.getReservedAmount();
             assertNull(amount);
         } catch (NullPointerException npe) {
diff --git a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java
index a9aedcf..3008519 100644
--- a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java
+++ b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java
@@ -16,16 +16,14 @@
 // under the License.
 package com.cloud.resourcelimit;
 
+import com.cloud.configuration.ResourceLimit;
+import com.cloud.vpc.MockResourceLimitManagerImpl;
 import junit.framework.TestCase;
-
 import org.apache.log4j.Logger;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import com.cloud.configuration.ResourceLimit;
-import com.cloud.vpc.MockResourceLimitManagerImpl;
-
 public class ResourceLimitManagerImplTest extends TestCase {
     private static final Logger s_logger = Logger.getLogger(ResourceLimitManagerImplTest.class);
 
diff --git a/server/src/test/java/com/cloud/server/ConfigurationServerImplTest.java b/server/src/test/java/com/cloud/server/ConfigurationServerImplTest.java
index 8b0af99..1478892 100644
--- a/server/src/test/java/com/cloud/server/ConfigurationServerImplTest.java
+++ b/server/src/test/java/com/cloud/server/ConfigurationServerImplTest.java
@@ -16,18 +16,6 @@
 // under the License.
 package com.cloud.server;
 
-import org.apache.cloudstack.framework.config.ConfigDepot;
-import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-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.runners.MockitoJUnitRunner;
-
 import com.cloud.configuration.ConfigurationManager;
 import com.cloud.configuration.dao.ResourceCountDao;
 import com.cloud.dc.dao.DataCenterDao;
@@ -41,6 +29,17 @@
 import com.cloud.storage.dao.DiskOfferingDao;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.db.TransactionLegacy;
+import org.apache.cloudstack.framework.config.ConfigDepot;
+import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+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.runners.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ConfigurationServerImplTest {
diff --git a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
index cf8df1a..2095d90 100644
--- a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
+++ b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
@@ -16,12 +16,6 @@
 // under the License.
 package com.cloud.server;
 
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.when;
-
 import com.cloud.dc.Vlan.VlanType;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.host.DetailVO;
@@ -49,9 +43,8 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.UserVmDetailVO;
 import com.cloud.vm.UserVmVO;
-import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.UserVmDao;
-
+import com.cloud.vm.dao.UserVmDetailsDao;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseCmd;
@@ -61,28 +54,34 @@
 import org.apache.cloudstack.api.command.user.userdata.ListUserDataCmd;
 import org.apache.cloudstack.api.command.user.userdata.RegisterUserDataCmd;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
 import org.apache.cloudstack.framework.config.ConfigKey;
-
+import org.apache.cloudstack.userdata.UserDataManager;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(CallContext.class)
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class ManagementServerImplTest {
 
     @Mock
@@ -121,23 +120,23 @@
     @Mock
     UserVmDao _userVmDao;
 
+    @Mock
+    UserDataManager userDataManager;
+
     @Spy
     ManagementServerImpl spy = new ManagementServerImpl();
 
-    ConfigKey mockConfig;
-
     @Mock
     UserVmDetailsDao userVmDetailsDao;
 
     @Mock
     HostDetailsDao hostDetailsDao;
+    private AutoCloseable closeable;
 
     @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
+    public void setup() throws IllegalAccessException, NoSuchFieldException {
+        closeable = MockitoAnnotations.openMocks(this);
         CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
-        mockConfig = Mockito.mock(ConfigKey.class);
-        Whitebox.setInternalState(ipAddressManagerImpl.getClass(), "SystemVmPublicIpReservationModeStrictness", mockConfig);
         spy._accountMgr = _accountMgr;
         spy.userDataDao = _userDataDao;
         spy.templateDao = _templateDao;
@@ -145,11 +144,19 @@
         spy.annotationDao = annotationDao;
         spy._UserVmDetailsDao = userVmDetailsDao;
         spy._detailsDao = hostDetailsDao;
+        spy.userDataManager = userDataManager;
     }
 
     @After
     public void tearDown() throws Exception {
         CallContext.unregister();
+        closeable.close();
+    }
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
+        Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -205,7 +212,7 @@
 
     @Test
     public void setParametersTestWhenStateIsFreeAndSystemVmPublicIsTrue() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
-        Mockito.when(mockConfig.value()).thenReturn(Boolean.TRUE);
+        overrideDefaultConfigValue(ipAddressManagerImpl.SystemVmPublicIpReservationModeStrictness, "_defaultValue", "true");
 
         ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
         Mockito.when(cmd.getNetworkId()).thenReturn(10L);
@@ -228,8 +235,8 @@
     }
 
     @Test
-    public void setParametersTestWhenStateIsFreeAndSystemVmPublicIsFalse() {
-        Mockito.when(mockConfig.value()).thenReturn(Boolean.FALSE);
+    public void setParametersTestWhenStateIsFreeAndSystemVmPublicIsFalse() throws NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ipAddressManagerImpl.SystemVmPublicIpReservationModeStrictness, "_defaultValue", "false");
         ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
         Mockito.when(cmd.getNetworkId()).thenReturn(10L);
         Mockito.when(cmd.getZoneId()).thenReturn(null);
@@ -251,8 +258,8 @@
     }
 
     @Test
-    public void setParametersTestWhenStateIsNullAndSystemVmPublicIsFalse() {
-        Mockito.when(mockConfig.value()).thenReturn(Boolean.FALSE);
+    public void setParametersTestWhenStateIsNullAndSystemVmPublicIsFalse() throws NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ipAddressManagerImpl.SystemVmPublicIpReservationModeStrictness, "_defaultValue", "false");
         ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
         Mockito.when(cmd.getNetworkId()).thenReturn(10L);
         Mockito.when(cmd.getZoneId()).thenReturn(null);
@@ -273,8 +280,8 @@
     }
 
     @Test
-    public void setParametersTestWhenStateIsNullAndSystemVmPublicIsTrue() {
-        Mockito.when(mockConfig.value()).thenReturn(Boolean.TRUE);
+    public void setParametersTestWhenStateIsNullAndSystemVmPublicIsTrue() throws NoSuchFieldException, IllegalAccessException {
+        overrideDefaultConfigValue(ipAddressManagerImpl.SystemVmPublicIpReservationModeStrictness, "_defaultValue", "true");
         ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
         Mockito.when(cmd.getNetworkId()).thenReturn(10L);
         Mockito.when(cmd.getZoneId()).thenReturn(null);
@@ -296,261 +303,252 @@
 
     @Test
     public void testSuccessfulRegisterUserdata() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(account.getAccountId()).thenReturn(1L);
+            when(account.getDomainId()).thenReturn(2L);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
+            when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
 
-        RegisterUserDataCmd cmd = Mockito.mock(RegisterUserDataCmd.class);
-        when(cmd.getUserData()).thenReturn("testUserdata");
-        when(cmd.getName()).thenReturn("testName");
-        when(cmd.getHttpMethod()).thenReturn(BaseCmd.HTTPMethod.GET);
+            String testUserData = "testUserdata";
+            RegisterUserDataCmd cmd = Mockito.mock(RegisterUserDataCmd.class);
+            when(cmd.getUserData()).thenReturn(testUserData);
+            when(cmd.getName()).thenReturn("testName");
+            when(cmd.getHttpMethod()).thenReturn(BaseCmd.HTTPMethod.GET);
 
-        when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
-        when(_userDataDao.findByUserData(account.getAccountId(), account.getDomainId(), "testUserdata")).thenReturn(null);
+            when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
+            when(_userDataDao.findByUserData(account.getAccountId(), account.getDomainId(), testUserData)).thenReturn(null);
+            when(userDataManager.validateUserData(testUserData, BaseCmd.HTTPMethod.GET)).thenReturn(testUserData);
 
-        UserData userData = spy.registerUserData(cmd);
-        Assert.assertEquals("testName", userData.getName());
-        Assert.assertEquals("testUserdata", userData.getUserData());
-        Assert.assertEquals(1L, userData.getAccountId());
-        Assert.assertEquals(2L, userData.getDomainId());
+            UserData userData = spy.registerUserData(cmd);
+            Assert.assertEquals("testName", userData.getName());
+            Assert.assertEquals("testUserdata", userData.getUserData());
+            Assert.assertEquals(1L, userData.getAccountId());
+            Assert.assertEquals(2L, userData.getDomainId());
+        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
     public void testRegisterExistingUserdata() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(account.getAccountId()).thenReturn(1L);
+            when(account.getDomainId()).thenReturn(2L);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
+            when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
 
-        RegisterUserDataCmd cmd = Mockito.mock(RegisterUserDataCmd.class);
-        when(cmd.getUserData()).thenReturn("testUserdata");
-        when(cmd.getName()).thenReturn("testName");
+            RegisterUserDataCmd cmd = Mockito.mock(RegisterUserDataCmd.class);
+            when(cmd.getUserData()).thenReturn("testUserdata");
+            when(cmd.getName()).thenReturn("testName");
 
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
-        when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
-        when(_userDataDao.findByUserData(account.getAccountId(), account.getDomainId(), "testUserdata")).thenReturn(userData);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
+            when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
+            when(_userDataDao.findByUserData(account.getAccountId(), account.getDomainId(), "testUserdata")).thenReturn(userData);
 
-        spy.registerUserData(cmd);
+            spy.registerUserData(cmd);
+        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
     public void testRegisterExistingName() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(account.getAccountId()).thenReturn(1L);
+            when(account.getDomainId()).thenReturn(2L);
+            Mockito.when(callContextMock.getCallingAccount()).thenReturn(account);
+            when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
 
-        RegisterUserDataCmd cmd = Mockito.mock(RegisterUserDataCmd.class);
-        when(cmd.getUserData()).thenReturn("testUserdata");
-        when(cmd.getName()).thenReturn("testName");
+            RegisterUserDataCmd cmd = Mockito.mock(RegisterUserDataCmd.class);
+            when(cmd.getName()).thenReturn("testName");
 
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
-        when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(userData);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
+            when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(userData);
 
-        spy.registerUserData(cmd);
+            spy.registerUserData(cmd);
+        }
     }
 
     @Test
     public void testSuccessfulDeleteUserdata() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
+            when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
 
-        DeleteUserDataCmd cmd = Mockito.mock(DeleteUserDataCmd.class);
-        when(cmd.getAccountName()).thenReturn("testAccountName");
-        when(cmd.getDomainId()).thenReturn(1L);
-        when(cmd.getProjectId()).thenReturn(2L);
-        when(cmd.getId()).thenReturn(1L);
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
+            DeleteUserDataCmd cmd = Mockito.mock(DeleteUserDataCmd.class);
+            when(cmd.getAccountName()).thenReturn("testAccountName");
+            when(cmd.getDomainId()).thenReturn(1L);
+            when(cmd.getProjectId()).thenReturn(2L);
+            when(cmd.getId()).thenReturn(1L);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
 
-        Mockito.when(userData.getId()).thenReturn(1L);
-        when(_userDataDao.findById(1L)).thenReturn(userData);
-        when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
-        when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>());
-        when(_userVmDao.findByUserDataId(1L)).thenReturn(new ArrayList<UserVmVO>());
-        when(_userDataDao.remove(1L)).thenReturn(true);
+            Mockito.when(userData.getId()).thenReturn(1L);
+            when(_userDataDao.findById(1L)).thenReturn(userData);
+            when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>());
+            when(_userVmDao.findByUserDataId(1L)).thenReturn(new ArrayList<UserVmVO>());
+            when(_userDataDao.remove(1L)).thenReturn(true);
 
-        boolean result = spy.deleteUserData(cmd);
-        Assert.assertEquals(true, result);
+            boolean result = spy.deleteUserData(cmd);
+            Assert.assertEquals(true, result);
+        }
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void testDeleteUserdataLinkedToTemplate() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
+            when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
 
-        DeleteUserDataCmd cmd = Mockito.mock(DeleteUserDataCmd.class);
-        when(cmd.getAccountName()).thenReturn("testAccountName");
-        when(cmd.getDomainId()).thenReturn(1L);
-        when(cmd.getProjectId()).thenReturn(2L);
-        when(cmd.getId()).thenReturn(1L);
+            DeleteUserDataCmd cmd = Mockito.mock(DeleteUserDataCmd.class);
+            when(cmd.getAccountName()).thenReturn("testAccountName");
+            when(cmd.getDomainId()).thenReturn(1L);
+            when(cmd.getProjectId()).thenReturn(2L);
+            when(cmd.getId()).thenReturn(1L);
 
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
-        Mockito.when(userData.getId()).thenReturn(1L);
-        when(_userDataDao.findById(1L)).thenReturn(userData);
-        when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
+            Mockito.when(userData.getId()).thenReturn(1L);
+            when(_userDataDao.findById(1L)).thenReturn(userData);
 
-        VMTemplateVO vmTemplateVO = Mockito.mock(VMTemplateVO.class);
-        List<VMTemplateVO> linkedTemplates = new ArrayList<>();
-        linkedTemplates.add(vmTemplateVO);
-        when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(linkedTemplates);
+            VMTemplateVO vmTemplateVO = Mockito.mock(VMTemplateVO.class);
+            List<VMTemplateVO> linkedTemplates = new ArrayList<>();
+            linkedTemplates.add(vmTemplateVO);
+            when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(linkedTemplates);
 
-        spy.deleteUserData(cmd);
+            spy.deleteUserData(cmd);
+        }
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void testDeleteUserdataUsedByVM() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
+            when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
 
-        DeleteUserDataCmd cmd = Mockito.mock(DeleteUserDataCmd.class);
-        when(cmd.getAccountName()).thenReturn("testAccountName");
-        when(cmd.getDomainId()).thenReturn(1L);
-        when(cmd.getProjectId()).thenReturn(2L);
-        when(cmd.getId()).thenReturn(1L);
+            DeleteUserDataCmd cmd = Mockito.mock(DeleteUserDataCmd.class);
+            when(cmd.getAccountName()).thenReturn("testAccountName");
+            when(cmd.getDomainId()).thenReturn(1L);
+            when(cmd.getProjectId()).thenReturn(2L);
+            when(cmd.getId()).thenReturn(1L);
 
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
-        Mockito.when(userData.getId()).thenReturn(1L);
-        when(_userDataDao.findById(1L)).thenReturn(userData);
-        when(_userDataDao.findByName(account.getAccountId(), account.getDomainId(), "testName")).thenReturn(null);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
+            Mockito.when(userData.getId()).thenReturn(1L);
+            when(_userDataDao.findById(1L)).thenReturn(userData);
 
-        when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>());
+            when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>());
 
-        UserVmVO userVmVO = Mockito.mock(UserVmVO.class);
-        List<UserVmVO> vms = new ArrayList<>();
-        vms.add(userVmVO);
-        when(_userVmDao.findByUserDataId(1L)).thenReturn(vms);
+            UserVmVO userVmVO = Mockito.mock(UserVmVO.class);
+            List<UserVmVO> vms = new ArrayList<>();
+            vms.add(userVmVO);
+            when(_userVmDao.findByUserDataId(1L)).thenReturn(vms);
 
-        spy.deleteUserData(cmd);
+            spy.deleteUserData(cmd);
+        }
     }
 
     @Test
     public void testListUserDataById() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);;
 
-        ListUserDataCmd cmd = Mockito.mock(ListUserDataCmd.class);
-        when(cmd.getAccountName()).thenReturn("testAccountName");
-        when(cmd.getDomainId()).thenReturn(1L);
-        when(cmd.getProjectId()).thenReturn(2L);
-        when(cmd.getId()).thenReturn(1L);
-        when(cmd.isRecursive()).thenReturn(false);
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
+            ListUserDataCmd cmd = Mockito.mock(ListUserDataCmd.class);
+            when(cmd.getAccountName()).thenReturn("testAccountName");
+            when(cmd.getDomainId()).thenReturn(1L);
+            when(cmd.getProjectId()).thenReturn(2L);
+            when(cmd.getId()).thenReturn(1L);
+            when(cmd.isRecursive()).thenReturn(false);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
 
-        SearchBuilder<UserDataVO> sb = Mockito.mock(SearchBuilder.class);
-        when(_userDataDao.createSearchBuilder()).thenReturn(sb);
-        when(sb.entity()).thenReturn(userData);
+            SearchBuilder<UserDataVO> sb = Mockito.mock(SearchBuilder.class);
+            when(_userDataDao.createSearchBuilder()).thenReturn(sb);
+            when(sb.entity()).thenReturn(userData);
 
-        SearchCriteria<UserDataVO> sc = Mockito.mock(SearchCriteria.class);
-        when(sb.create()).thenReturn(sc);
+            SearchCriteria<UserDataVO> sc = Mockito.mock(SearchCriteria.class);
+            when(sb.create()).thenReturn(sc);
 
-        List<UserDataVO> userDataList = new ArrayList<UserDataVO>();
-        userDataList.add(userData);
-        Pair<List<UserDataVO>, Integer> result = new Pair(userDataList, 1);
-        when(_userDataDao.searchAndCount(nullable(SearchCriteria.class), nullable(Filter.class))).thenReturn(result);
+            List<UserDataVO> userDataList = new ArrayList<UserDataVO>();
+            userDataList.add(userData);
+            Pair<List<UserDataVO>, Integer> result = new Pair(userDataList, 1);
+            when(_userDataDao.searchAndCount(nullable(SearchCriteria.class), nullable(Filter.class))).thenReturn(result);
 
-        Pair<List<? extends UserData>, Integer> userdataResultList = spy.listUserDatas(cmd);
+            Pair<List<? extends UserData>, Integer> userdataResultList = spy.listUserDatas(cmd);
 
-        Assert.assertEquals(userdataResultList.first().get(0), userDataList.get(0));
+            Assert.assertEquals(userdataResultList.first().get(0), userDataList.get(0));
+        }
     }
 
     @Test
     public void testListUserDataByName() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
 
-        ListUserDataCmd cmd = Mockito.mock(ListUserDataCmd.class);
-        when(cmd.getAccountName()).thenReturn("testAccountName");
-        when(cmd.getDomainId()).thenReturn(1L);
-        when(cmd.getProjectId()).thenReturn(2L);
-        when(cmd.getName()).thenReturn("testSearchUserdataName");
-        when(cmd.isRecursive()).thenReturn(false);
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
+            ListUserDataCmd cmd = Mockito.mock(ListUserDataCmd.class);
+            when(cmd.getAccountName()).thenReturn("testAccountName");
+            when(cmd.getDomainId()).thenReturn(1L);
+            when(cmd.getProjectId()).thenReturn(2L);
+            when(cmd.getName()).thenReturn("testSearchUserdataName");
+            when(cmd.isRecursive()).thenReturn(false);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
 
-        SearchBuilder<UserDataVO> sb = Mockito.mock(SearchBuilder.class);
-        when(_userDataDao.createSearchBuilder()).thenReturn(sb);
-        when(sb.entity()).thenReturn(userData);
+            SearchBuilder<UserDataVO> sb = Mockito.mock(SearchBuilder.class);
+            when(_userDataDao.createSearchBuilder()).thenReturn(sb);
+            when(sb.entity()).thenReturn(userData);
 
-        SearchCriteria<UserDataVO> sc = Mockito.mock(SearchCriteria.class);
-        when(sb.create()).thenReturn(sc);
+            SearchCriteria<UserDataVO> sc = Mockito.mock(SearchCriteria.class);
+            when(sb.create()).thenReturn(sc);
 
-        List<UserDataVO> userDataList = new ArrayList<UserDataVO>();
-        userDataList.add(userData);
-        Pair<List<UserDataVO>, Integer> result = new Pair(userDataList, 1);
-        when(_userDataDao.searchAndCount(nullable(SearchCriteria.class), nullable(Filter.class))).thenReturn(result);
+            List<UserDataVO> userDataList = new ArrayList<UserDataVO>();
+            userDataList.add(userData);
+            Pair<List<UserDataVO>, Integer> result = new Pair(userDataList, 1);
+            when(_userDataDao.searchAndCount(nullable(SearchCriteria.class), nullable(Filter.class))).thenReturn(result);
 
-        Pair<List<? extends UserData>, Integer> userdataResultList = spy.listUserDatas(cmd);
+            Pair<List<? extends UserData>, Integer> userdataResultList = spy.listUserDatas(cmd);
 
-        Assert.assertEquals(userdataResultList.first().get(0), userDataList.get(0));
+            Assert.assertEquals(userdataResultList.first().get(0), userDataList.get(0));
+        }
     }
 
     @Test
     public void testListUserDataByKeyword() {
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = PowerMockito.mock(CallContext.class);
-        when(CallContext.current()).thenReturn(callContextMock);
-        when(account.getAccountId()).thenReturn(1L);
-        when(account.getDomainId()).thenReturn(2L);
-        when(callContextMock.getCallingAccount()).thenReturn(account);
-        when(_accountMgr.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            when(CallContext.current()).thenReturn(callContextMock);
+            when(callContextMock.getCallingAccount()).thenReturn(account);
 
-        ListUserDataCmd cmd = Mockito.mock(ListUserDataCmd.class);
-        when(cmd.getAccountName()).thenReturn("testAccountName");
-        when(cmd.getDomainId()).thenReturn(1L);
-        when(cmd.getProjectId()).thenReturn(2L);
-        when(cmd.getKeyword()).thenReturn("testSearchUserdataKeyword");
-        when(cmd.isRecursive()).thenReturn(false);
-        UserDataVO userData = Mockito.mock(UserDataVO.class);
+            ListUserDataCmd cmd = Mockito.mock(ListUserDataCmd.class);
+            when(cmd.getAccountName()).thenReturn("testAccountName");
+            when(cmd.getDomainId()).thenReturn(1L);
+            when(cmd.getProjectId()).thenReturn(2L);
+            when(cmd.getKeyword()).thenReturn("testSearchUserdataKeyword");
+            when(cmd.isRecursive()).thenReturn(false);
+            UserDataVO userData = Mockito.mock(UserDataVO.class);
 
-        SearchBuilder<UserDataVO> sb = Mockito.mock(SearchBuilder.class);
-        when(_userDataDao.createSearchBuilder()).thenReturn(sb);
-        when(sb.entity()).thenReturn(userData);
+            SearchBuilder<UserDataVO> sb = Mockito.mock(SearchBuilder.class);
+            when(_userDataDao.createSearchBuilder()).thenReturn(sb);
+            when(sb.entity()).thenReturn(userData);
 
-        SearchCriteria<UserDataVO> sc = Mockito.mock(SearchCriteria.class);
-        when(sb.create()).thenReturn(sc);
+            SearchCriteria<UserDataVO> sc = Mockito.mock(SearchCriteria.class);
+            when(sb.create()).thenReturn(sc);
 
-        List<UserDataVO> userDataList = new ArrayList<UserDataVO>();
-        userDataList.add(userData);
-        Pair<List<UserDataVO>, Integer> result = new Pair(userDataList, 1);
-        when(_userDataDao.searchAndCount(nullable(SearchCriteria.class), nullable(Filter.class))).thenReturn(result);
+            List<UserDataVO> userDataList = new ArrayList<UserDataVO>();
+            userDataList.add(userData);
+            Pair<List<UserDataVO>, Integer> result = new Pair(userDataList, 1);
+            when(_userDataDao.searchAndCount(nullable(SearchCriteria.class), nullable(Filter.class))).thenReturn(result);
 
-        Pair<List<? extends UserData>, Integer> userdataResultList = spy.listUserDatas(cmd);
+            Pair<List<? extends UserData>, Integer> userdataResultList = spy.listUserDatas(cmd);
 
-        Assert.assertEquals(userdataResultList.first().get(0), userDataList.get(0));
+            Assert.assertEquals(userdataResultList.first().get(0), userDataList.get(0));
+        }
     }
 
     private UserVmVO mockFilterUefiHostsTestVm(String uefiValue) {
@@ -649,4 +647,41 @@
         Assert.assertNotNull(result.second());
         Assert.assertEquals(0, result.second().size());
     }
+
+    @Test
+    public void testZoneWideVolumeRequiresStorageMotionNonManaged() {
+        PrimaryDataStore dataStore = Mockito.mock(PrimaryDataStore.class);
+        Mockito.when(dataStore.isManaged()).thenReturn(false);
+        Assert.assertFalse(spy.zoneWideVolumeRequiresStorageMotion(dataStore,
+                Mockito.mock(Host.class), Mockito.mock(Host.class)));
+    }
+
+    @Test
+    public void testZoneWideVolumeRequiresStorageMotionSameClusterHost() {
+        PrimaryDataStore dataStore = Mockito.mock(PrimaryDataStore.class);
+        Mockito.when(dataStore.isManaged()).thenReturn(true);
+        Host host1 = Mockito.mock(Host.class);
+        Mockito.when(host1.getClusterId()).thenReturn(1L);
+        Host host2 = Mockito.mock(Host.class);
+        Mockito.when(host2.getClusterId()).thenReturn(1L);
+        Assert.assertFalse(spy.zoneWideVolumeRequiresStorageMotion(dataStore, host1, host2));
+    }
+
+    @Test
+    public void testZoneWideVolumeRequiresStorageMotionDriverDependent() {
+        PrimaryDataStore dataStore = Mockito.mock(PrimaryDataStore.class);
+        Mockito.when(dataStore.isManaged()).thenReturn(true);
+        PrimaryDataStoreDriver driver = Mockito.mock(PrimaryDataStoreDriver.class);
+        Mockito.when(dataStore.getDriver()).thenReturn(driver);
+        Host host1 = Mockito.mock(Host.class);
+        Mockito.when(host1.getClusterId()).thenReturn(1L);
+        Host host2 = Mockito.mock(Host.class);
+        Mockito.when(host2.getClusterId()).thenReturn(2L);
+
+        Mockito.when(driver.zoneWideVolumesAvailableWithoutClusterMotion()).thenReturn(true);
+        Assert.assertFalse(spy.zoneWideVolumeRequiresStorageMotion(dataStore, host1, host2));
+
+        Mockito.when(driver.zoneWideVolumesAvailableWithoutClusterMotion()).thenReturn(false);
+        Assert.assertTrue(spy.zoneWideVolumeRequiresStorageMotion(dataStore, host1, host2));
+    }
 }
diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java b/server/src/test/java/com/cloud/server/StatsCollectorTest.java
index e010c8d..1f6a35c 100644
--- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java
+++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java
@@ -18,40 +18,6 @@
 //
 package com.cloud.server;
 
-import static org.mockito.Mockito.when;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-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.Properties;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.commons.collections.CollectionUtils;
-import org.influxdb.InfluxDB;
-import org.influxdb.InfluxDBFactory;
-import org.influxdb.dto.BatchPoints;
-import org.influxdb.dto.BatchPoints.Builder;
-import org.influxdb.dto.Point;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.stubbing.Answer;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.modules.junit4.PowerMockRunnerDelegate;
-
 import com.cloud.agent.api.VmDiskStatsEntry;
 import com.cloud.agent.api.VmStatsEntry;
 import com.cloud.hypervisor.Hypervisor;
@@ -66,10 +32,42 @@
 import com.google.gson.Gson;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.commons.collections.CollectionUtils;
+import org.influxdb.InfluxDB;
+import org.influxdb.InfluxDBFactory;
+import org.influxdb.dto.BatchPoints;
+import org.influxdb.dto.BatchPoints.Builder;
+import org.influxdb.dto.Point;
+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.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockRunnerDelegate(DataProviderRunner.class)
-@PrepareForTest({InfluxDBFactory.class, BatchPoints.class})
+import java.net.URI;
+import java.net.URISyntaxException;
+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.Properties;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
 public class StatsCollectorTest {
 
     @InjectMocks
@@ -83,31 +81,50 @@
     private static final String DEFAULT_DATABASE_NAME = "cloudstack";
 
     @Mock
-    VmStatsDao vmStatsDaoMock;
+    VmStatsDao vmStatsDaoMock = Mockito.mock(VmStatsDao.class);
 
     @Mock
     VmStatsEntry statsForCurrentIterationMock;
 
     @Captor
-    ArgumentCaptor<VmStatsVO> vmStatsVOCaptor;
+    ArgumentCaptor<VmStatsVO> vmStatsVOCaptor = ArgumentCaptor.forClass(VmStatsVO.class);
 
     @Captor
-    ArgumentCaptor<Boolean> booleanCaptor;
+    ArgumentCaptor<Boolean> booleanCaptor = ArgumentCaptor.forClass(Boolean.class);
 
     @Mock
-    Boolean accumulateMock;
+    VmStatsVO vmStatsVoMock1 = Mockito.mock(VmStatsVO.class);
 
     @Mock
-    VmStatsVO vmStatsVoMock1, vmStatsVoMock2;
+    VmStatsVO vmStatsVoMock2 = Mockito.mock(VmStatsVO.class);
 
     @Mock
-    VmStatsEntry vmStatsEntryMock;
+    VmStatsEntry vmStatsEntryMock = Mockito.mock(VmStatsEntry.class);
 
     @Mock
-    VolumeStatsDao volumeStatsDao;
+    VolumeStatsDao volumeStatsDao = Mockito.mock(VolumeStatsDao.class);
 
     private static Gson gson = new Gson();
 
+    private MockedStatic<InfluxDBFactory> influxDBFactoryMocked;
+
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+        statsCollector.vmStatsDao = vmStatsDaoMock;
+        statsCollector.volumeStatsDao = volumeStatsDao;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (influxDBFactoryMocked != null) {
+            influxDBFactoryMocked.close();
+        }
+        closeable.close();
+    }
+
     @Test
     public void createInfluxDbConnectionTest() {
         configureAndTestCreateInfluxDbConnection(true);
@@ -123,8 +140,8 @@
         statsCollector.externalStatsPort = INFLUXDB_DEFAULT_PORT;
         InfluxDB influxDbConnection = Mockito.mock(InfluxDB.class);
         when(influxDbConnection.databaseExists(DEFAULT_DATABASE_NAME)).thenReturn(databaseExists);
-        PowerMockito.mockStatic(InfluxDBFactory.class);
-        PowerMockito.when(InfluxDBFactory.connect(URL)).thenReturn(influxDbConnection);
+        influxDBFactoryMocked = Mockito.mockStatic(InfluxDBFactory.class);
+        influxDBFactoryMocked.when(() -> InfluxDBFactory.connect(URL)).thenReturn(influxDbConnection);
 
         InfluxDB returnedConnection = statsCollector.createInfluxDbConnection();
 
@@ -137,21 +154,22 @@
         Mockito.doNothing().when(influxDbConnection).write(Mockito.any(Point.class));
         Builder builder = Mockito.mock(Builder.class);
         BatchPoints batchPoints = Mockito.mock(BatchPoints.class);
-        PowerMockito.mockStatic(BatchPoints.class);
-        PowerMockito.when(BatchPoints.database(DEFAULT_DATABASE_NAME)).thenReturn(builder);
-        when(builder.build()).thenReturn(batchPoints);
-        Map<String, String> tagsToAdd = new HashMap<>();
-        tagsToAdd.put("hostId", "1");
-        Map<String, Object> fieldsToAdd = new HashMap<>();
-        fieldsToAdd.put("total_memory_kbs", 10000000);
-        Point point = Point.measurement("measure").tag(tagsToAdd).time(System.currentTimeMillis(), TimeUnit.MILLISECONDS).fields(fieldsToAdd).build();
-        List<Point> points = new ArrayList<>();
-        points.add(point);
-        when(batchPoints.point(point)).thenReturn(batchPoints);
+        try (MockedStatic<BatchPoints> ignored = Mockito.mockStatic(BatchPoints.class)) {
+            Mockito.when(BatchPoints.database(DEFAULT_DATABASE_NAME)).thenReturn(builder);
+            when(builder.build()).thenReturn(batchPoints);
+            Map<String, String> tagsToAdd = new HashMap<>();
+            tagsToAdd.put("hostId", "1");
+            Map<String, Object> fieldsToAdd = new HashMap<>();
+            fieldsToAdd.put("total_memory_kbs", 10000000);
+            Point point = Point.measurement("measure").tag(tagsToAdd).time(System.currentTimeMillis(), TimeUnit.MILLISECONDS).fields(fieldsToAdd).build();
+            List<Point> points = new ArrayList<>();
+            points.add(point);
+            when(batchPoints.point(point)).thenReturn(batchPoints);
 
-        statsCollector.writeBatches(influxDbConnection, DEFAULT_DATABASE_NAME, points);
+            statsCollector.writeBatches(influxDbConnection, DEFAULT_DATABASE_NAME, points);
 
-        Mockito.verify(influxDbConnection).write(batchPoints);
+            Mockito.verify(influxDbConnection).write(batchPoints);
+        }
     }
 
     @Test
@@ -232,7 +250,8 @@
         configureAndTestisCurrentVmDiskStatsDifferentFromPrevious(123l, 123l, 123l, 123l, false);
     }
 
-    private void configureAndTestisCurrentVmDiskStatsDifferentFromPrevious(long bytesRead, long bytesWrite, long ioRead, long ioWrite, boolean expectedResult) {
+    private void configureAndTestisCurrentVmDiskStatsDifferentFromPrevious(long bytesRead, long bytesWrite, long ioRead,
+            long ioWrite, boolean expectedResult) {
         VmDiskStatisticsVO previousVmDiskStatisticsVO = new VmDiskStatisticsVO(1l, 1l, 1l, 1l);
         previousVmDiskStatisticsVO.setCurrentBytesRead(123l);
         previousVmDiskStatisticsVO.setCurrentBytesWrite(123l);
@@ -251,12 +270,13 @@
 
     @Test
     @DataProvider({
-        "0,0,0,0,true", "1,0,0,0,false", "0,1,0,0,false", "0,0,1,0,false",
-        "0,0,0,1,false", "1,0,0,1,false", "1,0,1,0,false", "1,1,0,0,false",
-        "0,1,1,0,false", "0,1,0,1,false", "0,0,1,1,false", "0,1,1,1,false",
-        "1,1,0,1,false", "1,0,1,1,false", "1,1,1,0,false", "1,1,1,1,false",
+            "0,0,0,0,true", "1,0,0,0,false", "0,1,0,0,false", "0,0,1,0,false",
+            "0,0,0,1,false", "1,0,0,1,false", "1,0,1,0,false", "1,1,0,0,false",
+            "0,1,1,0,false", "0,1,0,1,false", "0,0,1,1,false", "0,1,1,1,false",
+            "1,1,0,1,false", "1,0,1,1,false", "1,1,1,0,false", "1,1,1,1,false",
     })
-    public void configureAndTestCheckIfDiskStatsAreZero(long bytesRead, long bytesWrite, long ioRead, long ioWrite, boolean expected) {
+    public void configureAndTestCheckIfDiskStatsAreZero(long bytesRead, long bytesWrite, long ioRead, long ioWrite,
+            boolean expected) {
         VmDiskStatsEntry vmDiskStatsEntry = new VmDiskStatsEntry();
         vmDiskStatsEntry.setBytesRead(bytesRead);
         vmDiskStatsEntry.setBytesWrite(bytesWrite);
@@ -311,17 +331,16 @@
         VmStatsVO actual = vmStatsVOCaptor.getAllValues().get(0);
         Assert.assertEquals(Long.valueOf(2L), actual.getVmId());
         Assert.assertEquals(Long.valueOf(1L), actual.getMgmtServerId());
-        Assert.assertEquals(expectedVmStatsStr, actual.getVmStatsData());
+        Assert.assertEquals(convertJsonToOrderedMap(expectedVmStatsStr), convertJsonToOrderedMap(actual.getVmStatsData()));
         Assert.assertEquals(timestamp, actual.getTimestamp());
     }
 
     @Test
     public void getVmStatsTestWithAccumulateNotNull() {
         Mockito.doReturn(Arrays.asList(vmStatsVoMock1)).when(vmStatsDaoMock).findByVmIdOrderByTimestampDesc(Mockito.anyLong());
-        Mockito.doReturn(true).when(accumulateMock).booleanValue();
         Mockito.doReturn(vmStatsEntryMock).when(statsCollector).getLatestOrAccumulatedVmMetricsStats(Mockito.anyList(), Mockito.anyBoolean());
 
-        VmStats result = statsCollector.getVmStats(1L, accumulateMock);
+        VmStats result = statsCollector.getVmStats(1L, false);
 
         Mockito.verify(statsCollector).getLatestOrAccumulatedVmMetricsStats(Mockito.anyList(), booleanCaptor.capture());
         boolean actualArg = booleanCaptor.getValue().booleanValue();
@@ -369,8 +388,8 @@
                 + "\"networkWriteKBs\":1.1,\"diskReadIOs\":3.0,\"diskWriteIOs\":3.1,\"diskReadKBs\":2.0,"
                 + "\"diskWriteKBs\":2.1,\"memoryKBs\":1.0,\"intFreeMemoryKBs\":1.0,"
                 + "\"targetMemoryKBs\":1.0,\"numCPUs\":1,\"entityType\":\"vm\"}";
-        Mockito.doReturn(fakeStatsData1).when(vmStatsVoMock1).getVmStatsData();
-        Mockito.doReturn(fakeStatsData2).when(vmStatsVoMock2).getVmStatsData();
+        Mockito.when(vmStatsVoMock1.getVmStatsData()).thenReturn(fakeStatsData1);
+        Mockito.when(vmStatsVoMock2.getVmStatsData()).thenReturn(fakeStatsData2);
 
         VmStatsEntry result = statsCollector.accumulateVmMetricsStats(new ArrayList<VmStatsVO>(
                 Arrays.asList(vmStatsVoMock1, vmStatsVoMock2)));
@@ -398,6 +417,7 @@
 
         Assert.assertTrue(statsCollector.isDbLocal());
     }
+
     @Test
     public void testIsDbIpv4Local() {
         Properties p = new Properties();
@@ -406,6 +426,7 @@
 
         Assert.assertTrue(statsCollector.isDbLocal());
     }
+
     @Test
     public void testIsDbSymbolicLocal() {
         Properties p = new Properties();
@@ -414,6 +435,7 @@
 
         Assert.assertTrue(statsCollector.isDbLocal());
     }
+
     @Test
     public void testIsDbOnSameIp() {
         Properties p = new Properties();
@@ -423,6 +445,7 @@
 
         Assert.assertTrue(statsCollector.isDbLocal());
     }
+
     @Test
     public void testIsDbNotLocal() {
         Properties p = new Properties();
@@ -435,7 +458,7 @@
 
     private void performPersistVolumeStatsTest(Hypervisor.HypervisorType hypervisorType) {
         Date timestamp = new Date();
-        String vmName= "vm";
+        String vmName = "vm";
         String path = "path";
         long ioReadDiff = 100;
         long ioWriteDiff = 200;
@@ -462,7 +485,7 @@
         }
         List<VolumeStatsVO> persistedStats = new ArrayList<>();
         Mockito.when(volumeStatsDao.persist(Mockito.any(VolumeStatsVO.class))).thenAnswer((Answer<VolumeStatsVO>) invocation -> {
-            VolumeStatsVO statsVO = (VolumeStatsVO)invocation.getArguments()[0];
+            VolumeStatsVO statsVO = (VolumeStatsVO) invocation.getArguments()[0];
             persistedStats.add(statsVO);
             return statsVO;
         });
@@ -489,4 +512,14 @@
     public void testPersistVolumeStatsVmware() {
         performPersistVolumeStatsTest(Hypervisor.HypervisorType.VMware);
     }
+
+    private Map<String, String> convertJsonToOrderedMap(String json) {
+        Map<String, String> jsonMap = new TreeMap<String, String>();
+        String[] keyValuePairs = json.replace("{", "").replace("}","").split(",");
+        for (String pair: keyValuePairs) {
+            String[] keyValue = pair.split(":");
+            jsonMap.put(keyValue[0], keyValue[1]);
+        }
+        return jsonMap;
+    }
 }
diff --git a/server/src/test/java/com/cloud/snapshot/SnapshotDaoTest.java b/server/src/test/java/com/cloud/snapshot/SnapshotDaoTest.java
index 309d8e7..f43201d 100644
--- a/server/src/test/java/com/cloud/snapshot/SnapshotDaoTest.java
+++ b/server/src/test/java/com/cloud/snapshot/SnapshotDaoTest.java
@@ -16,23 +16,20 @@
 // under the License.
 package com.cloud.snapshot;
 
-import java.util.List;
-
-import javax.inject.Inject;
-
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.dao.SnapshotDaoImpl;
+import com.cloud.utils.component.ComponentContext;
+import junit.framework.Assert;
+import junit.framework.TestCase;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
-import com.cloud.storage.Snapshot;
-import com.cloud.storage.SnapshotVO;
-import com.cloud.storage.dao.SnapshotDaoImpl;
-import com.cloud.utils.component.ComponentContext;
-
-import junit.framework.Assert;
-import junit.framework.TestCase;
+import javax.inject.Inject;
+import java.util.List;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = "classpath:/SnapshotDaoTestContext.xml")
diff --git a/server/src/test/java/com/cloud/snapshot/SnapshotDaoTestConfiguration.java b/server/src/test/java/com/cloud/snapshot/SnapshotDaoTestConfiguration.java
index 1d6cf4c..a04fb07 100644
--- a/server/src/test/java/com/cloud/snapshot/SnapshotDaoTestConfiguration.java
+++ b/server/src/test/java/com/cloud/snapshot/SnapshotDaoTestConfiguration.java
@@ -17,18 +17,6 @@
 
 package com.cloud.snapshot;
 
-import java.io.IOException;
-
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.ComponentScan.Filter;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-
-import org.apache.cloudstack.test.utils.SpringUtils;
-
 import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl;
 import com.cloud.dc.dao.ClusterDaoImpl;
 import com.cloud.dc.dao.HostPodDaoImpl;
@@ -40,6 +28,16 @@
 import com.cloud.tags.dao.ResourceTagsDaoImpl;
 import com.cloud.vm.dao.NicDaoImpl;
 import com.cloud.vm.dao.VMInstanceDaoImpl;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+
+import java.io.IOException;
 
 @Configuration
 @ComponentScan(basePackageClasses = {SnapshotDaoImpl.class, ResourceTagsDaoImpl.class, VMInstanceDaoImpl.class, VolumeDaoImpl.class, NicDaoImpl.class, HostDaoImpl.class,
diff --git a/server/src/test/java/com/cloud/storage/ImageStoreDetailsUtilTest.java b/server/src/test/java/com/cloud/storage/ImageStoreDetailsUtilTest.java
index a294bb5..8860ca0 100755
--- a/server/src/test/java/com/cloud/storage/ImageStoreDetailsUtilTest.java
+++ b/server/src/test/java/com/cloud/storage/ImageStoreDetailsUtilTest.java
@@ -16,13 +16,7 @@
 // under the License.
 package com.cloud.storage;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.HashMap;
-import java.util.Map;
-
+import com.cloud.capacity.CapacityManager;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
@@ -31,7 +25,12 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import com.cloud.capacity.CapacityManager;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class ImageStoreDetailsUtilTest {
 
diff --git a/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java b/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
index ecbc950..dbceac9 100644
--- a/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
+++ b/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
@@ -16,13 +16,21 @@
 // under the License.
 package com.cloud.storage;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.exception.ConnectionException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.host.Host;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.dao.VMInstanceDao;
 
 import org.apache.cloudstack.framework.config.ConfigDepot;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.commons.collections.MapUtils;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,13 +40,10 @@
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.agent.api.StoragePoolInfo;
-import com.cloud.dc.DataCenterVO;
-import com.cloud.dc.dao.DataCenterDao;
-import com.cloud.host.Host;
-import com.cloud.storage.dao.VolumeDao;
-import com.cloud.vm.VMInstanceVO;
-import com.cloud.vm.dao.VMInstanceDao;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 @RunWith(MockitoJUnitRunner.class)
 public class StorageManagerImplTest {
@@ -165,6 +170,52 @@
         PrimaryDataStoreDao storagePoolDao = Mockito.mock(PrimaryDataStoreDao.class);
         storageManagerImpl._storagePoolDao = storagePoolDao;
         Assert.assertTrue(storageManagerImpl.storagePoolCompatibleWithVolumePool(storagePool, volume));
+
+    }
+
+    @Test
+    public void testExtractUriParamsAsMapWithSolidFireUrl() {
+        String sfUrl = "MVIP=1.2.3.4;SVIP=6.7.8.9;clusterAdminUsername=admin;" +
+                "clusterAdminPassword=password;clusterDefaultMinIops=1000;" +
+                "clusterDefaultMaxIops=2000;clusterDefaultBurstIopsPercentOfMaxIops=2";
+        Map<String,String> uriParams = storageManagerImpl.extractUriParamsAsMap(sfUrl);
+        Assert.assertTrue(MapUtils.isEmpty(uriParams));
+    }
+
+    @Test
+    public void testExtractUriParamsAsMapWithNFSUrl() {
+        String scheme = "nfs";
+        String host = "HOST";
+        String path = "/PATH";
+        String sfUrl = String.format("%s://%s%s", scheme, host, path);
+        Map<String,String> uriParams = storageManagerImpl.extractUriParamsAsMap(sfUrl);
+        Assert.assertTrue(MapUtils.isNotEmpty(uriParams));
+        Assert.assertEquals(scheme, uriParams.get("scheme"));
+        Assert.assertEquals(host, uriParams.get("host"));
+        Assert.assertEquals(path, uriParams.get("hostPath"));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testCreateLocalStorageHostFailure() {
+        Map<String, Object> test = new HashMap<>();
+        test.put("host", null);
+        try {
+            storageManagerImpl.createLocalStorage(test);
+        } catch (ConnectionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testCreateLocalStoragePathFailure() {
+        Map<String, Object> test = new HashMap<>();
+        test.put("host", "HOST");
+        test.put("hostPath", "");
+        try {
+            storageManagerImpl.createLocalStorage(test);
+        } catch (ConnectionException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     @Test
@@ -195,8 +246,7 @@
                 .thenReturn(new ArrayList<>()); //new installation
         storageManagerImpl.enableDefaultDatastoreDownloadRedirectionForExistingInstallations();
         Mockito.verify(configurationDao, Mockito.never())
-                .update(StorageManager.DataStoreDownloadFollowRedirects.key(),
-                        StorageManager.DataStoreDownloadFollowRedirects.defaultValue());
+                .update(StorageManager.DataStoreDownloadFollowRedirects.key(),StorageManager.DataStoreDownloadFollowRedirects.defaultValue());
     }
 
 }
diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
index cc40824..b017a2d 100644
--- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
+++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
@@ -20,18 +20,19 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
@@ -39,6 +40,7 @@
 
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
@@ -56,6 +58,7 @@
 import org.apache.cloudstack.framework.jobs.AsyncJobManager;
 import org.apache.cloudstack.framework.jobs.dao.AsyncJobJoinMapDao;
 import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
+import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao;
 import org.apache.cloudstack.snapshot.SnapshotHelper;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
@@ -74,12 +77,10 @@
 import org.mockito.InOrder;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import com.cloud.api.query.dao.ServiceOfferingJoinDao;
@@ -118,6 +119,7 @@
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.Pair;
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.fsm.NoTransitionException;
@@ -130,8 +132,7 @@
 import com.cloud.vm.snapshot.VMSnapshotVO;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore("javax.management.*")
+@RunWith(MockitoJUnitRunner.class)
 public class VolumeApiServiceImplTest {
 
     @Spy
@@ -224,6 +225,9 @@
     private SnapshotDao snapshotDaoMock;
 
     @Mock
+    private SnapshotPolicyDetailsDao snapshotPolicyDetailsDao;
+
+    @Mock
     private Project projectMock;
 
     @Mock
@@ -535,7 +539,7 @@
         when(volumeDataFactoryMock.getVolume(anyLong())).thenReturn(volumeInfoMock);
         when(volumeInfoMock.getState()).thenReturn(Volume.State.Allocated);
         lenient().when(volumeInfoMock.getPoolId()).thenReturn(1L);
-        volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null);
+        volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null, null);
     }
 
     @Test
@@ -544,11 +548,11 @@
         when(volumeInfoMock.getState()).thenReturn(Volume.State.Ready);
         when(volumeInfoMock.getInstanceId()).thenReturn(null);
         when(volumeInfoMock.getPoolId()).thenReturn(1L);
-        when(volumeServiceMock.takeSnapshot(Mockito.any(VolumeInfo.class))).thenReturn(snapshotInfoMock);
+        when(volumeServiceMock.takeSnapshot(any(VolumeInfo.class))).thenReturn(snapshotInfoMock);
         final TaggedResourceService taggedResourceService = Mockito.mock(TaggedResourceService.class);
-        Mockito.lenient().when(taggedResourceService.createTags(anyObject(), anyObject(), anyObject(), anyObject())).thenReturn(null);
+        Mockito.lenient().when(taggedResourceService.createTags(any(), any(), any(), any())).thenReturn(null);
         ReflectionTestUtils.setField(volumeApiServiceImpl, "taggedResourceService", taggedResourceService);
-        volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null);
+        volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null, null);
     }
 
     @Test
@@ -596,7 +600,7 @@
     @Test
     public void testAllocSnapshotNonManagedStorageArchive() {
         try {
-            volumeApiServiceImpl.allocSnapshot(6L, 1L, "test", Snapshot.LocationType.SECONDARY);
+            volumeApiServiceImpl.allocSnapshot(6L, 1L, "test", Snapshot.LocationType.SECONDARY, null);
         } catch (InvalidParameterValueException e) {
             Assert.assertEquals(e.getMessage(), "VolumeId: 6 LocationType is supported only for managed storage");
             return;
@@ -649,29 +653,35 @@
 
     @Test
     public void getStoragePoolTagsTestStorageWithoutTags() {
-        Mockito.when(storagePoolTagsDao.getStoragePoolTags(storagePoolMockId)).thenReturn(new ArrayList<>());
-
-        String returnedStoragePoolTags = volumeApiServiceImpl.getStoragePoolTags(storagePoolMock);
+        Pair<List<String>, Boolean> returnedStoragePoolTags = volumeApiServiceImpl.getStoragePoolTags(storagePoolMock);
 
         Assert.assertNull(returnedStoragePoolTags);
     }
 
     @Test
     public void getStoragePoolTagsTestStorageWithTags() {
-        ArrayList<String> tags = new ArrayList<>();
-        String tag1 = "tag1";
-        String tag2 = "tag2";
-        String tag3 = "tag3";
+        StoragePoolTagVO tag1 = new StoragePoolTagVO(1,"tag1", false);
+        StoragePoolTagVO tag2 = new StoragePoolTagVO(1,"tag2", false);
+        StoragePoolTagVO tag3 = new StoragePoolTagVO(1,"tag3", false);
+        List<StoragePoolTagVO> tags = Arrays.asList(tag1, tag2, tag3);
 
-        tags.add(tag1);
-        tags.add(tag2);
-        tags.add(tag3);
+        Mockito.when(storagePoolTagsDao.findStoragePoolTags(storagePoolMockId)).thenReturn(tags);
 
-        Mockito.when(storagePoolTagsDao.getStoragePoolTags(storagePoolMockId)).thenReturn(tags);
+        Pair<List<String>, Boolean> returnedStoragePoolTags = volumeApiServiceImpl.getStoragePoolTags(storagePoolMock);
 
-        String returnedStoragePoolTags = volumeApiServiceImpl.getStoragePoolTags(storagePoolMock);
+        Assert.assertEquals(new Pair<>(Arrays.asList("tag1","tag2","tag3"), false), returnedStoragePoolTags);
+    }
 
-        Assert.assertEquals("tag1,tag2,tag3", returnedStoragePoolTags);
+    @Test
+    public void getStoragePoolTagsTestStorageWithRuleTag() {
+        StoragePoolTagVO tag1 = new StoragePoolTagVO(1,"tag1", true);
+        List<StoragePoolTagVO> tags = List.of(tag1);
+
+        Mockito.when(storagePoolTagsDao.findStoragePoolTags(storagePoolMockId)).thenReturn(tags);
+
+        Pair<List<String>, Boolean> returnedStoragePoolTags = volumeApiServiceImpl.getStoragePoolTags(storagePoolMock);
+
+        Assert.assertEquals(new Pair<>(List.of("tag1"), true), returnedStoragePoolTags);
     }
 
     @Test
@@ -756,7 +766,7 @@
 
         Mockito.when(newDiskOfferingMock.getTags()).thenReturn("tag1");
 
-        Mockito.doReturn("tag1").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.doReturn(new Pair<>(List.of("tag1"), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         volumeApiServiceImpl.validateConditionsToReplaceDiskOfferingOfVolume(volumeVoMock, newDiskOfferingMock, storagePoolMock);
 
@@ -1137,7 +1147,7 @@
         Mockito.doReturn("A,B,C").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.doReturn("A").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.doReturn(new Pair<>(List.of("A"), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1150,7 +1160,7 @@
         Mockito.doReturn("A,B,C").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.doReturn("A,B,C,D,X,Y").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.doReturn(new Pair<>(List.of("A","B","C","D","X","Y"), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1163,7 +1173,7 @@
         Mockito.doReturn("").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.lenient().doReturn("A,B,C,D,X,Y").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.lenient().doReturn(new Pair<>(List.of("A,B,C,D,X,Y"), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1176,7 +1186,7 @@
         Mockito.doReturn("A").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.doReturn("").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.doReturn(new Pair<>(List.of(""), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1189,7 +1199,7 @@
         Mockito.doReturn("").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.lenient().doReturn("").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.lenient().doReturn(new Pair<>(List.of(""), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1202,7 +1212,7 @@
         Mockito.doReturn("A,B").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.doReturn("C,D").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.doReturn(new Pair<>(List.of("C,D"), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1215,7 +1225,7 @@
         Mockito.doReturn("A").when(diskOfferingVoMock).getTags();
 
         StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
-        Mockito.doReturn("A").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+        Mockito.doReturn(new Pair<>(List.of("A"), false)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
 
         boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
 
@@ -1223,6 +1233,45 @@
     }
 
     @Test
+    public void doesTargetStorageSupportDiskOfferingTestStorageRuleTagWithDiskOfferingTagThatMatches() {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("A").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        Mockito.doReturn(new Pair<>(List.of("tags[0] == 'A'"), true)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void doesTargetStorageSupportDiskOfferingTestStorageRuleTagWithDiskOfferingTagThatDoesNotMatch() {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("'").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        Mockito.doReturn(new Pair<>(List.of("tags[0] == 'A'"), true)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void doesTargetStorageSupportDiskOfferingTestStorageRuleTagWithNullDiskOfferingTag() {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn(null).when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        Mockito.doReturn(new Pair<>(List.of("tags[0] == 'A'"), true)).when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = volumeApiServiceImpl.doesTargetStorageSupportDiskOffering(storagePoolMock, diskOfferingVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
     public void validateIfVmHaveBackupsTestExceptionWhenTryToDetachVolumeFromVMWhichBackupOffering() {
         try {
             UserVmVO vm = Mockito.mock(UserVmVO.class);
@@ -1319,11 +1368,11 @@
 
         ServiceOfferingJoinVO serviceOfferingJoinVO = Mockito.mock(ServiceOfferingJoinVO.class);
         when(serviceOfferingJoinVO.getRootDiskSize()).thenReturn(rootDisk);
-        when(serviceOfferingJoinDao.findById(Mockito.anyLong())).thenReturn(serviceOfferingJoinVO);
+        when(serviceOfferingJoinDao.findById(anyLong())).thenReturn(serviceOfferingJoinVO);
 
         VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
         when(template.getFormat()).thenReturn(imageFormat);
-        when(templateDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(template);
+        when(templateDao.findByIdIncludingRemoved(anyLong())).thenReturn(template);
 
         boolean result = volumeApiServiceImpl.isNotPossibleToResize(volume, diskOffering);
         Assert.assertEquals(expectedIsNotPossibleToResize, result);
@@ -1342,27 +1391,27 @@
     }
 
     @Test (expected = PermissionDeniedException.class)
-    @PrepareForTest (CollectionUtils.class)
     public void checkIfVolumeCanBeReassignedTestVolumeWithSnapshots() {
         Mockito.doReturn(null).when(volumeVoMock).getInstanceId();
-        Mockito.doReturn(snapshotVOArrayListMock).when(snapshotDaoMock).listByStatusNotIn(Mockito.anyLong(), Mockito.any(), Mockito.any());
+        Mockito.doReturn(snapshotVOArrayListMock).when(snapshotDaoMock).listByStatusNotIn(anyLong(), any(), any());
 
-        PowerMockito.mockStatic(CollectionUtils.class);
-        PowerMockito.when(CollectionUtils.isNotEmpty(snapshotVOArrayListMock)).thenReturn(true);
+        try (MockedStatic<CollectionUtils> ignored = Mockito.mockStatic(CollectionUtils.class)) {
+            Mockito.when(CollectionUtils.isNotEmpty(snapshotVOArrayListMock)).thenReturn(true);
 
-        volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
+            volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
+        }
     }
 
     @Test
-    @PrepareForTest (CollectionUtils.class)
     public void checkIfVolumeCanBeReassignedTestValidVolume() {
         Mockito.doReturn(null).when(volumeVoMock).getInstanceId();
-        Mockito.doReturn(snapshotVOArrayListMock).when(snapshotDaoMock).listByStatusNotIn(Mockito.anyLong(), Mockito.any(), Mockito.any());
+        Mockito.doReturn(snapshotVOArrayListMock).when(snapshotDaoMock).listByStatusNotIn(anyLong(), any(), any());
 
-        PowerMockito.mockStatic(CollectionUtils.class);
-        PowerMockito.when(CollectionUtils.isNotEmpty(snapshotVOArrayListMock)).thenReturn(false);
+        try (MockedStatic<CollectionUtils> ignored = Mockito.mockStatic(CollectionUtils.class)) {
+            Mockito.when(CollectionUtils.isNotEmpty(snapshotVOArrayListMock)).thenReturn(false);
 
-        volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
+            volumeApiServiceImpl.validateVolume(volumeVoMock.getUuid(), volumeVoMock);
+        }
     }
 
     @Test (expected = InvalidParameterValueException.class)
@@ -1377,13 +1426,11 @@
 
     @Test (expected = InvalidParameterValueException.class)
     public void validateAccountsTestDisabledNewAccount() {
-        Mockito.doReturn(Account.State.DISABLED).when(accountMock).getState();
         volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, null, accountMock);
     }
 
     @Test (expected = InvalidParameterValueException.class)
     public void validateAccountsTestLockedNewAccount() {
-        Mockito.doReturn(Account.State.LOCKED).when(accountMock).getState();
         volumeApiServiceImpl.validateAccounts(accountMock.getUuid(), volumeVoMock, null, accountMock);
     }
 
@@ -1399,35 +1446,33 @@
     }
 
     @Test
-    @PrepareForTest(UsageEventUtils.class)
     public void updateVolumeAccountTest() {
-        PowerMockito.mockStatic(UsageEventUtils.class);
-        Account newAccountMock = new AccountVO(accountMockId+1);
+        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
+            Account newAccountMock = new AccountVO(accountMockId + 1);
 
-        Mockito.doReturn(volumeVoMock).when(volumeDaoMock).persist(volumeVoMock);
+            Mockito.doReturn(volumeVoMock).when(volumeDaoMock).persist(volumeVoMock);
 
-        volumeApiServiceImpl.updateVolumeAccount(accountMock, volumeVoMock, newAccountMock);
+            volumeApiServiceImpl.updateVolumeAccount(accountMock, volumeVoMock, newAccountMock);
 
-        PowerMockito.verifyStatic(UsageEventUtils.class);
-        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(),
-                volumeVoMock.getName(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplayVolume());
+            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(),
+                    volumeVoMock.getName(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplayVolume()));
 
-        Mockito.verify(resourceLimitServiceMock).decrementResourceCount(accountMock.getAccountId(), ResourceType.volume);
-        Mockito.verify(resourceLimitServiceMock).decrementResourceCount(accountMock.getAccountId(), ResourceType.primary_storage, volumeVoMock.getSize());
+            Mockito.verify(resourceLimitServiceMock).decrementResourceCount(accountMock.getAccountId(), ResourceType.volume);
+            Mockito.verify(resourceLimitServiceMock).decrementResourceCount(accountMock.getAccountId(), ResourceType.primary_storage, volumeVoMock.getSize());
 
-        Mockito.verify(volumeVoMock).setAccountId(newAccountMock.getAccountId());
-        Mockito.verify(volumeVoMock).setDomainId(newAccountMock.getDomainId());
+            Mockito.verify(volumeVoMock).setAccountId(newAccountMock.getAccountId());
+            Mockito.verify(volumeVoMock).setDomainId(newAccountMock.getDomainId());
 
-        Mockito.verify(volumeDaoMock).persist(volumeVoMock);
+            Mockito.verify(volumeDaoMock).persist(volumeVoMock);
 
-        Mockito.verify(resourceLimitServiceMock).incrementResourceCount(newAccountMock.getAccountId(), ResourceType.volume);
-        Mockito.verify(resourceLimitServiceMock).incrementResourceCount(newAccountMock.getAccountId(), ResourceType.primary_storage, volumeVoMock.getSize());
+            Mockito.verify(resourceLimitServiceMock).incrementResourceCount(newAccountMock.getAccountId(), ResourceType.volume);
+            Mockito.verify(resourceLimitServiceMock).incrementResourceCount(newAccountMock.getAccountId(), ResourceType.primary_storage, volumeVoMock.getSize());
 
-        PowerMockito.verifyStatic(UsageEventUtils.class);
-        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(),
-                volumeVoMock.getName(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplayVolume());
+            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(),
+                    volumeVoMock.getName(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplayVolume()));
 
-        Mockito.verify(volumeServiceMock).moveVolumeOnSecondaryStorageToAnotherAccount(volumeVoMock, accountMock, newAccountMock);
+            Mockito.verify(volumeServiceMock).moveVolumeOnSecondaryStorageToAnotherAccount(volumeVoMock, accountMock, newAccountMock);
+        }
     }
 
 
@@ -1462,67 +1507,59 @@
     }
 
     @Test
-    @PrepareForTest(UsageEventUtils.class)
     public void publishVolumeCreationUsageEventTestNullDiskOfferingId() {
         Mockito.doReturn(null).when(volumeVoMock).getDiskOfferingId();
-        PowerMockito.mockStatic(UsageEventUtils.class);
+        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
 
-        volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
+            volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
 
-        PowerMockito.verifyStatic(UsageEventUtils.class);
-        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
-                null, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay());
-
+            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
+                    null, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay()));
+        }
     }
 
     @Test
-    @PrepareForTest(UsageEventUtils.class)
     public void publishVolumeCreationUsageEventTestNullDiskOfferingVo() {
         Mockito.doReturn(diskOfferingMockId).when(volumeVoMock).getDiskOfferingId();
         Mockito.doReturn(null).when(_diskOfferingDao).findById(diskOfferingMockId);
-        PowerMockito.mockStatic(UsageEventUtils.class);
+        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
 
-        volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
+            volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
 
-        PowerMockito.verifyStatic(UsageEventUtils.class);
-        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
-                null, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay());
-
+            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
+                    null, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay()));
+        }
     }
 
     @Test
-    @PrepareForTest(UsageEventUtils.class)
     public void publishVolumeCreationUsageEventTestDiskOfferingVoTypeNotDisk() {
         Mockito.doReturn(diskOfferingMockId).when(volumeVoMock).getDiskOfferingId();
         Mockito.doReturn(newDiskOfferingMock).when(_diskOfferingDao).findById(diskOfferingMockId);
         Mockito.doReturn(true).when(newDiskOfferingMock).isComputeOnly();
 
-        PowerMockito.mockStatic(UsageEventUtils.class);
+        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
 
-        volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
+            volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
 
-        PowerMockito.verifyStatic(UsageEventUtils.class);
-        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
-                null, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay());
-
+            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
+                    null, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay()));
+        }
     }
 
     @Test
-    @PrepareForTest(UsageEventUtils.class)
     public void publishVolumeCreationUsageEventTestOfferingIdNotNull() {
         Mockito.doReturn(diskOfferingMockId).when(volumeVoMock).getDiskOfferingId();
         Mockito.doReturn(newDiskOfferingMock).when(_diskOfferingDao).findById(diskOfferingMockId);
         Mockito.doReturn(false).when(newDiskOfferingMock).isComputeOnly();
         Mockito.doReturn(offeringMockId).when(newDiskOfferingMock).getId();
 
-        PowerMockito.mockStatic(UsageEventUtils.class);
+        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
 
-        volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
+            volumeApiServiceImpl.publishVolumeCreationUsageEvent(volumeVoMock);
 
-        PowerMockito.verifyStatic(UsageEventUtils.class);
-        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
-                offeringMockId, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay());
-
+            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volumeVoMock.getAccountId(), volumeVoMock.getDataCenterId(), volumeVoMock.getId(), volumeVoMock.getName(),
+                    offeringMockId, volumeVoMock.getTemplateId(), volumeVoMock.getSize(), Volume.class.getName(), volumeVoMock.getUuid(), volumeVoMock.isDisplay()));
+        }
     }
 
     private void testBaseListOrderedHostsHypervisorVersionInDc(List<String> hwVersions, HypervisorType hypervisorType,
@@ -1602,7 +1639,6 @@
             Mockito.when(migrateVolumeCmd.getStoragePoolId()).thenReturn(2L);
             VolumeVO vol = Mockito.mock(VolumeVO.class);
             Mockito.when(volumeDaoMock.findById(1L)).thenReturn(vol);
-            Mockito.when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
             Mockito.when(vol.getState()).thenReturn(Volume.State.Ready);
             Mockito.when(vol.getPoolId()).thenReturn(1L);
             Mockito.when(vol.getInstanceId()).thenReturn(null);
@@ -1611,17 +1647,12 @@
             Mockito.when(_diskOfferingDao.findById(1L)).thenReturn(diskOffering);
 
             StoragePoolVO srcStoragePoolVOMock = Mockito.mock(StoragePoolVO.class);
-            StoragePool destPool = Mockito.mock(StoragePool.class);
             PrimaryDataStore dataStore = Mockito.mock(PrimaryDataStore.class);
 
             Mockito.when(vol.getPassphraseId()).thenReturn(1L);
             Mockito.when(primaryDataStoreDaoMock.findById(1L)).thenReturn(srcStoragePoolVOMock);
             Mockito.when(srcStoragePoolVOMock.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
             Mockito.when(dataStoreMgr.getDataStore(2L, DataStoreRole.Primary)).thenReturn( dataStore);
-            Mockito.doNothing().when(snapshotHelper).checkKvmVolumeSnapshotsOnlyInPrimaryStorage(vol, HypervisorType.KVM);
-            Mockito.when(destPool.getUuid()).thenReturn("bd525970-3d2a-4230-880d-261892129ef3");
-
-            Mockito.when(storageMgr.storagePoolCompatibleWithVolumePool(destPool, vol)).thenReturn(false);
 
             volumeApiServiceImpl.migrateVolume(migrateVolumeCmd);
         } catch (InvalidParameterValueException e) {
@@ -1630,4 +1661,166 @@
             // test passed
         }
     }
+
+    @Test
+    public void testValidationsForCheckVolumeAPI() {
+        VolumeVO volume = mock(VolumeVO.class);
+
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(1L);
+        UserVmVO vm = mock(UserVmVO.class);
+        when(userVmDaoMock.findById(1L)).thenReturn(vm);
+        when(vm.getState()).thenReturn(State.Stopped);
+        when(volume.getState()).thenReturn(Volume.State.Ready);
+        when(volume.getId()).thenReturn(1L);
+        when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
+        when(volume.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
+
+        volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidationsForCheckVolumeAPIWithRunningVM() {
+        VolumeVO volume = mock(VolumeVO.class);
+
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(1L);
+        UserVmVO vm = mock(UserVmVO.class);
+        when(userVmDaoMock.findById(1L)).thenReturn(vm);
+        when(vm.getState()).thenReturn(State.Running);
+
+        volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidationsForCheckVolumeAPIWithNonexistedVM() {
+        VolumeVO volume = mock(VolumeVO.class);
+
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(1L);
+        when(userVmDaoMock.findById(1L)).thenReturn(null);
+
+        volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidationsForCheckVolumeAPIWithAllocatedVolume() {
+        VolumeVO volume = mock(VolumeVO.class);
+
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(1L);
+        UserVmVO vm = mock(UserVmVO.class);
+        when(userVmDaoMock.findById(1L)).thenReturn(vm);
+        when(vm.getState()).thenReturn(State.Stopped);
+        when(volume.getState()).thenReturn(Volume.State.Allocated);
+
+        volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidationsForCheckVolumeAPIWithNonKVMhypervisor() {
+        VolumeVO volume = mock(VolumeVO.class);
+
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(1L);
+        UserVmVO vm = mock(UserVmVO.class);
+        when(userVmDaoMock.findById(1L)).thenReturn(vm);
+        when(vm.getState()).thenReturn(State.Stopped);
+        when(volume.getState()).thenReturn(Volume.State.Ready);
+        when(volume.getId()).thenReturn(1L);
+        when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.VMware);
+
+        volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
+    }
+
+    @Test
+    public void testCheckAndRepairVolume() throws ResourceAllocationException {
+
+        CheckAndRepairVolumeCmd cmd  = mock(CheckAndRepairVolumeCmd.class);
+        when(cmd.getId()).thenReturn(1L);
+        when(cmd.getRepair()).thenReturn(null);
+
+        VolumeVO volume = mock(VolumeVO.class);
+        when(volumeDaoMock.findById(1L)).thenReturn(volume);
+
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(null);
+        when(volume.getState()).thenReturn(Volume.State.Ready);
+        when(volume.getId()).thenReturn(1L);
+        when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
+
+        VolumeInfo volumeInfo = mock(VolumeInfo.class);
+        when(volumeDataFactoryMock.getVolume(1L)).thenReturn(volumeInfo);
+
+        String checkResult = "{\n" +
+                "    \"image-end-offset\": 6442582016,\n" +
+                "    \"total-clusters\": 163840,\n" +
+                "    \"check-errors\": 0,\n" +
+                "    \"leaks\": 124,\n" +
+                "    \"allocated-clusters\": 98154,\n" +
+                "    \"filename\": \"/var/lib/libvirt/images/26be20c7-b9d0-43f6-a76e-16c70737a0e0\",\n" +
+                "    \"format\": \"qcow2\",\n" +
+                "    \"fragmented-clusters\": 96135\n" +
+                "}";
+
+        String repairResult = null;
+        Pair<String, String> result = new Pair<>(checkResult, repairResult);
+        when(volumeServiceMock.checkAndRepairVolume(volumeInfo)).thenReturn(result);
+        when(volume.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
+
+        Pair<String, String> finalresult = volumeApiServiceImpl.checkAndRepairVolume(cmd);
+
+        Assert.assertEquals(result, finalresult);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidationsForCheckVolumeAPIWithInvalidVolumeFormat() {
+        VolumeVO volume = mock(VolumeVO.class);
+        AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+
+        lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
+
+        when(volume.getInstanceId()).thenReturn(1L);
+        UserVmVO vm = mock(UserVmVO.class);
+        when(userVmDaoMock.findById(1L)).thenReturn(vm);
+        when(vm.getState()).thenReturn(State.Stopped);
+        when(volume.getState()).thenReturn(Volume.State.Ready);
+        when(volume.getId()).thenReturn(1L);
+        when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
+        when(volume.getFormat()).thenReturn(Storage.ImageFormat.RAW);
+
+        volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
+    }
 }
diff --git a/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTest.java b/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTest.java
index cf7cf97..db8c588 100644
--- a/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTest.java
+++ b/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTest.java
@@ -16,18 +16,15 @@
 // under the License.
 package com.cloud.storage.dao;
 
-import javax.inject.Inject;
-
+import com.cloud.storage.StoragePoolStatus;
 import junit.framework.TestCase;
-
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
-
-import com.cloud.storage.StoragePoolStatus;
+import javax.inject.Inject;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = "classpath:/StoragePoolDaoTestContext.xml")
diff --git a/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTestConfiguration.java b/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTestConfiguration.java
index 14d8f06..e02c8fb 100644
--- a/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTestConfiguration.java
+++ b/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTestConfiguration.java
@@ -17,8 +17,8 @@
 
 package com.cloud.storage.dao;
 
-import java.io.IOException;
-
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
+import org.apache.cloudstack.test.utils.SpringUtils;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.ComponentScan.Filter;
 import org.springframework.context.annotation.Configuration;
@@ -27,8 +27,7 @@
 import org.springframework.core.type.classreading.MetadataReaderFactory;
 import org.springframework.core.type.filter.TypeFilter;
 
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
-import org.apache.cloudstack.test.utils.SpringUtils;
+import java.io.IOException;
 
 @Configuration
 @ComponentScan(basePackageClasses = {PrimaryDataStoreDaoImpl.class, StoragePoolDetailsDaoImpl.class},
diff --git a/server/src/test/java/com/cloud/storage/listener/StoragePoolMonitorTest.java b/server/src/test/java/com/cloud/storage/listener/StoragePoolMonitorTest.java
index 838077a..fa6b71d 100644
--- a/server/src/test/java/com/cloud/storage/listener/StoragePoolMonitorTest.java
+++ b/server/src/test/java/com/cloud/storage/listener/StoragePoolMonitorTest.java
@@ -16,16 +16,6 @@
 // under the License.
 package com.cloud.storage.listener;
 
-import static org.mockito.ArgumentMatchers.nullable;
-
-import java.util.Collections;
-
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
 import com.cloud.agent.api.StartupRoutingCommand;
 import com.cloud.exception.StorageUnavailableException;
 import com.cloud.host.HostVO;
@@ -33,6 +23,15 @@
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.StorageManagerImpl;
 import com.cloud.storage.StoragePoolStatus;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.nullable;
 
 public class StoragePoolMonitorTest {
 
@@ -61,7 +60,7 @@
     @Test
     public void testProcessConnectStoragePoolNormal() throws Exception {
         Mockito.when(poolDao.listBy(nullable(Long.class), nullable(Long.class), nullable(Long.class), Mockito.any(ScopeType.class))).thenReturn(Collections.singletonList(pool));
-        Mockito.when(poolDao.findZoneWideStoragePoolsByTags(Mockito.anyLong(), Mockito.any(String[].class))).thenReturn(Collections.<StoragePoolVO>emptyList());
+        Mockito.when(poolDao.findZoneWideStoragePoolsByTags(Mockito.anyLong(), Mockito.any(String[].class), Mockito.anyBoolean())).thenReturn(Collections.<StoragePoolVO>emptyList());
         Mockito.when(poolDao.findZoneWideStoragePoolsByHypervisor(Mockito.anyLong(), Mockito.any(Hypervisor.HypervisorType.class))).thenReturn(Collections.<StoragePoolVO>emptyList());
         Mockito.doReturn(true).when(storageManager).connectHostToSharedPool(host.getId(), pool.getId());
 
@@ -74,7 +73,7 @@
     @Test
     public void testProcessConnectStoragePoolFailureOnHost() throws Exception {
         Mockito.when(poolDao.listBy(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.any(ScopeType.class))).thenReturn(Collections.singletonList(pool));
-        Mockito.when(poolDao.findZoneWideStoragePoolsByTags(Mockito.anyLong(), Mockito.any(String[].class))).thenReturn(Collections.<StoragePoolVO>emptyList());
+        Mockito.when(poolDao.findZoneWideStoragePoolsByTags(Mockito.anyLong(), Mockito.any(String[].class), Mockito.anyBoolean())).thenReturn(Collections.<StoragePoolVO>emptyList());
         Mockito.when(poolDao.findZoneWideStoragePoolsByHypervisor(Mockito.anyLong(), Mockito.any(Hypervisor.HypervisorType.class))).thenReturn(Collections.<StoragePoolVO>emptyList());
         Mockito.doThrow(new StorageUnavailableException("unable to mount storage", 123L)).when(storageManager).connectHostToSharedPool(Mockito.anyLong(), Mockito.anyLong());
 
diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java
new file mode 100644
index 0000000..e6c2a0d
--- /dev/null
+++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java
@@ -0,0 +1,408 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.storage.snapshot;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
+import org.apache.cloudstack.framework.async.AsyncCallFuture;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.org.Grouping;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotZoneDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.ResourceLimitService;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.Pair;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SnapshotManagerImplTest {
+    @Mock
+    AccountDao accountDao;
+    @Mock
+    SnapshotDao snapshotDao;
+    @Mock
+    AccountManager accountManager;
+    @Mock
+    SnapshotService snapshotService;
+    @Mock
+    SnapshotDataFactory snapshotFactory;
+    @Mock
+    ResourceLimitService resourceLimitService;
+    @Mock
+    DataCenterDao dataCenterDao;
+    @Mock
+    SnapshotDataStoreDao snapshotStoreDao;
+    @Mock
+    DataStoreManager dataStoreManager;
+    @Mock
+    SnapshotZoneDao snapshotZoneDao;
+    @Mock
+    VolumeDao volumeDao;
+    @InjectMocks
+    SnapshotManagerImpl snapshotManager = new SnapshotManagerImpl();
+
+    @Test
+    public void testGetSnapshotZoneImageStoreValid() {
+        final long snapshotId = 1L;
+        final long zoneId = 1L;
+        final long storeId = 1L;
+        SnapshotDataStoreVO ref = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref.getDataStoreId()).thenReturn(storeId);
+        Mockito.when(ref.getRole()).thenReturn(DataStoreRole.Image);
+        List<SnapshotDataStoreVO> snapshotStoreList = List.of(Mockito.mock(SnapshotDataStoreVO.class), ref);
+        Mockito.when(dataStoreManager.getStoreZoneId(storeId, DataStoreRole.Image)).thenReturn(zoneId);
+        Mockito.when(dataStoreManager.getDataStore(storeId, DataStoreRole.Image)).thenReturn(Mockito.mock(DataStore.class));
+        Mockito.when(snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image)).thenReturn(snapshotStoreList);
+        DataStore store = snapshotManager.getSnapshotZoneImageStore(snapshotId, zoneId);
+        Assert.assertNotNull(store);
+    }
+
+    @Test
+    public void testGetSnapshotZoneImageStoreNull() {
+        final long snapshotId = 1L;
+        final long zoneId = 1L;
+        final long storeId = 1L;
+        SnapshotDataStoreVO ref = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref.getDataStoreId()).thenReturn(storeId);
+        Mockito.when(ref.getRole()).thenReturn(DataStoreRole.Image);
+        List<SnapshotDataStoreVO> snapshotStoreList = List.of(ref);
+        Mockito.when(dataStoreManager.getStoreZoneId(storeId, DataStoreRole.Image)).thenReturn(100L);
+        Mockito.when(snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image)).thenReturn(snapshotStoreList);
+        DataStore store = snapshotManager.getSnapshotZoneImageStore(snapshotId, zoneId);
+        Assert.assertNull(store);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetStoreRefsAndZonesForSnapshotDeleteException() {
+        final long snapshotId = 1L;
+        final long zoneId = 1L;
+        Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(null);
+        snapshotManager.getStoreRefsAndZonesForSnapshotDelete(snapshotId, zoneId);
+    }
+
+    @Test
+    public void testGetStoreRefsAndZonesForSnapshotDeleteMultiZones() {
+        final long snapshotId = 1L;
+        SnapshotDataStoreVO ref = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref.getDataStoreId()).thenReturn(1L);
+        Mockito.when(ref.getRole()).thenReturn(DataStoreRole.Image);
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(2L);
+        Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
+        List<SnapshotDataStoreVO> snapshotStoreList = List.of(ref, ref1);
+        Mockito.when(snapshotStoreDao.findBySnapshotId(snapshotId)).thenReturn(snapshotStoreList);
+        Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(100L);
+        Mockito.when(dataStoreManager.getStoreZoneId(2L, DataStoreRole.Image)).thenReturn(101L);
+        Pair<List<SnapshotDataStoreVO>, List<Long>> pair = snapshotManager.getStoreRefsAndZonesForSnapshotDelete(snapshotId, null);
+        Assert.assertNotNull(pair.first());
+        Assert.assertNotNull(pair.second());
+        Assert.assertEquals(snapshotStoreList.size(), pair.first().size());
+        Assert.assertEquals(2, pair.second().size());
+    }
+
+    @Test
+    public void testGetStoreRefsAndZonesForSnapshotDeleteSingle() {
+        final long snapshotId = 1L;
+        final long zoneId = 1L;
+        Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(Mockito.mock(DataCenterVO.class));
+        SnapshotDataStoreVO ref = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref.getDataStoreId()).thenReturn(1L);
+        Mockito.when(ref.getRole()).thenReturn(DataStoreRole.Image);
+        SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref1.getDataStoreId()).thenReturn(2L);
+        Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Primary);
+        SnapshotDataStoreVO ref2 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref2.getDataStoreId()).thenReturn(3L);
+        Mockito.when(ref2.getRole()).thenReturn(DataStoreRole.Image);
+        List<SnapshotDataStoreVO> snapshotStoreList = List.of(ref, ref1, ref2);
+        Mockito.when(snapshotStoreDao.findBySnapshotId(snapshotId)).thenReturn(snapshotStoreList);
+        Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(zoneId);
+        Mockito.when(dataStoreManager.getStoreZoneId(2L, DataStoreRole.Primary)).thenReturn(zoneId);
+        Mockito.when(dataStoreManager.getStoreZoneId(3L, DataStoreRole.Image)).thenReturn(2L);
+        Pair<List<SnapshotDataStoreVO>, List<Long>> pair = snapshotManager.getStoreRefsAndZonesForSnapshotDelete(snapshotId, zoneId);
+        Assert.assertNotNull(pair.first());
+        Assert.assertNotNull(pair.second());
+        Assert.assertEquals(snapshotStoreList.size() - 1, pair.first().size());
+        Assert.assertEquals(1, pair.second().size());
+    }
+    @Test
+    public void testValidatePolicyZonesNoZones() {
+        snapshotManager.validatePolicyZones(null, Mockito.mock(VolumeVO.class), Mockito.mock(Account.class));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidatePolicyZonesVolumeEdgeZone() {
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L);
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Edge);
+        Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone);
+        snapshotManager.validatePolicyZones(List.of(1L), volumeVO, Mockito.mock(Account.class));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidatePolicyZonesNullZone() {
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L);
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Core);
+        Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone);
+        Mockito.when(dataCenterDao.findById(2L)).thenReturn(null);
+        snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class));
+    }
+
+    @Test(expected = PermissionDeniedException.class)
+    public void testValidatePolicyZonesDisabledZone() {
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L);
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Core);
+        Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone);
+        DataCenterVO zone1 = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone1.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
+        Mockito.when(dataCenterDao.findById(2L)).thenReturn(zone1);
+        Mockito.when(accountManager.isRootAdmin(Mockito.any())).thenReturn(false);
+        snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class));
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidatePolicyZonesEdgeZone() {
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L);
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Core);
+        Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone);
+        DataCenterVO zone1 = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone1.getType()).thenReturn(DataCenter.Type.Edge);
+        Mockito.when(zone1.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(dataCenterDao.findById(2L)).thenReturn(zone1);
+        snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class));
+    }
+
+    @Test
+    public void testValidatePolicyZonesValidZone() {
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L);
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Core);
+        Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone);
+        DataCenterVO zone1 = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone1.getType()).thenReturn(DataCenter.Type.Core);
+        Mockito.when(zone1.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(dataCenterDao.findById(2L)).thenReturn(zone1);
+        snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class));
+    }
+
+    @Test
+    public void testCopyNewSnapshotToZonesNoZones() {
+        snapshotManager.copyNewSnapshotToZones(1L, 1L, new ArrayList<>());
+    }
+
+    @Test
+    public void testCopyNewSnapshotToZones() {
+        final long snapshotId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getId()).thenReturn(snapshotId);
+        Mockito.when(snapshotVO.getAccountId()).thenReturn(1L);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        final long zoneId = 1L;
+        final long storeId = 1L;
+        final long destZoneId = 2L;
+        DataCenterVO zone = Mockito.mock(DataCenterVO.class);
+        Mockito.when(zone.getId()).thenReturn(destZoneId);
+        Mockito.when(dataCenterDao.findById(destZoneId)).thenReturn(zone);
+        SnapshotDataStoreVO ref = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(ref.getDataStoreId()).thenReturn(storeId);
+        Mockito.when(ref.getRole()).thenReturn(DataStoreRole.Image);
+        List<SnapshotDataStoreVO> snapshotStoreList = List.of(Mockito.mock(SnapshotDataStoreVO.class), ref);
+        Mockito.when(dataStoreManager.getStoreZoneId(storeId, DataStoreRole.Image)).thenReturn(zoneId);
+        DataStore store = Mockito.mock(DataStore.class);
+        Mockito.when(store.getId()).thenReturn(storeId);
+        Mockito.when(dataStoreManager.getDataStore(storeId, DataStoreRole.Image)).thenReturn(store);
+        Mockito.when(snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image)).thenReturn(snapshotStoreList);
+        Mockito.when(snapshotFactory.getSnapshot(Mockito.anyLong(), Mockito.any())).thenReturn(Mockito.mock(SnapshotInfo.class));
+        CreateCmdResult result = Mockito.mock(CreateCmdResult.class);
+        Mockito.when(result.isFailed()).thenReturn(false);
+        Mockito.when(result.getPath()).thenReturn("SOMEPATH");
+        AsyncCallFuture<CreateCmdResult> future = Mockito.mock(AsyncCallFuture.class);
+        Mockito.when(dataStoreManager.getImageStoresByScopeExcludingReadOnly(Mockito.any())).thenReturn(List.of(Mockito.mock(DataStore.class)));
+        Mockito.when(dataStoreManager.getImageStoreWithFreeCapacity(Mockito.anyList())).thenReturn(Mockito.mock(DataStore.class));
+        Mockito.when(snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, 1L, 1L)).thenReturn(Mockito.mock(SnapshotDataStoreVO.class));
+        AccountVO account = Mockito.mock(AccountVO.class);
+        Mockito.when(account.getId()).thenReturn(1L);
+        Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(account);
+        SnapshotResult result1 = Mockito.mock(SnapshotResult.class);
+        Mockito.when(result1.isFailed()).thenReturn(false);
+        AsyncCallFuture<SnapshotResult> future1 = Mockito.mock(AsyncCallFuture.class);
+        try {
+            Mockito.doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(), Mockito.any(), Mockito.anyLong());
+            Mockito.when(future.get()).thenReturn(result);
+            Mockito.when(snapshotService.queryCopySnapshot(Mockito.any())).thenReturn(future);
+            Mockito.when(future1.get()).thenReturn(result1);
+            Mockito.when(snapshotService.copySnapshot(Mockito.any(SnapshotInfo.class), Mockito.anyString(), Mockito.any(DataStore.class))).thenReturn(future1);
+        } catch (ResourceAllocationException | ResourceUnavailableException | ExecutionException | InterruptedException e) {
+            Assert.fail(e.getMessage());
+        }
+        List<Long> addedZone = new ArrayList<>();
+        Mockito.doAnswer((Answer<Void>) invocation -> {
+            Long zoneId1 = (Long) invocation.getArguments()[1];
+            addedZone.add(zoneId1);
+            return null;
+        }).when(snapshotZoneDao).addSnapshotToZone(Mockito.anyLong(), Mockito.anyLong());
+        try (MockedStatic<ActionEventUtils> utilities = Mockito.mockStatic(ActionEventUtils.class)) {
+            utilities.when(() -> ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
+                    Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyLong())).thenReturn(1L);
+            snapshotManager.copyNewSnapshotToZones(snapshotId, 1L, List.of(2L));
+            Assert.assertEquals(1, addedZone.size());
+        }
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetCheckedSnapshotForCopyNoSnapshot() {
+        snapshotManager.getCheckedSnapshotForCopy(1L, List.of(100L), null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetCheckedSnapshotForCopyNoSnapshotBackup() {
+        final long snapshotId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L), null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetCheckedSnapshotForCopyNotOnSecondary() {
+        final long snapshotId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp);
+        Mockito.when(snapshotVO.getLocationType()).thenReturn(Snapshot.LocationType.PRIMARY);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L), null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetCheckedSnapshotForCopyDestNotSpecified() {
+        final long snapshotId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        snapshotManager.getCheckedSnapshotForCopy(snapshotId, new ArrayList<>(), null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetCheckedSnapshotForCopyDestContainsSource() {
+        final long snapshotId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp);
+        Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(VolumeVO.class));
+        snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 1L), 1L);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGetCheckedSnapshotForCopyNullSourceZone() {
+        final long snapshotId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp);
+        Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L);
+        Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(volumeVO);
+        snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 101L), null);
+    }
+
+    @Test
+    public void testGetCheckedSnapshotForCopyValid() {
+        final long snapshotId = 1L;
+        final Long zoneId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp);
+        Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(zoneId);
+        Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(volumeVO);
+        Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(Mockito.mock(DataCenterVO.class));
+        Pair<SnapshotVO, Long> result = snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 101L), null);
+        Assert.assertNotNull(result.first());
+        Assert.assertEquals(zoneId, result.second());
+    }
+
+    @Test
+    public void testGetCheckedSnapshotForCopyNullDest() {
+        final long snapshotId = 1L;
+        final Long zoneId = 1L;
+        SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp);
+        Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L);
+        Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        Mockito.when(volumeVO.getDataCenterId()).thenReturn(zoneId);
+        Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(volumeVO);
+        Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(Mockito.mock(DataCenterVO.class));
+        Pair<SnapshotVO, Long> result = snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 101L), null);
+        Assert.assertNotNull(result.first());
+        Assert.assertEquals(zoneId, result.second());
+    }
+
+    @Test
+    public void testGetCheckedDestinationZoneForSnapshotCopy() {
+        long zoneId = 1L;
+        DataCenterVO dataCenterVO = Mockito.mock(DataCenterVO.class);
+        Mockito.when(dataCenterVO.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(dataCenterVO.getType()).thenReturn(DataCenter.Type.Core);
+        Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(dataCenterVO);
+        Assert.assertNotNull(snapshotManager.getCheckedDestinationZoneForSnapshotCopy(zoneId, false));
+    }
+}
diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java
index 8f623d5..fb7319b 100755
--- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java
+++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java
@@ -25,14 +25,13 @@
 import static org.mockito.Mockito.when;
 
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
@@ -56,14 +55,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.BDDMockito;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.mockito.verification.VerificationMode;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
 
 import com.cloud.configuration.Resource.ResourceType;
 import com.cloud.dc.DataCenter;
@@ -74,13 +71,13 @@
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.resource.ResourceManager;
+import com.cloud.server.ResourceTag;
 import com.cloud.server.TaggedResourceService;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.ScopeType;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotPolicyVO;
 import com.cloud.storage.SnapshotVO;
-import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.SnapshotDao;
@@ -103,10 +100,10 @@
 import com.cloud.vm.snapshot.VMSnapshotVO;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(GlobalLock.class)
+@RunWith(MockitoJUnitRunner.class)
 public class SnapshotManagerTest {
-    @Spy
+
+    @InjectMocks
     SnapshotManagerImpl _snapshotMgr = new SnapshotManagerImpl();
     @Mock
     SnapshotDao _snapshotDao;
@@ -197,47 +194,27 @@
 
     @Before
     public void setup() throws ResourceAllocationException {
-        MockitoAnnotations.initMocks(this);
-        _snapshotMgr._snapshotDao = _snapshotDao;
-        _snapshotMgr._volsDao = _volumeDao;
-        _snapshotMgr._vmDao = _vmDao;
-        _snapshotMgr.volFactory = volumeFactory;
-        _snapshotMgr.snapshotFactory = snapshotFactory;
-        _snapshotMgr._storageStrategyFactory = _storageStrategyFactory;
-        _snapshotMgr._accountMgr = _accountMgr;
-        _snapshotMgr._resourceLimitMgr = _resourceLimitMgr;
-        _snapshotMgr._storagePoolDao = _storagePoolDao;
-        _snapshotMgr._resourceMgr = _resourceMgr;
-        _snapshotMgr._vmSnapshotDao = _vmSnapshotDao;
-        _snapshotMgr._snapshotStoreDao = snapshotStoreDao;
-        _snapshotMgr.snapshotHelper = snapshotHelperMock;
-        _snapshotMgr._snapshotPolicyDao = snapshotPolicyDaoMock;
-        _snapshotMgr._snapSchedMgr = snapshotSchedulerMock;
-        _snapshotMgr.taggedResourceService = taggedResourceServiceMock;
-        _snapshotMgr.dataCenterDao = dataCenterDao;
 
         when(_snapshotDao.findById(anyLong())).thenReturn(snapshotMock);
         when(snapshotMock.getVolumeId()).thenReturn(TEST_VOLUME_ID);
-        when(snapshotMock.isRecursive()).thenReturn(false);
 
         when(_volumeDao.findById(anyLong())).thenReturn(volumeMock);
         when(volumeMock.getState()).thenReturn(Volume.State.Ready);
+        when(volumeMock.getId()).thenReturn(TEST_VOLUME_ID);
         when(volumeFactory.getVolume(anyLong())).thenReturn(volumeInfoMock);
         when(volumeInfoMock.getDataStore()).thenReturn(storeMock);
         when(volumeInfoMock.getState()).thenReturn(Volume.State.Ready);
         when(storeMock.getId()).thenReturn(TEST_STORAGE_POOL_ID);
 
-        when(snapshotFactory.getSnapshot(anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(snapshotInfoMock);
+        when(snapshotFactory.getSnapshotWithRoleAndZone(anyLong(), Mockito.any(DataStoreRole.class), Mockito.anyLong())).thenReturn(snapshotInfoMock);
         when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.BACKUP))).thenReturn(snapshotStrategy);
         when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.REVERT))).thenReturn(snapshotStrategy);
-        when(_storageStrategyFactory.getSnapshotStrategy(Mockito.any(SnapshotVO.class), Mockito.eq(SnapshotOperation.DELETE))).thenReturn(snapshotStrategy);
 
-        doNothing().when(_accountMgr).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
-        doNothing().when(_snapshotMgr._resourceLimitMgr).checkResourceLimit(any(Account.class), any(ResourceType.class));
-        doNothing().when(_snapshotMgr._resourceLimitMgr).checkResourceLimit(any(Account.class), any(ResourceType.class), anyLong());
-        doNothing().when(_snapshotMgr._resourceLimitMgr).decrementResourceCount(anyLong(), any(ResourceType.class), anyLong());
-        doNothing().when(_snapshotMgr._resourceLimitMgr).incrementResourceCount(anyLong(), any(ResourceType.class));
-        doNothing().when(_snapshotMgr._resourceLimitMgr).incrementResourceCount(anyLong(), any(ResourceType.class), anyLong());
+        doNothing().when(_resourceLimitMgr).checkResourceLimit(any(Account.class), any(ResourceType.class));
+        doNothing().when(_resourceLimitMgr).checkResourceLimit(any(Account.class), any(ResourceType.class), anyLong());
+        doNothing().when(_resourceLimitMgr).decrementResourceCount(anyLong(), any(ResourceType.class), anyLong());
+        doNothing().when(_resourceLimitMgr).incrementResourceCount(anyLong(), any(ResourceType.class));
+        doNothing().when(_resourceLimitMgr).incrementResourceCount(anyLong(), any(ResourceType.class), anyLong());
 
         Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
         UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
@@ -254,7 +231,7 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         CallContext.unregister();
     }
 
@@ -271,7 +248,6 @@
     @Test(expected = InvalidParameterValueException.class)
     public void testAllocSnapshotF2() throws ResourceAllocationException {
         when(_vmDao.findById(anyLong())).thenReturn(vmMock);
-        when(vmMock.getId()).thenReturn(TEST_VM_ID);
         when(vmMock.getState()).thenReturn(State.Stopped);
         when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
         when(volumeInfoMock.getInstanceId()).thenReturn(TEST_VM_ID);
@@ -319,13 +295,10 @@
     }
 
     @Test(expected = InvalidParameterValueException.class)
-    public void testDeleteSnapshotF1() {
-        when(snapshotStrategy.deleteSnapshot(TEST_SNAPSHOT_ID)).thenReturn(true);
+    public void testDeleteSnapshotDestroyedFailure() {
+        when(_snapshotDao.findById(TEST_SNAPSHOT_ID)).thenReturn(snapshotMock);
         when(snapshotMock.getState()).thenReturn(Snapshot.State.Destroyed);
-        when(snapshotMock.getAccountId()).thenReturn(2L);
-        when(snapshotMock.getDataCenterId()).thenReturn(2L);
-
-        _snapshotMgr.deleteSnapshot(TEST_SNAPSHOT_ID);
+        _snapshotMgr.deleteSnapshot(TEST_SNAPSHOT_ID, null);
     }
 
     // vm state not stopped
@@ -342,9 +315,7 @@
     public void testRevertSnapshotF2() {
         when(_vmDao.findById(anyLong())).thenReturn(vmMock);
         when(vmMock.getState()).thenReturn(State.Stopped);
-        when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer);
-        when(volumeMock.getFormat()).thenReturn(ImageFormat.VHD);
-        doReturn(DataStoreRole.Image).when(snapshotHelperMock).getDataStoreRole(Mockito.any());
+        doReturn(DataStoreRole.Image).when(snapshotHelperMock).getDataStoreRole(any());
         Snapshot snapshot = _snapshotMgr.revertSnapshot(TEST_SNAPSHOT_ID);
         Assert.assertNull(snapshot);
     }
@@ -354,11 +325,9 @@
     public void testRevertSnapshotF3() {
         when(_vmDao.findById(anyLong())).thenReturn(vmMock);
         when(vmMock.getState()).thenReturn(State.Stopped);
-        when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
-        when(volumeMock.getFormat()).thenReturn(ImageFormat.QCOW2);
-        when (snapshotStrategy.revertSnapshot(Mockito.any(SnapshotInfo.class))).thenReturn(true);
+        when (snapshotStrategy.revertSnapshot(any(SnapshotInfo.class))).thenReturn(true);
         when(_volumeDao.update(anyLong(), any(VolumeVO.class))).thenReturn(true);
-        doReturn(DataStoreRole.Image).when(snapshotHelperMock).getDataStoreRole(Mockito.any());
+        doReturn(DataStoreRole.Image).when(snapshotHelperMock).getDataStoreRole(any());
         Snapshot snapshot = _snapshotMgr.revertSnapshot(TEST_SNAPSHOT_ID);
         Assert.assertNotNull(snapshot);
     }
@@ -378,7 +347,6 @@
         when(_vmDao.findById(nullable(Long.class))).thenReturn(vmMock);
         when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
         when(_vmSnapshotDao.findById(nullable(Long.class))).thenReturn(vmSnapshotMock);
-        when(snapshotStoreDao.findParent(any(DataStoreRole.class), nullable(Long.class), nullable(Long.class))).thenReturn(null);
         when(snapshotFactory.getSnapshot(nullable(Long.class), nullable(DataStore.class))).thenReturn(snapshotInfoMock);
         when(storeMock.create(snapshotInfoMock)).thenReturn(snapshotInfoMock);
         when(snapshotStoreDao.findByStoreSnapshot(nullable(DataStoreRole.class), nullable(Long.class), nullable(Long.class))).thenReturn(snapshotStoreMock);
@@ -397,9 +365,7 @@
         when(_vmDao.findById(nullable(Long.class))).thenReturn(vmMock);
         when(vmMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
         when(_vmSnapshotDao.findById(nullable(Long.class))).thenReturn(vmSnapshotMock);
-        when(snapshotStoreDao.findParent(any(DataStoreRole.class), nullable(Long.class), nullable(Long.class))).thenReturn(snapshotStoreMock);
         when(snapshotStoreDao.findByStoreSnapshot(nullable(DataStoreRole.class), nullable(Long.class), nullable(Long.class))).thenReturn(snapshotStoreMock);
-        when(snapshotStoreMock.getInstallPath()).thenReturn("VM_SNAPSHOT_NAME");
         when(vmSnapshotMock.getName()).thenReturn("VM_SNAPSHOT_NAME");
         Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID);
         Assert.assertNull(snapshot);
@@ -407,22 +373,26 @@
 
     @Test(expected = CloudRuntimeException.class)
     public void testArchiveSnapshotSnapshotNotOnPrimary() {
-        when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(null);
+        when(snapshotFactory.getSnapshotOnPrimaryStore(anyLong())).thenReturn(null);
         _snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID);
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void testArchiveSnapshotSnapshotNotReady() {
-        when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(snapshotInfoMock);
+        when(snapshotFactory.getSnapshotOnPrimaryStore(anyLong())).thenReturn(snapshotInfoMock);
         when(snapshotInfoMock.getStatus()).thenReturn(ObjectInDataStoreStateMachine.State.Destroyed);
         _snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID);
     }
 
-    public void assertSnapshotPolicyResultAgainstPreBuiltInstance(SnapshotPolicyVO snapshotPolicyVo){
+    public void assertSnapshotPolicyResultAgainstPreBuiltInstance(SnapshotPolicyVO snapshotPolicyVo, Short interval){
         Assert.assertEquals(snapshotPolicyVoInstance.getVolumeId(), snapshotPolicyVo.getVolumeId());
         Assert.assertEquals(snapshotPolicyVoInstance.getSchedule(), snapshotPolicyVo.getSchedule());
         Assert.assertEquals(snapshotPolicyVoInstance.getTimezone(), snapshotPolicyVo.getTimezone());
-        Assert.assertEquals(snapshotPolicyVoInstance.getInterval(), snapshotPolicyVo.getInterval());
+        if (interval != null) {
+            Assert.assertEquals(interval.shortValue(), snapshotPolicyVo.getInterval());
+        } else {
+            Assert.assertEquals(snapshotPolicyVoInstance.getInterval(), snapshotPolicyVo.getInterval());
+        }
         Assert.assertEquals(snapshotPolicyVoInstance.getMaxSnaps(), snapshotPolicyVo.getMaxSnaps());
         Assert.assertEquals(snapshotPolicyVoInstance.isDisplay(), snapshotPolicyVo.isDisplay());
         Assert.assertEquals(snapshotPolicyVoInstance.isActive(), snapshotPolicyVo.isActive());
@@ -430,116 +400,104 @@
 
     @Test
     public void validateCreateSnapshotPolicy(){
-        Mockito.doReturn(snapshotPolicyVoInstance).when(snapshotPolicyDaoMock).persist(Mockito.any());
-        Mockito.doReturn(null).when(snapshotSchedulerMock).scheduleNextSnapshotJob(Mockito.any());
+        Mockito.doReturn(snapshotPolicyVoInstance).when(snapshotPolicyDaoMock).persist(any());
+        Mockito.doReturn(null).when(snapshotSchedulerMock).scheduleNextSnapshotJob(any());
 
         SnapshotPolicyVO result = _snapshotMgr.createSnapshotPolicy(TEST_VOLUME_ID, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL,
-          TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY);
+          TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, null);
 
-        assertSnapshotPolicyResultAgainstPreBuiltInstance(result);
+        assertSnapshotPolicyResultAgainstPreBuiltInstance(result, null);
     }
 
     @Test
     public void validateUpdateSnapshotPolicy(){
-        Mockito.doReturn(true).when(snapshotPolicyDaoMock).update(Mockito.anyLong(), Mockito.any());
-        Mockito.doNothing().when(snapshotSchedulerMock).scheduleOrCancelNextSnapshotJobOnDisplayChange(Mockito.any(), Mockito.anyBoolean());
-        Mockito.doReturn(true).when(taggedResourceServiceMock).deleteTags(Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.doReturn(true).when(snapshotPolicyDaoMock).update(anyLong(), any());
+        Mockito.doNothing().when(snapshotSchedulerMock).scheduleOrCancelNextSnapshotJobOnDisplayChange(any(), Mockito.anyBoolean());
+        Mockito.doReturn(true).when(taggedResourceServiceMock).deleteTags(any(), any(), any());
 
         SnapshotPolicyVO snapshotPolicyVo = new SnapshotPolicyVO(TEST_VOLUME_ID, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL,
           TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY);
 
         _snapshotMgr.updateSnapshotPolicy(snapshotPolicyVo, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE,
-          TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE);
+          TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null);
 
-        assertSnapshotPolicyResultAgainstPreBuiltInstance(snapshotPolicyVo);
+        assertSnapshotPolicyResultAgainstPreBuiltInstance(snapshotPolicyVo, null);
     }
 
     @Test
     public void validateCreateTagsForSnapshotPolicyWithNullTags(){
         _snapshotMgr.createTagsForSnapshotPolicy(null, snapshotPolicyVoMock);
-        Mockito.verify(taggedResourceServiceMock, Mockito.never()).createTags(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.verify(taggedResourceServiceMock, Mockito.never()).createTags(any(), any(), any(), any());
     }
 
     @Test
     public void validateCreateTagsForSnapshotPolicyWithEmptyTags(){
         _snapshotMgr.createTagsForSnapshotPolicy(new HashMap<>(), snapshotPolicyVoMock);
-        Mockito.verify(taggedResourceServiceMock, Mockito.never()).createTags(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.verify(taggedResourceServiceMock, Mockito.never()).createTags(any(), any(), any(), any());
     }
 
     @Test
     public void validateCreateTagsForSnapshotPolicyWithValidTags(){
-        Mockito.doReturn(null).when(taggedResourceServiceMock).createTags(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.doReturn(null).when(taggedResourceServiceMock).createTags(any(), any(), any(), any());
 
         Map map = new HashMap<>();
         map.put("test", "test");
 
         _snapshotMgr.createTagsForSnapshotPolicy(map, snapshotPolicyVoMock);
-        Mockito.verify(taggedResourceServiceMock, Mockito.times(1)).createTags(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+        Mockito.verify(taggedResourceServiceMock, Mockito.times(1)).createTags(any(), any(), any(), any());
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void validatePersistSnapshotPolicyLockIsNotAquiredMustThrowException() {
-        PowerMockito.mockStatic(GlobalLock.class);
-        BDDMockito.given(GlobalLock.getInternLock(Mockito.anyString())).willReturn(globalLockMock);
-        Mockito.doReturn(false).when(globalLockMock).lock(Mockito.anyInt());
+        try (MockedStatic<GlobalLock> ignored = Mockito.mockStatic(GlobalLock.class)) {
+            BDDMockito.given(GlobalLock.getInternLock(Mockito.anyString())).willReturn(globalLockMock);
+            Mockito.doReturn(false).when(globalLockMock).lock(Mockito.anyInt());
 
-        _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS,
-                TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, mapStringStringMock);
+            _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS,
+                    TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, mapStringStringMock, null);
+        }
+    }
+
+    private void testPersistSnapshotPolicyLockAcquired(boolean forUpdate) {
+        try (MockedStatic<GlobalLock> ignored = Mockito.mockStatic(GlobalLock.class)) {
+
+            BDDMockito.given(GlobalLock.getInternLock(Mockito.anyString())).willReturn(globalLockMock);
+            Mockito.doReturn(true).when(globalLockMock).lock(Mockito.anyInt());
+            List<SnapshotPolicyVO> persistedPolicies = new ArrayList<>();
+            List<SnapshotPolicyVO> updatedPolicies = new ArrayList<>();
+            Mockito.when(snapshotPolicyDaoMock.persist(Mockito.any(SnapshotPolicyVO.class))).thenAnswer((Answer<SnapshotPolicyVO>) invocation -> {
+                SnapshotPolicyVO policy = (SnapshotPolicyVO)invocation.getArguments()[0];
+                persistedPolicies.add(policy);
+                return policy;
+            });
+            Mockito.when(snapshotPolicyDaoMock.update(Mockito.anyLong(), Mockito.any(SnapshotPolicyVO.class))).thenAnswer((Answer<Boolean>) invocation -> {
+                SnapshotPolicyVO policy = (SnapshotPolicyVO)invocation.getArguments()[1];
+                updatedPolicies.add(policy);
+                return true;
+            });
+
+            for (IntervalType intervalType : listIntervalTypes) {
+                Mockito.doReturn(forUpdate ? snapshotPolicyVoInstance : null).when(snapshotPolicyDaoMock).findOneByVolumeInterval(Mockito.anyLong(), Mockito.eq(intervalType));
+                SnapshotPolicyVO result = _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, intervalType,
+                        TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null, null);
+
+                assertSnapshotPolicyResultAgainstPreBuiltInstance(result, (short)intervalType.ordinal());
+            }
+
+            Assert.assertEquals(forUpdate ? 0 : listIntervalTypes.size(), persistedPolicies.size());
+            Assert.assertEquals(forUpdate ? listIntervalTypes.size() : 0, updatedPolicies.size());
+            Mockito.verify(taggedResourceServiceMock, Mockito.never()).createTags(Mockito.anyList(), Mockito.any(ResourceTag.ResourceObjectType.class), Mockito.anyMap(), Mockito.anyString());
+        }
     }
 
     @Test
-    public void validatePersistSnapshotPolicyLockAquiredCreateSnapshotPolicy() {
-        PowerMockito.mockStatic(GlobalLock.class);
-
-        BDDMockito.given(GlobalLock.getInternLock(Mockito.anyString())).willReturn(globalLockMock);
-        Mockito.doReturn(true).when(globalLockMock).lock(Mockito.anyInt());
-
-        for (IntervalType intervalType : listIntervalTypes) {
-
-            Mockito.doReturn(null).when(snapshotPolicyDaoMock).findOneByVolumeInterval(Mockito.anyLong(), Mockito.eq(intervalType));
-            Mockito.doReturn(snapshotPolicyVoInstance).when(_snapshotMgr).createSnapshotPolicy(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.eq(intervalType),
-              Mockito.anyInt(), Mockito.anyBoolean());
-            Mockito.doNothing().when(_snapshotMgr).createTagsForSnapshotPolicy(mapStringStringMock, snapshotPolicyVoMock);
-
-            SnapshotPolicyVO result = _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, intervalType,
-              TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null);
-
-            assertSnapshotPolicyResultAgainstPreBuiltInstance(result);
-        }
-
-        VerificationMode timesVerification = Mockito.times(listIntervalTypes.size());
-        Mockito.verify(_snapshotMgr, timesVerification).createSnapshotPolicy(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.any(DateUtil.IntervalType.class),
-            Mockito.anyInt(), Mockito.anyBoolean());
-        Mockito.verify(_snapshotMgr, Mockito.never()).updateSnapshotPolicy(Mockito.any(SnapshotPolicyVO.class), Mockito.anyString(), Mockito.anyString(),
-          Mockito.any(DateUtil.IntervalType.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyBoolean());
-        Mockito.verify(_snapshotMgr, timesVerification).createTagsForSnapshotPolicy(Mockito.any(), Mockito.any());
+    public void validatePersistSnapshotPolicyLockAcquiredCreateSnapshotPolicy() {
+        testPersistSnapshotPolicyLockAcquired(false);
     }
 
     @Test
-    public void validatePersistSnapshotPolicyLockAquiredUpdateSnapshotPolicy() {
-        PowerMockito.mockStatic(GlobalLock.class);
-
-        BDDMockito.given(GlobalLock.getInternLock(Mockito.anyString())).willReturn(globalLockMock);
-        Mockito.doReturn(true).when(globalLockMock).lock(Mockito.anyInt());
-
-        for (IntervalType intervalType : listIntervalTypes) {
-            Mockito.doReturn(snapshotPolicyVoInstance).when(snapshotPolicyDaoMock).findOneByVolumeInterval(Mockito.anyLong(), Mockito.eq(intervalType));
-            Mockito.doNothing().when(_snapshotMgr).updateSnapshotPolicy(Mockito.any(SnapshotPolicyVO.class), Mockito.anyString(), Mockito.anyString(),
-              Mockito.any(DateUtil.IntervalType.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyBoolean());
-            Mockito.doNothing().when(_snapshotMgr).createTagsForSnapshotPolicy(mapStringStringMock, snapshotPolicyVoMock);
-
-            SnapshotPolicyVO result = _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, intervalType,
-              TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null);
-
-            assertSnapshotPolicyResultAgainstPreBuiltInstance(result);
-        }
-
-        VerificationMode timesVerification = Mockito.times(listIntervalTypes.size());
-        Mockito.verify(_snapshotMgr, Mockito.never()).createSnapshotPolicy(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.any(DateUtil.IntervalType.class),
-            Mockito.anyInt(), Mockito.anyBoolean());
-        Mockito.verify(_snapshotMgr, timesVerification).updateSnapshotPolicy(Mockito.any(SnapshotPolicyVO.class), Mockito.anyString(), Mockito.anyString(),
-          Mockito.any(DateUtil.IntervalType.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyBoolean());
-        Mockito.verify(_snapshotMgr, timesVerification).createTagsForSnapshotPolicy(Mockito.any(), Mockito.any());
+    public void validatePersistSnapshotPolicyLockAcquiredUpdateSnapshotPolicy() {
+        testPersistSnapshotPolicyLockAcquired(true);
     }
 
     private void mockForBackupSnapshotToSecondaryZoneTest(final Boolean configValue, final DataCenter.Type dcType) {
diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java
new file mode 100644
index 0000000..971af28
--- /dev/null
+++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java
@@ -0,0 +1,218 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.storage.snapshot;
+
+import com.cloud.storage.Snapshot;
+import com.cloud.storage.SnapshotPolicyVO;
+import com.cloud.storage.SnapshotScheduleVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotPolicyDao;
+import com.cloud.storage.dao.SnapshotScheduleDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountVO;
+import com.cloud.user.dao.AccountDao;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Date;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SnapshotSchedulerImplTest {
+
+    @Spy
+    @InjectMocks
+    SnapshotSchedulerImpl snapshotSchedulerImplSpy = new SnapshotSchedulerImpl();
+
+    @Mock
+    SnapshotPolicyDao snapshotPolicyDaoMock;
+
+    @Mock
+    SnapshotPolicyVO snapshotPolicyVoMock;
+
+    @Mock
+    SnapshotScheduleDao snapshotScheduleDaoMock;
+
+    @Mock
+    AccountDao accountDaoMock;
+
+    @Mock
+    VolumeDao volumeDaoMock;
+
+    @Mock
+    VolumeVO volumeVoMock;
+
+    @Mock
+    AccountVO accountVoMock;
+
+    @Test
+    public void scheduleNextSnapshotJobTestParameterIsNullReturnNull() {
+        SnapshotScheduleVO snapshotScheduleVO = null;
+
+        Date result = snapshotSchedulerImplSpy.scheduleNextSnapshotJob(snapshotScheduleVO);
+
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void scheduleNextSnapshotJobTestIsManualPolicyIdReturnNull() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+        snapshotScheduleVO.setPolicyId(Snapshot.MANUAL_POLICY_ID);
+
+        Date result = snapshotSchedulerImplSpy.scheduleNextSnapshotJob(snapshotScheduleVO);
+
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void scheduleNextSnapshotJobTestPolicyIsNotNullDoNotCallExpunge() {
+        Date expected = new Date();
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+        snapshotScheduleVO.setPolicyId(1l);
+
+        Mockito.doReturn(snapshotPolicyVoMock).when(snapshotPolicyDaoMock).findById(Mockito.anyLong());
+        Mockito.doReturn(expected).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotPolicyVO.class));
+
+        Date result = snapshotSchedulerImplSpy.scheduleNextSnapshotJob(snapshotScheduleVO);
+        Assert.assertEquals(expected, result);
+
+        Mockito.verify(snapshotScheduleDaoMock, Mockito.never()).expunge(Mockito.anyLong());
+    }
+
+    @Test
+    public void scheduleNextSnapshotJobTestPolicyIsNullCallExpunge() {
+        Date expected = new Date();
+        SnapshotPolicyVO snapshotPolicyVO = null;
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+        snapshotScheduleVO.setPolicyId(1l);
+
+        Mockito.doReturn(snapshotPolicyVO).when(snapshotPolicyDaoMock).findById(Mockito.anyLong());
+        Mockito.doReturn(true).when(snapshotScheduleDaoMock).expunge(Mockito.anyLong());
+        Mockito.doReturn(expected).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(snapshotPolicyVO);
+
+        Date result = snapshotSchedulerImplSpy.scheduleNextSnapshotJob(snapshotScheduleVO);
+        Assert.assertEquals(expected, result);
+
+        Mockito.verify(snapshotScheduleDaoMock).expunge(Mockito.anyLong());
+    }
+
+    @Test
+    public void isAccountRemovedOrDisabledTestVolumeAccountIsNullReturnTrue() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(null).when(accountDaoMock).findById(Mockito.anyLong());
+
+        boolean result = snapshotSchedulerImplSpy.isAccountRemovedOrDisabled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void isAccountRemovedOrDisabledTestVolumeAccountStateIsDisabledReturnTrue() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(accountVoMock).when(accountDaoMock).findById(Mockito.anyLong());
+        Mockito.doReturn(Account.State.DISABLED).when(accountVoMock).getState();
+
+        boolean result = snapshotSchedulerImplSpy.isAccountRemovedOrDisabled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void isAccountRemovedOrDisabledTestVolumeAccountStateIsNotNullNorDisabledReturnFalse() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(accountVoMock).when(accountDaoMock).findById(Mockito.anyLong());
+        Mockito.doReturn(Account.State.ENABLED).when(accountVoMock).getState();
+
+        boolean result = snapshotSchedulerImplSpy.isAccountRemovedOrDisabled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void canSnapshotBeScheduledTestVolumeIsRemovedReturnFalse() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(new Date()).when(volumeVoMock).getRemoved();
+
+        boolean result = snapshotSchedulerImplSpy.canSnapshotBeScheduled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void canSnapshotBeScheduledTestVolumeIsNotAttachedToStoragePoolReturnFalse() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(null).when(volumeVoMock).getPoolId();
+
+        boolean result = snapshotSchedulerImplSpy.canSnapshotBeScheduled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void canSnapshotBeScheduledTestAccountIsRemovedOrDisabledReturnFalse() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(1l).when(volumeVoMock).getPoolId();
+        Mockito.doReturn(true).when(snapshotSchedulerImplSpy).isAccountRemovedOrDisabled(Mockito.any(), Mockito.any());
+
+        boolean result = snapshotSchedulerImplSpy.canSnapshotBeScheduled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void canSnapshotBeScheduledTestSnapshotPolicyIsRemovedCallRemove() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+
+        Mockito.doReturn(1l).when(volumeVoMock).getPoolId();
+        Mockito.doReturn(false).when(snapshotSchedulerImplSpy).isAccountRemovedOrDisabled(Mockito.any(), Mockito.any());
+        Mockito.doReturn(null).when(snapshotPolicyDaoMock).findById(Mockito.any());
+
+        boolean result = snapshotSchedulerImplSpy.canSnapshotBeScheduled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertTrue(result);
+
+        Mockito.verify(snapshotScheduleDaoMock).remove(Mockito.anyLong());
+    }
+
+    @Test
+    public void canSnapshotBeScheduledTestSnapshotPolicyIsNotRemovedDoNotCallRemove() {
+        SnapshotScheduleVO snapshotScheduleVO = new SnapshotScheduleVO();
+        SnapshotPolicyVO snapshotPolicyVO = new SnapshotPolicyVO();
+
+        Mockito.doReturn(1l).when(volumeVoMock).getPoolId();
+        Mockito.doReturn(false).when(snapshotSchedulerImplSpy).isAccountRemovedOrDisabled(Mockito.any(), Mockito.any());
+        Mockito.doReturn(snapshotPolicyVO).when(snapshotPolicyDaoMock).findById(Mockito.any());
+
+        boolean result = snapshotSchedulerImplSpy.canSnapshotBeScheduled(snapshotScheduleVO, volumeVoMock);
+
+        Assert.assertTrue(result);
+
+        Mockito.verify(snapshotScheduleDaoMock, Mockito.never()).remove(Mockito.anyLong());
+    }
+}
diff --git a/server/src/test/java/com/cloud/tags/TaggedResourceManagerImplTest.java b/server/src/test/java/com/cloud/tags/TaggedResourceManagerImplTest.java
index d25f2fe..c158b7d 100644
--- a/server/src/test/java/com/cloud/tags/TaggedResourceManagerImplTest.java
+++ b/server/src/test/java/com/cloud/tags/TaggedResourceManagerImplTest.java
@@ -19,12 +19,11 @@
 
 package com.cloud.tags;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import junit.framework.TestCase;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.junit.Assert;
 import org.junit.Test;
@@ -35,12 +34,11 @@
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.server.ResourceTag;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-
-import junit.framework.TestCase;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 @RunWith(MockitoJUnitRunner.class)
 public class TaggedResourceManagerImplTest extends TestCase {
diff --git a/server/src/test/java/com/cloud/template/HypervisorTemplateAdapterTest.java b/server/src/test/java/com/cloud/template/HypervisorTemplateAdapterTest.java
index c50ca65..8657c07 100644
--- a/server/src/test/java/com/cloud/template/HypervisorTemplateAdapterTest.java
+++ b/server/src/test/java/com/cloud/template/HypervisorTemplateAdapterTest.java
@@ -18,23 +18,26 @@
 
 package com.cloud.template;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.event.EventTypes;
+import com.cloud.event.UsageEventUtils;
+import com.cloud.event.UsageEventVO;
+import com.cloud.event.dao.UsageEventDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.org.Grouping;
+import com.cloud.server.StatsCollector;
+import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.TemplateProfile;
+import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.dao.VMTemplateZoneDao;
+import com.cloud.test.TestAppender;
+import com.cloud.user.AccountVO;
+import com.cloud.user.ResourceLimitService;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@@ -47,41 +50,48 @@
 import org.apache.cloudstack.framework.events.EventBus;
 import org.apache.cloudstack.framework.events.EventBusException;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper;
 import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
+import org.apache.log4j.Level;
+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.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.cloud.dc.dao.DataCenterDao;
-import com.cloud.event.EventTypes;
-import com.cloud.event.UsageEventUtils;
-import com.cloud.event.UsageEventVO;
-import com.cloud.event.dao.UsageEventDao;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.storage.Storage.ImageFormat;
-import com.cloud.storage.TemplateProfile;
-import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
-import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.dao.VMTemplateZoneDao;
-import com.cloud.user.AccountVO;
-import com.cloud.user.ResourceLimitService;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.utils.component.ComponentContext;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(ComponentContext.class)
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class HypervisorTemplateAdapterTest {
     @Mock
     EventBus _bus;
@@ -121,18 +131,37 @@
     ConfigurationDao _configDao;
 
     @Mock
-    DataStoreManager storeMgr;
+    DataStoreManager dataStoreManagerMock;
 
+    @Mock
+    HeuristicRuleHelper heuristicRuleHelperMock;
+
+    @Mock
+    StatsCollector statsCollectorMock;
+
+    @Spy
     @InjectMocks
-    HypervisorTemplateAdapter _adapter;
+    HypervisorTemplateAdapter _adapter = new HypervisorTemplateAdapter();
 
     //UsageEventUtils reflection abuse helpers
     private Map<String, Object> oldFields = new HashMap<>();
     private List<UsageEventVO> usageEvents = new ArrayList<>();
 
+    private MockedStatic<ComponentContext> componentContextMocked;
+
+    private AutoCloseable closeable;
+
     @Before
     public void before() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (componentContextMocked != null) {
+            componentContextMocked.close();
+        }
+        closeable.close();
     }
 
     public UsageEventUtils setupUsageUtils() throws EventBusException {
@@ -155,7 +184,7 @@
             }
         }).when(_bus).publish(any(Event.class));
 
-        PowerMockito.mockStatic(ComponentContext.class);
+        componentContextMocked = Mockito.mockStatic(ComponentContext.class);
         when(ComponentContext.getComponent(eq(EventBus.class))).thenReturn(_bus);
 
         UsageEventUtils utils = new UsageEventUtils();
@@ -289,6 +318,282 @@
     }
 
     @Test
+    public void createTemplateWithinZonesTestZoneIdsNullShouldCallListAllZones() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        VMTemplateVO vmTemplateVOMock = Mockito.mock(VMTemplateVO.class);
+
+        Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(null);
+        Mockito.doReturn(null).when(_adapter).getImageStoresThrowsExceptionIfNotFound(Mockito.any(List.class), Mockito.any(TemplateProfile.class));
+
+        _adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
+
+        Mockito.verify(_dcDao, Mockito.times(1)).listAllZones();
+    }
+
+    @Test
+    public void createTemplateWithinZonesTestZoneIdsNotNullShouldNotCallListAllZones() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        VMTemplateVO vmTemplateVOMock = Mockito.mock(VMTemplateVO.class);
+        List<Long> zoneIds = List.of(1L);
+
+        Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(zoneIds);
+        Mockito.doReturn(null).when(_adapter).getImageStoresThrowsExceptionIfNotFound(Mockito.any(List.class), Mockito.any(TemplateProfile.class));
+        Mockito.doReturn(null).when(_adapter).verifyHeuristicRulesForZone(Mockito.any(VMTemplateVO.class), Mockito.anyLong());
+        Mockito.doNothing().when(_adapter).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class));
+
+        _adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
+
+        Mockito.verify(_dcDao, Mockito.times(0)).listAllZones();
+    }
+
+    @Test
+    public void createTemplateWithinZonesTestZoneDoesNotHaveActiveHeuristicRulesShouldCallStandardImageStoreAllocation() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        VMTemplateVO vmTemplateVOMock = Mockito.mock(VMTemplateVO.class);
+        List<Long> zoneIds = List.of(1L);
+
+        Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(zoneIds);
+        Mockito.doReturn(null).when(_adapter).getImageStoresThrowsExceptionIfNotFound(Mockito.any(List.class), Mockito.any(TemplateProfile.class));
+        Mockito.doReturn(null).when(_adapter).verifyHeuristicRulesForZone(Mockito.any(VMTemplateVO.class), Mockito.anyLong());
+        Mockito.doNothing().when(_adapter).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class));
+
+        _adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
+
+        Mockito.verify(_adapter, Mockito.times(1)).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class));
+    }
+
+    @Test
+    public void createTemplateWithinZonesTestZoneWithHeuristicRuleShouldCallValidateSecondaryStorageAndCreateTemplate() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        VMTemplateVO vmTemplateVOMock = Mockito.mock(VMTemplateVO.class);
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        List<Long> zoneIds = List.of(1L);
+
+        Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(zoneIds);
+        Mockito.doReturn(null).when(_adapter).getImageStoresThrowsExceptionIfNotFound(Mockito.any(List.class), Mockito.any(TemplateProfile.class));
+        Mockito.doReturn(dataStoreMock).when(_adapter).verifyHeuristicRulesForZone(Mockito.any(VMTemplateVO.class), Mockito.anyLong());
+        Mockito.doNothing().when(_adapter).validateSecondaryStorageAndCreateTemplate(Mockito.any(List.class), Mockito.any(VMTemplateVO.class), Mockito.isNull());
+
+        _adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
+
+        Mockito.verify(_adapter, Mockito.times(1)).validateSecondaryStorageAndCreateTemplate(Mockito.any(List.class), Mockito.any(VMTemplateVO.class), Mockito.isNull());
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void getImageStoresThrowsExceptionIfNotFoundTestNullImageStoreShouldThrowCloudRuntimeException() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        List<Long> zoneIds = List.of(1L);
+
+        Mockito.when(dataStoreManagerMock.getImageStoresByZoneIds(Mockito.anyLong())).thenReturn(null);
+
+        _adapter.getImageStoresThrowsExceptionIfNotFound(zoneIds, templateProfileMock);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void getImageStoresThrowsExceptionIfNotFoundTestEmptyImageStoreShouldThrowCloudRuntimeException() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        List<Long> zoneIds = List.of(1L);
+        List<DataStore> imageStoresList = new ArrayList<>();
+
+        Mockito.when(dataStoreManagerMock.getImageStoresByZoneIds(Mockito.anyLong())).thenReturn(imageStoresList);
+
+        _adapter.getImageStoresThrowsExceptionIfNotFound(zoneIds, templateProfileMock);
+    }
+
+    @Test
+    public void getImageStoresThrowsExceptionIfNotFoundTestNonEmptyImageStoreShouldNotThrowCloudRuntimeException() {
+        TemplateProfile templateProfileMock = Mockito.mock(TemplateProfile.class);
+        List<Long> zoneIds = List.of(1L);
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        List<DataStore> imageStoresList = List.of(dataStoreMock);
+
+        Mockito.when(dataStoreManagerMock.getImageStoresByZoneIds(Mockito.anyLong())).thenReturn(imageStoresList);
+
+        _adapter.getImageStoresThrowsExceptionIfNotFound(zoneIds, templateProfileMock);
+    }
+
+    @Test
+    public void verifyHeuristicRulesForZoneTestTemplateIsISOFormatShouldCheckForISOHeuristicType() {
+        VMTemplateVO vmTemplateVOMock = Mockito.mock(VMTemplateVO.class);
+
+        Mockito.when(vmTemplateVOMock.getFormat()).thenReturn(ImageFormat.ISO);
+        _adapter.verifyHeuristicRulesForZone(vmTemplateVOMock, 1L);
+
+        Mockito.verify(heuristicRuleHelperMock, Mockito.times(1)).getImageStoreIfThereIsHeuristicRule(1L, HeuristicType.ISO, vmTemplateVOMock);
+    }
+
+    @Test
+    public void verifyHeuristicRulesForZoneTestTemplateNotISOFormatShouldCheckForTemplateHeuristicType() {
+        VMTemplateVO vmTemplateVOMock = Mockito.mock(VMTemplateVO.class);
+
+        Mockito.when(vmTemplateVOMock.getFormat()).thenReturn(ImageFormat.QCOW2);
+        _adapter.verifyHeuristicRulesForZone(vmTemplateVOMock, 1L);
+
+        Mockito.verify(heuristicRuleHelperMock, Mockito.times(1)).getImageStoreIfThereIsHeuristicRule(1L, HeuristicType.TEMPLATE, vmTemplateVOMock);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestZoneIdIsNullShouldReturnFalse() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = null;
+        Set<Long> zoneSet = null;
+        boolean isTemplatePrivate = false;
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.WARN, Pattern.quote(String.format("Zone ID is null, cannot allocate ISO/template in image store [%s].", dataStoreMock)));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestZoneIsNullShouldReturnFalse() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = 1L;
+        Set<Long> zoneSet = null;
+        boolean isTemplatePrivate = false;
+        DataCenterVO dataCenterVOMock = null;
+
+        Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
+        Mockito.when(dataStoreMock.getId()).thenReturn(2L);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.WARN, Pattern.quote(String.format("Unable to find zone by id [%s], so skip downloading template to its image store [%s].",
+                zoneId, dataStoreMock.getId())));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestZoneIsDisabledShouldReturnFalse() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = 1L;
+        Set<Long> zoneSet = null;
+        boolean isTemplatePrivate = false;
+        DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
+
+        Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
+        Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
+        Mockito.when(dataStoreMock.getId()).thenReturn(2L);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.INFO, Pattern.quote(String.format("Zone [%s] is disabled. Skip downloading template to its image store [%s].", zoneId, dataStoreMock.getId())));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestImageStoreDoesNotHaveEnoughCapacityShouldReturnFalse() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = 1L;
+        Set<Long> zoneSet = null;
+        boolean isTemplatePrivate = false;
+        DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
+
+        Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
+        Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(dataStoreMock.getId()).thenReturn(2L);
+        Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(false);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.INFO, Pattern.quote(String.format("Image store doesn't have enough capacity. Skip downloading template to this image store [%s].",
+                dataStoreMock.getId())));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestImageStoreHasEnoughCapacityAndZoneSetIsNullShouldReturnTrue() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = 1L;
+        Set<Long> zoneSet = null;
+        boolean isTemplatePrivate = false;
+        DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
+
+        Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
+        Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.INFO, Pattern.quote(String.format("Zone set is null; therefore, the ISO/template should be allocated in every secondary storage " +
+                "of zone [%s].", dataCenterVOMock)));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestTemplateIsPrivateAndItIsAlreadyAllocatedToTheSameZoneShouldReturnFalse() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = 1L;
+        Set<Long> zoneSet = Set.of(1L);
+        boolean isTemplatePrivate = true;
+        DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
+
+        Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
+        Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.INFO, Pattern.quote(String.format("The template is private and it is already allocated in a secondary storage in zone [%s]; " +
+                "therefore, image store [%s] will be skipped.", dataCenterVOMock, dataStoreMock)));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void isZoneAndImageStoreAvailableTestTemplateIsPrivateAndItIsNotAlreadyAllocatedToTheSameZoneShouldReturnTrue() {
+        DataStore dataStoreMock = Mockito.mock(DataStore.class);
+        Long zoneId = 1L;
+        Set<Long> zoneSet = new HashSet<>();
+        boolean isTemplatePrivate = true;
+        DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
+
+        Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
+        Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.INFO, Pattern.quote(String.format("Private template will be allocated in image store [%s] in zone [%s].",
+                dataStoreMock, dataCenterVOMock)));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HypervisorTemplateAdapter.s_logger, testLogAppender);
+
+        boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertTrue(result);
+    }
+
+    @Test
     public void testCheckZoneImageStoresDirectDownloadTemplate() {
         VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
         Mockito.when(templateVO.isDirectDownload()).thenReturn(true);
@@ -299,7 +604,7 @@
     public void testCheckZoneImageStoresRegularTemplateWithStore() {
         VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
         Mockito.when(templateVO.isDirectDownload()).thenReturn(false);
-        Mockito.when(storeMgr.getImageStoresByScope(Mockito.any())).thenReturn(List.of(Mockito.mock(DataStore.class)));
+        Mockito.when(dataStoreManagerMock.getImageStoresByScope(Mockito.any())).thenReturn(List.of(Mockito.mock(DataStore.class)));
         _adapter.checkZoneImageStores(templateVO, List.of(1L));
     }
 
@@ -307,7 +612,7 @@
     public void testCheckZoneImageStoresRegularTemplateNoStore() {
         VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
         Mockito.when(templateVO.isDirectDownload()).thenReturn(false);
-        Mockito.when(storeMgr.getImageStoresByScope(Mockito.any())).thenReturn(new ArrayList<>());
+        Mockito.when(dataStoreManagerMock.getImageStoresByScope(Mockito.any())).thenReturn(new ArrayList<>());
         _adapter.checkZoneImageStores(templateVO, List.of(1L));
     }
 }
diff --git a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java
index 5dc50bc..a69795c 100755
--- a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java
+++ b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java
@@ -31,18 +31,20 @@
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.HypervisorGuruManager;
-import com.cloud.storage.Storage;
-import com.cloud.storage.TemplateProfile;
 import com.cloud.projects.ProjectManager;
+import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.GuestOSVO;
 import com.cloud.storage.Snapshot;
 import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
 import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StoragePoolStatus;
+import com.cloud.storage.TemplateProfile;
 import com.cloud.storage.VMTemplateStoragePoolVO;
 import com.cloud.storage.VMTemplateStorageResourceAssoc;
 import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.GuestOSDao;
 import com.cloud.storage.dao.LaunchPermissionDao;
 import com.cloud.storage.dao.SnapshotDao;
@@ -68,13 +70,19 @@
 import com.cloud.vm.dao.VMInstanceDao;
 import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd;
 import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
 import org.apache.cloudstack.api.command.user.userdata.LinkUserDataToTemplateCmd;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
 import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@@ -82,6 +90,9 @@
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+import org.apache.cloudstack.snapshot.SnapshotHelper;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@@ -89,6 +100,8 @@
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper;
+import org.apache.cloudstack.storage.template.VnfTemplateManager;
 import org.apache.cloudstack.test.utils.SpringUtils;
 import org.junit.After;
 import org.junit.Assert;
@@ -98,7 +111,6 @@
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
-import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
@@ -122,8 +134,6 @@
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
-import org.apache.cloudstack.snapshot.SnapshotHelper;
 
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -131,12 +141,11 @@
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.eq;
 
 @RunWith(SpringJUnit4ClassRunner.class)
-@PrepareForTest(CallContext.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
 public class TemplateManagerImplTest {
 
@@ -193,6 +202,11 @@
 
     @Inject
     AccountManager _accountMgr;
+    @Inject
+    VnfTemplateManager vnfTemplateManager;
+
+    @Inject
+    HeuristicRuleHelper heuristicRuleHelperMock;
 
     public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
         AtomicInteger ai = new AtomicInteger(0);
@@ -479,6 +493,7 @@
         when(mockCreateCmd.getOsTypeId()).thenReturn(1L);
         when(mockCreateCmd.getEventDescription()).thenReturn("test");
         when(mockCreateCmd.getDetails()).thenReturn(null);
+        when(mockCreateCmd.getZoneId()).thenReturn(null);
 
         Account mockTemplateOwner = mock(Account.class);
 
@@ -588,6 +603,153 @@
         Assert.assertEquals(template, resultTemplate);
     }
 
+    @Test
+    public void getImageStoreTestStoreUuidIsNotNullShouldReturnAValidImageStoreIfValidUuid() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+
+        Mockito.when(dataStoreManager.getDataStore(Mockito.anyString(), Mockito.any(DataStoreRole.class))).thenReturn(dataStore);
+
+        templateManager.getImageStore("UUID", 1L, volumeVO);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void getImageStoreTestStoreUuidIsNotNullShouldThrowCloudRuntimeExceptionIfInvalidUuid() {
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+
+        Mockito.when(dataStoreManager.getDataStore(Mockito.anyString(), Mockito.any(DataStoreRole.class))).thenReturn(null);
+
+        templateManager.getImageStore("UUID", 1L, volumeVO);
+    }
+
+    @Test
+    public void getImageStoreTestStoreUuidIsNullAndThereIsNoActiveHeuristicRulesShouldCallGetImageStoreWithFreeCapacity() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+
+        Mockito.when(dataStoreManager.getDataStore(Mockito.anyString(), Mockito.any(DataStoreRole.class))).thenReturn(null);
+        Mockito.when(heuristicRuleHelperMock.getImageStoreIfThereIsHeuristicRule(Mockito.anyLong(), Mockito.any(HeuristicType.class), Mockito.any(VolumeVO.class))).thenReturn(null);
+        Mockito.when(dataStoreManager.getImageStoreWithFreeCapacity(Mockito.anyLong())).thenReturn(dataStore);
+
+        templateManager.getImageStore(null, 1L, volumeVO);
+        Mockito.verify(dataStoreManager, Mockito.times(1)).getImageStoreWithFreeCapacity(Mockito.anyLong());
+    }
+
+    @Test
+    public void getImageStoreTestStoreUuidIsNullAndThereIsActiveHeuristicRulesShouldNotCallGetImageStoreWithFreeCapacity() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+
+        Mockito.when(dataStoreManager.getDataStore(Mockito.anyString(), Mockito.any(DataStoreRole.class))).thenReturn(null);
+        Mockito.when(heuristicRuleHelperMock.getImageStoreIfThereIsHeuristicRule(Mockito.anyLong(), Mockito.any(HeuristicType.class), Mockito.any(VolumeVO.class))).thenReturn(dataStore);
+
+        templateManager.getImageStore(null, 1L, volumeVO);
+        Mockito.verify(dataStoreManager, Mockito.times(0)).getImageStoreWithFreeCapacity(Mockito.anyLong());
+    }
+
+    @Test
+    public void testRegisterTemplateWithTemplateType() {
+        RegisterTemplateCmd cmd = Mockito.mock(RegisterTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+        Assert.assertEquals(Storage.TemplateType.SYSTEM, type);
+    }
+
+    @Test
+    public void testRegisterTemplateWithoutTemplateType() {
+        RegisterTemplateCmd cmd = Mockito.mock(RegisterTemplateCmd.class);
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+        Assert.assertEquals(Storage.TemplateType.USER, type);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRegisterTemplateWithSystemTemplateTypeByUser() {
+        RegisterVnfTemplateCmd cmd = Mockito.mock(RegisterVnfTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, false, true);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRegisterVnfTemplateWithTemplateType() {
+        RegisterVnfTemplateCmd cmd = Mockito.mock(RegisterVnfTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, false, true);
+        Assert.assertEquals(Storage.TemplateType.VNF, type);
+    }
+
+    @Test
+    public void testRegisterVnfTemplateWithoutTemplateType() {
+        RegisterVnfTemplateCmd cmd = Mockito.mock(RegisterVnfTemplateCmd.class);
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, false, true);
+        Assert.assertEquals(Storage.TemplateType.VNF, type);
+    }
+
+    @Test
+    public void testUpdateTemplateWithTemplateType() {
+        UpdateTemplateCmd cmd = Mockito.mock(UpdateTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+        Assert.assertEquals(Storage.TemplateType.SYSTEM, type);
+    }
+
+    @Test
+    public void testUpdateTemplateWithoutTemplateType() {
+        UpdateTemplateCmd cmd = Mockito.mock(UpdateTemplateCmd.class);
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+        Assert.assertNull(type);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testUpdateTemplateWithInvalidTemplateType() {
+        UpdateTemplateCmd cmd = Mockito.mock(UpdateTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn("invalidtype");
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testUpdateTemplateWithInvalidTemplateTypeForRouting() {
+        UpdateTemplateCmd cmd = Mockito.mock(UpdateTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.USER.toString());
+        when(cmd.isRoutingType()).thenReturn(true);
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testUpdateTemplateWithInvalidCrossZonesForSystem() {
+        UpdateTemplateCmd cmd = Mockito.mock(UpdateTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, false);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testUpdateTemplateWithSystemTemplateTypeByUser() {
+        UpdateVnfTemplateCmd cmd = Mockito.mock(UpdateVnfTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, false, true);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testUpdateVnfTemplateWithTemplateType() {
+        UpdateVnfTemplateCmd cmd = Mockito.mock(UpdateVnfTemplateCmd.class);
+        when(cmd.getTemplateType()).thenReturn(Storage.TemplateType.SYSTEM.toString());
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, false, true);
+        Assert.assertEquals(Storage.TemplateType.VNF, type);
+    }
+
+    @Test
+    public void testUpdateVnfTemplateWithoutTemplateType() {
+        UpdateVnfTemplateCmd cmd = Mockito.mock(UpdateVnfTemplateCmd.class);
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, false, true);
+        Assert.assertNull(type);
+    }
+
+    @Test
+    public void testDeleteTemplateWithTemplateType() {
+        DeleteTemplateCmd cmd = new DeleteTemplateCmd();
+        Storage.TemplateType type = templateManager.validateTemplateType(cmd, true, true);
+        Assert.assertNull(type);
+    }
+
     @Configuration
     @ComponentScan(basePackageClasses = {TemplateManagerImpl.class},
             includeFilters = {@ComponentScan.Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)},
@@ -790,6 +952,11 @@
         }
 
         @Bean
+        public VnfTemplateManager vnfTemplateManager() {
+            return Mockito.mock(VnfTemplateManager.class);
+        }
+
+        @Bean
         public SnapshotHelper snapshotHelper() {
             return Mockito.mock(SnapshotHelper.class);
         }
@@ -799,6 +966,16 @@
             return Mockito.mock(SnapshotService.class);
         }
 
+        @Bean
+        public SecondaryStorageHeuristicDao secondaryStorageHeuristicDao() {
+            return Mockito.mock(SecondaryStorageHeuristicDao.class);
+        }
+
+        @Bean
+        public HeuristicRuleHelper heuristicRuleHelper() {
+            return Mockito.mock(HeuristicRuleHelper.class);
+        }
+
         public static class Library implements TypeFilter {
             @Override
             public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {
diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
index 2f3a68e..6d9211d 100644
--- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
+++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java
@@ -16,33 +16,6 @@
 // under the License.
 package com.cloud.user;
 
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.HashMap;
-
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
-import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
-import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
-import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
-import org.apache.cloudstack.auth.UserAuthenticator;
-import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication;
-import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-import static org.mockito.ArgumentMatchers.nullable;
-
 import com.cloud.acl.DomainChecker;
 import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
 import com.cloud.domain.Domain;
@@ -60,6 +33,33 @@
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.snapshot.VMSnapshotVO;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
+import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
+import org.apache.cloudstack.auth.UserAuthenticator;
+import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication;
+import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.ArgumentMatchers.nullable;
 
 @RunWith(MockitoJUnitRunner.class)
 public class AccountManagerImplTest extends AccountManagetImplTestBase {
@@ -200,24 +200,24 @@
         userAccountVO.setSource(User.Source.UNKNOWN);
         userAccountVO.setState(Account.State.DISABLED.toString());
         Mockito.when(userAccountDaoMock.getUserAccount("test", 1L)).thenReturn(userAccountVO);
-        Mockito.when(userAuthenticator.authenticate("test", "fail", 1L, null)).thenReturn(failureAuthenticationPair);
-        Mockito.lenient().when(userAuthenticator.authenticate("test", null, 1L, null)).thenReturn(successAuthenticationPair);
-        Mockito.lenient().when(userAuthenticator.authenticate("test", "", 1L, null)).thenReturn(successAuthenticationPair);
+        Mockito.when(userAuthenticator.authenticate("test", "fail", 1L, new HashMap<>())).thenReturn(failureAuthenticationPair);
+        Mockito.lenient().when(userAuthenticator.authenticate("test", null, 1L, new HashMap<>())).thenReturn(successAuthenticationPair);
+        Mockito.lenient().when(userAuthenticator.authenticate("test", "", 1L, new HashMap<>())).thenReturn(successAuthenticationPair);
 
         //Test for incorrect password. authentication should fail
-        UserAccount userAccount = accountManagerImpl.authenticateUser("test", "fail", 1L, InetAddress.getByName("127.0.0.1"), null);
+        UserAccount userAccount = accountManagerImpl.authenticateUser("test", "fail", 1L, InetAddress.getByName("127.0.0.1"), new HashMap<>());
         Assert.assertNull(userAccount);
 
         //Test for null password. authentication should fail
-        userAccount = accountManagerImpl.authenticateUser("test", null, 1L, InetAddress.getByName("127.0.0.1"), null);
+        userAccount = accountManagerImpl.authenticateUser("test", null, 1L, InetAddress.getByName("127.0.0.1"), new HashMap<>());
         Assert.assertNull(userAccount);
 
         //Test for empty password. authentication should fail
-        userAccount = accountManagerImpl.authenticateUser("test", "", 1L, InetAddress.getByName("127.0.0.1"), null);
+        userAccount = accountManagerImpl.authenticateUser("test", "", 1L, InetAddress.getByName("127.0.0.1"), new HashMap<>());
         Assert.assertNull(userAccount);
 
         //Verifying that the authentication method is only called when password is specified
-        Mockito.verify(userAuthenticator, Mockito.times(1)).authenticate("test", "fail", 1L, null);
+        Mockito.verify(userAuthenticator, Mockito.times(1)).authenticate("test", "fail", 1L, new HashMap<>());
         Mockito.verify(userAuthenticator, Mockito.never()).authenticate("test", null, 1L, null);
         Mockito.verify(userAuthenticator, Mockito.never()).authenticate("test", "", 1L, null);
     }
@@ -974,4 +974,17 @@
 
         Assert.assertEquals("345543", response.getSecretCode());
     }
+
+    @Test
+    public void testGetActiveUserAccountByEmail() {
+        String email = "test@example.com";
+        Long domainId = 1L;
+        List<UserAccountVO> userAccountVOList = new ArrayList<>();
+        UserAccountVO userAccountVO = new UserAccountVO();
+        userAccountVOList.add(userAccountVO);
+        Mockito.when(userAccountDaoMock.getUserAccountByEmail(email, domainId)).thenReturn(userAccountVOList);
+        List<UserAccount> userAccounts = accountManagerImpl.getActiveUserAccountByEmail(email, domainId);
+        Assert.assertEquals(userAccountVOList.size(), userAccounts.size());
+        Assert.assertEquals(userAccountVOList.get(0), userAccounts.get(0));
+    }
 }
diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java
index 6cc2da2..21474a5 100644
--- a/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java
+++ b/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java
@@ -16,36 +16,6 @@
 // under the License.
 package com.cloud.user;
 
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-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 org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
-import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity;
-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.Mockito;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnitRunner;
-
 import com.cloud.domain.DomainVO;
 import com.cloud.event.EventTypes;
 import com.cloud.event.UsageEventUtils;
@@ -61,6 +31,35 @@
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.dao.VmStatsDao;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity;
+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.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+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 static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class AccountManagerImplVolumeDeleteEventTest extends AccountManagetImplTestBase {
diff --git a/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java b/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java
index 7bc14a4..7f9fa48 100644
--- a/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java
+++ b/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java
@@ -16,30 +16,6 @@
 // under the License.
 package com.cloud.user;
 
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.cloudstack.acl.SecurityChecker;
-import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
-import org.apache.cloudstack.auth.UserAuthenticator;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.engine.service.api.OrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.messagebus.MessageBus;
-import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao;
-import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao;
-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.Spy;
-import org.mockito.junit.MockitoJUnitRunner;
-
 import com.cloud.configuration.ConfigurationManager;
 import com.cloud.configuration.dao.ResourceCountDao;
 import com.cloud.configuration.dao.ResourceLimitDao;
@@ -81,6 +57,29 @@
 import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.VMSnapshotManager;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+import org.apache.cloudstack.acl.SecurityChecker;
+import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
+import org.apache.cloudstack.auth.UserAuthenticator;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.service.api.OrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao;
+import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao;
+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.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 @RunWith(MockitoJUnitRunner.class)
 public class AccountManagetImplTestBase {
diff --git a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java
index 6b0c612..3192631 100644
--- a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java
+++ b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java
@@ -17,16 +17,32 @@
 
 package com.cloud.user;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-
+import com.cloud.api.query.dao.DiskOfferingJoinDao;
 import com.cloud.api.query.dao.NetworkOfferingJoinDao;
+import com.cloud.api.query.dao.ServiceOfferingJoinDao;
 import com.cloud.api.query.dao.VpcOfferingJoinDao;
+import com.cloud.configuration.ConfigurationManager;
+import com.cloud.configuration.Resource.ResourceOwnerType;
 import com.cloud.configuration.ResourceLimit;
+import com.cloud.configuration.dao.ResourceCountDao;
+import com.cloud.configuration.dao.ResourceLimitDao;
+import com.cloud.dc.DedicatedResourceVO;
+import com.cloud.dc.dao.DedicatedResourceDao;
+import com.cloud.domain.Domain;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
 import com.cloud.domain.dao.DomainDetailsDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.network.dao.NetworkDomainDao;
+import com.cloud.projects.ProjectManager;
+import com.cloud.projects.dao.ProjectDao;
+import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.UuidUtils;
+import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.context.CallContext;
@@ -41,37 +57,18 @@
 import org.mockito.InjectMocks;
 import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.api.query.dao.DiskOfferingJoinDao;
-import com.cloud.api.query.dao.ServiceOfferingJoinDao;
-import com.cloud.configuration.ConfigurationManager;
-import com.cloud.configuration.Resource.ResourceOwnerType;
-import com.cloud.configuration.dao.ResourceCountDao;
-import com.cloud.configuration.dao.ResourceLimitDao;
-import com.cloud.dc.DedicatedResourceVO;
-import com.cloud.dc.dao.DedicatedResourceDao;
-import com.cloud.domain.Domain;
-import com.cloud.domain.DomainVO;
-import com.cloud.domain.dao.DomainDao;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.network.dao.NetworkDomainDao;
-import com.cloud.projects.ProjectManager;
-import com.cloud.projects.dao.ProjectDao;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.utils.db.Filter;
-import com.cloud.utils.db.GlobalLock;
-import com.cloud.utils.db.SearchCriteria;
-import com.cloud.utils.exception.CloudRuntimeException;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
 
-@PowerMockIgnore("javax.management.*")
-@RunWith(PowerMockRunner.class)
+
+@RunWith(MockitoJUnitRunner.class)
 public class DomainManagerImplTest {
     @Mock
     DomainDao domainDaoMock;
@@ -317,13 +314,13 @@
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void validateNetworkDomainTestNullNetworkDomainDoesNotCallVerifyDomainName() {
-        PowerMockito.mockStatic(NetUtils.class);
-        PowerMockito.when(NetUtils.verifyDomainName(Mockito.anyString())).thenReturn(true);
-        domainManager.validateNetworkDomain(null);
-        PowerMockito.verifyStatic(NetUtils.class, Mockito.never());
-        NetUtils.verifyDomainName(Mockito.anyString());
+        try (MockedStatic<NetUtils> ignored = Mockito.mockStatic(NetUtils.class)) {
+            Mockito.when(NetUtils.verifyDomainName(Mockito.anyString())).thenReturn(true);
+            domainManager.validateNetworkDomain(null);
+            Mockito.verify(NetUtils.class, Mockito.never());
+            NetUtils.verifyDomainName(Mockito.anyString());
+        }
     }
 
     @Test (expected = InvalidParameterValueException.class)
@@ -386,29 +383,28 @@
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
     public void createDomainTest(){
         Mockito.doNothing().when(domainManager).validateDomainNameAndNetworkDomain(Mockito.any(String.class), Mockito.any(Long.class), Mockito.any(String.class));
 
         DomainVO domainVoMock = Mockito.mock(DomainVO.class);
         Mockito.doReturn(domainVoMock).when(domainManager).createDomainVo("test",1L,2L,"netTest","uuidTest");
         Mockito.doReturn(domainVoMock).when(domainDaoMock).create(domainVoMock);
-        PowerMockito.mockStatic(CallContext.class);
-        CallContext callContextMock = Mockito.mock(CallContext.class);
-        PowerMockito.when(CallContext.current()).thenReturn(callContextMock);
+        try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
+            CallContext callContextMock = Mockito.mock(CallContext.class);
+            Mockito.when(CallContext.current()).thenReturn(callContextMock);
 
-        Domain actualDomain = domainManager.createDomain("test",1L,2L,"netTest","uuidTest");
+            Domain actualDomain = domainManager.createDomain("test", 1L, 2L, "netTest", "uuidTest");
 
-        Mockito.verify(domainManager).validateDomainNameAndNetworkDomain("test", 1L, "netTest");
-        Mockito.verify(domainManager).createDomainVo("test",1L,2L,"netTest","uuidTest");
+            Mockito.verify(domainManager).validateDomainNameAndNetworkDomain("test", 1L, "netTest");
+            Mockito.verify(domainManager).createDomainVo("test", 1L, 2L, "netTest", "uuidTest");
 
-        Mockito.verify(domainDaoMock).create(domainVoMock);
-        Mockito.verify(_resourceCountDao).createResourceCounts(domainVoMock.getId(), ResourceLimit.ResourceOwnerType.Domain);
-        PowerMockito.verifyStatic(CallContext.class);
-        CallContext.current();
-        Mockito.verify(callContextMock).putContextParameter(Domain.class, domainVoMock.getUuid());
-        Mockito.verify(_messageBus).publish("DomainManagerImpl", DomainManager.MESSAGE_ADD_DOMAIN_EVENT, PublishScope.LOCAL, domainVoMock.getId());
-        Assert.assertEquals(domainVoMock, actualDomain);
+            Mockito.verify(domainDaoMock).create(domainVoMock);
+            Mockito.verify(_resourceCountDao).createResourceCounts(domainVoMock.getId(), ResourceLimit.ResourceOwnerType.Domain);
+            CallContext.current();
+            Mockito.verify(callContextMock).putContextParameter(Domain.class, domainVoMock.getUuid());
+            Mockito.verify(_messageBus).publish("DomainManagerImpl", DomainManager.MESSAGE_ADD_DOMAIN_EVENT, PublishScope.LOCAL, domainVoMock.getId());
+            Assert.assertEquals(domainVoMock, actualDomain);
+        }
     }
 
 }
diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
index b8a1af8..fe7748b 100644
--- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
+++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java
@@ -16,29 +16,7 @@
 // under the License.
 package com.cloud.user;
 
-import java.util.List;
-import java.util.Map;
-import java.net.InetAddress;
-
-import javax.naming.ConfigurationException;
-
 import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
-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.MoveUserCmd;
-import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
-import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.springframework.stereotype.Component;
-
-import org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.acl.RoleType;
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
-import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
-import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
-import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
-import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
-
 import com.cloud.api.query.vo.ControlledViewEntity;
 import com.cloud.dc.DataCenter;
 import com.cloud.domain.Domain;
@@ -56,6 +34,25 @@
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
+import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
+import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
+import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
+import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
+import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
+import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
+import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
+import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.springframework.stereotype.Component;
+
+import javax.naming.ConfigurationException;
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Map;
 
 @Component
 public class MockAccountManagerImpl extends ManagerBase implements Manager, AccountManager {
@@ -180,6 +177,11 @@
     }
 
     @Override
+    public List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId) {
+        return null;
+    }
+
+    @Override
     public Account getActiveAccountById(long accountId) {
         // TODO Auto-generated method stub
         return null;
diff --git a/server/src/test/java/com/cloud/user/MockUsageEventDao.java b/server/src/test/java/com/cloud/user/MockUsageEventDao.java
index 029625d..52e1b1a 100644
--- a/server/src/test/java/com/cloud/user/MockUsageEventDao.java
+++ b/server/src/test/java/com/cloud/user/MockUsageEventDao.java
@@ -16,13 +16,6 @@
 // under the License.
 package com.cloud.user;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
 import com.cloud.event.UsageEventVO;
 import com.cloud.event.dao.UsageEventDao;
 import com.cloud.utils.Pair;
@@ -32,6 +25,12 @@
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 public class MockUsageEventDao implements UsageEventDao{
 
     List<UsageEventVO> persistedItems;
@@ -263,6 +262,11 @@
     }
 
     @Override
+    public UsageEventVO findOneBy(SearchCriteria<UsageEventVO> sc, Filter filter) {
+        return null;
+    }
+
+    @Override
     public Class<UsageEventVO> getEntityBeanType() {
         return null;
     }
@@ -294,6 +298,11 @@
     }
 
     @Override
+    public List<UsageEventVO> findByUuids(String... uuids) {
+        return null;
+    }
+
+    @Override
     public List<UsageEventVO> listLatestEvents(Date endDate) {
         return null;
     }
diff --git a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
index cdee399..eecd7cc 100644
--- a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
+++ b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
@@ -16,21 +16,44 @@
 // under the License.
 package com.cloud.vm;
 
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-
+import com.cloud.capacity.Capacity;
+import com.cloud.capacity.CapacityManager;
+import com.cloud.capacity.dao.CapacityDao;
+import com.cloud.configuration.Config;
+import com.cloud.dc.ClusterDetailsDao;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.HostPodDao;
+import com.cloud.deploy.DataCenterDeployment;
+import com.cloud.deploy.DeploymentClusterPlanner;
+import com.cloud.deploy.DeploymentPlanner.ExcludeList;
+import com.cloud.deploy.FirstFitPlanner;
+import com.cloud.exception.InsufficientServerCapacityException;
+import com.cloud.gpu.dao.HostGpuGroupsDao;
+import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
+import com.cloud.host.dao.HostDetailsDao;
+import com.cloud.host.dao.HostTagsDao;
 import com.cloud.offering.ServiceOffering;
+import com.cloud.resource.ResourceManager;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.service.dao.ServiceOfferingDetailsDao;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.GuestOSCategoryDao;
+import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.dao.UserVmDetailsDao;
+import com.cloud.vm.dao.VMInstanceDao;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.framework.config.ConfigDepot;
@@ -61,43 +84,18 @@
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
-import com.cloud.capacity.Capacity;
-import com.cloud.capacity.CapacityManager;
-import com.cloud.capacity.dao.CapacityDao;
-import com.cloud.configuration.Config;
-import com.cloud.dc.ClusterDetailsDao;
-import com.cloud.dc.DataCenterVO;
-import com.cloud.dc.dao.ClusterDao;
-import com.cloud.dc.dao.DataCenterDao;
-import com.cloud.dc.dao.HostPodDao;
-import com.cloud.deploy.DataCenterDeployment;
-import com.cloud.deploy.DeploymentClusterPlanner;
-import com.cloud.deploy.DeploymentPlanner.ExcludeList;
-import com.cloud.deploy.FirstFitPlanner;
-import com.cloud.exception.InsufficientServerCapacityException;
-import com.cloud.gpu.dao.HostGpuGroupsDao;
-import com.cloud.host.Host;
-import com.cloud.host.dao.HostDao;
-import com.cloud.host.dao.HostTagsDao;
-import com.cloud.resource.ResourceManager;
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.service.dao.ServiceOfferingDao;
-import com.cloud.service.dao.ServiceOfferingDetailsDao;
-import com.cloud.storage.StorageManager;
-import com.cloud.storage.dao.DiskOfferingDao;
-import com.cloud.storage.dao.GuestOSCategoryDao;
-import com.cloud.storage.dao.GuestOSDao;
-import com.cloud.storage.dao.StoragePoolHostDao;
-import com.cloud.storage.dao.VolumeDao;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.user.AccountVO;
-import com.cloud.utils.Pair;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.vm.dao.UserVmDao;
-import com.cloud.vm.dao.UserVmDetailsDao;
-import com.cloud.vm.dao.VMInstanceDao;
-import com.cloud.host.dao.HostDetailsDao;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index 22a9bed..303a9b0 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -16,8 +16,100 @@
 // under the License.
 package com.cloud.vm;
 
+import com.cloud.api.query.dao.ServiceOfferingJoinDao;
+import com.cloud.api.query.vo.ServiceOfferingJoinVO;
+import com.cloud.configuration.Resource;
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.deploy.DataCenterDeployment;
+import com.cloud.deploy.DeployDestination;
+import com.cloud.deploy.DeploymentPlanner;
+import com.cloud.deploy.DeploymentPlanningManager;
+import com.cloud.exception.InsufficientAddressCapacityException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InsufficientServerCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.network.NetworkModel;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.security.SecurityGroupVO;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.server.ManagementService;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.GuestOSVO;
+import com.cloud.storage.ScopeType;
+import com.cloud.storage.Storage;
+import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeApiService;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VolumeDao;
+import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountService;
+import com.cloud.user.AccountVO;
+import com.cloud.user.ResourceLimitService;
+import com.cloud.user.UserData;
+import com.cloud.user.UserDataVO;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.user.dao.UserDataDao;
+import com.cloud.uservm.UserVm;
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.dao.NicDao;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.dao.UserVmDetailsDao;
+import com.cloud.vm.snapshot.VMSnapshotVO;
+import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+
+import org.apache.cloudstack.api.BaseCmd.HTTPMethod;
+import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
+import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
+import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd;
+import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd;
+import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd;
+import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.template.VnfTemplateManager;
+import org.apache.cloudstack.userdata.UserDataManager;
+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.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -31,75 +123,9 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.when;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.cloud.template.VirtualMachineTemplate;
-import com.cloud.user.UserData;
-import com.cloud.user.UserDataVO;
-import com.cloud.user.dao.UserDataDao;
-import com.cloud.utils.exception.CloudRuntimeException;
-import org.apache.cloudstack.api.BaseCmd.HTTPMethod;
-import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd;
-import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
-import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd;
-import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-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.Mockito;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.springframework.test.util.ReflectionTestUtils;
-
-import com.cloud.configuration.Resource;
-import com.cloud.dc.DataCenter;
-import com.cloud.dc.DataCenterVO;
-import com.cloud.dc.dao.DataCenterDao;
-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.hypervisor.Hypervisor;
-import com.cloud.network.NetworkModel;
-import com.cloud.network.dao.NetworkDao;
-import com.cloud.network.dao.NetworkVO;
-import com.cloud.offering.ServiceOffering;
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.service.dao.ServiceOfferingDao;
-import com.cloud.storage.DiskOfferingVO;
-import com.cloud.storage.GuestOSVO;
-import com.cloud.storage.VMTemplateVO;
-import com.cloud.storage.VolumeApiService;
-import com.cloud.storage.VolumeVO;
-import com.cloud.storage.dao.DiskOfferingDao;
-import com.cloud.storage.dao.GuestOSDao;
-import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.user.AccountService;
-import com.cloud.user.AccountVO;
-import com.cloud.user.ResourceLimitService;
-import com.cloud.user.UserVO;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.uservm.UserVm;
-import com.cloud.utils.db.EntityManager;
-import com.cloud.vm.dao.NicDao;
-import com.cloud.vm.dao.UserVmDao;
-import com.cloud.vm.dao.UserVmDetailsDao;
-
 @RunWith(MockitoJUnitRunner.class)
 public class UserVmManagerImplTest {
 
@@ -173,6 +199,9 @@
     private AccountDao accountDao;
 
     @Mock
+    private UserDao userDao;
+
+    @Mock
     ResourceLimitService resourceLimitMgr;
 
     @Mock
@@ -182,21 +211,61 @@
     UserDataDao userDataDao;
 
     @Mock
+    PrimaryDataStoreDao primaryDataStoreDao;
+
+    @Mock
+    VirtualMachineManager virtualMachineManager;
+
+    @Mock
+    DeploymentPlanningManager planningManager;
+
+    @Mock
+    HostDao hostDao;
+
+    @Mock
     private VolumeVO volumeVOMock;
 
     @Mock
     private VolumeDao volumeDaoMock;
 
     @Mock
+    private SnapshotDao snapshotDaoMock;
+
+    @Mock
+    private VMSnapshotDao vmSnapshotDaoMock;
+
+    @Mock
     AccountVO account;
 
     @Mock
+    VMTemplateVO vmTemplateVoMock;
+
+    @Mock
+    ManagementService managementServiceMock;
+
+    @Mock
     private ServiceOfferingVO serviceOffering;
 
+    @Mock
+    UserDataManager userDataManager;
+
+    @Mock
+    VirtualMachineProfile virtualMachineProfile;
+
+    @Mock
+    VirtualMachineTemplate templateMock;
+
+    @Mock
+    VnfTemplateManager vnfTemplateManager;
+
+    @Mock
+    ServiceOfferingJoinDao serviceOfferingJoinDao;
+
     private static final long vmId = 1l;
     private static final long zoneId = 2L;
     private static final long accountId = 3L;
     private static final long serviceOfferingId = 10L;
+    private static final long templateId = 11L;
 
     private static final long GiB_TO_BYTES = 1024 * 1024 * 1024;
 
@@ -220,6 +289,8 @@
         customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, "123");
         lenient().doNothing().when(resourceLimitMgr).incrementResourceCount(anyLong(), any(Resource.ResourceType.class));
         lenient().doNothing().when(resourceLimitMgr).decrementResourceCount(anyLong(), any(Resource.ResourceType.class), anyLong());
+
+        Mockito.when(virtualMachineProfile.getId()).thenReturn(vmId);
     }
 
     @After
@@ -271,7 +342,6 @@
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
     public void validateInputsAndPermissionForUpdateVirtualMachineCommandTest() {
         Mockito.doNothing().when(userVmManagerImpl).validateGuestOsIdForUpdateVirtualMachineCommand(updateVmCommand);
 
@@ -548,13 +618,10 @@
     public void verifyIfHypervisorSupportRootdiskSizeOverrideTest() {
         Hypervisor.HypervisorType[] hypervisorTypeArray = Hypervisor.HypervisorType.values();
         int exceptionCounter = 0;
-        int expectedExceptionCounter = hypervisorTypeArray.length - 4;
+        int expectedExceptionCounter = hypervisorTypeArray.length - 5;
 
         for(int i = 0; i < hypervisorTypeArray.length; i++) {
-            if (Hypervisor.HypervisorType.KVM == hypervisorTypeArray[i]
-                    || Hypervisor.HypervisorType.XenServer == hypervisorTypeArray[i]
-                    || Hypervisor.HypervisorType.VMware == hypervisorTypeArray[i]
-                    || Hypervisor.HypervisorType.Simulator == hypervisorTypeArray[i]) {
+            if (UserVmManagerImpl.ROOT_DISK_SIZE_OVERRIDE_SUPPORTING_HYPERVISORS.contains(hypervisorTypeArray[i])) {
                 userVmManagerImpl.verifyIfHypervisorSupportsRootdiskSizeOverride(hypervisorTypeArray[i]);
             } else {
                 try {
@@ -705,29 +772,6 @@
     }
 
     @Test
-    public void testUserDataAppend() {
-        String userData = "testUserdata";
-        String templateUserData = "testTemplateUserdata";
-        Long userDataId = 1L;
-
-        VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class);
-        when(template.getUserDataId()).thenReturn(2L);
-        when(template.getUserDataOverridePolicy()).thenReturn(UserData.UserDataOverridePolicy.APPEND);
-
-        UserDataVO templateUserDataVO = Mockito.mock(UserDataVO.class);
-        doReturn(templateUserDataVO).when(userDataDao).findById(2L);
-        when(templateUserDataVO.getUserData()).thenReturn(templateUserData);
-
-        UserDataVO apiUserDataVO = Mockito.mock(UserDataVO.class);
-        doReturn(apiUserDataVO).when(userDataDao).findById(userDataId);
-        when(apiUserDataVO.getUserData()).thenReturn(userData);
-
-        String finalUserdata = userVmManagerImpl.finalizeUserData(null, userDataId, template);
-
-        Assert.assertEquals(finalUserdata, templateUserData+userData);
-    }
-
-    @Test
     public void testUserDataWithoutTemplate() {
         String userData = "testUserdata";
         Long userDataId = 1L;
@@ -772,7 +816,6 @@
     }
 
     @Test(expected = InvalidParameterValueException.class)
-    @PrepareForTest(CallContext.class)
     public void testResetVMUserDataVMStateNotStopped() {
         CallContext callContextMock = Mockito.mock(CallContext.class);
         Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
@@ -798,7 +841,6 @@
     }
 
     @Test(expected = InvalidParameterValueException.class)
-    @PrepareForTest(CallContext.class)
     public void testResetVMUserDataDontAcceptBothUserdataAndUserdataId() {
         CallContext callContextMock = Mockito.mock(CallContext.class);
         Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
@@ -827,7 +869,6 @@
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
     public void testResetVMUserDataSuccessResetWithUserdata() {
         CallContext callContextMock = Mockito.mock(CallContext.class);
         Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
@@ -846,10 +887,13 @@
         when(templateDao.findByIdIncludingRemoved(2L)).thenReturn(template);
         when(template.getUserDataId()).thenReturn(null);
 
-        when(cmd.getUserData()).thenReturn("testUserdata");
+        String testUserData = "testUserdata";
+        when(cmd.getUserData()).thenReturn(testUserData);
         when(cmd.getUserdataId()).thenReturn(null);
         when(cmd.getHttpMethod()).thenReturn(HTTPMethod.GET);
 
+        when(userDataManager.validateUserData(testUserData, HTTPMethod.GET)).thenReturn(testUserData);
+
         try {
             doNothing().when(userVmManagerImpl).updateUserData(userVmVO);
             userVmManagerImpl.resetVMUserData(cmd);
@@ -864,7 +908,6 @@
     }
 
     @Test
-    @PrepareForTest(CallContext.class)
     public void testResetVMUserDataSuccessResetWithUserdataId() {
         CallContext callContextMock = Mockito.mock(CallContext.class);
         Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
@@ -883,12 +926,15 @@
         when(templateDao.findByIdIncludingRemoved(2L)).thenReturn(template);
         when(template.getUserDataId()).thenReturn(null);
 
+        String testUserData = "testUserdata";
         when(cmd.getUserdataId()).thenReturn(1L);
         UserDataVO apiUserDataVO = Mockito.mock(UserDataVO.class);
         when(userDataDao.findById(1L)).thenReturn(apiUserDataVO);
-        when(apiUserDataVO.getUserData()).thenReturn("testUserdata");
+        when(apiUserDataVO.getUserData()).thenReturn(testUserData);
         when(cmd.getHttpMethod()).thenReturn(HTTPMethod.GET);
 
+        when(userDataManager.validateUserData(testUserData, HTTPMethod.GET)).thenReturn(testUserData);
+
         try {
             doNothing().when(userVmManagerImpl).updateUserData(userVmVO);
             userVmManagerImpl.resetVMUserData(cmd);
@@ -927,4 +973,412 @@
 
         userVmManagerImpl.createVirtualMachine(deployVMCmd);
     }
+
+    @Test
+    public void createVirtualMachine() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
+        DeployVMCmd deployVMCmd = new DeployVMCmd();
+        ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId);
+        ReflectionTestUtils.setField(deployVMCmd, "templateId", templateId);
+        ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId);
+        deployVMCmd._accountService = accountService;
+
+        when(accountService.finalyzeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId);
+        when(accountService.getActiveAccountById(accountId)).thenReturn(account);
+        when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock);
+        when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering);
+        when(serviceOffering.getState()).thenReturn(ServiceOffering.State.Active);
+
+        when(entityManager.findById(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock);
+        when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF);
+        when(templateMock.isDeployAsIs()).thenReturn(false);
+        when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
+        when(templateMock.getUserDataId()).thenReturn(null);
+        Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class));
+
+        ServiceOfferingJoinVO svcOfferingMock = Mockito.mock(ServiceOfferingJoinVO.class);
+        when(serviceOfferingJoinDao.findById(anyLong())).thenReturn(svcOfferingMock);
+        when(_dcMock.isLocalStorageEnabled()).thenReturn(true);
+        when(_dcMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
+        Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(),
+                any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(),
+                any(), any(), any(), any(), eq(true), any());
+
+        UserVm result = userVmManagerImpl.createVirtualMachine(deployVMCmd);
+        assertEquals(userVmVoMock, result);
+        Mockito.verify(vnfTemplateManager).validateVnfApplianceNics(templateMock, null);
+        Mockito.verify(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(),
+                any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(),
+                any(), any(), any(), any(), eq(true), any());
+    }
+
+    private List<VolumeVO> mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(int localVolumes, int nonLocalVolumes) {
+        List<VolumeVO> volumes = new ArrayList<>();
+        for (int i=0; i< localVolumes + nonLocalVolumes; ++i) {
+            VolumeVO vol = Mockito.mock(VolumeVO.class);
+            long index = i + 1;
+            Mockito.when(vol.getDiskOfferingId()).thenReturn(index);
+            Mockito.when(vol.getPoolId()).thenReturn(index);
+            DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
+            Mockito.when(diskOfferingDao.findById(index)).thenReturn(diskOffering);
+            StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
+            Mockito.when(primaryDataStoreDao.findById(index)).thenReturn(storagePool);
+            if (i < localVolumes) {
+                if ((localVolumes + nonLocalVolumes) % 2 == 0) {
+                    Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(true);
+                } else {
+
+                    Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false);
+                    Mockito.when(storagePool.isLocal()).thenReturn(true);
+                }
+            } else {
+                Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false);
+                Mockito.when(storagePool.isLocal()).thenReturn(false);
+            }
+            volumes.add(vol);
+        }
+        return volumes;
+    }
+
+    @Test
+    public void testIsAnyVmVolumeUsingLocalStorage() {
+        Assert.assertTrue(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(1, 0)));
+        Assert.assertTrue(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(2, 0)));
+        Assert.assertTrue(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(1, 1)));
+        Assert.assertFalse(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(0, 2)));
+        Assert.assertFalse(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(0, 0)));
+    }
+
+    private List<VolumeVO> mockVolumesForIsAllVmVolumesOnZoneWideStore(int nullPoolIdVolumes, int nullPoolVolumes, int zoneVolumes, int nonZoneVolumes) {
+        List<VolumeVO> volumes = new ArrayList<>();
+        for (int i=0; i< nullPoolIdVolumes + nullPoolVolumes + zoneVolumes + nonZoneVolumes; ++i) {
+            VolumeVO vol = Mockito.mock(VolumeVO.class);
+            volumes.add(vol);
+            if (i < nullPoolIdVolumes) {
+                Mockito.when(vol.getPoolId()).thenReturn(null);
+                continue;
+            }
+            long index = i + 1;
+            Mockito.when(vol.getPoolId()).thenReturn(index);
+            if (i < nullPoolVolumes) {
+                Mockito.when(primaryDataStoreDao.findById(index)).thenReturn(null);
+                continue;
+            }
+            StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
+            Mockito.when(primaryDataStoreDao.findById(index)).thenReturn(storagePool);
+            if (i < zoneVolumes) {
+                Mockito.when(storagePool.getScope()).thenReturn(ScopeType.ZONE);
+            } else {
+                Mockito.when(storagePool.getScope()).thenReturn(ScopeType.CLUSTER);
+            }
+        }
+        return volumes;
+    }
+
+    @Test
+    public void testIsAllVmVolumesOnZoneWideStoreCombinations() {
+        Assert.assertTrue(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 1, 0)));
+        Assert.assertTrue(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 2, 0)));
+        Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 1, 1)));
+        Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 0, 0)));
+        Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(1, 0, 1, 1)));
+        Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 1, 1, 1)));
+    }
+
+    private Pair<VMInstanceVO, Host> mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(boolean nullPlan, Host destinationHost) {
+        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+        Mockito.when(vm.getId()).thenReturn(1L);
+        Mockito.when(vm.getServiceOfferingId()).thenReturn(1L);
+        Host host = Mockito.mock(Host.class);
+        Mockito.when(host.getId()).thenReturn(1L);
+        Mockito.when(hostDao.findById(1L)).thenReturn(Mockito.mock(HostVO.class));
+        Mockito.when(virtualMachineManager.getMigrationDeployment(Mockito.any(VirtualMachine.class),
+                        Mockito.any(Host.class), Mockito.nullable(Long.class),
+                        Mockito.any(DeploymentPlanner.ExcludeList.class)))
+                .thenReturn(Mockito.mock(DataCenterDeployment.class));
+        if (!nullPlan) {
+            try {
+                DeployDestination destination = Mockito.mock(DeployDestination.class);
+                Mockito.when(destination.getHost()).thenReturn(destinationHost);
+                Mockito.when(planningManager.planDeployment(Mockito.any(VirtualMachineProfile.class),
+                                Mockito.any(DataCenterDeployment.class), Mockito.any(DeploymentPlanner.ExcludeList.class),
+                                Mockito.nullable(DeploymentPlanner.class)))
+                        .thenReturn(destination);
+            } catch (InsufficientServerCapacityException e) {
+                Assert.fail("Failed to mock DeployDestination");
+            }
+        }
+        return new Pair<>(vm, host);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testChooseVmMigrationDestinationUsingVolumePoolMapNullDestination() {
+        Pair<VMInstanceVO, Host> pair = mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(true, null);
+        userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), pair.second(), null);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testChooseVmMigrationDestinationUsingVolumePoolMapNullHost() {
+        Pair<VMInstanceVO, Host> pair = mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(false, null);
+        userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), pair.second(), null);
+    }
+
+    @Test
+    public void testChooseVmMigrationDestinationUsingVolumePoolMapValid() {
+        Host destinationHost = Mockito.mock(Host.class);
+        Pair<VMInstanceVO, Host> pair = mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(false, destinationHost);
+        Assert.assertEquals(destinationHost, userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), pair.second(), null));
+    }
+
+    @Test
+    public void testUpdateVncPasswordIfItHasChanged() {
+        String vncPassword = "12345678";
+        userVmManagerImpl.updateVncPasswordIfItHasChanged(vncPassword, vncPassword, virtualMachineProfile);
+        Mockito.verify(userVmDao, Mockito.never()).update(vmId, userVmVoMock);
+    }
+
+    @Test
+    public void testUpdateVncPasswordIfItHasChangedNewPassword() {
+        String vncPassword = "12345678";
+        String newPassword = "87654321";
+        Mockito.when(userVmVoMock.getId()).thenReturn(vmId);
+        userVmManagerImpl.updateVncPasswordIfItHasChanged(vncPassword, newPassword, virtualMachineProfile);
+        Mockito.verify(userVmDao).findById(vmId);
+        Mockito.verify(userVmDao).update(vmId, userVmVoMock);
+    }
+
+    @Test
+    public void testGetSecurityGroupIdList() {
+        DeployVnfApplianceCmd cmd = Mockito.mock(DeployVnfApplianceCmd.class);
+        Mockito.doReturn(new ArrayList<Long>()).when(userVmManagerImpl).getSecurityGroupIdList(cmd);
+        SecurityGroupVO securityGroupVO = Mockito.mock(SecurityGroupVO.class);
+        long securityGroupId = 100L;
+        when(securityGroupVO.getId()).thenReturn(securityGroupId);
+        Mockito.doReturn(securityGroupVO).when(vnfTemplateManager).createSecurityGroupForVnfAppliance(any(), any(), any(), any(DeployVnfApplianceCmd.class));
+
+        List<Long> securityGroupIds = userVmManagerImpl.getSecurityGroupIdList(cmd, null, null, null);
+
+        Assert.assertEquals(1, securityGroupIds.size());
+        Assert.assertEquals(securityGroupId, securityGroupIds.get(0).longValue());
+
+        Mockito.verify(userVmManagerImpl).getSecurityGroupIdList(cmd);
+        Mockito.verify(vnfTemplateManager).createSecurityGroupForVnfAppliance(any(), any(), any(), any(DeployVnfApplianceCmd.class));
+    }
+
+    @Test
+    public void getCurrentVmPasswordOrDefineNewPasswordTestTemplateIsNotPasswordEnabledReturnPreDefinedString() {
+        String expected = "saved_password";
+
+        Mockito.doReturn(false).when(vmTemplateVoMock).isEnablePassword();
+
+        String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword("", userVmVoMock, vmTemplateVoMock);
+
+        Assert.assertEquals(expected, result);
+    }
+
+    @Test
+    public void getCurrentVmPasswordOrDefineNewPasswordTestVmHasPasswordReturnCurrentPassword() {
+        String expected = "current_password";
+
+        Mockito.doReturn(true).when(vmTemplateVoMock).isEnablePassword();
+        Mockito.doReturn(expected).when(userVmVoMock).getDetail("password");
+
+        String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword("", userVmVoMock, vmTemplateVoMock);
+
+        Assert.assertEquals(expected, result);
+    }
+
+    @Test
+    public void getCurrentVmPasswordOrDefineNewPasswordTestUserDefinedPasswordReturnNewPasswordAndSetVmPassword() {
+        String expected = "new_password";
+
+        Mockito.doReturn(true).when(vmTemplateVoMock).isEnablePassword();
+        Mockito.doReturn(null).when(userVmVoMock).getDetail("password");
+        Mockito.doCallRealMethod().when(userVmVoMock).setPassword(Mockito.any());
+        Mockito.doCallRealMethod().when(userVmVoMock).getPassword();
+
+        String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword(expected, userVmVoMock, vmTemplateVoMock);
+
+        Assert.assertEquals(expected, result);
+        Assert.assertEquals(expected, userVmVoMock.getPassword());
+    }
+
+    @Test
+    public void getCurrentVmPasswordOrDefineNewPasswordTestUserDefinedPasswordReturnRandomPasswordAndSetVmPassword() {
+        String expected = "random_password";
+
+        Mockito.doReturn(true).when(vmTemplateVoMock).isEnablePassword();
+        Mockito.doReturn(null).when(userVmVoMock).getDetail("password");
+        Mockito.doReturn(expected).when(managementServiceMock).generateRandomPassword();
+        Mockito.doCallRealMethod().when(userVmVoMock).setPassword(Mockito.any());
+        Mockito.doCallRealMethod().when(userVmVoMock).getPassword();
+
+        String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword("", userVmVoMock, vmTemplateVoMock);
+
+        Assert.assertEquals(expected, result);
+        Assert.assertEquals(expected, userVmVoMock.getPassword());
+    }
+
+    @Test
+    public void testSetVmRequiredFieldsForImportNotImport() {
+        userVmManagerImpl.setVmRequiredFieldsForImport(false, userVmVoMock, _dcMock,
+                Hypervisor.HypervisorType.VMware, Mockito.mock(HostVO.class), Mockito.mock(HostVO.class), VirtualMachine.PowerState.PowerOn);
+        Mockito.verify(userVmVoMock, never()).setDataCenterId(anyLong());
+    }
+
+    @Test
+    public void testSetVmRequiredFieldsForImportFromLastHost() {
+        HostVO lastHost = Mockito.mock(HostVO.class);
+        HostVO host = Mockito.mock(HostVO.class);
+        Mockito.when(_dcMock.getId()).thenReturn(1L);
+        Mockito.when(host.getId()).thenReturn(1L);
+        Mockito.when(lastHost.getId()).thenReturn(2L);
+        userVmManagerImpl.setVmRequiredFieldsForImport(true, userVmVoMock, _dcMock,
+                Hypervisor.HypervisorType.VMware, host, lastHost, VirtualMachine.PowerState.PowerOn);
+        Mockito.verify(userVmVoMock).setLastHostId(2L);
+        Mockito.verify(userVmVoMock).setState(VirtualMachine.State.Running);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVMNoVM() throws ResourceUnavailableException, InsufficientCapacityException {
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
+
+        RestoreVMCmd cmd = Mockito.mock(RestoreVMCmd.class);
+        when(cmd.getVmId()).thenReturn(vmId);
+        when(cmd.getTemplateId()).thenReturn(2L);
+        when(userVmDao.findById(vmId)).thenReturn(null);
+
+        userVmManagerImpl.restoreVM(cmd);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testRestoreVMWithVolumeSnapshots() throws ResourceUnavailableException, InsufficientCapacityException {
+        CallContext callContextMock = Mockito.mock(CallContext.class);
+        Mockito.lenient().doReturn(accountMock).when(callContextMock).getCallingAccount();
+        Mockito.lenient().doNothing().when(accountManager).checkAccess(accountMock, null, true, userVmVoMock);
+
+        RestoreVMCmd cmd = Mockito.mock(RestoreVMCmd.class);
+        when(cmd.getVmId()).thenReturn(vmId);
+        when(cmd.getTemplateId()).thenReturn(2L);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+
+        userVmManagerImpl.restoreVM(cmd);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(accountDao.findById(accountId)).thenReturn(null);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
+    }
+
+    @Test(expected = PermissionDeniedException.class)
+    public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(callerAccount.getState()).thenReturn(Account.State.DISABLED);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(userVmVoMock.getUuid()).thenReturn("a967643d-7633-4ab4-ac26-9c0b63f50cc1");
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Starting);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long currentTemplateId = 1l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(userVmVoMock.getUuid()).thenReturn("a967643d-7633-4ab4-ac26-9c0b63f50cc1");
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Running);
+        when(userVmVoMock.getTemplateId()).thenReturn(currentTemplateId);
+
+        VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class);
+        when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
+        when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(new ArrayList<VolumeVO>());
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long currentTemplateId = 1l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(userVmVoMock.getUuid()).thenReturn("a967643d-7633-4ab4-ac26-9c0b63f50cc1");
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Running);
+        when(userVmVoMock.getTemplateId()).thenReturn(currentTemplateId);
+
+        VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class);
+        when(currentTemplate.isDeployAsIs()).thenReturn(false);
+        when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
+        List<VolumeVO> volumes = new ArrayList<>();
+        VolumeVO rootVolume1 = Mockito.mock(VolumeVO.class);
+        volumes.add(rootVolume1);
+        VolumeVO rootVolume2 = Mockito.mock(VolumeVO.class);
+        volumes.add(rootVolume2);
+        when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(volumes);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testRestoreVirtualMachineWithVMSnapshots() throws ResourceUnavailableException, InsufficientCapacityException {
+        long userId = 1l;
+        long accountId = 2l;
+        long currentTemplateId = 1l;
+        long newTemplateId = 2l;
+        when(accountMock.getId()).thenReturn(userId);
+        when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
+        when(userVmVoMock.getAccountId()).thenReturn(accountId);
+        when(accountDao.findById(accountId)).thenReturn(callerAccount);
+        when(userVmVoMock.getState()).thenReturn(VirtualMachine.State.Running);
+        when(userVmVoMock.getTemplateId()).thenReturn(currentTemplateId);
+
+        VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class);
+        when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate);
+        List<VolumeVO> volumes = new ArrayList<>();
+        VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class);
+        volumes.add(rootVolumeOfVm);
+        when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(volumes);
+        List<VMSnapshotVO> vmSnapshots = new ArrayList<>();
+        VMSnapshotVO vmSnapshot = Mockito.mock(VMSnapshotVO.class);
+        vmSnapshots.add(vmSnapshot);
+        when(vmSnapshotDaoMock.findByVm(vmId)).thenReturn(vmSnapshots);
+
+        userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null);
+    }
 }
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerTest.java
index 7cc2c8a..4bdfd49 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerTest.java
@@ -17,59 +17,6 @@
 
 package com.cloud.vm;
 
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
-import org.apache.cloudstack.api.BaseCmd;
-import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
-import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd;
-import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd;
-import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnitRunner;
-
 import com.cloud.capacity.CapacityManager;
 import com.cloud.configuration.ConfigurationManager;
 import com.cloud.dc.DataCenter.NetworkType;
@@ -119,6 +66,55 @@
 import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.VMSnapshotVO;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
+import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd;
+import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd;
+import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class UserVmManagerTest {
@@ -841,26 +837,4 @@
         _userVmMgr.persistDeviceBusInfo(_vmMock, "lsilogic");
         verify(_vmDao, times(1)).saveDetails(any(UserVmVO.class));
     }
-
-    @Test
-    public void testValideBase64WithoutPadding() {
-        // fo should be encoded in base64 either as Zm8 or Zm8=
-        String encodedUserdata = "Zm8";
-        String encodedUserdataWithPadding = "Zm8=";
-
-        // Verify that we accept both but return the padded version
-        assertTrue("validate return the value with padding", encodedUserdataWithPadding.equals(_userVmMgr.validateUserData(encodedUserdata, BaseCmd.HTTPMethod.GET)));
-        assertTrue("validate return the value with padding", encodedUserdataWithPadding.equals(_userVmMgr.validateUserData(encodedUserdataWithPadding, BaseCmd.HTTPMethod.GET)));
-    }
-
-    @Test
-    public void testValidateUrlEncodedBase64() throws UnsupportedEncodingException {
-        // fo should be encoded in base64 either as Zm8 or Zm8=
-        String encodedUserdata = "Zm+8/w8=";
-        String urlEncodedUserdata = java.net.URLEncoder.encode(encodedUserdata, "UTF-8");
-
-        // Verify that we accept both but return the padded version
-        assertEquals("validate return the value with padding", encodedUserdata, _userVmMgr.validateUserData(encodedUserdata, BaseCmd.HTTPMethod.GET));
-        assertEquals("validate return the value with padding", encodedUserdata, _userVmMgr.validateUserData(urlEncodedUserdata, BaseCmd.HTTPMethod.GET));
-    }
 }
diff --git a/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoImplTest.java b/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoImplTest.java
index 16ab18c..5720619 100644
--- a/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoImplTest.java
+++ b/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoImplTest.java
@@ -16,19 +16,16 @@
 // under the License.
 package com.cloud.vm.dao;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
+import com.cloud.vm.UserVmCloneSettingVO;
 import junit.framework.TestCase;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
-import com.cloud.vm.UserVmCloneSettingVO;
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = "classpath:/CloneSettingDaoTestContext.xml")
diff --git a/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoTestConfiguration.java b/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoTestConfiguration.java
index 9c18732..28e8e18 100644
--- a/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoTestConfiguration.java
+++ b/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoTestConfiguration.java
@@ -17,8 +17,7 @@
 
 package com.cloud.vm.dao;
 
-import java.io.IOException;
-
+import org.apache.cloudstack.test.utils.SpringUtils;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.ComponentScan.Filter;
 import org.springframework.context.annotation.Configuration;
@@ -27,7 +26,7 @@
 import org.springframework.core.type.classreading.MetadataReaderFactory;
 import org.springframework.core.type.filter.TypeFilter;
 
-import org.apache.cloudstack.test.utils.SpringUtils;
+import java.io.IOException;
 
 @Configuration
 @ComponentScan(basePackageClasses = {UserVmCloneSettingDaoImpl.class}, includeFilters = {@Filter(value = UserVmCloneSettingDaoTestConfiguration.Library.class,
diff --git a/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java b/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java
index e25a069..fa0f489 100644
--- a/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java
+++ b/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java
@@ -16,16 +16,14 @@
 // under the License.
 package com.cloud.vm.dao;
 
-import javax.inject.Inject;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Test;
-
 import com.cloud.hypervisor.Hypervisor;
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VirtualMachine;
+import junit.framework.TestCase;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Test;
+
+import javax.inject.Inject;
 
 public class UserVmDaoImplTest extends TestCase {
     @Inject
diff --git a/server/src/test/java/com/cloud/vm/dao/UserVmDaoTestConfiguration.java b/server/src/test/java/com/cloud/vm/dao/UserVmDaoTestConfiguration.java
index 912be4e..0e7d73a 100644
--- a/server/src/test/java/com/cloud/vm/dao/UserVmDaoTestConfiguration.java
+++ b/server/src/test/java/com/cloud/vm/dao/UserVmDaoTestConfiguration.java
@@ -17,8 +17,7 @@
 
 package com.cloud.vm.dao;
 
-import java.io.IOException;
-
+import org.apache.cloudstack.test.utils.SpringUtils;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.ComponentScan.Filter;
 import org.springframework.context.annotation.Configuration;
@@ -27,7 +26,7 @@
 import org.springframework.core.type.classreading.MetadataReaderFactory;
 import org.springframework.core.type.filter.TypeFilter;
 
-import org.apache.cloudstack.test.utils.SpringUtils;
+import java.io.IOException;
 
 @Configuration
 @ComponentScan(basePackageClasses = {UserVmDaoImpl.class},
diff --git a/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java b/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java
index 91cdbb5..532b2fa 100644
--- a/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java
+++ b/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java
@@ -16,38 +16,6 @@
 // under the License.
 package com.cloud.vm.snapshot;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.acl.SecurityChecker.AccessType;
-import org.apache.cloudstack.api.ResourceDetail;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Matchers;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.exception.AgentUnavailableException;
 import com.cloud.exception.ConcurrentOperationException;
@@ -88,6 +56,37 @@
 import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.ResourceDetail;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class VMSnapshotManagerTest {
     @Spy
diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
index b9b8ca8..8c6e73f 100644
--- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
@@ -16,13 +16,38 @@
 // under the License.
 package com.cloud.vpc;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-
+import com.cloud.configuration.ConfigurationManager;
+import com.cloud.configuration.ConfigurationService;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenter.NetworkType;
+import com.cloud.dc.DataCenterGuestIpv6Prefix;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.HostPodVO;
+import com.cloud.dc.Pod;
+import com.cloud.dc.Vlan;
+import com.cloud.domain.Domain;
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.Network.Capability;
+import com.cloud.network.Network.GuestType;
+import com.cloud.network.Network.Provider;
+import com.cloud.network.Network.Service;
+import com.cloud.network.Networks.TrafficType;
+import com.cloud.offering.DiskOffering;
+import com.cloud.offering.NetworkOffering;
+import com.cloud.offering.NetworkOffering.Availability;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.offerings.NetworkOfferingVO;
+import com.cloud.offerings.dao.NetworkOfferingDaoImpl;
+import com.cloud.org.Grouping.AllocationState;
+import com.cloud.user.Account;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.net.NetUtils;
 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.CreateGuestNetworkIpv6PrefixCmd;
@@ -61,38 +86,11 @@
 import org.apache.cloudstack.region.PortableIpRange;
 import org.springframework.stereotype.Component;
 
-import com.cloud.configuration.ConfigurationManager;
-import com.cloud.configuration.ConfigurationService;
-import com.cloud.dc.ClusterVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.dc.DataCenter.NetworkType;
-import com.cloud.dc.DataCenterGuestIpv6Prefix;
-import com.cloud.dc.DataCenterVO;
-import com.cloud.dc.HostPodVO;
-import com.cloud.dc.Pod;
-import com.cloud.dc.Vlan;
-import com.cloud.domain.Domain;
-import com.cloud.exception.ConcurrentOperationException;
-import com.cloud.exception.InsufficientCapacityException;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.ResourceAllocationException;
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.network.Network.Capability;
-import com.cloud.network.Network.GuestType;
-import com.cloud.network.Network.Provider;
-import com.cloud.network.Network.Service;
-import com.cloud.network.Networks.TrafficType;
-import com.cloud.offering.DiskOffering;
-import com.cloud.offering.NetworkOffering;
-import com.cloud.offering.NetworkOffering.Availability;
-import com.cloud.offering.ServiceOffering;
-import com.cloud.offerings.NetworkOfferingVO;
-import com.cloud.offerings.dao.NetworkOfferingDaoImpl;
-import com.cloud.org.Grouping.AllocationState;
-import com.cloud.user.Account;
-import com.cloud.utils.Pair;
-import com.cloud.utils.component.ManagerBase;
-import com.cloud.utils.net.NetUtils;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 @Component
 public class MockConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService {
diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
index bd0c73f..288211c 100644
--- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
@@ -24,12 +24,17 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.dc.DataCenter;
+import com.cloud.network.PublicIpQuarantine;
+import com.cloud.utils.fsm.NoTransitionException;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.api.command.admin.address.ReleasePodIpCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd;
 import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd;
 import org.apache.cloudstack.api.command.admin.network.ListGuestVlansCmd;
 import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd;
+import org.apache.cloudstack.api.command.user.address.RemoveQuarantinedIpCmd;
+import org.apache.cloudstack.api.command.user.address.UpdateQuarantinedIpCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd;
 import org.apache.cloudstack.api.command.user.network.CreateNetworkPermissionsCmd;
 import org.apache.cloudstack.api.command.user.network.ListNetworkPermissionsCmd;
@@ -272,6 +277,15 @@
         return null;
     }
 
+    /* (non-Javadoc)
+     * @see com.cloud.network.NetworkService#getIp(String)
+     */
+    @Override
+    public IpAddress getIp(String ipAddress) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
     @Override
     public Network updateGuestNetwork(final UpdateNetworkCmd cmd) {
         // TODO Auto-generated method stub
@@ -812,6 +826,11 @@
     }
 
     @Override
+    public boolean stateTransitTo(Network network, Network.Event e) throws NoTransitionException {
+        return true;
+    }
+
+    @Override
     public boolean isNetworkInlineMode(Network network) {
         // TODO Auto-generated method stub
         return false;
@@ -1018,7 +1037,7 @@
     }
 
     @Override
-    public Pair<NicProfile, Integer> importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, boolean forced) {
+    public Pair<NicProfile, Integer> importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, DataCenter dataCenter, boolean forced) {
         return null;
     }
 
@@ -1054,4 +1073,14 @@
     @Override
     public void validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(final Long serviceOfferingId) {
     }
+
+    @Override
+    public PublicIpQuarantine updatePublicIpAddressInQuarantine(UpdateQuarantinedIpCmd cmd) {
+        return null;
+    }
+
+    @Override
+    public void removePublicIpAddressFromQuarantine(RemoveQuarantinedIpCmd cmd) {
+
+    }
 }
diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java
index 291a2e2..ebee9fe 100644
--- a/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java
@@ -17,14 +17,6 @@
 
 package com.cloud.vpc;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.Vlan;
 import com.cloud.exception.InsufficientAddressCapacityException;
@@ -59,6 +51,13 @@
 import com.cloud.vm.NicProfile;
 import com.cloud.vm.VirtualMachine;
 
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 public class MockNetworkModelImpl extends ManagerBase implements NetworkModel {
 
     @Inject
diff --git a/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java
index 9b091de..3b29b3b 100644
--- a/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java
@@ -16,15 +16,6 @@
 // under the License.
 package com.cloud.vpc;
 
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
-import com.cloud.utils.exception.CloudRuntimeException;
-import org.apache.cloudstack.user.ResourceReservation;
-import org.springframework.stereotype.Component;
-
 import com.cloud.configuration.Resource.ResourceType;
 import com.cloud.configuration.ResourceCount;
 import com.cloud.configuration.ResourceLimit;
@@ -33,6 +24,13 @@
 import com.cloud.user.Account;
 import com.cloud.user.ResourceLimitService;
 import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.user.ResourceReservation;
+import org.springframework.stereotype.Component;
+
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
 
 @Component
 public class MockResourceLimitManagerImpl extends ManagerBase implements ResourceLimitService {
diff --git a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java
index 6923693..3558eed 100644
--- a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java
@@ -16,11 +16,17 @@
 // under the License.
 package com.cloud.vpc;
 
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
+import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.Site2SiteCustomerGateway;
+import com.cloud.network.Site2SiteVpnConnection;
+import com.cloud.network.Site2SiteVpnGateway;
+import com.cloud.network.dao.Site2SiteVpnConnectionVO;
+import com.cloud.network.vpn.Site2SiteVpnManager;
+import com.cloud.network.vpn.Site2SiteVpnService;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.vm.DomainRouterVO;
 import org.apache.cloudstack.api.command.user.vpn.CreateVpnConnectionCmd;
 import org.apache.cloudstack.api.command.user.vpn.CreateVpnCustomerGatewayCmd;
 import org.apache.cloudstack.api.command.user.vpn.CreateVpnGatewayCmd;
@@ -34,17 +40,9 @@
 import org.apache.cloudstack.api.command.user.vpn.UpdateVpnCustomerGatewayCmd;
 import org.springframework.stereotype.Component;
 
-import com.cloud.exception.NetworkRuleConflictException;
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.network.Site2SiteCustomerGateway;
-import com.cloud.network.Site2SiteVpnConnection;
-import com.cloud.network.Site2SiteVpnGateway;
-import com.cloud.network.dao.Site2SiteVpnConnectionVO;
-import com.cloud.network.vpn.Site2SiteVpnManager;
-import com.cloud.network.vpn.Site2SiteVpnService;
-import com.cloud.utils.Pair;
-import com.cloud.utils.component.ManagerBase;
-import com.cloud.vm.DomainRouterVO;
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
 
 @Component
 public class MockSite2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager, Site2SiteVpnService {
diff --git a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnServiceProvider.java b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnServiceProvider.java
index 95814c2..a2360e0 100644
--- a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnServiceProvider.java
+++ b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnServiceProvider.java
@@ -17,16 +17,14 @@
 
 package com.cloud.vpc;
 
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
-import org.springframework.stereotype.Component;
-
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.network.Site2SiteVpnConnection;
 import com.cloud.network.element.Site2SiteVpnServiceProvider;
 import com.cloud.utils.component.ManagerBase;
+import org.springframework.stereotype.Component;
+
+import javax.naming.ConfigurationException;
+import java.util.Map;
 
 @Component
 public class MockSite2SiteVpnServiceProvider extends ManagerBase implements Site2SiteVpnServiceProvider {
diff --git a/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java b/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java
index fa14f81..3949fa8 100644
--- a/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java
+++ b/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java
@@ -17,15 +17,6 @@
 
 package com.cloud.vpc;
 
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.ConfigurationException;
-
-import org.apache.cloudstack.api.command.admin.router.UpgradeRouterCmd;
-import org.apache.cloudstack.api.command.admin.router.UpgradeRouterTemplateCmd;
-import org.springframework.stereotype.Component;
-
 import com.cloud.exception.AgentUnavailableException;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
@@ -44,6 +35,13 @@
 import com.cloud.vm.DomainRouterVO;
 import com.cloud.vm.Nic;
 import com.cloud.vm.VirtualMachineProfile;
+import org.apache.cloudstack.api.command.admin.router.UpgradeRouterCmd;
+import org.apache.cloudstack.api.command.admin.router.UpgradeRouterTemplateCmd;
+import org.springframework.stereotype.Component;
+
+import javax.naming.ConfigurationException;
+import java.util.List;
+import java.util.Map;
 
 @Component
 public class MockVpcVirtualNetworkApplianceManager extends ManagerBase implements VpcVirtualNetworkApplianceManager, VpcVirtualNetworkApplianceService {
diff --git a/server/src/test/java/com/cloud/vpc/NetworkACLServiceTest.java b/server/src/test/java/com/cloud/vpc/NetworkACLServiceTest.java
index b2ea7d8..0709243 100644
--- a/server/src/test/java/com/cloud/vpc/NetworkACLServiceTest.java
+++ b/server/src/test/java/com/cloud/vpc/NetworkACLServiceTest.java
@@ -15,31 +15,6 @@
 
 package com.cloud.vpc;
 
-import java.io.IOException;
-import java.util.UUID;
-
-import javax.inject.Inject;
-
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.test.utils.SpringUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.network.NetworkModel;
 import com.cloud.network.dao.NetworkDao;
@@ -64,8 +39,30 @@
 import com.cloud.user.UserVO;
 import com.cloud.utils.component.ComponentContext;
 import com.cloud.utils.db.EntityManager;
-
 import junit.framework.TestCase;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.UUID;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
diff --git a/server/src/test/java/com/cloud/vpc/VpcApiUnitTest.java b/server/src/test/java/com/cloud/vpc/VpcApiUnitTest.java
index 66e6d65..350fd5c 100644
--- a/server/src/test/java/com/cloud/vpc/VpcApiUnitTest.java
+++ b/server/src/test/java/com/cloud/vpc/VpcApiUnitTest.java
@@ -16,17 +16,6 @@
 // under the License.
 package com.cloud.vpc;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.network.Network.Service;
 import com.cloud.network.vpc.Vpc;
@@ -34,8 +23,16 @@
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.user.AccountVO;
 import com.cloud.utils.component.ComponentContext;
-
 import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations = "classpath:/VpcTestContext.xml")
diff --git a/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java b/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java
index 285a7ae..54bb085 100644
--- a/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java
+++ b/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java
@@ -17,23 +17,6 @@
 
 package com.cloud.vpc;
 
-import java.io.IOException;
-
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.ComponentScan.Filter;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-
-import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl;
-import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl;
-import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl;
-import org.apache.cloudstack.test.utils.SpringUtils;
-
 import com.cloud.alert.AlertManager;
 import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl;
 import com.cloud.configuration.dao.ResourceCountDaoImpl;
@@ -107,6 +90,21 @@
 import com.cloud.vpc.dao.MockVpcDaoImpl;
 import com.cloud.vpc.dao.MockVpcOfferingDaoImpl;
 import com.cloud.vpc.dao.MockVpcOfferingServiceMapDaoImpl;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl;
+import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl;
+import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+
+import java.io.IOException;
 
 @Configuration
 @ComponentScan(basePackageClasses = {VpcManagerImpl.class, NetworkElement.class, VpcOfferingDao.class, ConfigurationDaoImpl.class, ConfigurationGroupDaoImpl.class,
diff --git a/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java
index d82a5d8..012ae73 100644
--- a/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java
+++ b/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java
@@ -16,13 +16,12 @@
 // under the License.
 package com.cloud.vpc.dao;
 
-import java.util.HashMap;
-import java.util.Map;
-
+import com.cloud.utils.db.GenericDaoBase;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
 
-import com.cloud.utils.db.GenericDaoBase;
+import java.util.HashMap;
+import java.util.Map;
 
 public class MockConfigurationDaoImpl extends GenericDaoBase<ConfigurationVO, String> implements ConfigurationDao {
 
diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java
index 8e328fa..fe0b3ea 100644
--- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java
+++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java
@@ -16,11 +16,6 @@
 // under the License.
 package com.cloud.vpc.dao;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-
 import com.cloud.network.Network;
 import com.cloud.network.Network.GuestType;
 import com.cloud.network.Networks.TrafficType;
@@ -31,6 +26,10 @@
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 @DB()
 public class MockNetworkDaoImpl extends GenericDaoBase<NetworkVO, Long> implements NetworkDao {
 
diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkOfferingDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkOfferingDaoImpl.java
index cab6afa..43bb882 100644
--- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkOfferingDaoImpl.java
+++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkOfferingDaoImpl.java
@@ -16,12 +16,6 @@
 // under the License.
 package com.cloud.vpc.dao;
 
-import java.lang.reflect.Field;
-import java.util.List;
-
-
-import org.apache.log4j.Logger;
-
 import com.cloud.network.Network;
 import com.cloud.network.Network.GuestType;
 import com.cloud.network.Networks.TrafficType;
@@ -32,6 +26,10 @@
 import com.cloud.offerings.dao.NetworkOfferingDao;
 import com.cloud.offerings.dao.NetworkOfferingDaoImpl;
 import com.cloud.utils.db.DB;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Field;
+import java.util.List;
 
 @DB()
 public class MockNetworkOfferingDaoImpl extends NetworkOfferingDaoImpl implements NetworkOfferingDao {
diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkServiceMapDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkServiceMapDaoImpl.java
index 33f9332..d519264 100644
--- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkServiceMapDaoImpl.java
+++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkServiceMapDaoImpl.java
@@ -16,9 +16,6 @@
 // under the License.
 package com.cloud.vpc.dao;
 
-import java.util.List;
-
-
 import com.cloud.network.Network.Provider;
 import com.cloud.network.Network.Service;
 import com.cloud.network.dao.NetworkServiceMapDao;
@@ -26,6 +23,8 @@
 import com.cloud.utils.db.DB;
 import com.cloud.utils.db.GenericDaoBase;
 
+import java.util.List;
+
 @DB()
 public class MockNetworkServiceMapDaoImpl extends GenericDaoBase<NetworkServiceMapVO, Long> implements NetworkServiceMapDao {
 
diff --git a/server/src/test/java/com/cloud/vpc/dao/MockVpcDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockVpcDaoImpl.java
index 6322a1d..4ef5506 100644
--- a/server/src/test/java/com/cloud/vpc/dao/MockVpcDaoImpl.java
+++ b/server/src/test/java/com/cloud/vpc/dao/MockVpcDaoImpl.java
@@ -16,19 +16,17 @@
 // under the License.
 package com.cloud.vpc.dao;
 
-import java.lang.reflect.Field;
-import java.util.List;
-import java.util.Map;
-
-
-import org.apache.log4j.Logger;
-
 import com.cloud.network.vpc.Vpc;
 import com.cloud.network.vpc.Vpc.State;
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.db.GenericDaoBase;
+import org.apache.log4j.Logger;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
 
 @DB()
 public class MockVpcDaoImpl extends GenericDaoBase<VpcVO, Long> implements VpcDao {
diff --git a/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingServiceMapDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingServiceMapDaoImpl.java
index 59437bf..5685c50 100644
--- a/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingServiceMapDaoImpl.java
+++ b/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingServiceMapDaoImpl.java
@@ -16,15 +16,14 @@
 // under the License.
 package com.cloud.vpc.dao;
 
-import java.util.List;
-
-
 import com.cloud.network.Network.Service;
 import com.cloud.network.vpc.VpcOfferingServiceMapVO;
 import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao;
 import com.cloud.utils.db.DB;
 import com.cloud.utils.db.GenericDaoBase;
 
+import java.util.List;
+
 @DB()
 public class MockVpcOfferingServiceMapDaoImpl extends GenericDaoBase<VpcOfferingServiceMapVO, Long> implements VpcOfferingServiceMapDao {
 
diff --git a/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java
index 049bc17..a135046 100644
--- a/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java
@@ -17,9 +17,9 @@
 
 package org.apache.cloudstack.acl;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.utils.Pair;
 import org.apache.cloudstack.acl.dao.RoleDao;
 import org.apache.commons.collections.CollectionUtils;
 import org.junit.Assert;
@@ -32,9 +32,8 @@
 import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.utils.Pair;
+import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(MockitoJUnitRunner.class)
 public class RoleManagerImplTest {
@@ -166,7 +165,7 @@
         String roleName = "roleName";
         List<Role> roles = new ArrayList<>();
         Pair<ArrayList<RoleVO>, Integer> toBeReturned = new Pair(roles, 0);
-        Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByName(roleName, null, null, null);
+        Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByName(roleName, null, null, null, false);
 
         roleManagerImpl.findRolesByName(roleName);
         Mockito.verify(roleManagerImpl).removeRootAdminRolesIfNeeded(roles);
@@ -239,7 +238,7 @@
 
         Assert.assertEquals(0, returnedRoles.size());
         Mockito.verify(accountManagerMock, Mockito.times(1)).isRootAdmin(Mockito.anyLong());
-        Mockito.verify(roleDaoMock, Mockito.times(0)).findAllByRoleType(Mockito.any(RoleType.class));
+        Mockito.verify(roleDaoMock, Mockito.times(0)).findAllByRoleType(Mockito.any(RoleType.class), Mockito.anyBoolean());
     }
 
     @Test
@@ -250,11 +249,11 @@
         List<Role> roles = new ArrayList<>();
         roles.add(Mockito.mock(Role.class));
         Pair<ArrayList<RoleVO>, Integer> toBeReturned = new Pair(roles, 1);
-        Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.Admin, null, null);
+        Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.Admin, null, null, true);
         List<Role> returnedRoles = roleManagerImpl.findRolesByType(RoleType.Admin);
 
         Assert.assertEquals(1, returnedRoles.size());
-        Mockito.verify(accountManagerMock, Mockito.times(1)).isRootAdmin(Mockito.anyLong());
+        Mockito.verify(accountManagerMock, Mockito.times(2)).isRootAdmin(Mockito.anyLong());
     }
 
     @Test
@@ -265,11 +264,11 @@
         List<Role> roles = new ArrayList<>();
         roles.add(Mockito.mock(Role.class));
         Pair<ArrayList<RoleVO>, Integer> toBeReturned = new Pair(roles, 1);
-        Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.User, null, null);
+        Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.User, null, null, true);
         List<Role> returnedRoles = roleManagerImpl.findRolesByType(RoleType.User);
 
         Assert.assertEquals(1, returnedRoles.size());
-        Mockito.verify(accountManagerMock, Mockito.times(0)).isRootAdmin(Mockito.anyLong());
+        Mockito.verify(accountManagerMock, Mockito.times(1)).isRootAdmin(Mockito.anyLong());
     }
 
     @Test
diff --git a/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java b/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java
index 2e56484..361d026 100644
--- a/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java
+++ b/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java
@@ -16,23 +16,29 @@
 // under the License.
 package org.apache.cloudstack.affinity;
 
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-
+import com.cloud.dc.dao.DedicatedResourceDao;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventVO;
+import com.cloud.event.dao.EventDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceInUseException;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.projects.dao.ProjectDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountService;
+import com.cloud.user.AccountVO;
+import com.cloud.user.DomainManager;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.UserVmDao;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
 import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
@@ -60,29 +66,21 @@
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
-import com.cloud.dc.dao.DedicatedResourceDao;
-import com.cloud.domain.dao.DomainDao;
-import com.cloud.event.ActionEventUtils;
-import com.cloud.event.EventVO;
-import com.cloud.event.dao.EventDao;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.ResourceInUseException;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.projects.dao.ProjectDao;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.user.AccountService;
-import com.cloud.user.AccountVO;
-import com.cloud.user.DomainManager;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.user.dao.UserDao;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.utils.db.EntityManager;
-import com.cloud.vm.UserVmVO;
-import com.cloud.vm.VirtualMachine;
-import com.cloud.vm.dao.UserVmDao;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
diff --git a/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java
index 30e1e45..e25ea6a 100644
--- a/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java
@@ -16,24 +16,29 @@
 // under the License.
 package org.apache.cloudstack.affinity;
 
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.inject.Inject;
-import javax.naming.ConfigurationException;
-
+import com.cloud.dc.dao.DedicatedResourceDao;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventVO;
+import com.cloud.event.dao.EventDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceInUseException;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.projects.dao.ProjectDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountService;
+import com.cloud.user.AccountVO;
+import com.cloud.user.DomainManager;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.UserVmDao;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
 import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
@@ -63,29 +68,22 @@
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
-import com.cloud.dc.dao.DedicatedResourceDao;
-import com.cloud.domain.dao.DomainDao;
-import com.cloud.event.ActionEventUtils;
-import com.cloud.event.EventVO;
-import com.cloud.event.dao.EventDao;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.ResourceInUseException;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.projects.dao.ProjectDao;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.user.AccountService;
-import com.cloud.user.AccountVO;
-import com.cloud.user.DomainManager;
-import com.cloud.user.User;
-import com.cloud.user.UserVO;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.user.dao.UserDao;
-import com.cloud.utils.component.ComponentContext;
-import com.cloud.utils.db.EntityManager;
-import com.cloud.vm.UserVmVO;
-import com.cloud.vm.VirtualMachine;
-import com.cloud.vm.dao.UserVmDao;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
diff --git a/server/src/test/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java
index 98c2af5..4ba381e 100644
--- a/server/src/test/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java
@@ -16,14 +16,13 @@
 // under the License.
 package org.apache.cloudstack.agent.lb;
 
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-
+import com.cloud.agent.AgentManager;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.resource.ResourceState;
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.config.ApiServiceConfiguration;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.messagebus.MessageBus;
@@ -35,13 +34,13 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
-import com.cloud.agent.AgentManager;
-import com.cloud.host.Host;
-import com.cloud.host.HostVO;
-import com.cloud.host.dao.HostDao;
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.resource.ResourceState;
-import com.cloud.utils.exception.CloudRuntimeException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.mockito.Mockito.when;
 
 public class IndirectAgentLBServiceImplTest {
 
diff --git a/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java b/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java
index e4d5f45..e8182ba 100644
--- a/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java
+++ b/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java
@@ -16,15 +16,15 @@
 // under the License.
 package org.apache.cloudstack.agent.lb.algorithm;
 
+import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm;
+import org.junit.Assert;
+import org.junit.Test;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm;
-import org.junit.Assert;
-import org.junit.Test;
-
 public class IndirectAgentLBRoundRobinAlgorithmTest {
     private IndirectAgentLBAlgorithm algorithm = new IndirectAgentLBRoundRobinAlgorithm();
 
diff --git a/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java b/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java
index b7d18cc..6e1b33f 100644
--- a/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java
+++ b/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java
@@ -16,15 +16,15 @@
 // under the License.
 package org.apache.cloudstack.agent.lb.algorithm;
 
+import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm;
+import org.junit.Assert;
+import org.junit.Test;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm;
-import org.junit.Assert;
-import org.junit.Test;
-
 public class IndirectAgentLBShuffleAlgorithmTest {
     private IndirectAgentLBAlgorithm algorithm = new IndirectAgentLBShuffleAlgorithm();
 
diff --git a/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java b/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java
index b567b16..73e2a92 100644
--- a/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java
+++ b/server/src/test/java/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java
@@ -16,14 +16,14 @@
 // under the License.
 package org.apache.cloudstack.agent.lb.algorithm;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 public class IndirectAgentLBStaticAlgorithmTest {
     private IndirectAgentLBAlgorithm algorithm = new IndirectAgentLBStaticAlgorithm();
 
diff --git a/server/src/test/java/org/apache/cloudstack/annotation/AnnotationManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/annotation/AnnotationManagerImplTest.java
index 46bf508..f4dd2ca 100644
--- a/server/src/test/java/org/apache/cloudstack/annotation/AnnotationManagerImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/annotation/AnnotationManagerImplTest.java
@@ -16,26 +16,6 @@
 // under the License.
 package org.apache.cloudstack.annotation;
 
-import java.util.UUID;
-
-import org.apache.cloudstack.acl.Role;
-import org.apache.cloudstack.acl.RoleService;
-import org.apache.cloudstack.acl.RoleType;
-import org.apache.cloudstack.annotation.dao.AnnotationDao;
-import org.apache.cloudstack.api.ApiCommandResourceType;
-import org.apache.cloudstack.api.command.admin.annotation.RemoveAnnotationCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.dc.dao.HostPodDao;
@@ -66,6 +46,25 @@
 import com.cloud.vm.dao.InstanceGroupDao;
 import com.cloud.vm.dao.VMInstanceDao;
 import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+import org.apache.cloudstack.acl.Role;
+import org.apache.cloudstack.acl.RoleService;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.command.admin.annotation.RemoveAnnotationCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.UUID;
 
 @RunWith(MockitoJUnitRunner.class)
 public class AnnotationManagerImplTest {
diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java
index 077518c..37fbf04 100644
--- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java
+++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java
@@ -15,24 +15,41 @@
 // specific language governing permissions and limitations
 // under the License.
 package org.apache.cloudstack.backup;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
 
+import com.cloud.event.ActionEventUtils;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeApiService;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.fsm.NoTransitionException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
 import org.apache.cloudstack.backup.dao.BackupOfferingDao;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.utils.Pair;
+import java.util.Collections;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class BackupManagerTest {
     @Spy
     @InjectMocks
@@ -44,6 +61,15 @@
     @Mock
     BackupProvider backupProvider;
 
+    @Mock
+    VirtualMachineManager virtualMachineManager;
+
+    @Mock
+    VolumeApiService volumeApiService;
+
+    @Mock
+    VolumeDao volumeDao;
+
     private String[] hostPossibleValues = {"127.0.0.1", "hostname"};
     private String[] datastoresPossibleValues = {"e9804933-8609-4de3-bccc-6278072a496c", "datastore-name"};
 
@@ -54,15 +80,11 @@
         when(backupOfferingDao.findById(123l)).thenReturn(null);
 
         BackupOfferingVO offering = Mockito.spy(BackupOfferingVO.class);
-        when(offering.getId()).thenReturn(1234l);
         when(offering.getName()).thenCallRealMethod();
         when(offering.getDescription()).thenCallRealMethod();
         when(offering.isUserDrivenBackupAllowed()).thenCallRealMethod();
 
         BackupOfferingVO offeringUpdate = Mockito.spy(BackupOfferingVO.class);
-        when(offeringUpdate.getId()).thenReturn(1234l);
-        when(offeringUpdate.getName()).thenReturn("Old name");
-        when(offeringUpdate.getDescription()).thenReturn("Old description");
 
         when(backupOfferingDao.findById(1234l)).thenReturn(offering);
         when(backupOfferingDao.createForUpdate(1234l)).thenReturn(offeringUpdate);
@@ -189,4 +211,66 @@
         Mockito.verify(backupProvider, times(4)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(),
                 Mockito.anyString(), Mockito.anyString());
     }
+
+    @Test
+    public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException {
+        BackupOffering offering = Mockito.mock(BackupOffering.class);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+        BackupVO backup = Mockito.mock(BackupVO.class);
+
+        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
+            Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
+                    Mockito.eq(true), Mockito.eq(0))).thenReturn(1L);
+            Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.eq(0))).thenReturn(2L);
+
+            Mockito.when(volumeDao.findIncludingRemovedByInstanceAndType(1L, null)).thenReturn(Collections.singletonList(volumeVO));
+            Mockito.when(virtualMachineManager.stateTransitTo(Mockito.eq(vm), Mockito.eq(VirtualMachine.Event.RestoringRequested), Mockito.any())).thenReturn(true);
+            Mockito.when(volumeApiService.stateTransitTo(Mockito.eq(volumeVO), Mockito.eq(Volume.Event.RestoreRequested))).thenReturn(true);
+
+            Mockito.when(vm.getId()).thenReturn(1L);
+            Mockito.when(offering.getProvider()).thenReturn("veeam");
+            Mockito.doReturn(backupProvider).when(backupManager).getBackupProvider("veeam");
+            Mockito.when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(true);
+
+            backupManager.tryRestoreVM(backup, vm, offering, "Nothing to write here.");
+        }
+    }
+
+    @Test
+    public void tryRestoreVMTestRestoreFails() throws NoTransitionException {
+        BackupOffering offering = Mockito.mock(BackupOffering.class);
+        VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
+        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+        BackupVO backup = Mockito.mock(BackupVO.class);
+
+        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
+            Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
+                    Mockito.eq(true), Mockito.eq(0))).thenReturn(1L);
+            Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.eq(0))).thenReturn(2L);
+
+            Mockito.when(volumeDao.findIncludingRemovedByInstanceAndType(1L, null)).thenReturn(Collections.singletonList(volumeVO));
+            Mockito.when(virtualMachineManager.stateTransitTo(Mockito.eq(vm), Mockito.eq(VirtualMachine.Event.RestoringRequested), Mockito.any())).thenReturn(true);
+            Mockito.when(volumeApiService.stateTransitTo(Mockito.eq(volumeVO), Mockito.eq(Volume.Event.RestoreRequested))).thenReturn(true);
+            Mockito.when(virtualMachineManager.stateTransitTo(Mockito.eq(vm), Mockito.eq(VirtualMachine.Event.RestoringFailed), Mockito.any())).thenReturn(true);
+            Mockito.when(volumeApiService.stateTransitTo(Mockito.eq(volumeVO), Mockito.eq(Volume.Event.RestoreFailed))).thenReturn(true);
+
+            Mockito.when(vm.getId()).thenReturn(1L);
+            Mockito.when(offering.getProvider()).thenReturn("veeam");
+            Mockito.doReturn(backupProvider).when(backupManager).getBackupProvider("veeam");
+            Mockito.when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(false);
+            try {
+                backupManager.tryRestoreVM(backup, vm, offering, "Checking message error.");
+                fail("An exception is needed.");
+            } catch (CloudRuntimeException e) {
+                assertEquals("Error restoring VM from backup [Checking message error.].", e.getMessage());
+            }
+        }
+    }
 }
diff --git a/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java b/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java
index 5fb6bd6..c02a345 100644
--- a/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java
+++ b/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java
@@ -19,19 +19,12 @@
 
 package org.apache.cloudstack.ca;
 
-import static org.apache.cloudstack.ca.CAManager.AutomaticCertRenewal;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.nullable;
-
-import java.lang.reflect.Field;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.Status;
+import com.cloud.host.dao.HostDao;
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
 import org.apache.cloudstack.utils.security.CertUtils;
@@ -45,12 +38,18 @@
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.host.Host;
-import com.cloud.host.HostVO;
-import com.cloud.host.Status;
-import com.cloud.host.dao.HostDao;
-import com.cloud.storage.Storage;
-import com.cloud.utils.exception.CloudRuntimeException;
+import java.lang.reflect.Field;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.apache.cloudstack.ca.CAManager.AutomaticCertRenewal;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.nullable;
 
 @RunWith(MockitoJUnitRunner.class)
 public class CABackgroundTaskTest {
diff --git a/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java
index 5e57cc3..08fa552 100644
--- a/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java
@@ -19,20 +19,11 @@
 
 package org.apache.cloudstack.ca;
 
-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.nullable;
-
-import java.lang.reflect.Field;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.cert.X509Certificate;
-import java.util.Collections;
-import java.util.List;
-
+import com.cloud.agent.AgentManager;
+import com.cloud.certificate.CrlVO;
+import com.cloud.certificate.dao.CrlDao;
+import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.framework.ca.CAProvider;
 import org.apache.cloudstack.framework.ca.Certificate;
@@ -46,11 +37,19 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import com.cloud.agent.AgentManager;
-import com.cloud.certificate.CrlVO;
-import com.cloud.certificate.dao.CrlDao;
-import com.cloud.host.Host;
-import com.cloud.host.dao.HostDao;
+import java.lang.reflect.Field;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+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.nullable;
 
 @RunWith(MockitoJUnitRunner.class)
 public class CAManagerImplTest {
diff --git a/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
new file mode 100644
index 0000000..8aed790
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
@@ -0,0 +1,438 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.cluster;
+
+import com.cloud.api.query.dao.HostJoinDao;
+import com.cloud.api.query.vo.HostJoinVO;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventVO;
+import com.cloud.event.dao.EventDao;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.org.Cluster;
+import com.cloud.org.Grouping;
+import com.cloud.server.ManagementServer;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.VMInstanceDao;
+import org.apache.cloudstack.api.command.admin.cluster.GenerateClusterDrsPlanCmd;
+import org.apache.cloudstack.api.response.ClusterDrsPlanMigrationResponse;
+import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
+import org.apache.cloudstack.cluster.dao.ClusterDrsPlanDao;
+import org.apache.cloudstack.cluster.dao.ClusterDrsPlanMigrationDao;
+import org.apache.cloudstack.framework.config.ConfigKey;
+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.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import javax.naming.ConfigurationException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterDrsServiceImplTest {
+
+    @Mock
+    ClusterDrsAlgorithm condensedAlgorithm;
+
+    @Mock
+    ManagementServer managementServer;
+
+    @Mock
+    ClusterDrsAlgorithm balancedAlgorithm;
+
+    @Mock
+    GenerateClusterDrsPlanCmd cmd;
+
+    AutoCloseable closeable;
+
+    @Mock
+    private ClusterDao clusterDao;
+
+    @Mock
+    private ClusterDrsPlanDao drsPlanDao;
+
+    @Mock
+    private ClusterDrsPlanMigrationDao drsPlanMigrationDao;
+
+    @Mock
+    private EventDao eventDao;
+
+    @Mock
+    private HostDao hostDao;
+
+    @Mock
+    private HostJoinDao hostJoinDao;
+
+    @Mock
+    private ServiceOfferingDao serviceOfferingDao;
+
+    @Mock
+    private VMInstanceDao vmInstanceDao;
+
+    @Spy
+    @InjectMocks
+    private ClusterDrsServiceImpl clusterDrsService = new ClusterDrsServiceImpl();
+
+    private MockedStatic<GlobalLock> globalLockMocked;
+
+    @Before
+    public void setUp() throws NoSuchFieldException, IllegalAccessException {
+        closeable = MockitoAnnotations.openMocks(this);
+
+        HashMap<String, ClusterDrsAlgorithm> drsAlgorithmMap = new HashMap<>();
+        drsAlgorithmMap.put("balanced", balancedAlgorithm);
+        drsAlgorithmMap.put("condensed", condensedAlgorithm);
+
+        clusterDrsService.setDrsAlgorithms(List.of(new ClusterDrsAlgorithm[]{balancedAlgorithm, condensedAlgorithm}));
+        ReflectionTestUtils.setField(clusterDrsService, "drsAlgorithmMap", drsAlgorithmMap);
+        Field f = ConfigKey.class.getDeclaredField("_defaultValue");
+        f.setAccessible(true);
+        f.set(clusterDrsService.ClusterDrsAlgorithm, "balanced");
+        Mockito.when(cmd.getId()).thenReturn(1L);
+
+        globalLockMocked = Mockito.mockStatic(GlobalLock.class);
+        GlobalLock lock = Mockito.mock(GlobalLock.class);
+        Mockito.when(GlobalLock.getInternLock("cluster.drs.1")).thenReturn(lock);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        globalLockMocked.close();
+        closeable.close();
+    }
+
+    @Test
+    public void testGetCommands() {
+        assertFalse(clusterDrsService.getCommands().isEmpty());
+    }
+
+    @Test
+    public void testGetDrsPlan() throws ConfigurationException {
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getId()).thenReturn(1L);
+        Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+
+        HostVO host1 = Mockito.mock(HostVO.class);
+        Mockito.when(host1.getId()).thenReturn(1L);
+
+        HostVO host2 = Mockito.mock(HostVO.class);
+        Mockito.when(host2.getId()).thenReturn(2L);
+
+        VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
+        Mockito.when(vm1.getId()).thenReturn(1L);
+        Mockito.when(vm1.getHostId()).thenReturn(1L);
+
+        VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
+        Mockito.when(vm2.getHostId()).thenReturn(2L);
+
+        List<HostVO> hostList = new ArrayList<>();
+        hostList.add(host1);
+        hostList.add(host2);
+
+        HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
+        Mockito.when(hostJoin1.getId()).thenReturn(1L);
+        Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
+        Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
+        Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
+
+        HostJoinVO hostJoin2 = Mockito.mock(HostJoinVO.class);
+        Mockito.when(hostJoin2.getId()).thenReturn(2L);
+        Mockito.when(hostJoin2.getCpuUsedCapacity()).thenReturn(1000L);
+        Mockito.when(hostJoin2.getCpuReservedCapacity()).thenReturn(0L);
+        Mockito.when(hostJoin2.getMemUsedCapacity()).thenReturn(1024L);
+
+        List<VMInstanceVO> vmList = new ArrayList<>();
+        vmList.add(vm1);
+        vmList.add(vm2);
+
+        ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
+        Mockito.when(serviceOffering.getCpu()).thenReturn(1);
+        Mockito.when(serviceOffering.getRamSize()).thenReturn(1024);
+        Mockito.when(serviceOffering.getSpeed()).thenReturn(1000);
+
+        Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
+        Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
+        Mockito.when(balancedAlgorithm.needsDrs(Mockito.anyLong(), Mockito.anyList(), Mockito.anyList())).thenReturn(
+                true, false);
+        Mockito.when(
+                clusterDrsService.getBestMigration(Mockito.any(Cluster.class), Mockito.any(ClusterDrsAlgorithm.class),
+                        Mockito.anyList(), Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap())).thenReturn(
+                new Pair<>(vm1, host2));
+        Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(
+                serviceOffering);
+        Mockito.when(hostJoinDao.searchByIds(host1.getId(), host2.getId())).thenReturn(List.of(hostJoin1, hostJoin2));
+
+        List<Ternary<VirtualMachine, Host, Host>> iterations = clusterDrsService.getDrsPlan(cluster, 5);
+
+        Mockito.verify(hostDao, Mockito.times(1)).findByClusterId(1L);
+        Mockito.verify(vmInstanceDao, Mockito.times(1)).listByClusterId(1L);
+        Mockito.verify(balancedAlgorithm, Mockito.times(2)).needsDrs(Mockito.anyLong(), Mockito.anyList(),
+                Mockito.anyList());
+
+        assertEquals(1, iterations.size());
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGenerateDrsPlanClusterNotFound() {
+        Mockito.when(clusterDao.findById(1L)).thenReturn(null);
+        clusterDrsService.generateDrsPlan(cmd);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGenerateDrsPlanClusterDisabled() {
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getName()).thenReturn("testCluster");
+        Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
+
+        Mockito.when(clusterDao.findById(1L)).thenReturn(cluster);
+
+        clusterDrsService.generateDrsPlan(cmd);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGenerateDrsPlanClusterNotCloudManaged() {
+
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getName()).thenReturn("testCluster");
+        Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+
+        Mockito.when(clusterDao.findById(1L)).thenReturn(cluster);
+
+        clusterDrsService.generateDrsPlan(cmd);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testGenerateDrsPlanInvalidIterations() {
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getName()).thenReturn("testCluster");
+        Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+
+        Mockito.when(clusterDao.findById(1L)).thenReturn(cluster);
+        Mockito.when(cmd.getMaxMigrations()).thenReturn(0);
+
+        clusterDrsService.generateDrsPlan(cmd);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testGenerateDrsPlanConfigurationException() throws ConfigurationException {
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getId()).thenReturn(1L);
+        Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+        Mockito.when(clusterDao.findById(1L)).thenReturn(cluster);
+        Mockito.when(clusterDrsService.getDrsPlan(cluster, 5)).thenThrow(new ConfigurationException("test"));
+        Mockito.when(cmd.getMaxMigrations()).thenReturn(1);
+
+        clusterDrsService.generateDrsPlan(cmd);
+    }
+
+    @Test
+    public void testGenerateDrsPlan() throws ConfigurationException {
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getId()).thenReturn(1L);
+        Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
+
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(vm.getId()).thenReturn(1L);
+
+        Host srcHost = Mockito.mock(Host.class);
+        Mockito.when(srcHost.getId()).thenReturn(1L);
+
+        Host destHost = Mockito.mock(Host.class);
+        Mockito.when(destHost.getId()).thenReturn(2L);
+
+        Mockito.when(clusterDao.findById(1L)).thenReturn(cluster);
+        Mockito.when(eventDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(EventVO.class));
+        Mockito.when(cmd.getMaxMigrations()).thenReturn(2);
+        Mockito.doReturn(List.of(new Ternary<>(vm, srcHost,
+                destHost))).when(clusterDrsService).getDrsPlan(Mockito.any(Cluster.class), Mockito.anyInt());
+
+        ClusterDrsPlanMigrationResponse migrationResponse = Mockito.mock(ClusterDrsPlanMigrationResponse.class);
+
+        Mockito.when(clusterDrsService.getResponseObjectForMigrations(Mockito.anyList())).thenReturn(
+                List.of(migrationResponse));
+
+        try (MockedStatic<ActionEventUtils> ignored = Mockito.mockStatic(ActionEventUtils.class)) {
+            Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(),
+                    Mockito.anyLong(),
+                    Mockito.anyString(), Mockito.anyString(),
+                    Mockito.anyLong(), Mockito.anyString())).thenReturn(1L);
+
+            ClusterDrsPlanResponse response = clusterDrsService.generateDrsPlan(
+                    cmd);
+
+            assertEquals(1L, response.getMigrationPlans().size());
+            assertEquals(migrationResponse, response.getMigrationPlans().get(0));
+        }
+    }
+
+    @Test
+    public void testPoll() {
+        Mockito.doNothing().when(clusterDrsService).updateOldPlanMigrations();
+        Mockito.doNothing().when(clusterDrsService).processPlans();
+        Mockito.doNothing().when(clusterDrsService).generateDrsPlanForAllClusters();
+        Mockito.doNothing().when(clusterDrsService).cleanUpOldDrsPlans();
+
+        GlobalLock lock = Mockito.mock(GlobalLock.class);
+        Mockito.when(lock.lock(Mockito.anyInt())).thenReturn(true);
+
+        Mockito.when(GlobalLock.getInternLock(Mockito.anyString())).thenReturn(lock);
+
+        clusterDrsService.poll(new Date());
+
+        Mockito.verify(clusterDrsService, Mockito.times(1)).updateOldPlanMigrations();
+        Mockito.verify(clusterDrsService, Mockito.times(2)).processPlans();
+        Mockito.verify(clusterDrsService, Mockito.times(1)).generateDrsPlanForAllClusters();
+    }
+
+    @Test
+    public void testUpdateOldPlanMigrations() {
+        ClusterDrsPlanVO drsPlan1 = Mockito.mock(ClusterDrsPlanVO.class);
+        ClusterDrsPlanVO drsPlan2 = Mockito.mock(ClusterDrsPlanVO.class);
+
+        Mockito.when(drsPlanDao.listByStatus(ClusterDrsPlan.Status.IN_PROGRESS)).thenReturn(
+                List.of(drsPlan1, drsPlan2));
+
+        Mockito.doNothing().when(clusterDrsService).updateDrsPlanMigrations(drsPlan1);
+        Mockito.doNothing().when(clusterDrsService).updateDrsPlanMigrations(drsPlan2);
+
+        clusterDrsService.updateOldPlanMigrations();
+
+        Mockito.verify(clusterDrsService, Mockito.times(2)).updateDrsPlanMigrations(
+                Mockito.any(ClusterDrsPlanVO.class));
+    }
+
+    @Test
+    public void testGetBestMigration() throws ConfigurationException {
+        ClusterVO cluster = Mockito.mock(ClusterVO.class);
+        Mockito.when(cluster.getId()).thenReturn(1L);
+
+        HostVO destHost = Mockito.mock(HostVO.class);
+
+        HostVO host = Mockito.mock(HostVO.class);
+        Mockito.when(host.getId()).thenReturn(2L);
+
+        VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
+        Mockito.when(vm1.getId()).thenReturn(1L);
+        Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
+        Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
+        Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
+
+        VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
+        Mockito.when(vm2.getId()).thenReturn(2L);
+        Mockito.when(vm2.getType()).thenReturn(VirtualMachine.Type.User);
+        Mockito.when(vm2.getState()).thenReturn(VirtualMachine.State.Running);
+        Mockito.when(vm2.getDetails()).thenReturn(Collections.emptyMap());
+
+        List<VirtualMachine> vmList = new ArrayList<>();
+        vmList.add(vm1);
+        vmList.add(vm2);
+
+        Map<Long, List<VirtualMachine>> hostVmMap = new HashMap<>();
+        hostVmMap.put(host.getId(), new ArrayList<>());
+        hostVmMap.get(host.getId()).add(vm1);
+        hostVmMap.get(host.getId()).add(vm2);
+
+        Map<Long, ServiceOffering> vmIdServiceOfferingMap = new HashMap<>();
+
+        ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class);
+        for (VirtualMachine vm : vmList) {
+            vmIdServiceOfferingMap.put(vm.getId(), serviceOffering);
+        }
+
+        Mockito.when(managementServer.listHostsForMigrationOfVM(vm1, 0L, 500L, null, vmList)).thenReturn(
+                new Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>>(
+                        new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost,
+                        false)));
+        Mockito.when(managementServer.listHostsForMigrationOfVM(vm2, 0L, 500L, null, vmList)).thenReturn(
+                new Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>>(
+                        new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost,
+                        false)));
+        Mockito.when(balancedAlgorithm.getMetrics(cluster.getId(), vm1, serviceOffering, destHost, new HashMap<>(),
+                new HashMap<>(), false)).thenReturn(new Ternary<>(1.0, 0.5, 1.5));
+
+        Mockito.when(balancedAlgorithm.getMetrics(cluster.getId(), vm2, serviceOffering, destHost, new HashMap<>(),
+                new HashMap<>(), false)).thenReturn(new Ternary<>(1.0, 2.5, 1.5));
+
+        Pair<VirtualMachine, Host> bestMigration = clusterDrsService.getBestMigration(cluster, balancedAlgorithm,
+                vmList, vmIdServiceOfferingMap, new HashMap<>(), new HashMap<>());
+
+        assertEquals(destHost, bestMigration.second());
+        assertEquals(vm1, bestMigration.first());
+    }
+
+    @Test
+    public void testSavePlan() {
+        Mockito.when(drsPlanDao.persist(Mockito.any(ClusterDrsPlanVO.class))).thenReturn(
+                Mockito.mock(ClusterDrsPlanVO.class));
+        Mockito.when(drsPlanMigrationDao.persist(Mockito.any(ClusterDrsPlanMigrationVO.class))).thenReturn(
+                Mockito.mock(ClusterDrsPlanMigrationVO.class));
+
+        clusterDrsService.savePlan(1L,
+                List.of(new Ternary<>(Mockito.mock(VirtualMachine.class), Mockito.mock(Host.class),
+                                Mockito.mock(Host.class)),
+                        new Ternary<>(Mockito.mock(VirtualMachine.class), Mockito.mock(Host.class),
+                                Mockito.mock(Host.class))), 1L, ClusterDrsPlan.Type.AUTOMATED,
+                ClusterDrsPlan.Status.READY);
+
+        Mockito.verify(drsPlanDao, Mockito.times(1)).persist(Mockito.any(ClusterDrsPlanVO.class));
+        Mockito.verify(drsPlanMigrationDao, Mockito.times(2)).persist(Mockito.any(ClusterDrsPlanMigrationVO.class));
+    }
+
+    @Test
+    public void testProcessPlans() {
+        Mockito.when(drsPlanDao.listByStatus(ClusterDrsPlan.Status.READY)).thenReturn(
+                List.of(Mockito.mock(ClusterDrsPlanVO.class), Mockito.mock(ClusterDrsPlanVO.class)));
+
+        Mockito.doNothing().when(clusterDrsService).executeDrsPlan(Mockito.any(ClusterDrsPlanVO.class));
+
+        clusterDrsService.processPlans();
+
+        Mockito.verify(clusterDrsService, Mockito.times(2)).executeDrsPlan(Mockito.any(ClusterDrsPlanVO.class));
+    }
+}
diff --git a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java
index 5e7e6d3..7f7690b 100644
--- a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java
+++ b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsFilesListFactoryTest.java
@@ -16,12 +16,8 @@
 // under the License.
 package org.apache.cloudstack.diagnostics;
 
-import static org.junit.Assert.assertEquals;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
 import org.apache.cloudstack.diagnostics.fileprocessor.DiagnosticsFilesListFactory;
 import org.apache.cloudstack.diagnostics.fileprocessor.DomainRouterDiagnosticsFiles;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -34,8 +30,11 @@
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.vm.VMInstanceVO;
-import com.cloud.vm.VirtualMachine;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
 
 @RunWith(MockitoJUnitRunner.class)
 public class DiagnosticsFilesListFactoryTest {
diff --git a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java
index bb9a866..421880d 100644
--- a/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImplTest.java
@@ -18,9 +18,15 @@
 //
 package org.apache.cloudstack.diagnostics;
 
-import java.util.HashMap;
-import java.util.Map;
-
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.routing.NetworkElementCommand;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.dao.VMInstanceDao;
+import junit.framework.TestCase;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@@ -33,16 +39,8 @@
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import com.cloud.agent.AgentManager;
-import com.cloud.agent.api.routing.NetworkElementCommand;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.vm.VMInstanceVO;
-import com.cloud.vm.VirtualMachine;
-import com.cloud.vm.VirtualMachineManager;
-import com.cloud.vm.dao.VMInstanceDao;
-
-import junit.framework.TestCase;
+import java.util.HashMap;
+import java.util.Map;
 
 @RunWith(MockitoJUnitRunner.class)
 public class DiagnosticsServiceImplTest extends TestCase {
diff --git a/server/src/test/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerTest.java b/server/src/test/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerTest.java
index b246855..d29f588 100644
--- a/server/src/test/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerTest.java
+++ b/server/src/test/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerTest.java
@@ -16,39 +16,6 @@
 // under the License.
 package org.apache.cloudstack.network.lb;
 
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.inject.Inject;
-
-import junit.framework.TestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.ComponentScan.Filter;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO;
-import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
-import org.apache.cloudstack.test.utils.SpringUtils;
-
 import com.cloud.event.dao.UsageEventDao;
 import com.cloud.exception.InsufficientAddressCapacityException;
 import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
@@ -76,6 +43,35 @@
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.Ip;
 import com.cloud.utils.net.NetUtils;
+import junit.framework.TestCase;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO;
+import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * This class is responsible for unittesting the methods defined in ApplicationLoadBalancerService
diff --git a/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTest.java b/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTest.java
index bed558c..c84db90 100644
--- a/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTest.java
+++ b/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTest.java
@@ -16,33 +16,6 @@
 // under the License.
 package org.apache.cloudstack.network.router.deployment;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Matchers;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
 import com.cloud.dc.DataCenter.NetworkType;
 import com.cloud.dc.HostPodVO;
 import com.cloud.deploy.DeployDestination;
@@ -68,6 +41,32 @@
 import com.cloud.vm.DomainRouterVO;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class RouterDeploymentDefinitionTest extends RouterDeploymentDefinitionTestBase {
diff --git a/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTestBase.java b/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTestBase.java
index 1b64a2c..c748134 100644
--- a/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTestBase.java
+++ b/server/src/test/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinitionTestBase.java
@@ -16,20 +16,6 @@
 // under the License.
 package org.apache.cloudstack.network.router.deployment;
 
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.HostPodVO;
 import com.cloud.dc.Pod;
@@ -53,6 +39,19 @@
 import com.cloud.vm.VirtualMachineProfile.Param;
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class RouterDeploymentDefinitionTestBase {
diff --git a/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java b/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java
index bbddc6e..26601ff 100644
--- a/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java
+++ b/server/src/test/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinitionTest.java
@@ -16,25 +16,6 @@
 // under the License.
 package org.apache.cloudstack.network.router.deployment;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.lenient;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Matchers;
-import org.mockito.Mock;
-
 import com.cloud.deploy.DeployDestination;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientAddressCapacityException;
@@ -52,6 +33,24 @@
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.network.vpc.dao.VpcOfferingDao;
 import com.cloud.vm.DomainRouterVO;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class VpcRouterDeploymentDefinitionTest extends RouterDeploymentDefinitionTestBase {
 
diff --git a/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java b/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java
index 2efd2a1..f75860b 100644
--- a/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java
+++ b/server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java
@@ -16,32 +16,6 @@
 // under the License.
 package org.apache.cloudstack.network.ssl;
 
-import static org.apache.commons.io.FileUtils.readFileToString;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.net.URLDecoder;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.apache.cloudstack.api.command.user.loadbalancer.DeleteSslCertCmd;
-import org.apache.cloudstack.api.command.user.loadbalancer.UploadSslCertCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-
 import com.cloud.domain.DomainVO;
 import com.cloud.domain.dao.DomainDao;
 import com.cloud.network.dao.LoadBalancerCertMapDao;
@@ -57,6 +31,31 @@
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.db.TransactionLegacy;
+import org.apache.cloudstack.api.command.user.loadbalancer.DeleteSslCertCmd;
+import org.apache.cloudstack.api.command.user.loadbalancer.UploadSslCertCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.apache.commons.io.FileUtils.readFileToString;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.when;
 
 public class CertServiceTest {
 
diff --git a/server/src/test/java/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java b/server/src/test/java/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java
index 6beba0b..dac6674 100644
--- a/server/src/test/java/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java
+++ b/server/src/test/java/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java
@@ -17,8 +17,6 @@
 
 package org.apache.cloudstack.networkoffering;
 
-import java.io.IOException;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.alert.AlertManager;
 import com.cloud.api.query.dao.UserAccountJoinDaoImpl;
@@ -123,6 +121,8 @@
 import org.springframework.core.type.classreading.MetadataReaderFactory;
 import org.springframework.core.type.filter.TypeFilter;
 
+import java.io.IOException;
+
 @Configuration
 @ComponentScan(basePackageClasses = {AccountVlanMapDaoImpl.class, DomainVlanMapDaoImpl.class, VolumeDaoImpl.class, HostPodDaoImpl.class, DomainDaoImpl.class, ServiceOfferingDaoImpl.class,
     ServiceOfferingDetailsDaoImpl.class, VlanDaoImpl.class, IPAddressDaoImpl.class, ResourceTagsDaoImpl.class, AccountDaoImpl.class,
diff --git a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java
index 72bd7bb..d0b7ace 100644
--- a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java
+++ b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java
@@ -28,6 +28,7 @@
 
 import javax.inject.Inject;
 
+import com.cloud.network.dao.PublicIpQuarantineDao;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -61,7 +62,6 @@
 import com.cloud.user.UserVO;
 import com.cloud.utils.component.ComponentContext;
 import com.cloud.vm.dao.UserVmDetailsDao;
-
 import junit.framework.TestCase;
 
 @RunWith(SpringJUnit4ClassRunner.class)
@@ -104,6 +104,9 @@
     @Inject
     AnnotationDao annotationDao;
 
+    @Inject
+    PublicIpQuarantineDao publicIpQuarantineDao;
+
     @Override
     @Before
     public void setUp() {
@@ -249,5 +252,4 @@
         // System.out.println("Creating Vpc Network Offering");
         assertNotNull("Vpc Isolated network offering with Vpc and Netscaler provider ", off);
     }
-
 }
diff --git a/server/src/test/java/org/apache/cloudstack/privategw/AclOnPrivateGwTest.java b/server/src/test/java/org/apache/cloudstack/privategw/AclOnPrivateGwTest.java
index 0e85a57..8fee969 100644
--- a/server/src/test/java/org/apache/cloudstack/privategw/AclOnPrivateGwTest.java
+++ b/server/src/test/java/org/apache/cloudstack/privategw/AclOnPrivateGwTest.java
@@ -16,34 +16,6 @@
 // under the License.
 package org.apache.cloudstack.privategw;
 
-import java.io.IOException;
-
-import javax.naming.ConfigurationException;
-
-import junit.framework.Assert;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.api.command.user.vpc.CreatePrivateGatewayCmd;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.test.utils.SpringUtils;
-
 import com.cloud.configuration.ConfigurationManager;
 import com.cloud.dc.dao.DataCenterDao;
 import com.cloud.dc.dao.VlanDao;
@@ -75,6 +47,30 @@
 import com.cloud.user.AccountManager;
 import com.cloud.user.ResourceLimitService;
 import com.cloud.vm.dao.DomainRouterDao;
+import junit.framework.Assert;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.user.vpc.CreatePrivateGatewayCmd;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import javax.naming.ConfigurationException;
+import java.io.IOException;
 
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(loader = AnnotationConfigContextLoader.class)
diff --git a/server/src/test/java/org/apache/cloudstack/region/RegionManagerTest.java b/server/src/test/java/org/apache/cloudstack/region/RegionManagerTest.java
index d7bc537..2c42cad 100644
--- a/server/src/test/java/org/apache/cloudstack/region/RegionManagerTest.java
+++ b/server/src/test/java/org/apache/cloudstack/region/RegionManagerTest.java
@@ -17,19 +17,15 @@
 
 package org.apache.cloudstack.region;
 
-import java.util.HashMap;
-
-import javax.naming.ConfigurationException;
-
+import com.cloud.exception.InvalidParameterValueException;
 import junit.framework.Assert;
-
+import org.apache.cloudstack.region.dao.RegionDao;
 import org.junit.Test;
 import org.mockito.Matchers;
 import org.mockito.Mockito;
 
-import org.apache.cloudstack.region.dao.RegionDao;
-
-import com.cloud.exception.InvalidParameterValueException;
+import javax.naming.ConfigurationException;
+import java.util.HashMap;
 
 public class RegionManagerTest {
 
diff --git a/server/src/test/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancingRulesServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancingRulesServiceImplTest.java
index 3cdc8a1..9062b9e 100644
--- a/server/src/test/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancingRulesServiceImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancingRulesServiceImplTest.java
@@ -15,35 +15,6 @@
 
 package org.apache.cloudstack.region.gslb;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.when;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import com.cloud.user.User;
-import junit.framework.Assert;
-import junit.framework.TestCase;
-
-import org.apache.log4j.Logger;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import org.apache.cloudstack.api.command.user.region.ha.gslb.AssignToGlobalLoadBalancerRuleCmd;
-import org.apache.cloudstack.api.command.user.region.ha.gslb.CreateGlobalLoadBalancerRuleCmd;
-import org.apache.cloudstack.api.command.user.region.ha.gslb.DeleteGlobalLoadBalancerRuleCmd;
-import org.apache.cloudstack.api.command.user.region.ha.gslb.RemoveFromGlobalLoadBalancerRuleCmd;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.region.RegionVO;
-import org.apache.cloudstack.region.dao.RegionDao;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.network.dao.IPAddressDao;
@@ -58,9 +29,35 @@
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
+import com.cloud.user.User;
 import com.cloud.user.UserVO;
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.net.Ip;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import org.apache.cloudstack.api.command.user.region.ha.gslb.AssignToGlobalLoadBalancerRuleCmd;
+import org.apache.cloudstack.api.command.user.region.ha.gslb.CreateGlobalLoadBalancerRuleCmd;
+import org.apache.cloudstack.api.command.user.region.ha.gslb.DeleteGlobalLoadBalancerRuleCmd;
+import org.apache.cloudstack.api.command.user.region.ha.gslb.RemoveFromGlobalLoadBalancerRuleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.region.RegionVO;
+import org.apache.cloudstack.region.dao.RegionDao;
+import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.when;
 
 public class GlobalLoadBalancingRulesServiceImplTest extends TestCase {
 
diff --git a/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java b/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java
index d35bd5d..c42f920 100644
--- a/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java
+++ b/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java
@@ -16,14 +16,13 @@
 // under the License.
 package org.apache.cloudstack.service;
 
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.vm.VirtualMachine;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockitoAnnotations;
 
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.vm.VirtualMachine;
-
 public class ServiceOfferingVOTest {
     ServiceOfferingVO offeringCustom;
     ServiceOfferingVO offering;
diff --git a/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java b/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java
index 555df93..84e7144 100644
--- a/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java
+++ b/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java
@@ -19,19 +19,14 @@
 
 package org.apache.cloudstack.snapshot;
 
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.storage.DataStoreRole;
-import com.cloud.storage.VolumeVO;
-import com.cloud.storage.dao.SnapshotDao;
-import com.cloud.utils.exception.CloudRuntimeException;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
@@ -47,6 +42,13 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+
 @RunWith(MockitoJUnitRunner.class)
 public class SnapshotHelperTest {
 
@@ -77,6 +79,9 @@
     SnapshotDao snapshotDaoMock;
 
     @Mock
+    DataStoreManager dataStoreManager;
+
+    @Mock
     VolumeVO volumeVoMock;
 
     List<DataStoreRole> dataStoreRoles = Arrays.asList(DataStoreRole.values());
@@ -88,6 +93,7 @@
         snapshotHelperSpy.snapshotFactory = snapshotDataFactoryMock;
         snapshotHelperSpy.storageStrategyFactory = storageStrategyFactoryMock;
         snapshotHelperSpy.snapshotDao = snapshotDaoMock;
+        snapshotHelperSpy.dataStorageManager = dataStoreManager;
     }
 
     @Test
@@ -98,13 +104,17 @@
 
     @Test
     public void validateExpungeTemporarySnapshotKvmSnapshotOnPrimaryStorageExpungesSnapshot() {
+        DataStore store = Mockito.mock(DataStore.class);
+        Mockito.when(store.getRole()).thenReturn(DataStoreRole.Image);
+        Mockito.when(store.getId()).thenReturn(1L);
+        Mockito.when(snapshotInfoMock.getDataStore()).thenReturn(store);
         Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
-        Mockito.doReturn(true).when(snapshotDataStoreDaoMock).expungeReferenceBySnapshotIdAndDataStoreRole(Mockito.anyLong(), Mockito.any());
+        Mockito.doReturn(true).when(snapshotDataStoreDaoMock).expungeReferenceBySnapshotIdAndDataStoreRole(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
 
         snapshotHelperSpy.expungeTemporarySnapshot(true, snapshotInfoMock);
 
         Mockito.verify(snapshotServiceMock).deleteSnapshot(Mockito.any());
-        Mockito.verify(snapshotDataStoreDaoMock).expungeReferenceBySnapshotIdAndDataStoreRole(Mockito.anyLong(), Mockito.any());
+        Mockito.verify(snapshotDataStoreDaoMock).expungeReferenceBySnapshotIdAndDataStoreRole(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
     }
 
     @Test
@@ -139,20 +149,25 @@
 
     @Test
     public void validateGetSnapshotInfoByIdAndRoleSnapInfoFoundReturnIt() {
-        Mockito.doReturn(snapshotInfoMock).when(snapshotDataFactoryMock).getSnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
+        Mockito.doReturn(snapshotInfoMock).when(snapshotDataFactoryMock).getSnapshotOnPrimaryStore(Mockito.anyLong());
+        Mockito.doReturn(snapshotInfoMock).when(snapshotDataFactoryMock).getSnapshotWithRoleAndZone(Mockito.anyLong(), Mockito.any(DataStoreRole.class), Mockito.anyLong());
 
         dataStoreRoles.forEach(role -> {
-            SnapshotInfo result = snapshotHelperSpy.getSnapshotInfoByIdAndRole(0, role);
+            SnapshotInfo result = snapshotHelperSpy.getSnapshotInfoByIdAndRole(0, role, 1L);
             Assert.assertEquals(snapshotInfoMock, result);
         });
     }
 
-    @Test(expected = CloudRuntimeException.class)
+    @Test
     public void validateGetSnapshotInfoByIdAndRoleSnapInfoNotFoundThrowCloudRuntimeException() {
-        Mockito.doReturn(null).when(snapshotDataFactoryMock).getSnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
+        Mockito.doReturn(null).when(snapshotDataFactoryMock).getSnapshotOnPrimaryStore(Mockito.anyLong());
+        Mockito.doReturn(null).when(snapshotDataFactoryMock).getSnapshotWithRoleAndZone(Mockito.anyLong(), Mockito.any(DataStoreRole.class), Mockito.anyLong());
 
         dataStoreRoles.forEach(role -> {
-            snapshotHelperSpy.getSnapshotInfoByIdAndRole(0, role);
+            try {
+                snapshotHelperSpy.getSnapshotInfoByIdAndRole(0, role, 1L);
+                Assert.fail(String.format("Expected a CloudRuntimeException for datastore role: %s", role));
+            } catch (CloudRuntimeException ignored) {}
         });
     }
 
@@ -189,7 +204,7 @@
     }
 
     @Test
-    public void validateBackupSnapshotToSecondaryStorageIfNotExistsSnapshotIsNotBackupable(){
+    public void validateBackupSnapshotToSecondaryStorageIfNotExistsSnapshotIsNotBackupable() {
         Mockito.doReturn(false).when(snapshotHelperSpy).isSnapshotBackupable(Mockito.any(), Mockito.any(), Mockito.anyBoolean());
         SnapshotInfo result = snapshotHelperSpy.backupSnapshotToSecondaryStorageIfNotExists(snapshotInfoMock, DataStoreRole.Image, snapshotInfoMock, true);
         Assert.assertEquals(snapshotInfoMock, result);
@@ -198,7 +213,7 @@
     @Test (expected = CloudRuntimeException.class)
     public void validateBackupSnapshotToSecondaryStorageIfNotExistsGetSnapshotThrowsCloudRuntimeException(){
         Mockito.doReturn(true).when(snapshotHelperSpy).isSnapshotBackupable(Mockito.any(), Mockito.any(), Mockito.anyBoolean());
-        Mockito.doThrow(CloudRuntimeException.class).when(snapshotHelperSpy).getSnapshotInfoByIdAndRole(Mockito.anyLong(), Mockito.any());
+        Mockito.when(snapshotDataFactoryMock.getSnapshotOnPrimaryStore(Mockito.anyLong())).thenReturn(null);
 
         snapshotHelperSpy.backupSnapshotToSecondaryStorageIfNotExists(snapshotInfoMock, DataStoreRole.Image, snapshotInfoMock, true);
     }
@@ -206,7 +221,9 @@
     @Test
     public void validateBackupSnapshotToSecondaryStorageIfNotExistsReturnSnapshotInfo(){
         Mockito.doReturn(true).when(snapshotHelperSpy).isSnapshotBackupable(Mockito.any(), Mockito.any(), Mockito.anyBoolean());
-        Mockito.doReturn(snapshotInfoMock, snapshotInfoMock2).when(snapshotHelperSpy).getSnapshotInfoByIdAndRole(Mockito.anyLong(), Mockito.any());
+        Mockito.when(snapshotInfoMock.getDataStore()).thenReturn(Mockito.mock(DataStore.class));
+        Mockito.when(snapshotDataFactoryMock.getSnapshotOnPrimaryStore(Mockito.anyLong())).thenReturn(snapshotInfoMock);
+        Mockito.when(snapshotDataFactoryMock.getSnapshotWithRoleAndZone(Mockito.anyLong(), Mockito.any(DataStoreRole.class), Mockito.anyLong())).thenReturn(snapshotInfoMock2);
         Mockito.doReturn(snapshotStrategyMock).when(storageStrategyFactoryMock).getSnapshotStrategy(Mockito.any(), Mockito.any());
         Mockito.doReturn(null).when(snapshotStrategyMock).backupSnapshot(Mockito.any());
 
diff --git a/server/src/test/java/org/apache/cloudstack/storage/browser/StorageBrowserImplTest.java b/server/src/test/java/org/apache/cloudstack/storage/browser/StorageBrowserImplTest.java
new file mode 100644
index 0000000..7ba9f69
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/storage/browser/StorageBrowserImplTest.java
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.browser;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.UnsupportedAnswer;
+import com.cloud.api.query.dao.ImageStoreJoinDao;
+import com.cloud.api.query.vo.ImageStoreJoinVO;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.Storage;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.api.command.admin.storage.DownloadImageStoreObjectCmd;
+import org.apache.cloudstack.api.command.admin.storage.ListImageStoreObjectsCmd;
+import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolObjectsCmd;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
+import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class StorageBrowserImplTest {
+
+    @Mock
+    ImageStoreJoinDao imageStoreJoinDao;
+
+    @Mock
+    DataStoreManager dataStoreMgr;
+
+    @Mock
+    TemplateDataStoreDao templateDataStoreDao;
+
+    @Mock
+    SnapshotDataStoreDao snapshotDataStoreDao;
+
+    @Mock
+    SnapshotDao snapshotDao;
+
+    @Mock
+    EndPointSelector endPointSelector;
+
+    @Mock
+    VMTemplatePoolDao templatePoolDao;
+
+    @Mock
+    VMTemplateDao templateDao;
+
+    @Mock
+    VolumeDao volumeDao;
+
+    @Mock
+    VolumeDataStoreDao volumeDataStoreDao;
+
+    @InjectMocks
+    @Spy
+    private StorageBrowserImpl storageBrowser;
+
+    private AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetPathVolumeMapForPrimaryDSNoVolumes() {
+        List<String> paths = List.of("volume1", "volume2");
+        Mockito.when(volumeDao.listByPoolIdAndPaths(1, paths)).thenReturn(null);
+        Map<String, VolumeVO> result = storageBrowser.getPathVolumeMapForPrimaryDS(1L, paths);
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testGetPathVolumeMapForPrimaryDS() {
+        List<String> paths = List.of("volume1", "volume2");
+        VolumeVO volume = Mockito.mock(VolumeVO.class);
+        Mockito.when(volume.getPath()).thenReturn("volume1");
+
+        Mockito.when(volumeDao.listByPoolIdAndPaths(1, paths)).thenReturn(List.of(volume));
+        Map<String, VolumeVO> result = storageBrowser.getPathVolumeMapForPrimaryDS(1L, paths);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals(volume, result.get("volume1"));
+    }
+
+    @Test
+    public void testGetPathTemplateMapForPrimaryDSNoTemplates() {
+        List<String> paths = List.of("template1", "template2");
+        Mockito.when(templatePoolDao.listByPoolIdAndInstallPath(1L, paths)).thenReturn(null);
+        Map<String, VMTemplateVO> result = storageBrowser.getPathTemplateMapForPrimaryDS(1L, paths);
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testGetPathTemplateMapForPrimaryDS() {
+        List<String> paths = List.of("template1", "template2");
+        VMTemplateStoragePoolVO templatePool = Mockito.mock(VMTemplateStoragePoolVO.class);
+        Mockito.when(templatePool.getTemplateId()).thenReturn(5L);
+        Mockito.when(templatePool.getInstallPath()).thenReturn("template1");
+
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        Mockito.when(template.getId()).thenReturn(5L);
+
+        Mockito.when(templateDao.listByIds(List.of(5L))).thenReturn(List.of(template));
+
+        List<VMTemplateStoragePoolVO> templateStoragePoolList = List.of(templatePool);
+        Mockito.when(templatePoolDao.listByPoolIdAndInstallPath(1L, paths)).thenReturn(templateStoragePoolList);
+        Map<String, VMTemplateVO> result = storageBrowser.getPathTemplateMapForPrimaryDS(1L, paths);
+        Assert.assertEquals(1L, result.size());
+        Assert.assertEquals(template, result.get("template1"));
+    }
+
+    @Test
+    public void testGetFormattedPaths() {
+        List<String> paths = List.of("/path/to/file", "/path/to/file/", "path/to/file", "path/to/file/");
+        List<String> expectedFormattedPaths = List.of("path/to/file", "path/to/file", "path/to/file", "path/to/file");
+
+        List<String> formattedPaths = storageBrowser.getFormattedPaths(paths);
+
+        Assert.assertEquals(expectedFormattedPaths, formattedPaths);
+    }
+
+    @Test
+    public void testListImageStore() {
+        ListImageStoreObjectsCmd cmd = Mockito.mock(ListImageStoreObjectsCmd.class);
+        Long imageStoreId = 1L;
+        String path = "path/to/image/store";
+        Mockito.when(cmd.getStoreId()).thenReturn(imageStoreId);
+        Mockito.when(cmd.getPath()).thenReturn(path);
+        Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+        Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+
+        ImageStoreJoinVO imageStore = Mockito.mock(ImageStoreJoinVO.class);
+        Mockito.when(imageStoreJoinDao.findById(imageStoreId)).thenReturn(imageStore);
+
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        Mockito.when(dataStoreMgr.getDataStore(imageStoreId, imageStore.getRole())).thenReturn(dataStore);
+
+        ListDataStoreObjectsAnswer answer = Mockito.mock(ListDataStoreObjectsAnswer.class);
+        Mockito.doReturn(answer).when(storageBrowser).listObjectsInStore(dataStore, path, 0, 10);
+
+        ListResponse<DataStoreObjectResponse> response = storageBrowser.listImageStoreObjects(cmd);
+
+        Assert.assertNotNull(response);
+    }
+
+    @Test
+    public void testListPrimaryStore() {
+        ListStoragePoolObjectsCmd cmd = Mockito.mock(ListStoragePoolObjectsCmd.class);
+        Long storeId = 1L;
+        String path = "path/to/primary/store";
+        Mockito.when(cmd.getStoreId()).thenReturn(storeId);
+        Mockito.when(cmd.getPath()).thenReturn(path);
+        Mockito.when(cmd.getStartIndex()).thenReturn(0L);
+        Mockito.when(cmd.getPageSizeVal()).thenReturn(10L);
+
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        Mockito.when(dataStoreMgr.getDataStore(storeId, DataStoreRole.Primary)).thenReturn(dataStore);
+
+        ListDataStoreObjectsAnswer answer = Mockito.mock(ListDataStoreObjectsAnswer.class);
+        Mockito.doReturn(answer).when(storageBrowser).listObjectsInStore(dataStore, path, 0, 10);
+
+        ListResponse<DataStoreObjectResponse> response = storageBrowser.listPrimaryStoreObjects(cmd);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(answer.getPaths().size(), response.getResponses().size());
+    }
+
+    @Test
+    public void testGetCommands() {
+        List<Class<?>> expectedCmdList = new ArrayList<>();
+        expectedCmdList.add(ListImageStoreObjectsCmd.class);
+        expectedCmdList.add(ListStoragePoolObjectsCmd.class);
+        expectedCmdList.add(DownloadImageStoreObjectCmd.class);
+
+        List<Class<?>> cmdList = storageBrowser.getCommands();
+
+        Assert.assertNotNull(cmdList);
+        Assert.assertEquals(expectedCmdList, cmdList);
+    }
+
+    @Test
+    public void testListObjectsInStoreNoEndpoint() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        String path = "path/to/store";
+        int startIndex = 0;
+        int pageSize = 10;
+
+        Mockito.when(endPointSelector.select(dataStore)).thenReturn(null);
+
+        try {
+            storageBrowser.listObjectsInStore(dataStore, path, startIndex, pageSize);
+        } catch (CloudRuntimeException exception) {
+            Assert.assertEquals("No remote endpoint to send command", exception.getMessage());
+        }
+    }
+
+    @Test
+    public void testListObjectsInStoreBadCommand() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        String path = "path/to/store";
+        int startIndex = 0;
+        int pageSize = 10;
+
+        EndPoint ep = Mockito.mock(EndPoint.class);
+
+        Mockito.when(endPointSelector.select(dataStore)).thenReturn(ep);
+        Answer answer = Mockito.mock(UnsupportedAnswer.class);
+        Mockito.when(ep.sendMessage(Mockito.any())).thenReturn(answer);
+
+        try {
+            storageBrowser.listObjectsInStore(dataStore, path, startIndex, pageSize);
+        } catch (CloudRuntimeException exception) {
+            Assert.assertEquals("Failed to list datastore objects", exception.getMessage());
+        }
+    }
+
+    @Test
+    public void testListObjectsInStorePathDoesNotExist() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        String path = "path/to/store";
+        int startIndex = 0;
+        int pageSize = 10;
+
+        EndPoint ep = Mockito.mock(EndPoint.class);
+
+        Mockito.when(endPointSelector.select(dataStore)).thenReturn(ep);
+        ListDataStoreObjectsAnswer answer = Mockito.mock(ListDataStoreObjectsAnswer.class);
+        Mockito.when(ep.sendMessage(Mockito.any())).thenReturn(answer);
+        Mockito.when(answer.getResult()).thenReturn(true);
+        Mockito.when(answer.isPathExists()).thenReturn(false);
+        Mockito.when(dataStore.getUuid()).thenReturn("uuid");
+
+        try {
+            storageBrowser.listObjectsInStore(dataStore, path, startIndex, pageSize);
+        } catch (IllegalArgumentException exception) {
+            Assert.assertEquals("Path " + path + " doesn't exist in store: " + dataStore.getUuid(), exception.getMessage());
+        }
+    }
+
+    @Test
+    public void testListObjectsInStore() {
+        DataStore dataStore = Mockito.mock(DataStore.class);
+        String path = "path/to/store";
+        int startIndex = 0;
+        int pageSize = 10;
+
+        EndPoint ep = Mockito.mock(EndPoint.class);
+
+        Mockito.when(endPointSelector.select(dataStore)).thenReturn(ep);
+        ListDataStoreObjectsAnswer answer = Mockito.mock(ListDataStoreObjectsAnswer.class);
+        Mockito.when(ep.sendMessage(Mockito.any())).thenReturn(answer);
+        Mockito.when(answer.getResult()).thenReturn(true);
+        Mockito.when(answer.isPathExists()).thenReturn(true);
+
+        Assert.assertEquals(answer, storageBrowser.listObjectsInStore(dataStore, path, startIndex, pageSize));
+    }
+
+    @Test
+    public void testGetPathTemplateMapForSecondaryDS() {
+        long dataStoreId = 1L;
+        List<String> paths = List.of("/path1", "/path2");
+
+        TemplateDataStoreVO templateDataStore1 = Mockito.mock(TemplateDataStoreVO.class);
+        Mockito.when(templateDataStore1.getInstallPath()).thenReturn("/path1");
+        Mockito.when(templateDataStore1.getTemplateId()).thenReturn(1L);
+
+        TemplateDataStoreVO templateDataStore2 = Mockito.mock(TemplateDataStoreVO.class);
+        Mockito.when(templateDataStore2.getInstallPath()).thenReturn("/path2");
+        Mockito.when(templateDataStore2.getTemplateId()).thenReturn(2L);
+
+        List<TemplateDataStoreVO> templateList = List.of(templateDataStore1, templateDataStore2);
+
+        VMTemplateVO template1 = Mockito.mock(VMTemplateVO.class);
+        Mockito.when(template1.getId()).thenReturn(1L);
+
+        VMTemplateVO template2 = Mockito.mock(VMTemplateVO.class);
+        Mockito.when(template2.getId()).thenReturn(2L);
+
+        List<VMTemplateVO> templates = List.of(template1, template2);
+
+        Mockito.when(templateDataStoreDao.listByStoreIdAndInstallPaths(dataStoreId, paths)).thenReturn(templateList);
+        Mockito.when(templateDao.listByIds(List.of(1L, 2L))).thenReturn(templates);
+
+        Map<String, VMTemplateVO> expectedPathTemplateMap = new HashMap<>();
+        expectedPathTemplateMap.put("/path1", template1);
+        expectedPathTemplateMap.put("/path2", template2);
+
+        Map<String, VMTemplateVO> actualPathTemplateMap = storageBrowser.getPathTemplateMapForSecondaryDS(dataStoreId, paths);
+
+        Assert.assertEquals(expectedPathTemplateMap, actualPathTemplateMap);
+    }
+
+    @Test
+    public void testGetPathSnapshotMapForSecondaryDS() {
+        long dataStoreId = 1L;
+        List<String> paths = List.of("/path1", "/path2");
+
+        SnapshotDataStoreVO snapshotDataStore1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(snapshotDataStore1.getInstallPath()).thenReturn("/path1");
+        Mockito.when(snapshotDataStore1.getSnapshotId()).thenReturn(1L);
+
+        SnapshotDataStoreVO snapshotDataStore2 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(snapshotDataStore2.getInstallPath()).thenReturn("/path2");
+        Mockito.when(snapshotDataStore2.getSnapshotId()).thenReturn(2L);
+
+        List<SnapshotDataStoreVO> snapshotDataStoreList = List.of(snapshotDataStore1, snapshotDataStore2);
+
+        SnapshotVO snapshot1 = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshot1.getId()).thenReturn(1L);
+
+        SnapshotVO snapshot2 = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshot2.getId()).thenReturn(2L);
+
+        List<SnapshotVO> snapshots = List.of(snapshot1, snapshot2);
+
+        Mockito.when(snapshotDataStoreDao.listByStoreAndInstallPaths(dataStoreId, DataStoreRole.Image, paths)).thenReturn(snapshotDataStoreList);
+        Mockito.when(snapshotDao.listByIds(new Long[]{1L, 2L})).thenReturn(snapshots);
+
+        Map<String, SnapshotVO> expectedSnapshotPathMap = new HashMap<>();
+        expectedSnapshotPathMap.put("/path1", snapshot1);
+        expectedSnapshotPathMap.put("/path2", snapshot2);
+
+        Map<String, SnapshotVO> snapshotPathMap = storageBrowser.getPathSnapshotMapForSecondaryDS(dataStoreId, paths);
+
+        Assert.assertEquals(expectedSnapshotPathMap, snapshotPathMap);
+    }
+
+    @Test
+    public void testGetPathSnapshotMapForPrimaryDS() {
+        long dataStoreId = 1L;
+        List<String> paths = List.of("path1", "path2");
+        List<String> absPaths = List.of("/mnt/path1", "/mnt/path2");
+
+        SnapshotDataStoreVO snapshotDataStore1 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(snapshotDataStore1.getInstallPath()).thenReturn("/mnt/path1");
+        Mockito.when(snapshotDataStore1.getSnapshotId()).thenReturn(1L);
+
+        SnapshotDataStoreVO snapshotDataStore2 = Mockito.mock(SnapshotDataStoreVO.class);
+        Mockito.when(snapshotDataStore2.getInstallPath()).thenReturn("/mnt/path2");
+        Mockito.when(snapshotDataStore2.getSnapshotId()).thenReturn(2L);
+
+        List<SnapshotDataStoreVO> snapshotDataStoreList = List.of(snapshotDataStore1, snapshotDataStore2);
+
+        SnapshotVO snapshot1 = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshot1.getId()).thenReturn(1L);
+
+        SnapshotVO snapshot2 = Mockito.mock(SnapshotVO.class);
+        Mockito.when(snapshot2.getId()).thenReturn(2L);
+
+        List<SnapshotVO> snapshots = List.of(snapshot1, snapshot2);
+
+        Mockito.when(snapshotDataStoreDao.listByStoreAndInstallPaths(dataStoreId, DataStoreRole.Primary, absPaths)).thenReturn(snapshotDataStoreList);
+        Mockito.when(snapshotDao.listByIds(new Long[]{1L, 2L})).thenReturn(snapshots);
+
+        Map<String, SnapshotVO> expectedSnapshotPathMap = new HashMap<>();
+        expectedSnapshotPathMap.put("path1", snapshot1);
+        expectedSnapshotPathMap.put("path2", snapshot2);
+
+        Map<String, SnapshotVO> snapshotPathMap = storageBrowser.getPathSnapshotMapForPrimaryDS(dataStoreId, paths, absPaths);
+
+        Assert.assertEquals(expectedSnapshotPathMap, snapshotPathMap);
+    }
+
+    @Test
+    public void testGetResponse() {
+        ListDataStoreObjectsAnswer answer = Mockito.mock(ListDataStoreObjectsAnswer.class);
+        Mockito.when(answer.getPaths()).thenReturn(List.of("/path1", "/path2"));
+        Mockito.when(answer.getAbsPaths()).thenReturn(List.of("/path1", "/path2"));
+        Mockito.when(answer.getNames()).thenReturn(List.of("name1", "name2"));
+        Mockito.when(answer.getIsDirs()).thenReturn(List.of(true, false));
+        Mockito.when(answer.getSizes()).thenReturn(List.of(100L, 200L));
+        Mockito.when(answer.getLastModified()).thenReturn(List.of((new Date()).getTime(), (new Date()).getTime()));
+
+        List<String> paths = List.of("path1", "path2");
+        List<String> absPaths = List.of("/path1", "/path2");
+
+        Map<String, SnapshotVO> pathSnapshotMap = new HashMap<>();
+        pathSnapshotMap.put("path1", Mockito.mock(SnapshotVO.class));
+
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        Map<String, VMTemplateVO> pathTemplateMap = new HashMap<>();
+        pathTemplateMap.put("path2", template);
+        Mockito.when(template.getFormat()).thenReturn(Storage.ImageFormat.ISO);
+
+        Map<String, VolumeVO> pathVolumeMap = new HashMap<>();
+        pathVolumeMap.put("path1", Mockito.mock(VolumeVO.class));
+
+        DataStore dataStore = Mockito.mock(DataStore.class);
+
+        Mockito.when(dataStore.getRole()).thenReturn(DataStoreRole.Primary);
+        Mockito.when(dataStore.getId()).thenReturn(1L);
+        Mockito.doReturn(pathTemplateMap).when(storageBrowser).getPathTemplateMapForPrimaryDS(1L, paths);
+        Mockito.doReturn(pathSnapshotMap).when(storageBrowser).getPathSnapshotMapForPrimaryDS(1L, paths, absPaths);
+        Mockito.doReturn(pathVolumeMap).when(storageBrowser).getPathVolumeMapForPrimaryDS(1L, paths);
+
+        ListResponse<DataStoreObjectResponse> response = storageBrowser.getResponse(dataStore, answer);
+
+        Assert.assertEquals(2, response.getResponses().size());
+        Assert.assertEquals("name1", response.getResponses().get(0).getName());
+        Assert.assertEquals(true, response.getResponses().get(0).isDirectory());
+        Assert.assertEquals(100L, response.getResponses().get(0).getSize());
+        Assert.assertNotNull(response.getResponses().get(0).getLastUpdated());
+
+        Assert.assertEquals("name2", response.getResponses().get(1).getName());
+        Assert.assertEquals(false, response.getResponses().get(1).isDirectory());
+        Assert.assertEquals(200L, response.getResponses().get(1).getSize());
+        Assert.assertNotNull(response.getResponses().get(1).getLastUpdated());
+    }
+}
diff --git a/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java b/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java
new file mode 100644
index 0000000..6bf7eef
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.storage.heuristics;
+
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.test.TestAppender;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.secstorage.HeuristicVO;
+import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao;
+import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
+import org.apache.cloudstack.storage.heuristics.presetvariables.PresetVariables;
+import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
+import org.apache.log4j.Level;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.regex.Pattern;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HeuristicRuleHelperTest {
+
+    @Mock
+    SecondaryStorageHeuristicDao secondaryStorageHeuristicDaoMock;
+
+    @Mock
+    HeuristicVO heuristicVOMock;
+
+    @Mock
+    VMTemplateVO vmTemplateVOMock;
+
+    @Mock
+    SnapshotInfo snapshotInfoMock;
+
+    @Mock
+    VolumeVO volumeVOMock;
+
+    @Mock
+    DataStoreManager dataStoreManagerMock;
+
+    @Mock
+    DataStore dataStoreMock;
+
+    @Spy
+    @InjectMocks
+    HeuristicRuleHelper heuristicRuleHelperSpy = new HeuristicRuleHelper();
+
+    @Test
+    public void getImageStoreIfThereIsHeuristicRuleTestZoneDoesNotHaveHeuristicRuleShouldReturnNull() {
+        Long zoneId = 1L;
+
+        Mockito.when(secondaryStorageHeuristicDaoMock.findByZoneIdAndType(Mockito.anyLong(), Mockito.any(HeuristicType.class))).thenReturn(null);
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.DEBUG, Pattern.quote(String.format("No heuristic rules found for zone with ID [%s] and heuristic type [%s]. Returning null.",
+                zoneId, HeuristicType.TEMPLATE)));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HeuristicRuleHelper.LOGGER, testLogAppender);
+
+        DataStore result = heuristicRuleHelperSpy.getImageStoreIfThereIsHeuristicRule(zoneId, HeuristicType.TEMPLATE, null);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void getImageStoreIfThereIsHeuristicRuleTestZoneHasHeuristicRuleShouldCallInterpretHeuristicRule() {
+        Long zoneId = 1L;
+
+        Mockito.when(secondaryStorageHeuristicDaoMock.findByZoneIdAndType(Mockito.anyLong(), Mockito.any(HeuristicType.class))).thenReturn(heuristicVOMock);
+        Mockito.when(heuristicVOMock.getHeuristicRule()).thenReturn("rule");
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).interpretHeuristicRule(Mockito.anyString(), Mockito.any(HeuristicType.class), Mockito.isNull(),
+                Mockito.anyLong());
+
+        TestAppender.TestAppenderBuilder appenderBuilder = new TestAppender.TestAppenderBuilder();
+        appenderBuilder.addExpectedPattern(Level.DEBUG, Pattern.quote(String.format("Found the heuristic rule %s to apply for zone with ID [%s].", heuristicVOMock, zoneId)));
+        TestAppender testLogAppender = appenderBuilder.build();
+        TestAppender.safeAddAppender(HeuristicRuleHelper.LOGGER, testLogAppender);
+
+        DataStore result = heuristicRuleHelperSpy.getImageStoreIfThereIsHeuristicRule(zoneId, HeuristicType.TEMPLATE, null);
+
+        testLogAppender.assertMessagesLogged();
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void buildPresetVariablesTestWithTemplateHeuristicTypeShouldSetTemplateAndSecondaryStorageAndAccountPresetVariables() {
+        Mockito.doNothing().when(heuristicRuleHelperSpy).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setAccountPresetVariable(Mockito.anyLong());
+
+        heuristicRuleHelperSpy.buildPresetVariables(null, HeuristicType.TEMPLATE, 1L, vmTemplateVOMock);
+
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setTemplatePresetVariable(Mockito.any(VMTemplateVO.class));
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setAccountPresetVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+    }
+
+    @Test
+    public void buildPresetVariablesTestWithIsoHeuristicTypeShouldSetTemplateAndSecondaryStorageAndAccountPresetVariables() {
+        Mockito.doNothing().when(heuristicRuleHelperSpy).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setAccountPresetVariable(Mockito.anyLong());
+
+        heuristicRuleHelperSpy.buildPresetVariables(null, HeuristicType.ISO, 1L, vmTemplateVOMock);
+
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setTemplatePresetVariable(Mockito.any(VMTemplateVO.class));
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setAccountPresetVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+    }
+
+    @Test
+    public void buildPresetVariablesTestWithSnapshotHeuristicTypeShouldSetSnapshotAndSecondaryStorageAndAccountPresetVariables() {
+        Mockito.doNothing().when(heuristicRuleHelperSpy).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setAccountPresetVariable(Mockito.anyLong());
+
+        heuristicRuleHelperSpy.buildPresetVariables(null, HeuristicType.SNAPSHOT, 1L, snapshotInfoMock);
+
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setSnapshotPresetVariable(Mockito.any(SnapshotInfo.class));
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setAccountPresetVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+    }
+
+    @Test
+    public void buildPresetVariablesTestWithSnapshotHeuristicTypeShouldSetVolumeAndSecondaryStorageAndAccountPresetVariables() {
+        Mockito.doNothing().when(heuristicRuleHelperSpy).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.doReturn(null).when(heuristicRuleHelperSpy).setAccountPresetVariable(Mockito.anyLong());
+
+        heuristicRuleHelperSpy.buildPresetVariables(null, HeuristicType.VOLUME, 1L, volumeVOMock);
+
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setVolumePresetVariable(Mockito.any(VolumeVO.class));
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setSecondaryStoragesVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).setAccountPresetVariable(Mockito.anyLong());
+        Mockito.verify(heuristicRuleHelperSpy, Mockito.times(1)).injectPresetVariables(Mockito.isNull(), Mockito.any(PresetVariables.class));
+    }
+
+    @Test
+    public void interpretHeuristicRuleTestHeuristicRuleDoesNotReturnAStringShouldThrowCloudRuntimeException() {
+        String heuristicRule = "1";
+
+        Mockito.doNothing().when(heuristicRuleHelperSpy).buildPresetVariables(Mockito.any(JsInterpreter.class), Mockito.any(HeuristicType.class), Mockito.anyLong(),
+                Mockito.any());
+
+        String expectedMessage = String.format("Error while interpreting heuristic rule [%s], the rule did not return a String.", heuristicRule);
+        CloudRuntimeException assertThrows = Assert.assertThrows(CloudRuntimeException.class,
+                () -> heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L));
+        Assert.assertEquals(expectedMessage, assertThrows.getMessage());
+    }
+
+    @Test
+    public void interpretHeuristicRuleTestHeuristicRuleReturnAStringWithInvalidUuidShouldThrowCloudRuntimeException() {
+        String heuristicRule = "'uuid'";
+
+        Mockito.doNothing().when(heuristicRuleHelperSpy).buildPresetVariables(Mockito.any(JsInterpreter.class), Mockito.any(HeuristicType.class), Mockito.anyLong(),
+                Mockito.any());
+        Mockito.doReturn(null).when(dataStoreManagerMock).getImageStoreByUuid(Mockito.anyString());
+
+        String expectedMessage = String.format("Unable to find a secondary storage with the UUID [%s] returned by the heuristic rule [%s]. Check if the rule is " +
+                "returning a valid UUID.", "uuid", heuristicRule);
+        CloudRuntimeException assertThrows = Assert.assertThrows(CloudRuntimeException.class,
+                () -> heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L));
+        Assert.assertEquals(expectedMessage, assertThrows.getMessage());
+    }
+
+    @Test
+    public void interpretHeuristicRuleTestHeuristicRuleReturnAStringWithAValidUuidShouldReturnAImageStore() {
+        String heuristicRule = "'uuid'";
+
+        Mockito.doNothing().when(heuristicRuleHelperSpy).buildPresetVariables(Mockito.any(JsInterpreter.class), Mockito.any(HeuristicType.class), Mockito.anyLong(),
+                Mockito.any());
+        Mockito.doReturn(dataStoreMock).when(dataStoreManagerMock).getImageStoreByUuid(Mockito.anyString());
+
+        DataStore result = heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L);
+
+        Assert.assertNotNull(result);
+    }
+}
diff --git a/server/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImplTest.java
new file mode 100644
index 0000000..c3fa0d6
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImplTest.java
@@ -0,0 +1,389 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.template;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.exception.InsufficientAddressCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.IpAddressManager;
+import com.cloud.network.Network;
+import com.cloud.network.NetworkModel;
+import com.cloud.network.NetworkService;
+import com.cloud.network.VNF;
+import com.cloud.network.dao.FirewallRulesDao;
+import com.cloud.network.dao.IPAddressVO;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.firewall.FirewallService;
+import com.cloud.network.rules.FirewallRuleVO;
+import com.cloud.network.rules.RulesService;
+import com.cloud.network.security.SecurityGroup;
+import com.cloud.network.security.SecurityGroupManager;
+import com.cloud.network.security.SecurityGroupRuleVO;
+import com.cloud.network.security.SecurityGroupService;
+import com.cloud.network.security.SecurityGroupVO;
+import com.cloud.storage.VnfTemplateDetailVO;
+import com.cloud.storage.VnfTemplateNicVO;
+import com.cloud.storage.dao.VnfTemplateDetailsDao;
+import com.cloud.storage.dao.VnfTemplateNicDao;
+import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.user.Account;
+import com.cloud.uservm.UserVm;
+import com.cloud.vm.NicVO;
+import com.cloud.vm.dao.NicDao;
+import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd;
+import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd;
+
+import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VnfTemplateManagerImplTest {
+
+    @Spy
+    @InjectMocks
+    VnfTemplateManagerImpl vnfTemplateManagerImpl;
+
+    @Mock
+    VnfTemplateDetailsDao vnfTemplateDetailsDao;
+    @Mock
+    VnfTemplateNicDao vnfTemplateNicDao;
+
+    @Mock
+    VirtualMachineTemplate template;
+
+    @Mock
+    NicDao nicDao;
+
+    @Mock
+    NetworkDao networkDao;
+
+    @Mock
+    NetworkModel networkModel;
+
+    @Mock
+    SecurityGroupManager securityGroupManager;
+
+    @Mock
+    SecurityGroupService securityGroupService;
+
+    @Mock
+    NetworkService networkService;
+
+    @Mock
+    IpAddressManager ipAddressManager;
+
+    @Mock
+    RulesService rulesService;
+
+    @Mock
+    FirewallRulesDao firewallRulesDao;
+
+    @Mock
+    FirewallService firewallService;
+
+    final static long templateId = 100L;
+    final static long vmId = 101L;
+    final static long networkId = 101L;
+    final static long securityGroupId = 102L;
+    final static long zoneId = 103L;
+    final static long publicIpId = 104L;
+    final static String ipAddress = "10.10.10.10";
+    final static Integer sshPort = 2222;
+    final static Integer httpPort = 8080;
+    final static Integer httpsPort = 8443;
+    final Map<String, Object> vnfNics = new HashMap<>();
+    final Map<String, Object> vnfDetails = new HashMap<>();
+
+    @Before
+    public void setUp() {
+        vnfNics.put("0", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "1"),
+                Map.entry("name", "eth1"),
+                Map.entry("required", "true"),
+                Map.entry("description", "The second NIC of VNF appliance")
+        )));
+        vnfNics.put("1", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "2"),
+                Map.entry("name", "eth2"),
+                Map.entry("required", "false"),
+                Map.entry("description", "The third NIC of VNF appliance")
+        )));
+        vnfNics.put("2", new HashMap<>(Map.ofEntries(
+                Map.entry("deviceid", "0"),
+                Map.entry("name", "eth0"),
+                Map.entry("description", "The first NIC of VNF appliance")
+        )));
+
+        vnfDetails.put("0", new HashMap<>(Map.ofEntries(
+                Map.entry("accessMethods", "console,http,https"),
+                Map.entry("username", "admin"),
+                Map.entry("password", "password"),
+                Map.entry("version", "4.19.0"),
+                Map.entry("vendor", "cloudstack")
+        )));
+
+        VnfTemplateNicVO vnfNic1 = new VnfTemplateNicVO(templateId, 0L, "eth0", true, true, "first");
+        VnfTemplateNicVO vnfNic2 = new VnfTemplateNicVO(templateId, 1L, "eth1", true, true, "second");
+        VnfTemplateNicVO vnfNic3 = new VnfTemplateNicVO(templateId, 2L, "eth2", false, true, "third");
+        Mockito.doReturn(Arrays.asList(vnfNic1, vnfNic2, vnfNic3)).when(vnfTemplateNicDao).listByTemplateId(templateId);
+
+        when(template.getId()).thenReturn(templateId);
+    }
+
+    @Test
+    public void testPersistVnfTemplateRegister() {
+        RegisterVnfTemplateCmd cmd = new RegisterVnfTemplateCmd();
+        ReflectionTestUtils.setField(cmd,"vnfNics", vnfNics);
+        ReflectionTestUtils.setField(cmd,"vnfDetails", vnfDetails);
+
+        vnfTemplateManagerImpl.persistVnfTemplate(templateId, cmd);
+
+        Mockito.verify(vnfTemplateNicDao, Mockito.times(vnfNics.size())).persist(any(VnfTemplateNicVO.class));
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(0)).removeDetails(templateId);
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(5)).addDetail(eq(templateId), anyString(), anyString(), eq(true));
+    }
+
+    @Test
+    public void testPersistVnfTemplateUpdate() {
+        UpdateVnfTemplateCmd cmd = new UpdateVnfTemplateCmd();
+        ReflectionTestUtils.setField(cmd,"vnfNics", vnfNics);
+        ReflectionTestUtils.setField(cmd,"vnfDetails", vnfDetails);
+
+        vnfTemplateManagerImpl.updateVnfTemplate(templateId, cmd);
+
+        Mockito.verify(vnfTemplateNicDao, Mockito.times(vnfNics.size())).persist(any(VnfTemplateNicVO.class));
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(1)).removeDetails(templateId);
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(5)).addDetail(eq(templateId), anyString(), anyString(), eq(true));
+    }
+
+    @Test
+    public void testPersistVnfTemplateUpdateWithoutNics() {
+        UpdateVnfTemplateCmd cmd = new UpdateVnfTemplateCmd();
+        ReflectionTestUtils.setField(cmd,"vnfDetails", vnfDetails);
+        ReflectionTestUtils.setField(cmd,"cleanupVnfNics", true);
+
+        vnfTemplateManagerImpl.updateVnfTemplate(templateId, cmd);
+
+        Mockito.verify(vnfTemplateNicDao, Mockito.times(1)).deleteByTemplateId(templateId);
+        Mockito.verify(vnfTemplateNicDao, Mockito.times(0)).persist(any(VnfTemplateNicVO.class));
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(1)).removeDetails(templateId);
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(5)).addDetail(eq(templateId), anyString(), anyString(), eq(true));
+    }
+
+    @Test
+    public void testPersistVnfTemplateUpdateWithoutDetails() {
+        UpdateVnfTemplateCmd cmd = new UpdateVnfTemplateCmd();
+        ReflectionTestUtils.setField(cmd,"vnfNics", vnfNics);
+        ReflectionTestUtils.setField(cmd,"cleanupVnfDetails", true);
+
+        vnfTemplateManagerImpl.updateVnfTemplate(templateId, cmd);
+
+        Mockito.verify(vnfTemplateNicDao, Mockito.times(vnfNics.size())).persist(any(VnfTemplateNicVO.class));
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(1)).removeDetails(templateId);
+        Mockito.verify(vnfTemplateDetailsDao, Mockito.times(0)).addDetail(eq(templateId), anyString(), anyString(), eq(true));
+    }
+
+    @Test
+    public void testValidateVnfApplianceNicsWithRequiredNics() {
+        List<Long> networkIds = Arrays.asList(200L, 201L);
+        vnfTemplateManagerImpl.validateVnfApplianceNics(template, networkIds);
+    }
+
+    @Test
+    public void testValidateVnfApplianceNicsWithAllNics() {
+        List<Long> networkIds = Arrays.asList(200L, 201L, 202L);
+        vnfTemplateManagerImpl.validateVnfApplianceNics(template, networkIds);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateVnfApplianceNicsWithEmptyList() {
+        List<Long> networkIds = new ArrayList<>();
+        vnfTemplateManagerImpl.validateVnfApplianceNics(template, networkIds);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testValidateVnfApplianceNicsWithMissingNetworkId() {
+        List<Long> networkIds = Arrays.asList(200L);
+        vnfTemplateManagerImpl.validateVnfApplianceNics(template, networkIds);
+    }
+
+    @Test
+    public void testGetManagementNetworkAndIp() {
+        when(template.getId()).thenReturn(templateId);
+        VnfTemplateNicVO vnfNic1 = new VnfTemplateNicVO(templateId, 0L, "eth0", true, true, "first");
+        VnfTemplateNicVO vnfNic2 = new VnfTemplateNicVO(templateId, 1L, "eth1", true, false, "second");
+        VnfTemplateNicVO vnfNic3 = new VnfTemplateNicVO(templateId, 2L, "eth2", false, false, "third");
+        Mockito.doReturn(Arrays.asList(vnfNic1, vnfNic2, vnfNic3)).when(vnfTemplateNicDao).listByTemplateId(templateId);
+
+        UserVm vm = Mockito.mock(UserVm.class);
+        when(vm.getId()).thenReturn(vmId);
+        NicVO nic1 = Mockito.mock(NicVO.class);
+        NicVO nic2 = Mockito.mock(NicVO.class);
+        NicVO nic3 = Mockito.mock(NicVO.class);
+        when(nic1.getDeviceId()).thenReturn(0);
+        when(nic1.getIPv4Address()).thenReturn(ipAddress);
+        when(nic1.getNetworkId()).thenReturn(networkId);
+        when(nic2.getDeviceId()).thenReturn(1);
+        when(nic3.getDeviceId()).thenReturn(2);
+        Mockito.doReturn(Arrays.asList(nic1, nic2, nic3)).when(nicDao).listByVmId(vmId);
+
+        NetworkVO network = Mockito.mock(NetworkVO.class);
+        when(network.getId()).thenReturn(networkId);
+        when(network.getGuestType()).thenReturn(Network.GuestType.Isolated);
+        when(network.getVpcId()).thenReturn(null);
+        Mockito.doReturn(network).when(networkDao).findById(networkId);
+        when(networkModel.areServicesSupportedInNetwork(networkId, Network.Service.StaticNat)).thenReturn(true);
+        when(networkModel.areServicesSupportedInNetwork(networkId, Network.Service.Firewall)).thenReturn(true);
+
+        Map<Network, String> networkAndIpMap = vnfTemplateManagerImpl.getManagementNetworkAndIp(template, vm);
+
+        Assert.assertEquals(1, networkAndIpMap.size());
+        Assert.assertTrue(networkAndIpMap.containsKey(network));
+        Assert.assertTrue(networkAndIpMap.containsValue(ipAddress));
+    }
+
+    @Test
+    public void testGetOpenPortsForVnfAppliance() {
+        when(template.getId()).thenReturn(templateId);
+        VnfTemplateDetailVO accessMethodsDetail = Mockito.mock(VnfTemplateDetailVO.class);
+        when(accessMethodsDetail.getValue()).thenReturn("console,ssh-password,http,https");
+        when(vnfTemplateDetailsDao.findDetail(templateId, VNF.AccessDetail.ACCESS_METHODS.name().toLowerCase())).thenReturn(accessMethodsDetail);
+
+        VnfTemplateDetailVO sshPortDetail = Mockito.mock(VnfTemplateDetailVO.class);
+        when(sshPortDetail.getValue()).thenReturn(String.valueOf(sshPort));
+        when(vnfTemplateDetailsDao.findDetail(templateId, VNF.AccessDetail.SSH_PORT.name().toLowerCase())).thenReturn(sshPortDetail);
+
+        VnfTemplateDetailVO httpPortDetail = Mockito.mock(VnfTemplateDetailVO.class);
+        when(httpPortDetail.getValue()).thenReturn(String.valueOf(httpPort));
+        when(vnfTemplateDetailsDao.findDetail(templateId, VNF.AccessDetail.HTTP_PORT.name().toLowerCase())).thenReturn(httpPortDetail);
+
+        VnfTemplateDetailVO httpsPortDetail = Mockito.mock(VnfTemplateDetailVO.class);
+        when(httpsPortDetail.getValue()).thenReturn(String.valueOf(httpsPort));
+        when(vnfTemplateDetailsDao.findDetail(templateId, VNF.AccessDetail.HTTPS_PORT.name().toLowerCase())).thenReturn(httpsPortDetail);
+
+        Set<Integer> ports = vnfTemplateManagerImpl.getOpenPortsForVnfAppliance(template);
+
+        Assert.assertEquals(3, ports.size());
+        Assert.assertTrue(ports.contains(sshPort));
+        Assert.assertTrue(ports.contains(httpPort));
+        Assert.assertTrue(ports.contains(httpsPort));
+    }
+
+    @Test
+    public void testCreateSecurityGroupForVnfAppliance() {
+        DataCenter zone = Mockito.mock(DataCenter.class);
+        when(zone.isSecurityGroupEnabled()).thenReturn(true);
+
+        DeployVnfApplianceCmd cmd = Mockito.mock(DeployVnfApplianceCmd.class);
+        when(cmd.getVnfConfigureManagement()).thenReturn(true);
+        when(cmd.getVnfCidrlist()).thenReturn(Arrays.asList("0.0.0.0/0"));
+
+        Set<Integer> ports = new HashSet<>();
+        ports.add(sshPort);
+        ports.add(httpPort);
+        ports.add(httpsPort);
+        Mockito.doReturn(ports).when(vnfTemplateManagerImpl).getOpenPortsForVnfAppliance(template);
+
+        Account owner = Mockito.mock(Account.class);
+        when(owner.getDomainId()).thenReturn(1L);
+        when(owner.getAccountName()).thenReturn("admin");
+
+        SecurityGroupVO securityGroupVO = Mockito.mock(SecurityGroupVO.class);
+        when(securityGroupVO.getId()).thenReturn(securityGroupId);
+        Mockito.doReturn(securityGroupVO).when(securityGroupManager).createSecurityGroup(anyString(), anyString(), anyLong(), anyLong(), anyString());
+        SecurityGroupRuleVO securityGroupRuleVO = Mockito.mock(SecurityGroupRuleVO.class);
+        Mockito.doReturn(Arrays.asList(securityGroupRuleVO)).when(securityGroupService).authorizeSecurityGroupRule(anyLong(), anyString(), anyInt(), anyInt(),
+                any(), any(), any(), any(), any());
+
+        SecurityGroup result = vnfTemplateManagerImpl.createSecurityGroupForVnfAppliance(zone, template, owner, cmd);
+
+        Assert.assertEquals(result, securityGroupVO);
+        Mockito.verify(securityGroupService, Mockito.times(3)).authorizeSecurityGroupRule(anyLong(), anyString(), anyInt(), anyInt(),
+                any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testCreateIsolatedNetworkRulesForVnfAppliance() throws InsufficientAddressCapacityException, ResourceUnavailableException,
+            ResourceAllocationException, NetworkRuleConflictException {
+        DataCenter zone = Mockito.mock(DataCenter.class);
+        when(zone.getId()).thenReturn(zoneId);
+        Account owner = Mockito.mock(Account.class);
+        UserVm vm = Mockito.mock(UserVm.class);
+        when(vm.getId()).thenReturn(vmId);
+        DeployVnfApplianceCmd cmd = Mockito.mock(DeployVnfApplianceCmd.class);
+
+        Map<Network, String> networkAndIpMap = new HashMap<>();
+        NetworkVO network = Mockito.mock(NetworkVO.class);
+        when(network.getId()).thenReturn(networkId);
+        when(network.getVpcId()).thenReturn(null);
+        networkAndIpMap.put(network, ipAddress);
+        Mockito.doReturn(networkAndIpMap).when(vnfTemplateManagerImpl).getManagementNetworkAndIp(template, vm);
+
+        Set<Integer> ports = new HashSet<>();
+        ports.add(sshPort);
+        ports.add(httpPort);
+        ports.add(httpsPort);
+        Mockito.doReturn(ports).when(vnfTemplateManagerImpl).getOpenPortsForVnfAppliance(template);
+
+        FirewallRuleVO firewallRuleVO = Mockito.mock(FirewallRuleVO.class);
+
+        IPAddressVO publicIp = Mockito.mock(IPAddressVO.class);
+        when(publicIp.getId()).thenReturn(publicIpId);
+        when(publicIp.isSourceNat()).thenReturn(true).thenReturn(false);
+        Mockito.doReturn(publicIp).when(networkService).allocateIP(owner, zoneId, networkId, null, null);
+        Mockito.doReturn(publicIp).when(ipAddressManager).associateIPToGuestNetwork(publicIpId, networkId, false);
+        Mockito.doReturn(true).when(rulesService).enableStaticNat(publicIpId, vmId, networkId, ipAddress);
+        when(firewallRulesDao.persist(any())).thenReturn(firewallRuleVO);
+        Mockito.doReturn(true).when(firewallService).applyIngressFwRules(publicIpId, owner);
+
+        vnfTemplateManagerImpl.createIsolatedNetworkRulesForVnfAppliance(zone, template, owner, vm, cmd);
+
+        Mockito.verify(networkService, Mockito.times(2)).allocateIP(owner, zoneId, networkId, null, null);
+        Mockito.verify(ipAddressManager, Mockito.times(2)).associateIPToGuestNetwork(publicIpId, networkId, false);
+        Mockito.verify(rulesService, Mockito.times(1)).enableStaticNat(publicIpId, vmId, networkId, ipAddress);
+        Mockito.verify(firewallRulesDao, Mockito.times(3)).persist(any());
+        Mockito.verify(firewallService, Mockito.times(1)).applyIngressFwRules(publicIpId, owner);
+    }
+}
diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java
index 8ba1556..92131e4 100644
--- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java
@@ -17,90 +17,68 @@
 
 package org.apache.cloudstack.vm;
 
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import com.cloud.exception.UnsupportedServiceException;
-import com.cloud.storage.SnapshotVO;
-import com.cloud.storage.dao.SnapshotDao;
-import com.cloud.vm.NicVO;
-import com.cloud.vm.dao.NicDao;
-import com.cloud.vm.dao.UserVmDao;
-import com.cloud.vm.snapshot.VMSnapshotVO;
-import com.cloud.vm.snapshot.dao.VMSnapshotDao;
-import org.apache.cloudstack.api.ResponseGenerator;
-import org.apache.cloudstack.api.ResponseObject;
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
-import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
-import org.apache.cloudstack.api.response.UserVmResponse;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
-import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-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.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
 import com.cloud.agent.AgentManager;
 import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckVolumeAnswer;
+import com.cloud.agent.api.CheckVolumeCommand;
+import com.cloud.agent.api.ConvertInstanceAnswer;
+import com.cloud.agent.api.ConvertInstanceCommand;
+import com.cloud.agent.api.CopyRemoteVolumeAnswer;
+import com.cloud.agent.api.CopyRemoteVolumeCommand;
+import com.cloud.agent.api.GetRemoteVmsAnswer;
+import com.cloud.agent.api.GetRemoteVmsCommand;
 import com.cloud.agent.api.GetUnmanagedInstancesAnswer;
 import com.cloud.agent.api.GetUnmanagedInstancesCommand;
+import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.configuration.Resource;
 import com.cloud.dc.ClusterVO;
 import com.cloud.dc.DataCenter;
 import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.VmwareDatacenterVO;
 import com.cloud.dc.dao.ClusterDao;
 import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.VmwareDatacenterDao;
+import com.cloud.deploy.DeployDestination;
+import com.cloud.deploy.DeploymentPlanningManager;
+import com.cloud.event.ActionEventUtils;
 import com.cloud.event.UsageEventUtils;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.InsufficientServerCapacityException;
 import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.OperationTimedoutException;
 import com.cloud.exception.PermissionDeniedException;
+import com.cloud.exception.UnsupportedServiceException;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor;
+import com.cloud.hypervisor.HypervisorGuru;
+import com.cloud.hypervisor.HypervisorGuruManager;
 import com.cloud.network.Network;
 import com.cloud.network.NetworkModel;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.dao.NetworkVO;
-import com.cloud.offering.DiskOffering;
+import com.cloud.offering.NetworkOffering;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.resource.ResourceManager;
+import com.cloud.resource.ResourceState;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.ScopeType;
+import com.cloud.storage.Storage;
 import com.cloud.storage.StoragePool;
+import com.cloud.storage.StoragePoolHostVO;
 import com.cloud.storage.VMTemplateStoragePoolVO;
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.VolumeApiService;
 import com.cloud.storage.VolumeVO;
 import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
 import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VMTemplatePoolDao;
 import com.cloud.storage.dao.VolumeDao;
@@ -114,26 +92,85 @@
 import com.cloud.user.dao.UserDao;
 import com.cloud.uservm.UserVm;
 import com.cloud.utils.Pair;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.DiskProfile;
 import com.cloud.vm.NicProfile;
+import com.cloud.vm.NicVO;
 import com.cloud.vm.UserVmManager;
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.NicDao;
+import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.snapshot.dao.VMSnapshotDao;
+import org.apache.cloudstack.api.ResponseGenerator;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
+import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd;
+import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
+import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.BDDMockito;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(UsageEventUtils.class)
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
 public class UnmanagedVMsManagerImplTest {
 
     @InjectMocks
-    private UnmanagedVMsManager unmanagedVMsManager = new UnmanagedVMsManagerImpl();
+    private UnmanagedVMsManagerImpl unmanagedVMsManager = new UnmanagedVMsManagerImpl();
 
     @Mock
     private UserVmManager userVmManager;
     @Mock
     private ClusterDao clusterDao;
     @Mock
+    private ClusterVO clusterVO;
+    @Mock
+    private UserVmVO userVm;
+    @Mock
     private ResourceManager resourceManager;
     @Mock
     private VMTemplatePoolDao templatePoolDao;
@@ -183,23 +220,48 @@
     private NicDao nicDao;
     @Mock
     private HostDao hostDao;
+    @Mock
+    private VmwareDatacenterDao vmwareDatacenterDao;
+    @Mock
+    private HypervisorGuruManager hypervisorGuruManager;
+    @Mock
+    private ImageStoreDao imageStoreDao;
+    @Mock
+    private DataStoreManager dataStoreManager;
+    @Mock
+    private StoragePoolHostDao storagePoolHostDao;
 
     @Mock
     private VMInstanceVO virtualMachine;
     @Mock
     private NicVO nicVO;
+    @Mock
+    EntityManager entityMgr;
+    @Mock
+    DeploymentPlanningManager deploymentPlanningManager;
 
     private static final long virtualMachineId = 1L;
 
+    private AutoCloseable closeable;
+
+    private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
+
+    private UnmanagedInstanceTO instance;
+
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
+        actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
+        BDDMockito.given(ActionEventUtils.onStartedActionEvent(anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString(), anyBoolean(), anyLong()))
+                .willReturn(1L);
+        BDDMockito.given(ActionEventUtils.onCompletedActionEvent(anyLong(), anyLong(), anyString(), anyString(), anyString(), anyLong(), anyString(), anyLong()))
+                .willReturn(1L);
 
         AccountVO account = new AccountVO("admin", 1L, "", Account.Type.ADMIN, "uuid");
         UserVO user = new UserVO(1, "adminuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
         CallContext.register(user, account);
 
-        UnmanagedInstanceTO instance = new UnmanagedInstanceTO();
+        instance = new UnmanagedInstanceTO();
         instance.setName("TestInstance");
         instance.setCpuCores(2);
         instance.setCpuCoresPerSocket(1);
@@ -233,25 +295,26 @@
         clusterVO.setHypervisorType(Hypervisor.HypervisorType.VMware.toString());
         when(clusterDao.findById(Mockito.anyLong())).thenReturn(clusterVO);
         when(configurationDao.getValue(Mockito.anyString())).thenReturn(null);
-        doNothing().when(resourceLimitService).checkResourceLimit(any(Account.class), any(Resource.ResourceType.class), anyLong());
+        doNothing().when(resourceLimitService).checkResourceLimit(Mockito.any(Account.class), Mockito.any(Resource.ResourceType.class), Mockito.anyLong());
         List<HostVO> hosts = new ArrayList<>();
         HostVO hostVO = Mockito.mock(HostVO.class);
         when(hostVO.isInMaintenanceStates()).thenReturn(false);
         hosts.add(hostVO);
         when(hostVO.checkHostServiceOfferingTags(Mockito.any())).thenReturn(true);
         when(resourceManager.listHostsInClusterByStatus(Mockito.anyLong(), Mockito.any(Status.class))).thenReturn(hosts);
+        when(resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(any(Hypervisor.HypervisorType.class), Mockito.anyLong())).thenReturn(hosts);
         List<VMTemplateStoragePoolVO> templates = new ArrayList<>();
         when(templatePoolDao.listAll()).thenReturn(templates);
         List<VolumeVO> volumes = new ArrayList<>();
         when(volumeDao.findIncludingRemovedByZone(Mockito.anyLong())).thenReturn(volumes);
-        List<VMInstanceVO> vms = new ArrayList<>();
-        when(vmDao.listByHostId(Mockito.anyLong())).thenReturn(vms);
-        when(vmDao.listByLastHostIdAndStates(Mockito.anyLong())).thenReturn(vms);
         GetUnmanagedInstancesCommand cmd = Mockito.mock(GetUnmanagedInstancesCommand.class);
         HashMap<String, UnmanagedInstanceTO> map = new HashMap<>();
         map.put(instance.getName(), instance);
         Answer answer = new GetUnmanagedInstancesAnswer(cmd, "", map);
         when(agentManager.easySend(Mockito.anyLong(), Mockito.any(GetUnmanagedInstancesCommand.class))).thenReturn(answer);
+        GetRemoteVmsCommand remoteVmListcmd = Mockito.mock(GetRemoteVmsCommand.class);
+        Answer remoteVmListAnswer = new GetRemoteVmsAnswer(remoteVmListcmd, "", map);
+        when(agentManager.easySend(Mockito.anyLong(), any(GetRemoteVmsCommand.class))).thenReturn(remoteVmListAnswer);
         DataCenterVO zone = Mockito.mock(DataCenterVO.class);
         when(zone.getId()).thenReturn(1L);
         when(dataCenterDao.findById(Mockito.anyLong())).thenReturn(zone);
@@ -260,10 +323,8 @@
         users.add(Mockito.mock(UserVO.class));
         when(userDao.listByAccount(Mockito.anyLong())).thenReturn(users);
         VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
-        when(template.getId()).thenReturn(1L);
         when(template.getName()).thenReturn("Template");
         when(templateDao.findById(Mockito.anyLong())).thenReturn(template);
-        when(templateDao.findByName(Mockito.anyString())).thenReturn(template);
         ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
         when(serviceOffering.getId()).thenReturn(1L);
         when(serviceOffering.isDynamic()).thenReturn(false);
@@ -271,10 +332,8 @@
         when(serviceOffering.getRamSize()).thenReturn(instance.getMemory());
         when(serviceOffering.getSpeed()).thenReturn(instance.getCpuSpeed());
         when(serviceOfferingDao.findById(Mockito.anyLong())).thenReturn(serviceOffering);
+        when(serviceOfferingDao.findById(anyLong(), anyLong())).thenReturn(Mockito.mock(ServiceOfferingVO.class));
         DiskOfferingVO diskOfferingVO = Mockito.mock(DiskOfferingVO.class);
-        when(diskOfferingVO.getTags()).thenReturn("");
-        when(diskOfferingVO.isCustomized()).thenReturn(false);
-        when(diskOfferingVO.getDiskSize()).thenReturn(Long.MAX_VALUE);
         when(diskOfferingDao.findById(Mockito.anyLong())).thenReturn(diskOfferingVO);
         UserVmVO userVm = Mockito.mock(UserVmVO.class);
         when(userVm.getAccountId()).thenReturn(1L);
@@ -285,7 +344,6 @@
         when(userVm.getUuid()).thenReturn("abcd");
         when(userVm.isDisplayVm()).thenReturn(true);
         // Skip usage publishing and resource increment for test
-        when(userVm.getType()).thenReturn(VirtualMachine.Type.Instance);
         userVm.setInstanceName(instance.getName());
         userVm.setHostName(instance.getName());
         StoragePoolVO poolVO = Mockito.mock(StoragePoolVO.class);
@@ -297,8 +355,7 @@
         when(userVmManager.importVM(nullable(DataCenter.class), nullable(Host.class), nullable(VirtualMachineTemplate.class), nullable(String.class), nullable(String.class),
                 nullable(Account.class), nullable(String.class), nullable(Account.class), nullable(Boolean.class), nullable(String.class),
                 nullable(Long.class), nullable(Long.class), nullable(ServiceOffering.class), nullable(String.class),
-                nullable(String.class), nullable(Hypervisor.HypervisorType.class), nullable(Map.class), nullable(VirtualMachine.PowerState.class))).thenReturn(userVm);
-        when(volumeApiService.doesTargetStorageSupportDiskOffering(Mockito.any(StoragePool.class), Mockito.anyString())).thenReturn(true);
+                nullable(String.class), nullable(Hypervisor.HypervisorType.class), nullable(Map.class), nullable(VirtualMachine.PowerState.class), nullable(LinkedHashMap.class))).thenReturn(userVm);
         NetworkVO networkVO = Mockito.mock(NetworkVO.class);
         when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2);
         when(networkVO.getBroadcastUri()).thenReturn(URI.create(String.format("vlan://%d", instanceNic.getVlan())));
@@ -308,14 +365,10 @@
         networks.add(networkVO);
         when(networkDao.listByZone(Mockito.anyLong())).thenReturn(networks);
         doNothing().when(networkModel).checkNetworkPermissions(Mockito.any(Account.class), Mockito.any(Network.class));
-        doNothing().when(networkModel).checkRequestedIpAddresses(Mockito.anyLong(), Mockito.any(Network.IpAddresses.class));
         NicProfile profile = Mockito.mock(NicProfile.class);
         Integer deviceId = 100;
         Pair<NicProfile, Integer> pair = new Pair<NicProfile, Integer>(profile, deviceId);
-        when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), anyBoolean())).thenReturn(pair);
-        when(volumeManager.importVolume(Mockito.any(Volume.Type.class), Mockito.anyString(), Mockito.any(DiskOffering.class), Mockito.anyLong(),
-                Mockito.anyLong(), Mockito.anyLong(), Mockito.any(VirtualMachine.class), Mockito.any(VirtualMachineTemplate.class),
-                Mockito.any(Account.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString())).thenReturn(Mockito.mock(DiskProfile.class));
+        when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), nullable(DataCenter.class), Mockito.anyBoolean())).thenReturn(pair);
         when(volumeDao.findByInstance(Mockito.anyLong())).thenReturn(volumes);
         List<UserVmResponse> userVmResponses = new ArrayList<>();
         UserVmResponse userVmResponse = new UserVmResponse();
@@ -325,19 +378,12 @@
 
         when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine);
         when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Running);
-        when(virtualMachine.getInstanceName()).thenReturn("i-2-7-VM");
-        when(virtualMachine.getId()).thenReturn(virtualMachineId);
-        VolumeVO volumeVO = mock(VolumeVO.class);
-        when(volumeDao.findByInstance(virtualMachineId)).thenReturn(Collections.singletonList(volumeVO));
-        when(volumeVO.getInstanceId()).thenReturn(virtualMachineId);
-        when(volumeVO.getId()).thenReturn(virtualMachineId);
-        when(nicDao.listByVmId(virtualMachineId)).thenReturn(Collections.singletonList(nicVO));
-        when(nicVO.getNetworkId()).thenReturn(1L);
-        when(networkDao.findById(1L)).thenReturn(networkVO);
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
+        closeable.close();
+        actionEventUtilsMocked.close();
         CallContext.unregister();
     }
 
@@ -351,7 +397,7 @@
     public void listUnmanagedInstancesInvalidHypervisorTest() {
         ListUnmanagedInstancesCmd cmd = Mockito.mock(ListUnmanagedInstancesCmd.class);
         ClusterVO cluster = new ClusterVO(1, 1, "Cluster");
-        cluster.setHypervisorType(Hypervisor.HypervisorType.KVM.toString());
+        cluster.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString());
         when(clusterDao.findById(Mockito.anyLong())).thenReturn(cluster);
         unmanagedVMsManager.listUnmanagedInstances(cmd);
     }
@@ -370,11 +416,10 @@
     public void importUnmanagedInstanceTest() {
         ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class);
         when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance");
-        when(importUnmanageInstanceCmd.getAccountName()).thenReturn(null);
         when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null);
-        doNothing().when(hostDao).loadHostTags(null);
-        PowerMockito.mockStatic(UsageEventUtils.class);
-        unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
+        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
+            unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
+        }
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -382,7 +427,6 @@
         ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class);
         when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance");
         when(importUnmanageInstanceCmd.getName()).thenReturn("some name");
-        when(importUnmanageInstanceCmd.getMigrateAllowed()).thenReturn(false);
         unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
     }
 
@@ -390,7 +434,6 @@
     public void importUnmanagedInstanceMissingInstanceTest() {
         ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class);
         when(importUnmanageInstanceCmd.getName()).thenReturn("SomeInstance");
-        when(importUnmanageInstanceCmd.getAccountName()).thenReturn(null);
         when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null);
         unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd);
     }
@@ -415,21 +458,388 @@
 
     @Test(expected = UnsupportedServiceException.class)
     public void unmanageVMInstanceExistingVMSnapshotsTest() {
-        when(vmSnapshotDao.findByVm(virtualMachineId)).thenReturn(Arrays.asList(new VMSnapshotVO(), new VMSnapshotVO()));
+        when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None);
         unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
     }
 
     @Test(expected = UnsupportedServiceException.class)
     public void unmanageVMInstanceExistingVolumeSnapshotsTest() {
-        when(snapshotDao.listByVolumeId(virtualMachineId)).thenReturn(Arrays.asList(new SnapshotVO(), new SnapshotVO()));
+        when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None);
         unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
     }
 
     @Test(expected = UnsupportedServiceException.class)
     public void unmanageVMInstanceExistingISOAttachedTest() {
-        UserVmVO userVmVO = mock(UserVmVO.class);
-        when(userVmDao.findById(virtualMachineId)).thenReturn(userVmVO);
-        when(userVmVO.getIsoId()).thenReturn(3L);
+        when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.None);
         unmanagedVMsManager.unmanageVMInstance(virtualMachineId);
     }
+
+    @Test
+    public void testListRemoteInstancesTest() {
+        ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class);
+        when(cmd.getHypervisor()).thenReturn(Hypervisor.HypervisorType.KVM.toString());
+        when(cmd.getUsername()).thenReturn("user");
+        when(cmd.getPassword()).thenReturn("pass");
+        ListResponse response = unmanagedVMsManager.listVmsForImport(cmd);
+        Assert.assertEquals(1, response.getCount().intValue());
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testListRemoteInstancesTestNonKVM() {
+        ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class);
+        unmanagedVMsManager.listVmsForImport(cmd);
+    }
+    @Test
+    public void testImportFromExternalTest() throws InsufficientServerCapacityException {
+        String vmname = "TestInstance";
+        ImportVmCmd cmd = Mockito.mock(ImportVmCmd.class);
+        when(cmd.getHypervisor()).thenReturn(Hypervisor.HypervisorType.KVM.toString());
+        when(cmd.getName()).thenReturn(vmname);
+        when(cmd.getUsername()).thenReturn("user");
+        when(cmd.getPassword()).thenReturn("pass");
+        when(cmd.getImportSource()).thenReturn("external");
+        when(cmd.getDomainId()).thenReturn(null);
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        when(templateDao.findByName(anyString())).thenReturn(template);
+        HostVO host = Mockito.mock(HostVO.class);
+        when(userVmDao.getNextInSequence(Long.class, "id")).thenReturn(1L);
+        DeployDestination mockDest = Mockito.mock(DeployDestination.class);
+        when(deploymentPlanningManager.planDeployment(any(), any(), any(), any())).thenReturn(mockDest);
+        DiskProfile diskProfile = Mockito.mock(DiskProfile.class);
+        when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()))
+                .thenReturn(diskProfile);
+        Map<Volume, StoragePool> storage = new HashMap<>();
+        VolumeVO volume = Mockito.mock(VolumeVO.class);
+        StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
+        storage.put(volume, storagePool);
+        when(mockDest.getStorageForDisks()).thenReturn(storage);
+        when(mockDest.getHost()).thenReturn(host);
+        when(volumeDao.findById(anyLong())).thenReturn(volume);
+        CopyRemoteVolumeAnswer copyAnswer = Mockito.mock(CopyRemoteVolumeAnswer.class);
+        when(copyAnswer.getResult()).thenReturn(true);
+        when(agentManager.easySend(anyLong(), any(CopyRemoteVolumeCommand.class))).thenReturn(copyAnswer);
+        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
+            unmanagedVMsManager.importVm(cmd);
+        }
+    }
+
+    private void baseBasicParametersCheckForImportInstance(String name, Long domainId, String accountName) {
+        unmanagedVMsManager.basicParametersCheckForImportInstance(name, domainId, accountName);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testBasicParametersCheckForImportInstanceMissingName() {
+        baseBasicParametersCheckForImportInstance(null, 1L, "test");
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testBasicParametersCheckForImportInstanceMissingDomainAndAccount() {
+        baseBasicParametersCheckForImportInstance("vm", 1L, "");
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testBasicAccessChecksMissingClusterId() {
+        unmanagedVMsManager.basicAccessChecks(null);
+    }
+
+    @Test(expected = PermissionDeniedException.class)
+    public void testBasicAccessChecksNotAdminCaller() {
+        CallContext.unregister();
+        AccountVO account = new AccountVO("user", 1L, "", Account.Type.NORMAL, "uuid");
+        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
+        CallContext.register(user, account);
+        unmanagedVMsManager.basicAccessChecks(1L);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void testBasicAccessChecksUnsupportedHypervisorType() {
+        ClusterVO clusterVO = new ClusterVO(1L, 1L, "Cluster");
+        clusterVO.setHypervisorType(Hypervisor.HypervisorType.XenServer.toString());
+        when(clusterDao.findById(Mockito.anyLong())).thenReturn(clusterVO);
+        unmanagedVMsManager.basicAccessChecks(1L);
+    }
+
+    @Test
+    public void testGetTemplateForImportInstanceDefaultTemplate() {
+        String defaultTemplateName = "DefaultTemplate";
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        when(template.getName()).thenReturn(defaultTemplateName);
+        when(templateDao.findByName(anyString())).thenReturn(template);
+        VMTemplateVO templateForImportInstance = unmanagedVMsManager.getTemplateForImportInstance(null, Hypervisor.HypervisorType.KVM);
+        Assert.assertEquals(defaultTemplateName, templateForImportInstance.getName());
+    }
+
+    private enum VcenterParameter {
+        EXISTING, EXTERNAL, BOTH, NONE, EXISTING_INVALID, AGENT_UNAVAILABLE, CONVERT_FAILURE
+    }
+
+    private void baseTestImportVmFromVmwareToKvm(VcenterParameter vcenterParameter, boolean selectConvertHost,
+                                                 boolean selectTemporaryStorage) throws OperationTimedoutException, AgentUnavailableException {
+        long clusterId = 1L;
+        long zoneId = 1L;
+        long podId = 1L;
+        long existingDatacenterId = 1L;
+        String vcenterHost = "192.168.1.2";
+        String datacenter = "Datacenter";
+        String username = "administrator@vsphere.local";
+        String password = "password";
+        String host = "192.168.1.10";
+        String vmName = "TestInstanceFromVmware";
+        instance.setName(vmName);
+        long newVmId = 2L;
+        long networkId = 1L;
+        when(vmDao.getNextInSequence(Long.class, "id")).thenReturn(newVmId);
+
+        ClusterVO cluster = mock(ClusterVO.class);
+        when(cluster.getId()).thenReturn(clusterId);
+        when(cluster.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
+        when(cluster.getDataCenterId()).thenReturn(zoneId);
+        when(clusterDao.findById(clusterId)).thenReturn(cluster);
+
+        ImportVmCmd importVmCmd = Mockito.mock(ImportVmCmd.class);
+
+        when(importVmCmd.getName()).thenReturn(vmName);
+        when(importVmCmd.getClusterId()).thenReturn(clusterId);
+        when(importVmCmd.getDomainId()).thenReturn(null);
+        when(importVmCmd.getImportSource()).thenReturn(VmImportService.ImportSource.VMWARE.toString());
+        when(importVmCmd.getHostIp()).thenReturn(host);
+        when(importVmCmd.getNicNetworkList()).thenReturn(Map.of("NIC 1", networkId));
+        when(importVmCmd.getConvertInstanceHostId()).thenReturn(null);
+        when(importVmCmd.getConvertStoragePoolId()).thenReturn(null);
+
+        NetworkVO networkVO = Mockito.mock(NetworkVO.class);
+        when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2);
+        when(networkVO.getDataCenterId()).thenReturn(zoneId);
+        when(networkDao.findById(networkId)).thenReturn(networkVO);
+
+        HypervisorGuru vmwareGuru = mock(HypervisorGuru.class);
+        when(hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware)).thenReturn(vmwareGuru);
+        when(vmwareGuru.cloneHypervisorVMOutOfBand(anyString(), anyString(), anyMap())).thenReturn(instance);
+        when(vmwareGuru.removeClonedHypervisorVMOutOfBand(anyString(), anyString(), anyMap())).thenReturn(true);
+
+        HostVO convertHost = mock(HostVO.class);
+        long convertHostId = 1L;
+        when(convertHost.getStatus()).thenReturn(Status.Up);
+        when(convertHost.getResourceState()).thenReturn(ResourceState.Enabled);
+        when(convertHost.getId()).thenReturn(convertHostId);
+        when(convertHost.getName()).thenReturn("KVM-Convert-Host");
+        when(convertHost.getType()).thenReturn(Host.Type.Routing);
+        when(convertHost.getClusterId()).thenReturn(clusterId);
+        if (selectConvertHost) {
+            when(importVmCmd.getConvertInstanceHostId()).thenReturn(convertHostId);
+            when(hostDao.findById(convertHostId)).thenReturn(convertHost);
+        }
+
+        DataStoreTO dataStoreTO = mock(DataStoreTO.class);
+        DataStore dataStore = mock(DataStore.class);
+        when(dataStore.getTO()).thenReturn(dataStoreTO);
+
+        StoragePoolVO destPool = mock(StoragePoolVO.class);
+        when(destPool.getUuid()).thenReturn(UUID.randomUUID().toString());
+        when(destPool.getDataCenterId()).thenReturn(zoneId);
+        when(destPool.getClusterId()).thenReturn(null);
+        when(destPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        if (selectTemporaryStorage) {
+            long temporaryStoragePoolId = 1L;
+            when(importVmCmd.getConvertStoragePoolId()).thenReturn(temporaryStoragePoolId);
+            when(primaryDataStoreDao.findById(temporaryStoragePoolId)).thenReturn(destPool);
+            when(dataStoreManager.getPrimaryDataStore(temporaryStoragePoolId)).thenReturn(dataStore);
+        } else {
+            ImageStoreVO imageStoreVO = mock(ImageStoreVO.class);
+            when(imageStoreVO.getId()).thenReturn(1L);
+            when(imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs")).thenReturn(imageStoreVO);
+            when(dataStoreManager.getDataStore(1L, DataStoreRole.Image)).thenReturn(dataStore);
+        }
+        when(primaryDataStoreDao.listPoolsByCluster(clusterId)).thenReturn(List.of(destPool));
+        when(primaryDataStoreDao.listPoolByHostPath(Mockito.anyString(), Mockito.anyString())).thenReturn(List.of(destPool));
+
+        if (VcenterParameter.EXISTING == vcenterParameter) {
+            VmwareDatacenterVO datacenterVO = mock(VmwareDatacenterVO.class);
+            when(datacenterVO.getVcenterHost()).thenReturn(vcenterHost);
+            when(datacenterVO.getVmwareDatacenterName()).thenReturn(datacenter);
+            when(datacenterVO.getUser()).thenReturn(username);
+            when(datacenterVO.getPassword()).thenReturn(password);
+            when(importVmCmd.getExistingVcenterId()).thenReturn(existingDatacenterId);
+            when(vmwareDatacenterDao.findById(existingDatacenterId)).thenReturn(datacenterVO);
+        } else if (VcenterParameter.EXTERNAL == vcenterParameter) {
+            when(importVmCmd.getVcenter()).thenReturn(vcenterHost);
+            when(importVmCmd.getDatacenterName()).thenReturn(datacenter);
+            when(importVmCmd.getUsername()).thenReturn(username);
+            when(importVmCmd.getPassword()).thenReturn(password);
+        }
+
+        if (VcenterParameter.BOTH == vcenterParameter) {
+            when(importVmCmd.getExistingVcenterId()).thenReturn(existingDatacenterId);
+            when(importVmCmd.getVcenter()).thenReturn(vcenterHost);
+        } else if (VcenterParameter.NONE == vcenterParameter) {
+            when(importVmCmd.getExistingVcenterId()).thenReturn(null);
+            when(importVmCmd.getVcenter()).thenReturn(null);
+        } else if (VcenterParameter.EXISTING_INVALID == vcenterParameter) {
+            when(importVmCmd.getExistingVcenterId()).thenReturn(existingDatacenterId);
+            when(vmwareDatacenterDao.findById(existingDatacenterId)).thenReturn(null);
+        }
+
+        ConvertInstanceAnswer answer = mock(ConvertInstanceAnswer.class);
+        when(answer.getResult()).thenReturn(vcenterParameter != VcenterParameter.CONVERT_FAILURE);
+        when(answer.getConvertedInstance()).thenReturn(instance);
+        if (VcenterParameter.AGENT_UNAVAILABLE != vcenterParameter) {
+            when(agentManager.send(Mockito.eq(convertHostId), Mockito.any(ConvertInstanceCommand.class))).thenReturn(answer);
+        }
+
+        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
+            unmanagedVMsManager.importVm(importVmCmd);
+            verify(vmwareGuru).cloneHypervisorVMOutOfBand(Mockito.eq(host), Mockito.eq(vmName), anyMap());
+            verify(vmwareGuru).removeClonedHypervisorVMOutOfBand(Mockito.eq(host), Mockito.eq(vmName), anyMap());
+        }
+    }
+
+    @Test
+    public void testImportFromLocalDisk() throws InsufficientServerCapacityException {
+        testImportFromDisk("local");
+    }
+
+    @Test
+    public void testImportFromsharedStorage() throws InsufficientServerCapacityException {
+        testImportFromDisk("shared");
+    }
+
+    private void testImportFromDisk(String source) throws InsufficientServerCapacityException {
+        String vmname = "testVm";
+        ImportVmCmd cmd = Mockito.mock(ImportVmCmd.class);
+        when(cmd.getHypervisor()).thenReturn(Hypervisor.HypervisorType.KVM.toString());
+        when(cmd.getName()).thenReturn(vmname);
+        when(cmd.getImportSource()).thenReturn(source);
+        when(cmd.getDiskPath()).thenReturn("/var/lib/libvirt/images/test.qcow2");
+        when(cmd.getDomainId()).thenReturn(null);
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        when(templateDao.findByName(anyString())).thenReturn(template);
+        HostVO host = Mockito.mock(HostVO.class);
+        when(hostDao.findById(anyLong())).thenReturn(host);
+        NetworkOffering netOffering = Mockito.mock(NetworkOffering.class);
+        when(entityMgr.findById(NetworkOffering.class, 0L)).thenReturn(netOffering);
+        when(userVmDao.getNextInSequence(Long.class, "id")).thenReturn(1L);
+        DeployDestination mockDest = Mockito.mock(DeployDestination.class);
+        when(deploymentPlanningManager.planDeployment(any(), any(), any(), any())).thenReturn(mockDest);
+        DiskProfile diskProfile = Mockito.mock(DiskProfile.class);
+        when(volumeManager.allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()))
+                        .thenReturn(diskProfile);
+        Map<Volume, StoragePool> storage = new HashMap<>();
+        VolumeVO volume = Mockito.mock(VolumeVO.class);
+        StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
+        storage.put(volume, storagePool);
+        when(mockDest.getStorageForDisks()).thenReturn(storage);
+        when(mockDest.getHost()).thenReturn(host);
+        when(volumeDao.findById(anyLong())).thenReturn(volume);
+        CheckVolumeAnswer answer = Mockito.mock(CheckVolumeAnswer.class);
+        when(answer.getResult()).thenReturn(true);
+        when(agentManager.easySend(anyLong(), any(CheckVolumeCommand.class))).thenReturn(answer);
+        List<StoragePoolVO> storagePools = new ArrayList<>();
+        storagePools.add(storagePool);
+        when(primaryDataStoreDao.findLocalStoragePoolsByHostAndTags(anyLong(), any())).thenReturn(storagePools);
+        when(primaryDataStoreDao.findById(anyLong())).thenReturn(storagePool);
+        when(volumeApiService.doesTargetStorageSupportDiskOffering(any(StoragePool.class), any())).thenReturn(true);
+        StoragePoolHostVO storagePoolHost = Mockito.mock(StoragePoolHostVO.class);
+        when(storagePoolHostDao.findByPoolHost(anyLong(), anyLong())).thenReturn(storagePoolHost);
+        try (MockedStatic<UsageEventUtils> ignored = Mockito.mockStatic(UsageEventUtils.class)) {
+                unmanagedVMsManager.importVm(cmd);
+        }
+    }
+
+    public void testImportVmFromVmwareToKvmExistingVcenter() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING, false, false);
+    }
+
+    @Test
+    public void testImportVmFromVmwareToKvmExistingVcenterSetConvertHost() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING, true, false);
+    }
+
+    @Test
+    public void testImportVmFromVmwareToKvmExistingVcenterSetConvertHostAndTemporaryStorage() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING, true, true);
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testImportVmFromVmwareToKvmExistingVcenterExclusiveParameters() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.BOTH, false, false);
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testImportVmFromVmwareToKvmExistingVcenterMissingParameters() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.NONE, false, false);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testImportVmFromVmwareToKvmExistingVcenterInvalid() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.EXISTING_INVALID, false, false);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testImportVmFromVmwareToKvmExistingVcenterAgentUnavailable() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.AGENT_UNAVAILABLE, false, false);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testImportVmFromVmwareToKvmExistingVcenterConvertFailure() throws OperationTimedoutException, AgentUnavailableException {
+        baseTestImportVmFromVmwareToKvm(VcenterParameter.CONVERT_FAILURE, false, false);
+    }
+
+    private ClusterVO getClusterForTests() {
+        ClusterVO cluster = mock(ClusterVO.class);
+        when(cluster.getId()).thenReturn(1L);
+        when(cluster.getDataCenterId()).thenReturn(1L);
+        return cluster;
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testSelectInstanceConversionTemporaryLocationInvalidStorage() {
+        ClusterVO cluster = getClusterForTests();
+
+        long poolId = 1L;
+        when(primaryDataStoreDao.findById(poolId)).thenReturn(null);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, null);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testSelectInstanceConversionTemporaryLocationPoolInvalidScope() {
+        ClusterVO cluster = getClusterForTests();
+        long poolId = 1L;
+        StoragePoolVO pool = mock(StoragePoolVO.class);
+        Mockito.when(pool.getScope()).thenReturn(ScopeType.CLUSTER);
+        Mockito.when(pool.getClusterId()).thenReturn(100L);
+        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, null);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testSelectInstanceConversionTemporaryLocationLocalStoragePoolInvalid() {
+        ClusterVO cluster = getClusterForTests();
+        long poolId = 1L;
+        StoragePoolVO pool = mock(StoragePoolVO.class);
+        Mockito.when(pool.getScope()).thenReturn(ScopeType.HOST);
+        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
+        HostVO convertHost = Mockito.mock(HostVO.class);
+        Mockito.when(convertHost.getId()).thenReturn(1L);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, convertHost);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testSelectInstanceConversionTemporaryLocationStoragePoolInvalidType() {
+        ClusterVO cluster = getClusterForTests();
+        long poolId = 1L;
+        StoragePoolVO pool = mock(StoragePoolVO.class);
+        Mockito.when(pool.getScope()).thenReturn(ScopeType.CLUSTER);
+        Mockito.when(pool.getClusterId()).thenReturn(1L);
+        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
+        HostVO convertHost = Mockito.mock(HostVO.class);
+        Mockito.when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.RBD);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, poolId, convertHost);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testSelectInstanceConversionTemporaryLocationNoPoolAvailable() {
+        ClusterVO cluster = getClusterForTests();
+        Mockito.when(imageStoreDao.findOneByZoneAndProtocol(anyLong(), anyString())).thenReturn(null);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null);
+    }
 }
diff --git a/server/src/test/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImplTest.java
new file mode 100644
index 0000000..3d21be5
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImplTest.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 com.cloud.exception.InvalidParameterValueException;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.User;
+import com.cloud.uservm.UserVm;
+import com.cloud.utils.Pair;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.vm.UserVmManager;
+import com.cloud.vm.VirtualMachine;
+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;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import static org.junit.Assert.assertNotNull;
+
+public class VMScheduleManagerImplTest {
+
+    @Spy
+    @InjectMocks
+    VMScheduleManagerImpl vmScheduleManager = new VMScheduleManagerImpl();
+
+    @Mock
+    VMScheduleDao vmScheduleDao;
+
+    @Mock
+    VMScheduler vmScheduler;
+
+    @Mock
+    UserVmManager userVmManager;
+
+    @Mock
+    AccountManager accountManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Account callingAccount = Mockito.mock(Account.class);
+        User callingUser = Mockito.mock(User.class);
+        CallContext.register(callingUser, callingAccount);
+    }
+
+    private void validateResponse(VMScheduleResponse response, VMSchedule vmSchedule, VirtualMachine vm) {
+        assertNotNull(response);
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "id"), vmSchedule.getUuid());
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "vmId"), vm.getUuid());
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "schedule"), vmSchedule.getSchedule());
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "timeZone"), vmSchedule.getTimeZone());
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "action"), vmSchedule.getAction());
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "startDate"), vmSchedule.getStartDate());
+        Assert.assertEquals(ReflectionTestUtils.getField(response, "endDate"), vmSchedule.getEndDate());
+    }
+
+    @Test
+    public void createSchedule() {
+        UserVm vm = Mockito.mock(UserVm.class);
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        CreateVMScheduleCmd cmd = Mockito.mock(CreateVMScheduleCmd.class);
+
+        Mockito.when(cmd.getVmId()).thenReturn(1L);
+        Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
+        Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
+        Mockito.when(cmd.getAction()).thenReturn("start");
+        Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
+        Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
+        Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(vmScheduleDao.persist(Mockito.any(VMScheduleVO.class))).thenReturn(vmSchedule);
+        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
+        Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.eq(false), Mockito.eq(vm));
+        VMScheduleResponse response = vmScheduleManager.createSchedule(cmd);
+        Mockito.verify(vmScheduleDao, Mockito.times(1)).persist(Mockito.any(VMScheduleVO.class));
+
+        validateResponse(response, vmSchedule, vm);
+    }
+
+    @Test
+    public void createResponse() {
+        VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
+        UserVm vm = Mockito.mock(UserVm.class);
+        Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
+        Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);
+
+        VMScheduleResponse response = vmScheduleManager.createResponse(vmSchedule);
+        validateResponse(response, vmSchedule, vm);
+    }
+
+    @Test
+    public void listSchedule() {
+        UserVm vm = Mockito.mock(UserVm.class);
+        VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
+        VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
+        List<VMScheduleVO> vmScheduleList = new ArrayList<>();
+        vmScheduleList.add(vmSchedule1);
+        vmScheduleList.add(vmSchedule2);
+
+        Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
+        Mockito.when(
+                vmScheduleDao.searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
+                        Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong())
+        ).thenReturn(new Pair<>(vmScheduleList, vmScheduleList.size()));
+        Mockito.when(vmSchedule1.getVmId()).thenReturn(1L);
+        Mockito.when(vmSchedule2.getVmId()).thenReturn(1L);
+
+        ListVMScheduleCmd cmd = Mockito.mock(ListVMScheduleCmd.class);
+        Mockito.when(cmd.getVmId()).thenReturn(1L);
+
+        ListResponse<VMScheduleResponse> responseList = vmScheduleManager.listSchedule(cmd);
+        Mockito.verify(vmScheduleDao, Mockito.times(1)).searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
+                Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong());
+        assertNotNull(responseList);
+        Assert.assertEquals(2, (int) responseList.getCount());
+        Assert.assertEquals(2, responseList.getResponses().size());
+
+        for (int i = 0; i < responseList.getResponses().size(); i++) {
+            VMScheduleResponse response = responseList.getResponses().get(i);
+            VMScheduleVO vmSchedule = vmScheduleList.get(i);
+            validateResponse(response, vmSchedule, vm);
+        }
+    }
+
+    @Test
+    public void updateSchedule() {
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        UpdateVMScheduleCmd cmd = Mockito.mock(UpdateVMScheduleCmd.class);
+        UserVm vm = Mockito.mock(UserVm.class);
+        Mockito.when(cmd.getId()).thenReturn(1L);
+        Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
+        Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
+        Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
+        Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
+        Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
+        Mockito.when(vmScheduleDao.update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class))).thenReturn(true);
+        Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
+        Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
+        Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);
+
+        VMScheduleResponse response = vmScheduleManager.updateSchedule(cmd);
+        Mockito.verify(vmScheduleDao, Mockito.times(1)).update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class));
+
+        validateResponse(response, vmSchedule, vm);
+    }
+
+    @Test
+    public void removeScheduleByVmId() {
+        UserVm vm = Mockito.mock(UserVm.class);
+        VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
+        VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
+        List<VMScheduleVO> vmScheduleList = new ArrayList<>();
+        vmScheduleList.add(vmSchedule1);
+        vmScheduleList.add(vmSchedule2);
+        SearchCriteria<VMScheduleVO> sc = Mockito.mock(SearchCriteria.class);
+
+        Mockito.when(vm.getId()).thenReturn(1L);
+        Mockito.when(vmScheduleDao.getSearchCriteriaForVMId(vm.getId())).thenReturn(sc);
+        Mockito.when(vmScheduleDao.search(sc, null)).thenReturn(vmScheduleList);
+        Mockito.when(vmSchedule1.getId()).thenReturn(1L);
+        Mockito.when(vmSchedule2.getId()).thenReturn(2L);
+        Mockito.when(vmScheduleDao.remove(sc)).thenReturn(2);
+
+        long rowsRemoved = vmScheduleManager.removeScheduleByVmId(vm.getId(), false);
+
+        Mockito.verify(vmScheduleDao, Mockito.times(1)).remove(sc);
+        Assert.assertEquals(2, rowsRemoved);
+    }
+
+    @Test
+    public void removeSchedule() {
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        UserVm vm = Mockito.mock(UserVm.class);
+        DeleteVMScheduleCmd cmd = Mockito.mock(DeleteVMScheduleCmd.class);
+
+        Mockito.when(cmd.getId()).thenReturn(1L);
+        Mockito.when(cmd.getVmId()).thenReturn(1L);
+        Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
+        Mockito.when(userVmManager.getUserVm(cmd.getVmId())).thenReturn(vm);
+        Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
+        Mockito.when(vmScheduleDao.removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList())).thenReturn(1L);
+
+        Long rowsRemoved = vmScheduleManager.removeSchedule(cmd);
+
+        Mockito.verify(vmScheduleDao, Mockito.times(1)).removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList());
+        Assert.assertEquals(1L, (long) rowsRemoved);
+    }
+
+    @Test
+    public void validateStartDateEndDate() {
+        // Valid scenario 1
+        // Start date is before end date
+        Date startDate = DateUtils.addDays(new Date(), 1);
+        Date endDate = DateUtils.addDays(new Date(), 2);
+        vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
+
+        // Valid Scenario 2
+        // Start date is before current date and end date is null
+        endDate = null;
+        vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
+
+        // Invalid Scenario 2
+        // Start date is before current date
+        startDate = DateUtils.addDays(new Date(), -1);
+        try {
+            vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
+            Assert.fail("Should have thrown InvalidParameterValueException");
+        } catch (InvalidParameterValueException e) {
+            Assert.assertTrue(e.getMessage().contains("Invalid value for start date. Start date") &&
+                    e.getMessage().contains("can't be before current time"));
+        }
+
+        // Invalid Scenario 2
+        // Start date is after end date
+        startDate = DateUtils.addDays(new Date(), 2);
+        endDate = DateUtils.addDays(new Date(), 1);
+        try {
+            vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
+            Assert.fail("Should have thrown InvalidParameterValueException");
+        } catch (InvalidParameterValueException e) {
+            Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
+                    e.getMessage().contains("can't be before start date"));
+        }
+
+        // Invalid Scenario 3
+        // End date is before current date
+        startDate = DateUtils.addDays(new Date(), 1);
+        endDate = DateUtils.addDays(new Date(), -1);
+        try {
+            vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
+            Assert.fail("Should have thrown InvalidParameterValueException");
+        } catch (InvalidParameterValueException e) {
+            Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
+                    e.getMessage().contains("can't be before current time"));
+        }
+    }
+}
diff --git a/server/src/test/java/org/apache/cloudstack/vm/schedule/VMSchedulerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/schedule/VMSchedulerImplTest.java
new file mode 100644
index 0000000..d40cd61
--- /dev/null
+++ b/server/src/test/java/org/apache/cloudstack/vm/schedule/VMSchedulerImplTest.java
@@ -0,0 +1,397 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 com.cloud.event.ActionEventUtils;
+import com.cloud.user.User;
+import com.cloud.uservm.UserVm;
+import com.cloud.utils.component.ComponentContext;
+import com.cloud.vm.UserVmManager;
+import com.cloud.vm.VirtualMachine;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
+import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
+import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
+import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
+import org.apache.cloudstack.framework.jobs.AsyncJobManager;
+import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
+import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDao;
+import org.apache.commons.lang.time.DateUtils;
+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.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import javax.persistence.EntityExistsException;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VMSchedulerImplTest {
+    @Spy
+    @InjectMocks
+    private VMSchedulerImpl vmScheduler = new VMSchedulerImpl();
+
+    @Mock
+    private UserVmManager userVmManager;
+
+    @Mock
+    private VMScheduleDao vmScheduleDao;
+
+    @Mock
+    private VMScheduledJobDao vmScheduledJobDao;
+
+    @Mock
+    private EnumMap<VMSchedule.Action, String> actionEventMap;
+
+    @Mock
+    private AsyncJobManager asyncJobManager;
+
+    @Mock
+    private AsyncJobDispatcher asyncJobDispatcher;
+
+    private AutoCloseable closeable;
+
+    private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+        actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
+        Mockito.when(ActionEventUtils.onScheduledActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
+                       Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(),
+                       Mockito.anyLong()))
+               .thenReturn(1L);
+        Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
+                Mockito.anyString(), Mockito.anyBoolean(),
+                Mockito.anyString(),
+                Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        actionEventUtilsMocked.close();
+        closeable.close();
+    }
+
+    @Test
+    public void testProcessJobRunning() {
+        executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.STOP);
+        executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.FORCE_STOP);
+        executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.REBOOT);
+        executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.FORCE_REBOOT);
+        executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Stopped, VMSchedule.Action.START);
+    }
+
+    private void executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State state, VMSchedule.Action action) {
+        VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+
+        Long expectedValue = 1L;
+
+        prepareMocksForProcessJob(vm, vmScheduledJob, state, action, expectedValue);
+
+        Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
+
+        actionEventUtilsMocked.verify(() -> ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), null,
+                actionEventMap.get(action), true,
+                String.format("Executing action (%s) for VM Id:%s", vmScheduledJob.getAction(), vm.getUuid()),
+                vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0));
+        Assert.assertEquals(expectedValue, jobId);
+    }
+
+    private void prepareMocksForProcessJob(VirtualMachine vm, VMScheduledJob vmScheduledJob,
+                                           VirtualMachine.State vmState, VMSchedule.Action action,
+                                           Long executeJobReturnValue) {
+        Mockito.when(vm.getState()).thenReturn(vmState);
+        Mockito.when(vmScheduledJob.getAction()).thenReturn(action);
+
+        if (executeJobReturnValue != null) {
+            Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeStartVMJob(
+                    Mockito.any(VirtualMachine.class), Mockito.anyLong());
+            Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeStopVMJob(
+                    Mockito.any(VirtualMachine.class), Mockito.anyBoolean(), Mockito.anyLong());
+            Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeRebootVMJob(
+                    Mockito.any(VirtualMachine.class), Mockito.anyBoolean(), Mockito.anyLong());
+        }
+    }
+
+    @Test
+    public void testProcessJobInvalidAction() {
+        VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+
+        prepareMocksForProcessJob(vm, vmScheduledJob, VirtualMachine.State.Running, VMSchedule.Action.START, null);
+
+        Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
+
+        Assert.assertNull(jobId);
+    }
+
+    @Test
+    public void testProcessJobVMInInvalidState() {
+        VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+
+        prepareMocksForProcessJob(vm, vmScheduledJob, VirtualMachine.State.Unknown, VMSchedule.Action.START, null);
+
+        Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
+
+        Assert.assertNull(jobId);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleScheduleExists() {
+        Date now = DateUtils.setSeconds(new Date(), 0);
+        Date startDate = DateUtils.addDays(now, 1);
+        Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
+        UserVm vm = Mockito.mock(UserVm.class);
+
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
+        Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
+        Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
+        Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
+        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
+        Mockito.when(vmScheduledJobDao.persist(Mockito.any())).thenThrow(EntityExistsException.class);
+        Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+
+        Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleFutureSchedule() {
+        Date now = DateUtils.setSeconds(new Date(), 0);
+        Date startDate = DateUtils.addDays(now, 1);
+        Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
+        UserVm vm = Mockito.mock(UserVm.class);
+
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
+        Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
+        Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
+        Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
+        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
+        Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+
+        Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleFutureScheduleWithTimeZoneChecks() throws Exception {
+        // Ensure that Date vmSchedulerImpl.scheduleNextJob(VMScheduleVO vmSchedule) generates
+        // the correct scheduled time on basis of schedule(cron), start date
+        // and the timezone of the user. The system running the test can have any timezone.
+        String cron = "30 5 * * *";
+
+        Date now = DateUtils.setSeconds(new Date(), 0);
+        Date startDate = DateUtils.addDays(now, 1);
+
+        UserVm vm = Mockito.mock(UserVm.class);
+
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
+        Mockito.when(vmSchedule.getSchedule()).thenReturn(cron);
+        Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
+        Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
+        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
+
+        // The timezone of the user is EST. The cron expression is 30 5 * * *.
+        // The start date is 1 day from now. The expected scheduled time is 5:30 AM EST.
+        // The actual scheduled time is 10:30 AM UTC.
+        // The actual scheduled time is 5:30 AM EST.
+        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(startDate.toInstant(), vmSchedule.getTimeZoneId());
+        zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
+        Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
+
+        if (expectedScheduledTime.before(startDate)) {
+            expectedScheduledTime = DateUtils.addDays(expectedScheduledTime, 1);
+        }
+
+        Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+        Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleCurrentSchedule() {
+        Date now = DateUtils.setSeconds(new Date(), 0);
+        Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(now, 1), Calendar.MINUTE);
+        UserVm vm = Mockito.mock(UserVm.class);
+
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
+        Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
+        Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
+        Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
+        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
+        Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+
+        Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleCurrentScheduleWithTimeZoneChecks() throws Exception {
+        // Ensure that Date vmSchedulerImpl.scheduleNextJob(VMScheduleVO vmSchedule) generates
+        // the correct scheduled time on basis of schedule(cron), start date
+        // and the timezone of the user. The system running the test can have any timezone.
+        String cron = "30 5 * * *";
+
+        Date now = DateUtils.setSeconds(new Date(), 0);
+
+        UserVm vm = Mockito.mock(UserVm.class);
+
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
+        Mockito.when(vmSchedule.getSchedule()).thenReturn(cron);
+        Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
+        Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
+        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
+
+        // The timezone of the user is EST. The cron expression is 30 5 * * *.
+        // The start date is 1 day ago. The expected scheduled time is 5:30 AM EST.
+        // The actual scheduled time is 10:30 AM UTC.
+        // The actual scheduled time is 5:30 AM EST.
+        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now.toInstant(), vmSchedule.getTimeZoneId());
+        zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
+        Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
+
+        if (expectedScheduledTime.before(now)) {
+            expectedScheduledTime = DateUtils.addDays(expectedScheduledTime, 1);
+        }
+
+        Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+        Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleExpired() {
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEndDate()).thenReturn(DateUtils.addMinutes(new Date(), -5));
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
+        Date actualDate = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+        Assert.assertNull(actualDate);
+    }
+
+    @Test
+    public void testScheduleNextJobScheduleDisabled() {
+        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
+        Mockito.when(vmSchedule.getEnabled()).thenReturn(false);
+        Date actualDate = vmScheduler.scheduleNextJob(vmSchedule, new Date());
+        Assert.assertNull(actualDate);
+    }
+
+
+    @Test
+    public void testExecuteJobs() {
+        /*
+         Test VMSchedulerImpl.executeJobs() method with a map of VMScheduledJob objects
+         covering all the possible scenarios
+         1. When the job is executed successfully
+         2. When the job is skipped (processJob returns null)
+        */
+
+        VMScheduledJobVO job1 = Mockito.mock(VMScheduledJobVO.class);
+        VMScheduledJobVO job2 = Mockito.mock(VMScheduledJobVO.class);
+
+        UserVm vm1 = Mockito.mock(UserVm.class);
+        UserVm vm2 = Mockito.mock(UserVm.class);
+
+        Mockito.when(job1.getVmId()).thenReturn(1L);
+        Mockito.when(job1.getScheduledTime()).thenReturn(new Date());
+        Mockito.when(job1.getAction()).thenReturn(VMSchedule.Action.START);
+        Mockito.when(job1.getVmScheduleId()).thenReturn(1L);
+        Mockito.when(job2.getVmId()).thenReturn(2L);
+        Mockito.when(job2.getScheduledTime()).thenReturn(new Date());
+        Mockito.when(job2.getAction()).thenReturn(VMSchedule.Action.STOP);
+        Mockito.when(job2.getVmScheduleId()).thenReturn(2L);
+
+        Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm1);
+        Mockito.when(userVmManager.getUserVm(2L)).thenReturn(vm2);
+
+        Mockito.doReturn(1L).when(vmScheduler).processJob(job1, vm1);
+        Mockito.doReturn(null).when(vmScheduler).processJob(job2, vm2);
+
+        Mockito.when(vmScheduledJobDao.acquireInLockTable(job1.getId())).thenReturn(job1);
+        Mockito.when(vmScheduledJobDao.acquireInLockTable(job2.getId())).thenReturn(job2);
+
+        Map<Long, VMScheduledJob> jobs = new HashMap<>();
+        jobs.put(1L, job1);
+        jobs.put(2L, job2);
+
+        ReflectionTestUtils.setField(vmScheduler, "currentTimestamp", new Date());
+
+        vmScheduler.executeJobs(jobs);
+
+        Mockito.verify(vmScheduler, Mockito.times(2)).processJob(Mockito.any(), Mockito.any());
+        Mockito.verify(vmScheduledJobDao, Mockito.times(2)).acquireInLockTable(Mockito.anyLong());
+    }
+
+    @Test
+    public void testExecuteStopVMJob() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
+        try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
+            when(ComponentContext.inject(StopVMCmd.class)).thenReturn(Mockito.mock(StopVMCmd.class));
+            long jobId = vmScheduler.executeStopVMJob(vm, false, 1L);
+
+            Assert.assertEquals(1L, jobId);
+        }
+    }
+
+    @Test
+    public void testExecuteRebootVMJob() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
+        try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
+            when(ComponentContext.inject(RebootVMCmd.class)).thenReturn(Mockito.mock(RebootVMCmd.class));
+            long jobId = vmScheduler.executeRebootVMJob(vm, false, 1L);
+
+            Assert.assertEquals(1L, jobId);
+        }
+    }
+
+    @Test
+    public void testExecuteStartVMJob() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
+        try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
+            when(ComponentContext.inject(StartVMCmd.class)).thenReturn(Mockito.mock(StartVMCmd.class));
+            long jobId = vmScheduler.executeStartVMJob(vm, 1L);
+
+            Assert.assertEquals(1L, jobId);
+        }
+    }
+}
diff --git a/server/src/test/resources/createNetworkOffering.xml b/server/src/test/resources/createNetworkOffering.xml
index 0f558d1..28e6027 100644
--- a/server/src/test/resources/createNetworkOffering.xml
+++ b/server/src/test/resources/createNetworkOffering.xml
@@ -72,4 +72,5 @@
     <bean id="PassphraseDaoImpl" class="org.apache.cloudstack.secret.dao.PassphraseDaoImpl" />

     <bean id="configurationGroupDaoImpl" class="org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl" />

     <bean id="configurationSubGroupDaoImpl" class="org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl" />

+    <bean id="publicIpQuarantineDaoImpl" class="com.cloud.network.dao.PublicIpQuarantineDaoImpl" />

 </beans>

diff --git a/server/src/test/resources/db.properties b/server/src/test/resources/db.properties
index 5eefc45..eabc445 100644
--- a/server/src/test/resources/db.properties
+++ b/server/src/test/resources/db.properties
@@ -1,72 +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.

-

-

-# management server clustering parameters, change cluster.node.IP to the machine IP address

+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+# management server clustering parameters, change cluster.node.IP to the machine IP address
 # in which the management server is running
-cluster.node.IP=127.0.0.1

-cluster.servlet.port=9090

-

-# CloudStack database settings

-db.cloud.username=cloud

-db.cloud.password=cloud

-db.root.password=

-db.cloud.host=localhost

-db.cloud.driver=jdbc:mysql

-db.cloud.port=3306

-db.cloud.name=cloud

-

-# CloudStack database tuning parameters

-db.cloud.maxActive=250

-db.cloud.maxIdle=30

-db.cloud.maxWait=10000

-db.cloud.autoReconnect=true

-db.cloud.validationQuery=SELECT 1

-db.cloud.testOnBorrow=true

-db.cloud.testWhileIdle=true

-db.cloud.timeBetweenEvictionRunsMillis=40000

-db.cloud.minEvictableIdleTimeMillis=240000

-db.cloud.poolPreparedStatements=false

-db.cloud.url.params=prepStmtCacheSize=517&cachePrepStmts=true&prepStmtCacheSqlLimit=4096

-

-# usage database settings

-db.usage.username=cloud

-db.usage.password=cloud

-db.usage.host=localhost

-# It's not guaranteed that using a different DB provider than the one from the regular cloud DB will work

-db.usage.driver=jdbc:mysql

-db.usage.port=3306

-db.usage.name=cloud_usage

-

-# usage database tuning parameters

-db.usage.maxActive=100

-db.usage.maxIdle=30

-db.usage.maxWait=10000

-db.usage.autoReconnect=true

-

-# Simulator database settings

-db.simulator.username=cloud

-db.simulator.password=cloud

-db.simulator.host=localhost

-# It's not guaranteed that using a different DB provider than the one from the regular cloud DB will work

-db.simulator.driver=jdbc:mysql

-db.simulator.port=3306

-db.simulator.name=simulator

-db.simulator.maxActive=250

-db.simulator.maxIdle=30

-db.simulator.maxWait=10000

-db.simulator.autoReconnect=true

+cluster.node.IP=127.0.0.1
+cluster.servlet.port=9090
+
+# CloudStack database settings
+db.cloud.username=cloud
+db.cloud.password=cloud
+db.root.password=
+db.cloud.host=localhost
+db.cloud.driver=jdbc:mysql
+db.cloud.port=3306
+db.cloud.name=cloud
+
+# CloudStack database tuning parameters
+db.cloud.maxActive=250
+db.cloud.maxIdle=30
+db.cloud.maxWait=10000
+db.cloud.autoReconnect=true
+db.cloud.validationQuery=SELECT 1
+db.cloud.testOnBorrow=true
+db.cloud.testWhileIdle=true
+db.cloud.timeBetweenEvictionRunsMillis=40000
+db.cloud.minEvictableIdleTimeMillis=240000
+db.cloud.poolPreparedStatements=false
+db.cloud.url.params=prepStmtCacheSize=517&cachePrepStmts=true&prepStmtCacheSqlLimit=4096
+
+# usage database settings
+db.usage.username=cloud
+db.usage.password=cloud
+db.usage.host=localhost
+# It's not guaranteed that using a different DB provider than the one from the regular cloud DB will work
+db.usage.driver=jdbc:mysql
+db.usage.port=3306
+db.usage.name=cloud_usage
+
+# usage database tuning parameters
+db.usage.maxActive=100
+db.usage.maxIdle=30
+db.usage.maxWait=10000
+db.usage.autoReconnect=true
+
+# Simulator database settings
+db.simulator.username=cloud
+db.simulator.password=cloud
+db.simulator.host=localhost
+# It's not guaranteed that using a different DB provider than the one from the regular cloud DB will work
+db.simulator.driver=jdbc:mysql
+db.simulator.port=3306
+db.simulator.name=simulator
+db.simulator.maxActive=250
+db.simulator.maxIdle=30
+db.simulator.maxWait=10000
+db.simulator.autoReconnect=true
diff --git a/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/services/console-proxy/pom.xml b/services/console-proxy/pom.xml
index a3b8426..379dbda 100644
--- a/services/console-proxy/pom.xml
+++ b/services/console-proxy/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-services</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <build>
diff --git a/services/console-proxy/rdpconsole/pom.xml b/services/console-proxy/rdpconsole/pom.xml
index ed2dc71..9df8d02 100644
--- a/services/console-proxy/rdpconsole/pom.xml
+++ b/services/console-proxy/rdpconsole/pom.xml
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-service-console-proxy</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/services/console-proxy/rdpconsole/rdp-config.bat b/services/console-proxy/rdpconsole/rdp-config.bat
index f19b00d..614ee23 100755
--- a/services/console-proxy/rdpconsole/rdp-config.bat
+++ b/services/console-proxy/rdpconsole/rdp-config.bat
@@ -39,4 +39,3 @@
 rem Start TS service
 
 net start Termservice
-
diff --git a/services/console-proxy/rdpconsole/src/main/java/rdpclient/RdpClient.java b/services/console-proxy/rdpconsole/src/main/java/rdpclient/RdpClient.java
index a3db165..3dd7cf9 100644
--- a/services/console-proxy/rdpconsole/src/main/java/rdpclient/RdpClient.java
+++ b/services/console-proxy/rdpconsole/src/main/java/rdpclient/RdpClient.java
@@ -324,7 +324,7 @@
 
                 );
 
-        // Last element of handshake sequence will wake up queue and and socket
+        // Last element of handshake sequence will wake up queue and socket
         // output pull loop, which will switch links, between socket output and
         // queue, from push mode to pull mode.
         link(HANDSHAKE_END + " >queue", "queue", "OUT");
diff --git a/services/console-proxy/rdpconsole/src/main/java/streamer/debug/MockServer.java b/services/console-proxy/rdpconsole/src/main/java/streamer/debug/MockServer.java
index c8f08b4..d4e48c6 100644
--- a/services/console-proxy/rdpconsole/src/main/java/streamer/debug/MockServer.java
+++ b/services/console-proxy/rdpconsole/src/main/java/streamer/debug/MockServer.java
@@ -198,8 +198,8 @@
         return shutdowned;
     }
 
-    public void waitUntilShutdowned(long timeToWaitMiliseconds) throws InterruptedException {
-        long deadline = System.currentTimeMillis() + timeToWaitMiliseconds;
+    public void waitUntilShutdowned(long timeToWaitMilliseconds) throws InterruptedException {
+        long deadline = System.currentTimeMillis() + timeToWaitMilliseconds;
         while (!shutdowned && System.currentTimeMillis() < deadline) {
             Thread.sleep(10);
         }
diff --git a/services/console-proxy/rdpconsole/src/test/doc/dev-rdp-config.bat b/services/console-proxy/rdpconsole/src/test/doc/dev-rdp-config.bat
index 4e19157..f5afc0c 100755
--- a/services/console-proxy/rdpconsole/src/test/doc/dev-rdp-config.bat
+++ b/services/console-proxy/rdpconsole/src/test/doc/dev-rdp-config.bat
@@ -136,4 +136,3 @@
 rem Don't forget to set Windows profile as active in Network Monitor, so SSL traffic branch will appear under
 rem svnchost.exe, so you will be able to decrypt it (don't forget to save and reopen captured traffic to file first).
 rem 
-
diff --git a/services/console-proxy/rdpconsole/src/test/java/common/ClientTest.java b/services/console-proxy/rdpconsole/src/test/java/common/ClientTest.java
index 3f5aa90..a54b52e 100644
--- a/services/console-proxy/rdpconsole/src/test/java/common/ClientTest.java
+++ b/services/console-proxy/rdpconsole/src/test/java/common/ClientTest.java
@@ -21,20 +21,20 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.reflect.Whitebox;
+import org.mockito.junit.MockitoJUnitRunner;
 
+import org.springframework.test.util.ReflectionTestUtils;
 import streamer.Element;
 import streamer.SocketWrapper;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ClientTest {
 
     @Test(expected = NullPointerException.class)
     public void testAssemblePipelineWhenMainElementIsNull() throws Exception {
         SocketWrapper socketMock = mock(SocketWrapper.class);
         when(socketMock.getId()).thenReturn("socket");
-        Whitebox.setInternalState(Client.class, "socket", socketMock);
+        ReflectionTestUtils.setField(Client.class, "socket", socketMock);
         Element main = null;
 
         Client.assemblePipeline(main);
diff --git a/services/console-proxy/rdpconsole/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/services/console-proxy/rdpconsole/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/services/console-proxy/rdpconsole/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml
index d6eccfa..cdb0019 100644
--- a/services/console-proxy/server/pom.xml
+++ b/services/console-proxy/server/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-service-console-proxy</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelperTest.java b/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelperTest.java
index 690b0c1..53389ba 100644
--- a/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelperTest.java
+++ b/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelperTest.java
@@ -19,33 +19,30 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Map;
 
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ConsoleProxyHttpHandlerHelperTest {
 
-    @Mock
-    ConsoleProxyPasswordBasedEncryptor encryptor;
-
     @Test
-    @PrepareForTest({ConsoleProxy.class, ConsoleProxyHttpHandlerHelper.class})
     public void testQueryMapExtraParameter() throws Exception {
-        PowerMockito.mockStatic(ConsoleProxy.class);
-        PowerMockito.when(ConsoleProxy.getEncryptorPassword()).thenReturn("password");
-        PowerMockito.whenNew(ConsoleProxyPasswordBasedEncryptor.class).withArguments(Mockito.anyString()).thenReturn(encryptor);
-        Mockito.when(encryptor.decryptObject(Mockito.eq(ConsoleProxyClientParam.class), Mockito.anyString())).thenReturn(null);
+        try (MockedStatic<ConsoleProxy> ignore = Mockito.mockStatic(ConsoleProxy.class);
+             MockedConstruction<ConsoleProxyPasswordBasedEncryptor> ignored = Mockito.mockConstruction(ConsoleProxyPasswordBasedEncryptor.class, (mock, context) -> {
+                 Mockito.when(mock.decryptObject(Mockito.eq(ConsoleProxyClientParam.class), Mockito.anyString())).thenReturn(null);
+             });) {
+            Mockito.when(ConsoleProxy.getEncryptorPassword()).thenReturn("password");
 
-        String extraValidationToken = "test-token";
-        String query = String.format("token=SOME_TOKEN&extra=%s", extraValidationToken);
+            String extraValidationToken = "test-token";
+            String query = String.format("token=SOME_TOKEN&extra=%s", extraValidationToken);
 
-        Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(query);
-        Assert.assertTrue(queryMap.containsKey("extra"));
-        Assert.assertEquals(extraValidationToken, queryMap.get("extra"));
+            Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(query);
+            Assert.assertTrue(queryMap.containsKey("extra"));
+            Assert.assertEquals(extraValidationToken, queryMap.get("extra"));
+        }
     }
 }
diff --git a/services/console-proxy/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/services/console-proxy/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/services/console-proxy/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/services/pom.xml b/services/pom.xml
index 442ac77..8bcfa0c 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <build>
diff --git a/services/secondary-storage/controller/pom.xml b/services/secondary-storage/controller/pom.xml
index a8ab955..5a1373e 100644
--- a/services/secondary-storage/controller/pom.xml
+++ b/services/secondary-storage/controller/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-service-secondary-storage</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/services/secondary-storage/pom.xml b/services/secondary-storage/pom.xml
index 165885a..4bba35f 100644
--- a/services/secondary-storage/pom.xml
+++ b/services/secondary-storage/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-services</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <build>
diff --git a/services/secondary-storage/server/pom.xml b/services/secondary-storage/server/pom.xml
index 6641c7e..432486e 100644
--- a/services/secondary-storage/server/pom.xml
+++ b/services/secondary-storage/server/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack-service-secondary-storage</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
index d8f7395..cc17e48 100644
--- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
+++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
@@ -49,6 +49,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.naming.ConfigurationException;
 
@@ -60,10 +62,13 @@
 import org.apache.cloudstack.storage.command.DownloadCommand;
 import org.apache.cloudstack.storage.command.DownloadProgressCommand;
 import org.apache.cloudstack.storage.command.MoveVolumeCommand;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
 import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
 import org.apache.cloudstack.storage.command.UploadStatusAnswer;
 import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
 import org.apache.cloudstack.storage.command.UploadStatusCommand;
+import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
 import org.apache.cloudstack.storage.configdrive.ConfigDrive;
 import org.apache.cloudstack.storage.configdrive.ConfigDriveBuilder;
 import org.apache.cloudstack.storage.template.DownloadManager;
@@ -80,6 +85,7 @@
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
@@ -159,6 +165,7 @@
 import com.cloud.storage.template.VhdProcessor;
 import com.cloud.storage.template.VmdkProcessor;
 import com.cloud.utils.EncryptionUtil;
+import com.cloud.utils.LogUtils;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.Pair;
 import com.cloud.utils.SwiftUtil;
@@ -195,6 +202,8 @@
     private static final String POST_UPLOAD_KEY_LOCATION = "/etc/cloudstack/agent/ms-psk";
     private static final String ORIGINAL_FILE_EXTENSION = ".orig";
 
+    private static final String USE_HTTPS_TO_UPLOAD = "useHttpsToUpload";
+
     private static final Map<String, String> updatableConfigData = Maps.newHashMap();
     static {
 
@@ -272,6 +281,7 @@
 
     @Override
     public Answer executeRequest(Command cmd) {
+        s_logger.debug(LogUtils.logGsonWithoutException("Executing command %s [%s].", cmd.getClass().getSimpleName(), cmd));
         if (cmd instanceof DownloadProgressCommand) {
             return _dlMgr.handleDownloadCommand(this, (DownloadProgressCommand)cmd);
         } else if (cmd instanceof DownloadCommand) {
@@ -316,11 +326,19 @@
             return execute((CreateDatadiskTemplateCommand)cmd);
         } else if (cmd instanceof MoveVolumeCommand) {
             return execute((MoveVolumeCommand)cmd);
+        } else if (cmd instanceof ListDataStoreObjectsCommand) {
+            return execute((ListDataStoreObjectsCommand)cmd);
+        } else if (cmd instanceof QuerySnapshotZoneCopyCommand) {
+            return execute((QuerySnapshotZoneCopyCommand)cmd);
         } else {
             return Answer.createUnsupportedCommandAnswer(cmd);
         }
     }
 
+    private Answer execute(ListDataStoreObjectsCommand cmd) {
+        return listFilesAtPath(getRootDir(cmd.getStore().getUrl(), _nfsVersion), cmd.getPath(), cmd.getStartIndex(), cmd.getPageSize());
+    }
+
     private Answer execute(HandleConfigDriveIsoCommand cmd) {
         if (cmd.isCreate()) {
             if (cmd.getIsoData() == null) {
@@ -406,13 +424,17 @@
         NfsTO nfsImageStore = (NfsTO)srcStore;
         String secondaryStorageUrl = nfsImageStore.getUrl();
         assert (secondaryStorageUrl != null);
+
         String templateUrl = secondaryStorageUrl + File.separator + srcData.getPath();
+        String templateDetails = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(template, "uuid", "path", "name");
+        s_logger.debug(String.format("Trying to get disks of template [%s], using path [%s].", templateDetails, templateUrl));
+
         Pair<String, String> templateInfo = decodeTemplateRelativePathAndNameFromUrl(secondaryStorageUrl, templateUrl, template.getName());
         String templateRelativeFolderPath = templateInfo.first();
 
         try {
             String secondaryMountPoint = getRootDir(secondaryStorageUrl, _nfsVersion);
-            s_logger.info("MDOVE Secondary storage mount point: " + secondaryMountPoint);
+            s_logger.info(String.format("Trying to find template [%s] in secondary storage root mount point [%s].", templateDetails, secondaryMountPoint));
 
             String srcOVAFileName = getTemplateOnSecStorageFilePath(secondaryMountPoint, templateRelativeFolderPath, templateInfo.second(), ImageFormat.OVA.getFileExtension());
 
@@ -423,39 +445,46 @@
                 command.add("--no-same-permissions");
                 command.add("-xf", srcOVAFileName);
                 command.setWorkDir(secondaryMountPoint + File.separator + templateRelativeFolderPath);
-                s_logger.info("Executing command: " + command.toString());
+
+                s_logger.info(String.format("Trying to decompress OVA file [%s] using command [%s].", srcOVAFileName, command.toString()));
                 String result = command.execute();
                 if (result != null) {
-                    String msg = "Unable to unpack snapshot OVA file at: " + srcOVAFileName;
+                    String msg = String.format("Unable to unpack snapshot OVA file [%s] due to [%s].", srcOVAFileName, result);
                     s_logger.error(msg);
                     throw new Exception(msg);
                 }
 
+                String directory = secondaryMountPoint + File.separator + templateRelativeFolderPath;
                 command = new Script("chmod", 0, s_logger);
                 command.add("-R");
-                command.add("666", secondaryMountPoint + File.separator + templateRelativeFolderPath);
+                command.add("666", directory);
+
+                s_logger.debug(String.format("Trying to add, recursivelly, permission 666 to directory [%s] using command [%s].", directory, command.toString()));
                 result = command.execute();
                 if (result != null) {
-                    s_logger.warn("Unable to set permissions for " + secondaryMountPoint + File.separator + templateRelativeFolderPath + " due to " + result);
+                    s_logger.warn(String.format("Unable to set permissions 666 for directory [%s] due to [%s].", directory, result));
                 }
             }
 
             Script command = new Script("cp", _timeout, s_logger);
             command.add(ovfFilePath);
             command.add(ovfFilePath + ORIGINAL_FILE_EXTENSION);
+            s_logger.debug(String.format("Trying to copy file from [%s] to [%s] using command [%s].", ovfFilePath, ovfFilePath + ORIGINAL_FILE_EXTENSION, command.toString()));
             String result = command.execute();
             if (result != null) {
-                String msg = "Unable to rename original OVF, error msg: " + result;
+                String msg = String.format("Unable to copy original OVF file [%s] to [%s] due to [%s].", ovfFilePath, ovfFilePath + ORIGINAL_FILE_EXTENSION, result);
                 s_logger.error(msg);
             }
 
-            s_logger.debug("Reading OVF " + ovfFilePath + " to retrive the number of disks present in OVA");
+            s_logger.debug(String.format("Reading OVF file [%s] to retrive the number of disks present in OVA file.", ovfFilePath));
             OVFHelper ovfHelper = new OVFHelper();
 
             List<DatadiskTO> disks = ovfHelper.getOVFVolumeInfoFromFile(ovfFilePath, configurationId);
+            s_logger.debug(LogUtils.logGsonWithoutException("Found %s disks reading OVF file [%s] and using configuration id [%s]. The disks specifications are [%s].",
+                    disks.size(), ovfFilePath, configurationId, disks));
             return new GetDatadisksAnswer(disks);
         } catch (Exception e) {
-            String msg = "Get Datadisk Template Count failed due to " + e.getMessage();
+            String msg = String.format("Failed to get disks from template [%s] due to [%s].", templateDetails, e.getMessage());
             s_logger.error(msg, e);
             return new GetDatadisksAnswer(msg);
         }
@@ -584,7 +613,7 @@
      *  Template url may or may not end with .ova extension
      */
     public static Pair<String, String> decodeTemplateRelativePathAndNameFromUrl(String storeUrl, String templateUrl, String defaultName) {
-
+        s_logger.debug(String.format("Trying to get template relative path and name from URL [%s].", templateUrl));
         String templateName = null;
         String mountPoint = null;
         if (templateUrl.endsWith(".ova")) {
@@ -598,6 +627,7 @@
             templateName = templateUrl.substring(index + 1).replace(".ova", "");
 
             if (templateName == null || templateName.isEmpty()) {
+                s_logger.debug(String.format("Cannot find template name from URL [%s]. Using default name [%s].", templateUrl, defaultName));
                 templateName = defaultName;
             }
         } else {
@@ -608,11 +638,13 @@
             templateName = defaultName;
         }
 
+        s_logger.debug(String.format("Template relative path [%s] and name [%s] found from URL [%s].", mountPoint, templateName, templateUrl));
         return new Pair<String, String>(mountPoint, templateName);
     }
 
     public static String getTemplateOnSecStorageFilePath(String secStorageMountPoint, String templateRelativeFolderPath, String templateName, String fileExtension) {
-
+        s_logger.debug(String.format("Trying to find template [%s] with file extension [%s] in secondary storage mount point [%s] using relative folder path [%s].",
+                templateName, fileExtension, secStorageMountPoint, templateRelativeFolderPath));
         StringBuffer sb = new StringBuffer();
         sb.append(secStorageMountPoint);
         if (!secStorageMountPoint.endsWith("/")) {
@@ -699,17 +731,27 @@
     }
 
     private String getOVFFilePath(String srcOVAFileName) {
+        s_logger.debug(String.format("Trying to get OVF file from OVA path [%s].", srcOVAFileName));
+
         File file = new File(srcOVAFileName);
         assert (_storage != null);
         String[] files = _storage.listFiles(file.getParent());
-        if (files != null) {
-            for (String fileName : files) {
-                if (fileName.toLowerCase().endsWith(".ovf")) {
-                    File ovfFile = new File(fileName);
-                    return file.getParent() + File.separator + ovfFile.getName();
-                }
+
+        if (files == null) {
+            s_logger.warn(String.format("Cannot find any files in parent directory [%s] of OVA file [%s].", file.getParent(), srcOVAFileName));
+            return null;
+        }
+
+        s_logger.debug(String.format("Found [%s] files in parent directory of OVA file [%s]. Files found are [%s].", files.length + 1, file.getParent(), StringUtils.join(files, ", ")));
+        for (String fileName : files) {
+            if (fileName.toLowerCase().endsWith(".ovf")) {
+                File ovfFile = new File(fileName);
+                String ovfFilePath = file.getParent() + File.separator + ovfFile.getName();
+                s_logger.debug(String.format("Found OVF file [%s] from OVA file [%s].", ovfFilePath, srcOVAFileName));
+                return ovfFilePath;
             }
         }
+        s_logger.warn(String.format("Cannot find any OVF file in parent directory [%s] of OVA file [%s].", file.getParent(), srcOVAFileName));
         return null;
     }
 
@@ -2623,13 +2665,13 @@
             return _parent;
         }
         try {
+            s_logger.debug(String.format("Trying to get root directory from secondary storage URL [%s] using NFS version [%s].", secUrl, nfsVersion));
             URI uri = new URI(secUrl);
             String dir = mountUri(uri, nfsVersion);
             return _parent + "/" + dir;
         } catch (Exception e) {
-            String msg = "GetRootDir for " + secUrl + " failed due to " + e.toString();
-            s_logger.error(msg, e);
-            throw new CloudRuntimeException(msg);
+            String msg = String.format("Failed to get root directory from secondary storage URL [%s], using NFS version [%s], due to [%s].", secUrl, nfsVersion, e.getMessage());
+            throw new CloudRuntimeException(msg, e);
         }
     }
 
@@ -3498,7 +3540,7 @@
         return null;
     }
 
-    private String getPostUploadPSK() {
+    protected String getPostUploadPSK() {
         if (_ssvmPSK == null) {
             try {
                 _ssvmPSK = FileUtils.readFileToString(new File(POST_UPLOAD_KEY_LOCATION), "utf-8");
@@ -3534,14 +3576,7 @@
             throw new InvalidParameterValueException("content length is not set in the request or has invalid value.");
         }
 
-        //validate signature
-        String fullUrl = "https://" + hostname + "/upload/" + uuid;
-        String computedSignature = EncryptionUtil.generateSignature(metadata + fullUrl + timeout, getPostUploadPSK());
-        boolean isSignatureValid = computedSignature.equals(signature);
-        if (!isSignatureValid) {
-            updateStateMapWithError(uuid, "signature validation failed.");
-            throw new InvalidParameterValueException("signature validation failed.");
-        }
+        validatePostUploadRequestSignature(signature, hostname, uuid, metadata, timeout);
 
         //validate timeout
         DateTime timeoutDateTime = DateTime.parse(timeout, ISODateTimeFormat.dateTime());
@@ -3551,6 +3586,48 @@
         }
     }
 
+    /**
+     * Validates whether the provided signature matches the signature generated from the other parameters;
+     * throws an InvalidParameterValueException if it does not.
+     */
+    protected void validatePostUploadRequestSignature(String signature, String hostname, String uuid, String metadata, String timeout) {
+        s_logger.trace(String.format("Validating signature [%s] for post upload request [%s].", signature, uuid));
+        String protocol = getUploadProtocol();
+        String fullUrl = String.format("%s://%s/upload/%s", protocol, hostname, uuid);
+        String data = String.format("%s%s%s", metadata, fullUrl, timeout);
+
+        String computedSignature = EncryptionUtil.generateSignature(data, getPostUploadPSK());
+        s_logger.debug(String.format("Computed signature for post upload request [%s] is [%s].", uuid, computedSignature));
+
+        boolean isSignatureValid = computedSignature.equals(signature);
+        if (!isSignatureValid) {
+            s_logger.debug(String.format("Signature for post upload request [%s] is invalid.", uuid));
+            String errorMsg = "signature validation failed.";
+            updateStateMapWithError(uuid, errorMsg);
+            throw new InvalidParameterValueException(errorMsg);
+        }
+        s_logger.debug(String.format("Signature for post upload request [%s] is valid.", uuid));
+    }
+
+    /**
+     * Returns the protocol used for uploads as a string.
+     */
+    protected String getUploadProtocol() {
+        if (useHttpsToUpload()) {
+            s_logger.debug(String.format("Param [%s] is set to true; therefore, HTTPS is being used.", USE_HTTPS_TO_UPLOAD));
+            return NetUtils.HTTPS_PROTO;
+        }
+        s_logger.debug(String.format("Param [%s] is set to false; therefore, HTTP is being used.", USE_HTTPS_TO_UPLOAD));
+        return NetUtils.HTTP_PROTO;
+    }
+
+    /**
+     * Retrieves the value of "useHttpsToUpload" from the params as a boolean
+     */
+    protected boolean useHttpsToUpload() {
+        return BooleanUtils.toBoolean((String) _params.get(USE_HTTPS_TO_UPLOAD));
+    }
+
     private TemplateOrVolumePostUploadCommand getTemplateOrVolumePostUploadCmd(String metadata) {
         TemplateOrVolumePostUploadCommand cmd = null;
         try {
@@ -3562,4 +3639,32 @@
         return cmd;
     }
 
+    protected Answer execute(QuerySnapshotZoneCopyCommand cmd) {
+        SnapshotObjectTO snapshot = cmd.getSnapshot();
+        String parentPath = getRootDir(snapshot.getDataStore().getUrl(), _nfsVersion);
+        String path = snapshot.getPath();
+        File snapFile = new File(parentPath + File.separator + path);
+        if (snapFile.exists() && !snapFile.isDirectory()) {
+            return new QuerySnapshotZoneCopyAnswer(cmd, List.of(path));
+        }
+        int index = path.lastIndexOf(File.separator);
+        String snapDir = path.substring(0, index);
+        List<String> files = new ArrayList<>();
+        try (Stream<Path> stream = Files.list(Paths.get(parentPath + File.separator + snapDir))) {
+            List<String> fileNames = stream
+                    .filter(file -> !Files.isDirectory(file))
+                    .map(Path::getFileName)
+                    .map(Path::toString)
+                    .collect(Collectors.toList());
+            for (String file : fileNames) {
+                file = snapDir + "/" + file;
+                s_logger.debug(String.format("Found snapshot file %s", file));
+                files.add(file);
+            }
+        } catch (IOException ioe) {
+            s_logger.error("Error preparing file list for snapshot copy", ioe);
+        }
+        return new QuerySnapshotZoneCopyAnswer(cmd, files);
+    }
+
 }
diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
index e1497768..e1e6902 100644
--- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
+++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
@@ -16,12 +16,18 @@
 // under the License.
 package org.apache.cloudstack.storage.template;
 
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -37,55 +43,54 @@
 
 import javax.naming.ConfigurationException;
 
-import com.cloud.agent.api.to.OVFInformationTO;
-import com.cloud.storage.template.Processor;
-import com.cloud.storage.template.S3TemplateDownloader;
-import com.cloud.storage.template.TemplateDownloader;
-import com.cloud.storage.template.TemplateLocation;
-import com.cloud.storage.template.MetalinkTemplateDownloader;
-import com.cloud.storage.template.HttpTemplateDownloader;
-import com.cloud.storage.template.LocalTemplateDownloader;
-import com.cloud.storage.template.ScpTemplateDownloader;
-import com.cloud.storage.template.TemplateProp;
-import com.cloud.storage.template.OVAProcessor;
-import com.cloud.storage.template.IsoProcessor;
-import com.cloud.storage.template.QCOW2Processor;
-import com.cloud.storage.template.VmdkProcessor;
-import com.cloud.storage.template.RawImageProcessor;
-import com.cloud.storage.template.TARProcessor;
-import com.cloud.storage.template.VhdProcessor;
-import com.cloud.storage.template.TemplateConstants;
+import org.apache.cloudstack.storage.NfsMountManagerImpl.PathParser;
 import org.apache.cloudstack.storage.command.DownloadCommand;
 import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
 import org.apache.cloudstack.storage.command.DownloadProgressCommand;
 import org.apache.cloudstack.storage.command.DownloadProgressCommand.RequestType;
-import org.apache.cloudstack.storage.NfsMountManagerImpl.PathParser;
 import org.apache.cloudstack.storage.resource.NfsSecondaryStorageResource;
 import org.apache.cloudstack.storage.resource.SecondaryStorageResource;
+import org.apache.cloudstack.utils.security.ChecksumValue;
+import org.apache.cloudstack.utils.security.DigestHelper;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.storage.DownloadAnswer;
-import com.cloud.utils.net.Proxy;
 import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.NfsTO;
+import com.cloud.agent.api.to.OVFInformationTO;
 import com.cloud.agent.api.to.S3TO;
 import com.cloud.exception.InternalErrorException;
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.StorageLayer;
 import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import com.cloud.storage.template.SimpleHttpMultiFileDownloader;
+import com.cloud.storage.template.HttpTemplateDownloader;
+import com.cloud.storage.template.IsoProcessor;
+import com.cloud.storage.template.LocalTemplateDownloader;
+import com.cloud.storage.template.MetalinkTemplateDownloader;
+import com.cloud.storage.template.OVAProcessor;
+import com.cloud.storage.template.Processor;
 import com.cloud.storage.template.Processor.FormatInfo;
+import com.cloud.storage.template.QCOW2Processor;
+import com.cloud.storage.template.RawImageProcessor;
+import com.cloud.storage.template.S3TemplateDownloader;
+import com.cloud.storage.template.ScpTemplateDownloader;
+import com.cloud.storage.template.TARProcessor;
+import com.cloud.storage.template.TemplateConstants;
+import com.cloud.storage.template.TemplateDownloader;
 import com.cloud.storage.template.TemplateDownloader.DownloadCompleteCallback;
 import com.cloud.storage.template.TemplateDownloader.Status;
+import com.cloud.storage.template.TemplateLocation;
+import com.cloud.storage.template.TemplateProp;
+import com.cloud.storage.template.VhdProcessor;
+import com.cloud.storage.template.VmdkProcessor;
 import com.cloud.utils.NumbersUtil;
 import com.cloud.utils.component.ManagerBase;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.Proxy;
 import com.cloud.utils.script.Script;
 import com.cloud.utils.storage.QCOW2Utils;
-import org.apache.cloudstack.utils.security.ChecksumValue;
-import org.apache.cloudstack.utils.security.DigestHelper;
-import org.apache.commons.lang3.StringUtils;
-
-import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
 
 public class DownloadManagerImpl extends ManagerBase implements DownloadManager {
     private String _name;
@@ -186,17 +191,31 @@
             return installPathPrefix;
         }
 
+        private void cleanupFileWithDirectory(String path, boolean deleteDir) {
+            if (StringUtils.isEmpty(path)) {
+                return;
+            }
+            LOGGER.debug(String.format("Cleaning-up temporary download file %s", path));
+            File f = new File(path);
+            File dir = f.getParentFile();
+            f.delete();
+            if (deleteDir && dir != null) {
+                LOGGER.debug(String.format("Deleting directory %s, if empty, as part of cleanup", dir.getAbsolutePath()));
+                dir.delete();
+            }
+        }
+
         public void cleanup() {
             if (td != null) {
-                String dnldPath = td.getDownloadLocalPath();
-                if (dnldPath != null) {
-                    File f = new File(dnldPath);
-                    File dir = f.getParentFile();
-                    f.delete();
-                    if (dir != null) {
-                        dir.delete();
+                if (td instanceof SimpleHttpMultiFileDownloader) {
+                    SimpleHttpMultiFileDownloader httpMultiFileDownloader = (SimpleHttpMultiFileDownloader)td;
+                    List<String> files = new ArrayList<>(httpMultiFileDownloader.getDownloadedFilesMap().values());
+                    for (int i = 0; i < files.size(); ++i) {
+                        cleanupFileWithDirectory(files.get(i), i == files.size() - 1);
                     }
+                    return;
                 }
+                cleanupFileWithDirectory(td.getDownloadLocalPath(), true);
             }
 
         }
@@ -304,8 +323,7 @@
                     td.setStatus(Status.POST_DOWNLOAD_FINISHED);
                     td.setDownloadError("Install completed successfully at " + new SimpleDateFormat().format(new Date()));
                 }
-            }
-            else {
+            } else {
                 // For other TemplateDownloaders where files are locally available,
                 // we run the postLocalDownload() method.
                 td.setDownloadError("Download success, starting install ");
@@ -366,6 +384,98 @@
         return result;
     }
 
+    protected String getSnapshotInstallNameFromDownloadUrl(String url) {
+        URI uri;
+        try {
+            uri = new URI(url);
+        } catch (URISyntaxException ignored) {
+            return null;
+        }
+        String name = uri.getPath();
+        if (StringUtils.isEmpty(name) || !name.contains("/")) {
+            return null;
+        }
+        String[] items = uri.getPath().split("/");
+        name = items[items.length - 1];
+        if (items.length < 2) {
+            return name;
+        }
+        String parentDir = items[items.length - 2];
+        if (!parentDir.matches("\\d+") && name.startsWith(parentDir)) {
+            return parentDir + File.separator + name;
+        }
+        return name;
+    }
+
+    private String postLocalSnapshotSingleFileDownload(DownloadJob job, HttpTemplateDownloader td) {
+        String name = getSnapshotInstallNameFromDownloadUrl(td.getDownloadUrl());
+        final String downloadedFile = td.getDownloadLocalPath();
+        final String resourcePath = job.getInstallPathPrefix();
+        final String relativeResourcePath = job.getTmpltPath();
+        if (StringUtils.isEmpty(name)) {
+            name = UUID.randomUUID().toString();
+            LOGGER.warn(String.format("Unable to retrieve install filename for snapshot download %s, using a random UUID", downloadedFile));
+        }
+        Path srcPath = Paths.get(downloadedFile);
+        Path destPath = Paths.get(resourcePath + File.separator + name);
+        try {
+            LOGGER.debug(String.format("Trying to create missing directories (if any) to move snapshot %s.", destPath));
+            Files.createDirectories(destPath.getParent());
+            LOGGER.debug(String.format("Trying to move downloaded snapshot [%s] to [%s].", srcPath, destPath));
+            Files.move(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING);
+        } catch (IOException e) {
+            LOGGER.warn(String.format("Something is wrong while processing post snapshot download %s", resourcePath), e);
+            return "Unable process post snapshot download due to " + e.getMessage();
+        }
+        String installedPath = relativeResourcePath + File.separator + name;
+        job.setTmpltPath(installedPath);
+        job.setTemplatePhysicalSize(td.getDownloadedBytes());
+        return null;
+    }
+
+    private String postLocalSnapshotMultiFileDownload(DownloadJob job, SimpleHttpMultiFileDownloader td) {
+        Map<String, String> downloads = td.getDownloadedFilesMap();
+        String installDir = null;
+        try {
+            for (Map.Entry<String, String> entry : downloads.entrySet()) {
+                final String url = entry.getKey();
+                final String downloadedFile = entry.getValue();
+                final String name = url.substring(url.lastIndexOf("/"));
+                if (StringUtils.isEmpty(installDir)) {
+                    installDir = url.substring(0, url.lastIndexOf("/"));
+                    installDir = installDir.substring(installDir.lastIndexOf("/"));
+                    job.setTmpltPath(job.getTmpltPath() + installDir);
+                    installDir = job.getInstallPathPrefix() + installDir;
+                    Path installPath = Paths.get(installDir);
+                    LOGGER.debug(String.format("Trying to create missing directories (if any) to move snapshot files at %s.", installDir));
+                    Files.createDirectories(installPath);
+                }
+                final String filePath = installDir + name;
+                if (name.endsWith(".ovf")) {
+                    job.setTmpltPath(job.getTmpltPath() + name.replace(".ovf", ""));
+                }
+                Path srcPath = Paths.get(downloadedFile);
+                Path destPath = Paths.get(filePath);
+                LOGGER.debug(String.format("Trying to move downloaded snapshot file [%s] to [%s].", srcPath, destPath));
+                Files.move(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING);
+            }
+            job.setTemplatePhysicalSize(td.getDownloadedBytes());
+        } catch (IOException e) {
+            LOGGER.warn(String.format("Something is wrong while processing post snapshot download %s", job.getTmpltPath()), e);
+            return "Unable process post snapshot download due to " + e.getMessage();
+        }
+        return null;
+    }
+
+    private String postLocalSnapshotDownload(DownloadJob job, TemplateDownloader td) {
+        if (td instanceof HttpTemplateDownloader) {
+            return postLocalSnapshotSingleFileDownload(job, (HttpTemplateDownloader)td);
+        } else if(td instanceof SimpleHttpMultiFileDownloader) {
+            return postLocalSnapshotMultiFileDownload(job, (SimpleHttpMultiFileDownloader)td);
+        }
+        return null;
+    }
+
     /**
      * Post local download activity (install and cleanup). Executed in context of
      * downloader thread
@@ -376,13 +486,12 @@
     private String postLocalDownload(String jobId) {
         DownloadJob dnld = jobs.get(jobId);
         TemplateDownloader td = dnld.getTemplateDownloader();
-        String resourcePath = dnld.getInstallPathPrefix(); // path with mount
-        // directory
-        String finalResourcePath = dnld.getTmpltPath(); // template download
-        // path on secondary
-        // storage
         ResourceType resourceType = dnld.getResourceType();
-
+        if (ResourceType.SNAPSHOT.equals(resourceType)) {
+            return postLocalSnapshotDownload(dnld, td);
+        }
+        String resourcePath = dnld.getInstallPathPrefix(); // path with mount directory
+        String finalResourcePath = dnld.getTmpltPath(); // template download path on secondary storage
         File originalTemplate = new File(td.getDownloadLocalPath());
         if(StringUtils.isBlank(dnld.getChecksum())) {
             if (LOGGER.isInfoEnabled()) {
@@ -409,7 +518,7 @@
         File downloadedTemplate = new File(resourcePath + "/" + templateFilename);
 
         _storage.setWorldReadableAndWriteable(downloadedTemplate);
-        setPermissionsForTheDownloadedTemplate(dnld, resourcePath, resourceType);
+        setPermissionsForTheDownloadedTemplate(resourcePath, resourceType);
 
         TemplateLocation loc = new TemplateLocation(_storage, resourcePath);
         try {
@@ -468,7 +577,10 @@
         return templateName;
     }
 
-    private void setPermissionsForTheDownloadedTemplate(DownloadJob dnld, String resourcePath, ResourceType resourceType) {
+    private void setPermissionsForTheDownloadedTemplate(String resourcePath, ResourceType resourceType) {
+        if (ResourceType.SNAPSHOT.equals(resourceType)) {
+            return;
+        }
         // Set permissions for template/volume.properties
         String propertiesFile = resourcePath;
         if (resourceType == ResourceType.TEMPLATE) {
@@ -580,6 +692,32 @@
         return jobId;
     }
 
+    private String createTempDirAndPropertiesFile(ResourceType resourceType, String tmpDir) throws IOException {
+        if (!_storage.mkdirs(tmpDir)) {
+            LOGGER.warn("Unable to create " + tmpDir);
+            return "Unable to create " + tmpDir;
+        }
+        if (ResourceType.SNAPSHOT.equals(resourceType)) {
+            return null;
+        }
+        // TO DO - define constant for volume properties.
+        File file =
+                ResourceType.TEMPLATE == resourceType ?
+                        _storage.getFile(tmpDir + File.separator + TemplateLocation.Filename) :
+                        _storage.getFile(tmpDir + File.separator + "volume.properties");
+        if (file.exists()) {
+            if(! file.delete()) {
+                LOGGER.warn("Deletion of file '" + file.getAbsolutePath() + "' failed.");
+            }
+        }
+
+        if (!file.createNewFile()) {
+            LOGGER.warn("Unable to create new file: " + file.getAbsolutePath());
+            return "Unable to create new file: " + file.getAbsolutePath();
+        }
+        return null;
+    }
+
     @Override
     public String downloadPublicTemplate(long id, String url, String name, ImageFormat format, boolean hvm,
             Long accountId, String descr, String cksum, String installPathPrefix, String templatePath, String user,
@@ -590,64 +728,59 @@
         String tmpDir = installPathPrefix;
 
         try {
-
-            if (!_storage.mkdirs(tmpDir)) {
-                LOGGER.warn("Unable to create " + tmpDir);
-                return "Unable to create " + tmpDir;
+            String filesError = createTempDirAndPropertiesFile(resourceType, tmpDir);
+            if (StringUtils.isNotEmpty(filesError)) {
+                return filesError;
             }
-            // TO DO - define constant for volume properties.
-            File file =
-                    ResourceType.TEMPLATE == resourceType ? _storage.getFile(tmpDir + File.separator + TemplateLocation.Filename) : _storage.getFile(tmpDir + File.separator +
-                            "volume.properties");
-                    if (file.exists()) {
-                        if(! file.delete()) {
-                            LOGGER.warn("Deletion of file '" + file.getAbsolutePath() + "' failed.");
-                        }
-                    }
 
-                    if (!file.createNewFile()) {
-                        LOGGER.warn("Unable to create new file: " + file.getAbsolutePath());
-                        return "Unable to create new file: " + file.getAbsolutePath();
-                    }
-
-                    URI uri;
-                    try {
-                        uri = new URI(url);
-                    } catch (URISyntaxException e) {
-                        throw new CloudRuntimeException("URI is incorrect: " + url);
-                    }
-                    TemplateDownloader td;
-                    if ((uri != null) && (uri.getScheme() != null)) {
-                        if (uri.getPath().endsWith(".metalink")) {
-                            td = new MetalinkTemplateDownloader(_storage, url, tmpDir, new Completion(jobId), maxTemplateSizeInBytes);
-                        } else if (uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equalsIgnoreCase("https")) {
-                            td = new HttpTemplateDownloader(_storage, url, tmpDir, new Completion(jobId), maxTemplateSizeInBytes, user, password, proxy, resourceType);
-                        } else if (uri.getScheme().equalsIgnoreCase("file")) {
-                            td = new LocalTemplateDownloader(_storage, url, tmpDir, maxTemplateSizeInBytes, new Completion(jobId));
-                        } else if (uri.getScheme().equalsIgnoreCase("scp")) {
-                            td = new ScpTemplateDownloader(_storage, url, tmpDir, maxTemplateSizeInBytes, new Completion(jobId));
-                        } else if (uri.getScheme().equalsIgnoreCase("nfs") || uri.getScheme().equalsIgnoreCase("cifs")) {
-                            td = null;
-                            // TODO: implement this.
-                            throw new CloudRuntimeException("Scheme is not supported " + url);
-                        } else {
-                            throw new CloudRuntimeException("Scheme is not supported " + url);
-                        }
+            URI uri;
+            String checkUrl = url;
+            if (ResourceType.SNAPSHOT.equals(resourceType) && url.contains("\n")) {
+                checkUrl = url.substring(0, url.indexOf("\n") - 1);
+            }
+            try {
+                uri = new URI(checkUrl);
+            } catch (URISyntaxException e) {
+                throw new CloudRuntimeException("URI is incorrect: " + url);
+            }
+            TemplateDownloader td;
+            if (ResourceType.SNAPSHOT.equals(resourceType) && url.contains("\n") &&
+                    ("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme()))) {
+                String[] urls = url.split("\n");
+                td = new SimpleHttpMultiFileDownloader(_storage, urls, tmpDir, new Completion(jobId), maxTemplateSizeInBytes, resourceType);
+            } else {
+                if ((uri != null) && (uri.getScheme() != null)) {
+                    if (uri.getPath().endsWith(".metalink")) {
+                        td = new MetalinkTemplateDownloader(_storage, url, tmpDir, new Completion(jobId), maxTemplateSizeInBytes);
+                    } else if (uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equalsIgnoreCase("https")) {
+                        td = new HttpTemplateDownloader(_storage, url, tmpDir, new Completion(jobId), maxTemplateSizeInBytes, user, password, proxy, resourceType);
+                    } else if (uri.getScheme().equalsIgnoreCase("file")) {
+                        td = new LocalTemplateDownloader(_storage, url, tmpDir, maxTemplateSizeInBytes, new Completion(jobId));
+                    } else if (uri.getScheme().equalsIgnoreCase("scp")) {
+                        td = new ScpTemplateDownloader(_storage, url, tmpDir, maxTemplateSizeInBytes, new Completion(jobId));
+                    } else if (uri.getScheme().equalsIgnoreCase("nfs") || uri.getScheme().equalsIgnoreCase("cifs")) {
+                        td = null;
+                        // TODO: implement this.
+                        throw new CloudRuntimeException("Scheme is not supported " + url);
                     } else {
-                        throw new CloudRuntimeException("Unable to download from URL: " + url);
+                        throw new CloudRuntimeException("Scheme is not supported " + url);
                     }
-                    td.setFollowRedirects(followRedirects);
-                    // NOTE the difference between installPathPrefix and templatePath
-                    // here. instalPathPrefix is the absolute path for template
-                    // including mount directory
-                    // on ssvm, while templatePath is the final relative path on
-                    // secondary storage.
-                    DownloadJob dj = new DownloadJob(td, jobId, id, name, format, hvm, accountId, descr, cksum, installPathPrefix, resourceType);
-                    dj.setTmpltPath(templatePath);
-                    jobs.put(jobId, dj);
-                    threadPool.execute(td);
+                } else {
+                    throw new CloudRuntimeException("Unable to download from URL: " + url);
+                }
+            }
+            td.setFollowRedirects(followRedirects);
+            // NOTE the difference between installPathPrefix and templatePath
+            // here. instalPathPrefix is the absolute path for template
+            // including mount directory
+            // on ssvm, while templatePath is the final relative path on
+            // secondary storage.
+            DownloadJob dj = new DownloadJob(td, jobId, id, name, format, hvm, accountId, descr, cksum, installPathPrefix, resourceType);
+            dj.setTmpltPath(templatePath);
+            jobs.put(jobId, dj);
+            threadPool.execute(td);
 
-                    return jobId;
+            return jobId;
         } catch (IOException e) {
             LOGGER.warn("Unable to download to " + tmpDir, e);
             return null;
diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java
index 8e3f0b2..5c589b6 100644
--- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java
+++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java
@@ -281,6 +281,14 @@
             return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE);
         }
 
+        File file = new File("/mnt/SecStorage/" + cmd.getParent() + File.separator + cmd.getInstallPath());
+        // Return error if the file does not exist or is a directory
+        if (!file.exists() || file.isDirectory()) {
+            String errorString = "Error in finding the file " + file.getAbsolutePath();
+            s_logger.error(errorString);
+            return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE);
+        }
+
         // Create a random file under the directory for security reasons.
         String uuid = cmd.getExtractLinkUUID();
         // Create a symbolic link from the actual directory to the template location. The entity would be directly visible under /var/www/html/userdata/cmd.getInstallPath();
@@ -302,7 +310,7 @@
     public Answer handleDeleteEntityDownloadURLCommand(DeleteEntityDownloadURLCommand cmd) {
 
         //Delete the soft link. Example path = volumes/8/74eeb2c6-8ab1-4357-841f-2e9d06d1f360.vhd
-        s_logger.warn("handleDeleteEntityDownloadURLCommand Path:" + cmd.getPath() + " Type:" + cmd.getType().toString());
+        s_logger.warn("handleDeleteEntityDownloadURLCommand Path:" + cmd.getPath() + " Type:" + (cmd.getType() != null ? cmd.getType().toString(): ""));
         String path = cmd.getPath();
         Script command = new Script("/bin/bash", s_logger);
         command.add("-c");
diff --git a/services/secondary-storage/server/src/main/resources/META-INF/cloudstack/secondary-storage-discoverer/module.properties b/services/secondary-storage/server/src/main/resources/META-INF/cloudstack/secondary-storage-discoverer/module.properties
index 7ff8a3a..637b639 100644
--- a/services/secondary-storage/server/src/main/resources/META-INF/cloudstack/secondary-storage-discoverer/module.properties
+++ b/services/secondary-storage/server/src/main/resources/META-INF/cloudstack/secondary-storage-discoverer/module.properties
@@ -15,4 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 name=secondary-storage-discoverer
-parent=discoverer
\ No newline at end of file
+parent=discoverer
diff --git a/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java b/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java
index 96afcce..72b9f5a 100644
--- a/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java
+++ b/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java
@@ -22,54 +22,78 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 
-import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileWriter;
-import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Stream;
 
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.utils.EncryptionUtil;
+import com.cloud.utils.net.NetUtils;
 import org.apache.cloudstack.storage.command.DeleteCommand;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
+import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.log4j.Level;
 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.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
+import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.test.TestAppender;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({ "javax.xml.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class NfsSecondaryStorageResourceTest {
 
+    @Spy
     private NfsSecondaryStorageResource resource;
 
-    @Before
-    public void setUp() {
-        resource = new NfsSecondaryStorageResource();
-    }
+    private static final String HOSTNAME = "hostname";
+
+    private static final String UUID = "uuid";
+
+    private static final String METADATA = "metadata";
+
+    private static final String TIMEOUT = "timeout";
+
+    private static final String PSK = "6HyGMx9Vat7rZw1pMZrM4OlD4FFwLUPznTsFqVFSOIvk0mAWMRCVZ6UCq42gZvhp";
+
+    private static final String PROTOCOL = NetUtils.HTTP_PROTO;
+
+    private static final String EXPECTED_SIGNATURE = "expectedSignature";
+
+    private static final String COMPUTED_SIGNATURE = "computedSignature";
 
     @Test
-    @PrepareForTest(NfsSecondaryStorageResource.class)
     public void testSwiftWriteMetadataFile() throws Exception {
-        String filename = "testfile";
+        String metaFileName = "test_metadata_file";
         try {
-            String expected = "uniquename=test\nfilename=" + filename + "\nsize=100\nvirtualsize=1000";
+            String uniqueName = "test_unique_name";
+            String filename = "test_filename";
+            long size = 1024L;
+            long virtualSize = 2048L;
 
-            StringWriter stringWriter = new StringWriter();
-            BufferedWriter bufferWriter = new BufferedWriter(stringWriter);
-            PowerMockito.whenNew(BufferedWriter.class).withArguments(any(FileWriter.class)).thenReturn(bufferWriter);
+            File metaFile = resource.swiftWriteMetadataFile(metaFileName, uniqueName, filename, size, virtualSize);
 
-            resource.swiftWriteMetadataFile(filename, "test", filename, 100, 1000);
+            Assert.assertTrue(metaFile.exists());
+            Assert.assertEquals(metaFileName, metaFile.getName());
 
-            Assert.assertEquals(expected, stringWriter.toString());
+            String expectedContent = "uniquename=" + uniqueName + "\n" +
+                    "filename=" + filename + "\n" +
+                    "size=" + size + "\n" +
+                    "virtualsize=" + virtualSize;
+
+            String actualContent = new String(java.nio.file.Files.readAllBytes(metaFile.toPath()));
+            Assert.assertEquals(expectedContent, actualContent);
         } finally {
-            File remnance = new File(filename);
-            remnance.delete();
+            File metaFile = new File(metaFileName);
+            metaFile.delete();
         }
     }
 
@@ -108,4 +132,114 @@
                 "/snapshots/2/10",
                 "somename");
     }
+
+    @Test
+    public void testExecuteQuerySnapshotZoneCopyCommand() {
+        final String dir = "/snapshots/2/10/abc";
+        final String fileName = "abc";
+        DataStoreTO store = Mockito.mock(DataStoreTO.class);
+        SnapshotObjectTO object = Mockito.mock(SnapshotObjectTO.class);
+        Mockito.when(object.getDataStore()).thenReturn(store);
+        Mockito.when(object.getPath()).thenReturn(dir + File.separator + fileName);
+        QuerySnapshotZoneCopyCommand cmd = Mockito.mock(QuerySnapshotZoneCopyCommand.class);
+        Mockito.when(cmd.getSnapshot()).thenReturn(object);
+        Path p1 = Mockito.mock(Path.class);
+        Mockito.when(p1.getFileName()).thenReturn(p1);
+        Mockito.when(p1.toString()).thenReturn(fileName + ".vmdk");
+        Path p2 = Mockito.mock(Path.class);
+        Mockito.when(p2.getFileName()).thenReturn(p2);
+        Mockito.when(p2.toString()).thenReturn(fileName + ".ovf");
+        Stream<Path> paths = Stream.of(p1, p2);
+        try (MockedStatic<Files> files = Mockito.mockStatic(Files.class)) {
+            files.when(() -> Files.list(Mockito.any(Path.class))).thenReturn(paths);
+            files.when(() -> Files.isDirectory(Mockito.any(Path.class))).thenReturn(false);
+            QuerySnapshotZoneCopyAnswer answer = (QuerySnapshotZoneCopyAnswer)(resource.execute(cmd));
+            List<String> result = answer.getFiles();
+            Assert.assertEquals(2, result.size());
+            Assert.assertEquals(dir + File.separator + fileName + ".vmdk", result.get(0));
+            Assert.assertEquals(dir + File.separator + fileName + ".ovf", result.get(1));
+        }
+    }
+
+    private void prepareForValidatePostUploadRequestSignatureTests(MockedStatic<EncryptionUtil> encryptionUtilMock) {
+        Mockito.doReturn(PROTOCOL).when(resource).getUploadProtocol();
+        Mockito.doReturn(PSK).when(resource).getPostUploadPSK();
+        encryptionUtilMock.when(() -> EncryptionUtil.generateSignature(Mockito.anyString(), Mockito.anyString())).thenReturn(COMPUTED_SIGNATURE);
+        String fullUrl = String.format("%s://%s/upload/%s", PROTOCOL, HOSTNAME, UUID);
+        String data = String.format("%s%s%s", METADATA, fullUrl, TIMEOUT);
+        encryptionUtilMock.when(() -> EncryptionUtil.generateSignature(data, PSK)).thenReturn(EXPECTED_SIGNATURE);
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validatePostUploadRequestSignatureTestThrowExceptionWhenProtocolDiffers() {
+        try (MockedStatic<EncryptionUtil> encryptionUtilMock = Mockito.mockStatic(EncryptionUtil.class)) {
+            prepareForValidatePostUploadRequestSignatureTests(encryptionUtilMock);
+            Mockito.doReturn(NetUtils.HTTPS_PROTO).when(resource).getUploadProtocol();
+
+            resource.validatePostUploadRequestSignature(EXPECTED_SIGNATURE, HOSTNAME, UUID, METADATA, TIMEOUT);
+        }
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validatePostUploadRequestSignatureTestThrowExceptionWhenHostnameDiffers() {
+        try (MockedStatic<EncryptionUtil> encryptionUtilMock = Mockito.mockStatic(EncryptionUtil.class)) {
+            prepareForValidatePostUploadRequestSignatureTests(encryptionUtilMock);
+
+            resource.validatePostUploadRequestSignature(EXPECTED_SIGNATURE, "test", UUID, METADATA, TIMEOUT);
+        }
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validatePostUploadRequestSignatureTestThrowExceptionWhenUuidDiffers() {
+        try (MockedStatic<EncryptionUtil> encryptionUtilMock = Mockito.mockStatic(EncryptionUtil.class)) {
+            prepareForValidatePostUploadRequestSignatureTests(encryptionUtilMock);
+
+            resource.validatePostUploadRequestSignature(EXPECTED_SIGNATURE, HOSTNAME, "test", METADATA, TIMEOUT);
+        }
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validatePostUploadRequestSignatureTestThrowExceptionWhenMetadataDiffers() {
+        try (MockedStatic<EncryptionUtil> encryptionUtilMock = Mockito.mockStatic(EncryptionUtil.class)) {
+            prepareForValidatePostUploadRequestSignatureTests(encryptionUtilMock);
+
+            resource.validatePostUploadRequestSignature(EXPECTED_SIGNATURE, HOSTNAME, UUID, "test", TIMEOUT);
+        }
+    }
+
+    @Test(expected = InvalidParameterValueException.class)
+    public void validatePostUploadRequestSignatureTestThrowExceptionWhenTimeoutDiffers() {
+        try (MockedStatic<EncryptionUtil> encryptionUtilMock = Mockito.mockStatic(EncryptionUtil.class)) {
+            prepareForValidatePostUploadRequestSignatureTests(encryptionUtilMock);
+
+            resource.validatePostUploadRequestSignature(EXPECTED_SIGNATURE, HOSTNAME, UUID, METADATA, "test");
+        }
+    }
+
+    @Test
+    public void validatePostUploadRequestSignatureTestSuccessWhenDataIsTheSame() {
+        try (MockedStatic<EncryptionUtil> encryptionUtilMock = Mockito.mockStatic(EncryptionUtil.class)) {
+            prepareForValidatePostUploadRequestSignatureTests(encryptionUtilMock);
+
+            resource.validatePostUploadRequestSignature(EXPECTED_SIGNATURE, HOSTNAME, UUID, METADATA, TIMEOUT);
+        }
+    }
+
+    @Test
+    public void getUploadProtocolTestReturnHttpsWhenUseHttpsToUploadIsTrue() {
+        Mockito.doReturn(true).when(resource).useHttpsToUpload();
+
+        String result = resource.getUploadProtocol();
+
+        Assert.assertEquals(NetUtils.HTTPS_PROTO, result);
+    }
+
+    @Test
+    public void getUploadProtocolTestReturnHttpWhenUseHttpsToUploadIsFalse() {
+        Mockito.doReturn(false).when(resource).useHttpsToUpload();
+
+        String result = resource.getUploadProtocol();
+
+        Assert.assertEquals(NetUtils.HTTP_PROTO, result);
+    }
 }
diff --git a/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/template/DownloadManagerImplTest.java b/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/template/DownloadManagerImplTest.java
new file mode 100644
index 0000000..5fbf17e
--- /dev/null
+++ b/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/template/DownloadManagerImplTest.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.storage.template;
+
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DownloadManagerImplTest {
+
+    @InjectMocks
+    DownloadManagerImpl downloadManager = new DownloadManagerImpl();
+
+    @Test
+    public void testGetSnapshotInstallNameFromDownloadUrl() {
+        Map<String, String> urlNames = Map.of(
+                "http://HOST/copy/SecStorage/e7d75b93-08f3-3488-8089-632c5c3854bf/snapshots/2/8/8d4cd8d8-c66f-4cbe-88ce-0bf99e26fe79.vhd", "8d4cd8d8-c66f-4cbe-88ce-0bf99e26fe79.vhd",
+                "http://HOST/copy/SecStorage/24492d16-66a6-34df-84ea-cc335e7d5b4a/snapshots/2/6/a84ee92d-43cf-4151-908d-1e8ea6c43d35", "a84ee92d-43cf-4151-908d-1e8ea6c43d35",
+                "http://HOST/copy/SecStorage/0e3ec9a5-e23d-3edc-bc0f-ce6e641e12c3/snapshots/2/28/ce0e1e42-9268-414c-a874-1802d2d7b429/ce0e1e42-9268-414c-a874-1802d2d7b429.vmdk", "ce0e1e42-9268-414c-a874-1802d2d7b429/ce0e1e42-9268-414c-a874-1802d2d7b429.vmdk"
+        );
+        for (Map.Entry<String, String> entry: urlNames.entrySet()) {
+            String url = entry.getKey();
+            String filename = entry.getValue();
+            String name = downloadManager.getSnapshotInstallNameFromDownloadUrl(url);
+            Assert.assertEquals(filename, name);
+        }
+    }
+}
diff --git a/services/secondary-storage/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/services/secondary-storage/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/services/secondary-storage/server/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/setup/bindir/cloud-setup-databases.in b/setup/bindir/cloud-setup-databases.in
index 57bfbcb..41b54b0 100755
--- a/setup/bindir/cloud-setup-databases.in
+++ b/setup/bindir/cloud-setup-databases.in
@@ -543,7 +543,7 @@
 
     This command sets up the CloudStack Management Server and CloudStack Usage Server database configuration (connection credentials and host information) based on the first argument.
 
-    If the the --deploy-as option is present, this command will also connect to the database using the administrative credentials specified as the value for the --deploy-as argument, construct the database environment needed to run the CloudStack Management Server, and alter the password specified for the user in the first argument.  In this case, the user name specified in --deploy-as= cannot be the same as the user name specified for the connection credentials that the CloudStack Management Server will be set up with.
+    If the --deploy-as option is present, this command will also connect to the database using the administrative credentials specified as the value for the --deploy-as argument, construct the database environment needed to run the CloudStack Management Server, and alter the password specified for the user in the first argument.  In this case, the user name specified in --deploy-as= cannot be the same as the user name specified for the connection credentials that the CloudStack Management Server will be set up with.
 
     If a server-setup.xml cloud setup information file is specified with the --auto option, this command will also construct a customized database environment according to the cloud setup information in the file.
 
diff --git a/setup/bindir/cloud-sysvmadm.in b/setup/bindir/cloud-sysvmadm.in
index e826dea..a5bb9b3 100755
--- a/setup/bindir/cloud-sysvmadm.in
+++ b/setup/bindir/cloud-sysvmadm.in
@@ -498,5 +498,3 @@
 then
       restart_vpcs
 fi
-
-
diff --git a/setup/db/221to222upgrade.sh b/setup/db/221to222upgrade.sh
index 8fb1e75..5113c51 100644
--- a/setup/db/221to222upgrade.sh
+++ b/setup/db/221to222upgrade.sh
@@ -46,5 +46,3 @@
 exit 1
 fi
 echo Finished upgrade for database: cloud_usage from 2.2.1 to 2.2.2
-
-
diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql
index a241d9a..3f14fcc 100755
--- a/setup/db/create-schema.sql
+++ b/setup/db/create-schema.sql
@@ -801,7 +801,7 @@
   `instance_id` bigint unsigned NOT NULL COMMENT 'vm instance id',
   `dest_ip_address` char(40) NOT NULL COMMENT 'id_address',
   `dest_port_start` int(10) NOT NULL COMMENT 'starting port of the port range to map to',
-  `dest_port_end` int(10) NOT NULL COMMENT 'end port of the the port range to map to',
+  `dest_port_end` int(10) NOT NULL COMMENT 'end port of the port range to map to',
   PRIMARY KEY (`id`),
   CONSTRAINT `fk_port_forwarding_rules__id` FOREIGN KEY(`id`) REFERENCES `firewall_rules`(`id`) ON DELETE CASCADE,
   CONSTRAINT `fk_port_forwarding_rules__instance_id` FOREIGN KEY `fk_port_forwarding_rules__instance_id` (`instance_id`) REFERENCES `vm_instance` (`id`) ON DELETE CASCADE
@@ -1927,7 +1927,7 @@
   `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
   `name` varchar(64) NOT NULL COMMENT 'unique name for the certifiation',
   `certificate` text NOT NULL COMMENT 'the actual certificate being stored in the db',
-  `key` text COMMENT 'private key associated wih the certificate',
+  `key` text COMMENT 'private key associated with the certificate',
   `domain_suffix` varchar(256) NOT NULL COMMENT 'DNS domain suffix associated with the certificate',
   `seq` int,
   PRIMARY KEY (`id`),
@@ -2137,7 +2137,7 @@
   `allocation_state` varchar(32) NOT NULL DEFAULT 'Free' COMMENT 'Allocation state (Free/Shared/Dedicated/Provider) of the device',
   `is_dedicated` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if device/appliance is provisioned for dedicated use only',
   `is_inline` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer will be used in in-line configuration with firewall',
-  `is_managed` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer appliance is provisioned and its life cycle is managed by by cloudstack',
+  `is_managed` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 if load balancer appliance is provisioned and its life cycle is managed by cloudstack',
   `host_id` bigint unsigned NOT NULL COMMENT 'host id corresponding to the external load balancer device',
   `parent_host_id` bigint unsigned COMMENT 'if the load balancer appliance is cloudstack managed, then host id on which this appliance is provisioned',
   PRIMARY KEY (`id`),
@@ -2475,4 +2475,3 @@
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 SET foreign_key_checks = 1;
-
diff --git a/setup/db/index-20to21.sql b/setup/db/index-20to21.sql
index d259f79..43210e5 100644
--- a/setup/db/index-20to21.sql
+++ b/setup/db/index-20to21.sql
@@ -57,4 +57,3 @@
 
 -- drop foreign key constraits temporarily to allow data update in migration process
 ALTER TABLE `cloud`.`user_vm` DROP FOREIGN KEY `fk_user_vm__service_offering_id`;
-
diff --git a/setup/db/index-212to213.sql b/setup/db/index-212to213.sql
index d03b29a..348c513 100644
--- a/setup/db/index-212to213.sql
+++ b/setup/db/index-212to213.sql
@@ -17,4 +17,3 @@
 
 
 ALTER TABLE `cloud`.`user` DROP KEY `i_user__username__removed`;
-
diff --git a/setup/db/templates.sql b/setup/db/templates.sql
index ba51489..3f154ac 100755
--- a/setup/db/templates.sql
+++ b/setup/db/templates.sql
@@ -147,7 +147,7 @@
 INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (89, UUID(), 6, 'Windows Server 2003 Standard Edition(32-bit)');
 INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (90, UUID(), 6, 'Windows Server 2003 Standard Edition(64-bit)');
 INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (91, UUID(), 6, 'Windows Server 2003 Web Edition');
-INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (92, UUID(), 6, 'Microsoft Small Bussiness Server 2003');
+INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (92, UUID(), 6, 'Microsoft Small Business Server 2003');
 INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (93, UUID(), 6, 'Windows XP (32-bit)');
 INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (94, UUID(), 6, 'Windows XP (64-bit)');
 INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (95, UUID(), 6, 'Windows 2000 Advanced Server');
@@ -395,7 +395,7 @@
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Server 2003, Standard Edition (32-bit)', 89);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Server 2003, Standard Edition (64-bit)', 90);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Server 2003, Web Edition', 91);
-INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Small Bussiness Server 2003', 92);
+INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Small Business Server 2003', 92);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Vista (32-bit)', 56);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows Vista (64-bit)', 101);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ("VmWare", 'Microsoft Windows XP Professional (32-bit)', 93);
@@ -596,4 +596,3 @@
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ('KVM', 'DOS', 102);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ('KVM', 'Other', 60);
 INSERT INTO `cloud`.`guest_os_hypervisor` (hypervisor_type, guest_os_name, guest_os_id) VALUES ('KVM', 'Other', 103);
-
diff --git a/setup/dev/advdualzone.cfg b/setup/dev/advdualzone.cfg
index b11675d..97a9840 100644
--- a/setup/dev/advdualzone.cfg
+++ b/setup/dev/advdualzone.cfg
@@ -18,12 +18,12 @@
     "zones": [
         {
             "name": "zim1",
-            "guestcidraddress": "10.100.1.0/24",
-            "dns1": "10.147.100.6",
+            "guestcidraddress": "10.1.1.0/24",
+            "dns1": "10.147.28.6",
             "physical_networks": [
                 {
                     "broadcastdomainrange": "Zone",
-                    "vlan": "1100-1200",
+                    "vlan": "100-200",
                     "name": "z1-pnet",
                     "traffictypes": [
                         {
@@ -63,19 +63,19 @@
             },
             "ipranges": [
                 {
-                    "startip": "192.168.100.2",
-                    "endip": "192.168.100.200",
+                    "startip": "192.168.2.2",
+                    "endip": "192.168.2.200",
                     "netmask": "255.255.255.0",
                     "vlan": "50",
-                    "gateway": "192.168.100.1"
+                    "gateway": "192.168.2.1"
                 }
             ],
             "networktype": "Advanced",
             "pods": [
                 {
-                    "endip": "172.16.100.200",
+                    "endip": "172.16.15.200",
                     "name": "Z1P1",
-                    "startip": "172.16.100.2",
+                    "startip": "172.16.15.2",
                     "netmask": "255.255.255.0",
                     "clusters": [
                         {
@@ -96,11 +96,11 @@
                             "clustertype": "CloudManaged",
                             "primaryStorages": [
                                 {
-                                    "url": "nfs://10.147.100.6:/export/home/sandbox/z1p1",
+                                    "url": "nfs://10.147.28.6:/export/home/sandbox/z1p1",
                                     "name": "Z1PS1"
                                 },
                                 {
-                                    "url": "nfs://10.147.100.6:/export/home/sandbox/z1p2",
+                                    "url": "nfs://10.147.28.6:/export/home/sandbox/z1p2",
                                     "name": "Z1PS2"
                                 }
                             ]
@@ -123,35 +123,35 @@
                             "clustertype": "CloudManaged",
                             "primaryStorages": [
                                 {
-                                    "url": "nfs://10.147.100.6:/export/home/sandbox/z1p3",
+                                    "url": "nfs://10.147.28.6:/export/home/sandbox/z1p3",
                                     "name": "Z1PS3"
                                 },
                                 {
-                                    "url": "nfs://10.147.100.6:/export/home/sandbox/z1p4",
+                                    "url": "nfs://10.147.28.6:/export/home/sandbox/z1p4",
                                     "name": "Z1PS4"
                                 }
                             ]
                         }
                     ],
-                    "gateway": "172.16.100.1"
+                    "gateway": "172.16.15.1"
                 }
             ],
-            "internaldns1": "10.147.100.6",
+            "internaldns1": "10.147.28.6",
             "secondaryStorages": [
                 {
-                    "url": "nfs://10.147.100.6:/export/home/sandbox/z1secondary",
+                    "url": "nfs://10.147.28.6:/export/home/sandbox/z1secondary",
                     "provider" : "NFS"
                 }
             ]
         },
         {
             "name": "zim2",
-            "guestcidraddress": "10.200.1.0/24",
-            "dns1": "10.147.200.6",
+            "guestcidraddress": "10.1.2.0/24",
+            "dns1": "10.147.29.6",
             "physical_networks": [
                 {
                     "broadcastdomainrange": "Zone",
-                    "vlan": "2100-2200",
+                    "vlan": "300-400",
                     "name": "z2-pnet",
                     "traffictypes": [
                         {
@@ -191,19 +191,19 @@
             },
             "ipranges": [
                 {
-                    "startip": "192.168.200.2",
-                    "endip": "192.168.200.200",
+                    "startip": "192.168.3.2",
+                    "endip": "192.168.3.200",
                     "netmask": "255.255.255.0",
-                    "vlan": "50",
-                    "gateway": "192.168.200.1"
+                    "vlan": "51",
+                    "gateway": "192.168.3.1"
                 }
             ],
             "networktype": "Advanced",
             "pods": [
                 {
-                    "endip": "172.16.200.200",
+                    "endip": "172.16.16.200",
                     "name": "Z2P1",
-                    "startip": "172.16.200.2",
+                    "startip": "172.16.16.2",
                     "netmask": "255.255.255.0",
                     "clusters": [
                         {
@@ -224,11 +224,11 @@
                             "clustertype": "CloudManaged",
                             "primaryStorages": [
                                 {
-                                    "url": "nfs://10.147.200.6:/export/home/sandbox/z2p1",
+                                    "url": "nfs://10.147.29.6:/export/home/sandbox/z2p1",
                                     "name": "Z2PS1"
                                 },
                                 {
-                                    "url": "nfs://10.147.200.6:/export/home/sandbox/z2p2",
+                                    "url": "nfs://10.147.29.6:/export/home/sandbox/z2p2",
                                     "name": "Z2PS2"
                                 }
                             ]
@@ -251,20 +251,20 @@
                             "clustertype": "CloudManaged",
                             "primaryStorages": [
                                 {
-                                    "url": "nfs://10.147.200.6:/export/home/sandbox/z2p3",
+                                    "url": "nfs://10.147.29.6:/export/home/sandbox/z2p3",
                                     "name": "Z2PS3"
                                 },
                                 {
-                                    "url": "nfs://10.147.200.6:/export/home/sandbox/z2p4",
+                                    "url": "nfs://10.147.29.6:/export/home/sandbox/z2p4",
                                     "name": "Z2PS4"
                                 }
                             ]
                         }
                     ],
-                    "gateway": "172.16.200.1"
+                    "gateway": "172.16.16.1"
                 }
             ],
-            "internaldns1": "10.147.200.6",
+            "internaldns1": "10.147.29.6",
             "secondaryStorages": [
                 {
                     "url": "nfs://10.147.200.6:/export/home/sandbox/z2secondary",
diff --git a/systemvm/agent/certs/realhostip.key b/systemvm/agent/certs/realhostip.key
index 53bdc86..47031f4 100644
--- a/systemvm/agent/certs/realhostip.key
+++ b/systemvm/agent/certs/realhostip.key
@@ -21,4 +21,4 @@
 Vo1lAoGAb1gCoyBCzvi7sqFxm4V5oapnJeiQQJFjhoYWqGa26rQ+AvXXNuBcigIeDXNJPctSF0Uc
 p11KbbCgiruBbckvM1vGsk6Sx4leRk+IFHRpJktFUek4o0eUg0shOsyyvyet48Dfg0a8FvcxROs0
 gD+IYds5doiob/hcm1hnNB/3vk4=
------END PRIVATE KEY-----
\ No newline at end of file
+-----END PRIVATE KEY-----
diff --git a/systemvm/agent/js/jquery.js b/systemvm/agent/js/jquery.js
index b1ae21d..a0fb883 100644
--- a/systemvm/agent/js/jquery.js
+++ b/systemvm/agent/js/jquery.js
@@ -16,4 +16,4 @@
  *  Released under the MIT, BSD, and GPL Licenses.
  *  More information: http://sizzlejs.com/
  */
-(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa<ab.length;aa++){if(ab[aa]===ab[aa-1]){ab.splice(aa--,1)}}}}}return ab};F.matches=function(T,U){return F(T,null,null,U)};F.find=function(aa,T,ab){var Z,X;if(!aa){return[]}for(var W=0,V=I.order.length;W<V;W++){var Y=I.order[W],X;if((X=I.match[Y].exec(aa))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){X[1]=(X[1]||"").replace(/\\/g,"");Z=I.find[Y](X,T,ab);if(Z!=null){aa=aa.replace(I.match[Y],"");break}}}}if(!Z){Z=T.getElementsByTagName("*")}return{set:Z,expr:aa}};F.filter=function(ad,ac,ag,W){var V=ad,ai=[],aa=ac,Y,T,Z=ac&&ac[0]&&Q(ac[0]);while(ad&&ac.length){for(var ab in I.filter){if((Y=I.match[ab].exec(ad))!=null){var U=I.filter[ab],ah,af;T=false;if(aa==ai){ai=[]}if(I.preFilter[ab]){Y=I.preFilter[ab](Y,aa,ag,ai,W,Z);if(!Y){T=ah=true}else{if(Y===true){continue}}}if(Y){for(var X=0;(af=aa[X])!=null;X++){if(af){ah=U(af,Y,X,aa);var ae=W^!!ah;if(ag&&ah!=null){if(ae){T=true}else{aa[X]=false}}else{if(ae){ai.push(af);T=true}}}}}if(ah!==g){if(!ag){aa=ai}ad=ad.replace(I.match[ab],"");if(!T){return[]}break}}}if(ad==V){if(T==null){throw"Syntax error, unrecognized expression: "+ad}else{break}}V=ad}return aa};var I=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(T){return T.getAttribute("href")}},relative:{"+":function(aa,T,Z){var X=typeof T==="string",ab=X&&!/\W/.test(T),Y=X&&!ab;if(ab&&!Z){T=T.toUpperCase()}for(var W=0,V=aa.length,U;W<V;W++){if((U=aa[W])){while((U=U.previousSibling)&&U.nodeType!==1){}aa[W]=Y||U&&U.nodeName===T?U||false:U===T}}if(Y){F.filter(T,aa,true)}},">":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){var W=Y.parentNode;Z[V]=W.nodeName===U?W:false}}}else{for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){Z[V]=X?Y.parentNode:Y.parentNode===U}}if(X){F.filter(U,Z,true)}}},"":function(W,U,Y){var V=L++,T=S;if(!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("parentNode",U,V,W,X,Y)},"~":function(W,U,Y){var V=L++,T=S;if(typeof U==="string"&&!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("previousSibling",U,V,W,X,Y)}},find:{ID:function(U,V,W){if(typeof V.getElementById!=="undefined"&&!W){var T=V.getElementById(U[1]);return T?[T]:[]}},NAME:function(V,Y,Z){if(typeof Y.getElementsByName!=="undefined"){var U=[],X=Y.getElementsByName(V[1]);for(var W=0,T=X.length;W<T;W++){if(X[W].getAttribute("name")===V[1]){U.push(X[W])}}return U.length===0?null:U}},TAG:function(T,U){return U.getElementsByTagName(T[1])}},preFilter:{CLASS:function(W,U,V,T,Z,aa){W=" "+W[1].replace(/\\/g,"")+" ";if(aa){return W}for(var X=0,Y;(Y=U[X])!=null;X++){if(Y){if(Z^(Y.className&&(" "+Y.className+" ").indexOf(W)>=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return U<T[3]-0},gt:function(V,U,T){return U>T[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W<T;W++){if(Y[W]===Z){return false}}return true}}}},CHILD:function(T,W){var Z=W[1],U=T;switch(Z){case"only":case"first":while(U=U.previousSibling){if(U.nodeType===1){return false}}if(Z=="first"){return true}U=T;case"last":while(U=U.nextSibling){if(U.nodeType===1){return false}}return true;case"nth":var V=W[2],ac=W[3];if(V==1&&ac==0){return true}var Y=W[0],ab=T.parentNode;if(ab&&(ab.sizcache!==Y||!T.nodeIndex)){var X=0;for(U=ab.firstChild;U;U=U.nextSibling){if(U.nodeType===1){U.nodeIndex=++X}}ab.sizcache=Y}var aa=T.nodeIndex-ac;if(V==0){return aa==0}else{return(aa%V==0&&aa/V>=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V<T;V++){U.push(X[V])}}else{for(var V=0;X[V];V++){U.push(X[V])}}}return U}}var G;if(document.documentElement.compareDocumentPosition){G=function(U,T){var V=U.compareDocumentPosition(T)&4?-1:U===T?0:1;if(V===0){hasDuplicate=true}return V}}else{if("sourceIndex" in document.documentElement){G=function(U,T){var V=U.sourceIndex-T.sourceIndex;if(V===0){hasDuplicate=true}return V}}else{if(document.createRange){G=function(W,U){var V=W.ownerDocument.createRange(),T=U.ownerDocument.createRange();V.selectNode(W);V.collapse(true);T.selectNode(U);T.collapse(true);var X=V.compareBoundaryPoints(Range.START_TO_END,T);if(X===0){hasDuplicate=true}return X}}}}(function(){var U=document.createElement("form"),V="script"+(new Date).getTime();U.innerHTML="<input name='"+V+"'/>";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="<a href='#'></a>";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="<p class='TEST'></p>";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="<div class='test e'></div><div class='test'></div>";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1&&!ac){T.sizcache=Y;T.sizset=W}if(T.nodeName===Z){X=T;break}T=T[U]}ad[W]=X}}}function S(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1){if(!ac){T.sizcache=Y;T.sizset=W}if(typeof Z!=="string"){if(T===Z){X=true;break}}else{if(F.filter(Z,[T]).length>0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z<U;Z++){F(T,V[Z],W)}return F.filter(X,W)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(T){return T.offsetWidth===0||T.offsetHeight===0};F.selectors.filters.visible=function(T){return T.offsetWidth>0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});F.sort(function(J,I){return o.data(J.elem,"closest")-o.data(I.elem,"closest")});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){return(G=false)}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&l==l.top){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML='   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H<F;H++){this[H].style.display=o.data(this[H],"olddisplay")||""}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}}for(var G=0,F=this.length;G<F;G++){this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n);n=g}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})();
\ No newline at end of file
+(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa<ab.length;aa++){if(ab[aa]===ab[aa-1]){ab.splice(aa--,1)}}}}}return ab};F.matches=function(T,U){return F(T,null,null,U)};F.find=function(aa,T,ab){var Z,X;if(!aa){return[]}for(var W=0,V=I.order.length;W<V;W++){var Y=I.order[W],X;if((X=I.match[Y].exec(aa))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){X[1]=(X[1]||"").replace(/\\/g,"");Z=I.find[Y](X,T,ab);if(Z!=null){aa=aa.replace(I.match[Y],"");break}}}}if(!Z){Z=T.getElementsByTagName("*")}return{set:Z,expr:aa}};F.filter=function(ad,ac,ag,W){var V=ad,ai=[],aa=ac,Y,T,Z=ac&&ac[0]&&Q(ac[0]);while(ad&&ac.length){for(var ab in I.filter){if((Y=I.match[ab].exec(ad))!=null){var U=I.filter[ab],ah,af;T=false;if(aa==ai){ai=[]}if(I.preFilter[ab]){Y=I.preFilter[ab](Y,aa,ag,ai,W,Z);if(!Y){T=ah=true}else{if(Y===true){continue}}}if(Y){for(var X=0;(af=aa[X])!=null;X++){if(af){ah=U(af,Y,X,aa);var ae=W^!!ah;if(ag&&ah!=null){if(ae){T=true}else{aa[X]=false}}else{if(ae){ai.push(af);T=true}}}}}if(ah!==g){if(!ag){aa=ai}ad=ad.replace(I.match[ab],"");if(!T){return[]}break}}}if(ad==V){if(T==null){throw"Syntax error, unrecognized expression: "+ad}else{break}}V=ad}return aa};var I=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(T){return T.getAttribute("href")}},relative:{"+":function(aa,T,Z){var X=typeof T==="string",ab=X&&!/\W/.test(T),Y=X&&!ab;if(ab&&!Z){T=T.toUpperCase()}for(var W=0,V=aa.length,U;W<V;W++){if((U=aa[W])){while((U=U.previousSibling)&&U.nodeType!==1){}aa[W]=Y||U&&U.nodeName===T?U||false:U===T}}if(Y){F.filter(T,aa,true)}},">":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){var W=Y.parentNode;Z[V]=W.nodeName===U?W:false}}}else{for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){Z[V]=X?Y.parentNode:Y.parentNode===U}}if(X){F.filter(U,Z,true)}}},"":function(W,U,Y){var V=L++,T=S;if(!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("parentNode",U,V,W,X,Y)},"~":function(W,U,Y){var V=L++,T=S;if(typeof U==="string"&&!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("previousSibling",U,V,W,X,Y)}},find:{ID:function(U,V,W){if(typeof V.getElementById!=="undefined"&&!W){var T=V.getElementById(U[1]);return T?[T]:[]}},NAME:function(V,Y,Z){if(typeof Y.getElementsByName!=="undefined"){var U=[],X=Y.getElementsByName(V[1]);for(var W=0,T=X.length;W<T;W++){if(X[W].getAttribute("name")===V[1]){U.push(X[W])}}return U.length===0?null:U}},TAG:function(T,U){return U.getElementsByTagName(T[1])}},preFilter:{CLASS:function(W,U,V,T,Z,aa){W=" "+W[1].replace(/\\/g,"")+" ";if(aa){return W}for(var X=0,Y;(Y=U[X])!=null;X++){if(Y){if(Z^(Y.className&&(" "+Y.className+" ").indexOf(W)>=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return U<T[3]-0},gt:function(V,U,T){return U>T[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W<T;W++){if(Y[W]===Z){return false}}return true}}}},CHILD:function(T,W){var Z=W[1],U=T;switch(Z){case"only":case"first":while(U=U.previousSibling){if(U.nodeType===1){return false}}if(Z=="first"){return true}U=T;case"last":while(U=U.nextSibling){if(U.nodeType===1){return false}}return true;case"nth":var V=W[2],ac=W[3];if(V==1&&ac==0){return true}var Y=W[0],ab=T.parentNode;if(ab&&(ab.sizcache!==Y||!T.nodeIndex)){var X=0;for(U=ab.firstChild;U;U=U.nextSibling){if(U.nodeType===1){U.nodeIndex=++X}}ab.sizcache=Y}var aa=T.nodeIndex-ac;if(V==0){return aa==0}else{return(aa%V==0&&aa/V>=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V<T;V++){U.push(X[V])}}else{for(var V=0;X[V];V++){U.push(X[V])}}}return U}}var G;if(document.documentElement.compareDocumentPosition){G=function(U,T){var V=U.compareDocumentPosition(T)&4?-1:U===T?0:1;if(V===0){hasDuplicate=true}return V}}else{if("sourceIndex" in document.documentElement){G=function(U,T){var V=U.sourceIndex-T.sourceIndex;if(V===0){hasDuplicate=true}return V}}else{if(document.createRange){G=function(W,U){var V=W.ownerDocument.createRange(),T=U.ownerDocument.createRange();V.selectNode(W);V.collapse(true);T.selectNode(U);T.collapse(true);var X=V.compareBoundaryPoints(Range.START_TO_END,T);if(X===0){hasDuplicate=true}return X}}}}(function(){var U=document.createElement("form"),V="script"+(new Date).getTime();U.innerHTML="<input name='"+V+"'/>";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="<a href='#'></a>";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="<p class='TEST'></p>";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="<div class='test e'></div><div class='test'></div>";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1&&!ac){T.sizcache=Y;T.sizset=W}if(T.nodeName===Z){X=T;break}T=T[U]}ad[W]=X}}}function S(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1){if(!ac){T.sizcache=Y;T.sizset=W}if(typeof Z!=="string"){if(T===Z){X=true;break}}else{if(F.filter(Z,[T]).length>0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z<U;Z++){F(T,V[Z],W)}return F.filter(X,W)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(T){return T.offsetWidth===0||T.offsetHeight===0};F.selectors.filters.visible=function(T){return T.offsetWidth>0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});F.sort(function(J,I){return o.data(J.elem,"closest")-o.data(I.elem,"closest")});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){return(G=false)}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&l==l.top){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML='   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H<F;H++){this[H].style.display=o.data(this[H],"olddisplay")||""}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}}for(var G=0,F=this.length;G<F;G++){this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n);n=g}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})();
diff --git a/systemvm/agent/noVNC/app/locale/cs.json b/systemvm/agent/noVNC/app/locale/cs.json
index 589145e..eba5ee3 100644
--- a/systemvm/agent/noVNC/app/locale/cs.json
+++ b/systemvm/agent/noVNC/app/locale/cs.json
@@ -68,4 +68,4 @@
     "Password:": "Heslo",
     "Send Password": "Odeslat heslo",
     "Cancel": "Zrušit"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/de.json b/systemvm/agent/noVNC/app/locale/de.json
index 62e7336..15099d7 100644
--- a/systemvm/agent/noVNC/app/locale/de.json
+++ b/systemvm/agent/noVNC/app/locale/de.json
@@ -66,4 +66,4 @@
     "Password:": "Passwort:",
     "Cancel": "Abbrechen",
     "Canvas not supported.": "Canvas nicht unterstützt."
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/el.json b/systemvm/agent/noVNC/app/locale/el.json
index f801251..19cdb58 100644
--- a/systemvm/agent/noVNC/app/locale/el.json
+++ b/systemvm/agent/noVNC/app/locale/el.json
@@ -66,4 +66,4 @@
     "Password:": "Κωδικός Πρόσβασης:",
     "Cancel": "Ακύρωση",
     "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/es.json b/systemvm/agent/noVNC/app/locale/es.json
index b9e663a..61365e2 100644
--- a/systemvm/agent/noVNC/app/locale/es.json
+++ b/systemvm/agent/noVNC/app/locale/es.json
@@ -65,4 +65,4 @@
     "Password:": "Contraseña:",
     "Cancel": "Cancelar",
     "Canvas not supported.": "Canvas no soportado."
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/fr.json b/systemvm/agent/noVNC/app/locale/fr.json
index 22531f7..51e4589 100644
--- a/systemvm/agent/noVNC/app/locale/fr.json
+++ b/systemvm/agent/noVNC/app/locale/fr.json
@@ -75,4 +75,4 @@
     "Password:": "Mot de passe :",
     "Send Credentials": "Envoyer les identifiants",
     "Cancel": "Annuler"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/it.json b/systemvm/agent/noVNC/app/locale/it.json
index 6fd2570..552018d 100644
--- a/systemvm/agent/noVNC/app/locale/it.json
+++ b/systemvm/agent/noVNC/app/locale/it.json
@@ -69,4 +69,4 @@
     "Password:": "Password:",
     "Send Credentials": "Invia Credenziale",
     "Cancel": "Annulla"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/ja.json b/systemvm/agent/noVNC/app/locale/ja.json
index 43fc5bf..e85edf9 100644
--- a/systemvm/agent/noVNC/app/locale/ja.json
+++ b/systemvm/agent/noVNC/app/locale/ja.json
@@ -69,4 +69,4 @@
     "Password:": "パスワード:",
     "Send Credentials": "資格情報を送信",
     "Cancel": "キャンセル"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/ko.json b/systemvm/agent/noVNC/app/locale/ko.json
index e4ecddc..aa9c110 100644
--- a/systemvm/agent/noVNC/app/locale/ko.json
+++ b/systemvm/agent/noVNC/app/locale/ko.json
@@ -67,4 +67,4 @@
     "Password:": "비밀번호:",
     "Send Password": "비밀번호 전송",
     "Cancel": "취소"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/nl.json b/systemvm/agent/noVNC/app/locale/nl.json
index 0cdcc92..1ad7564 100644
--- a/systemvm/agent/noVNC/app/locale/nl.json
+++ b/systemvm/agent/noVNC/app/locale/nl.json
@@ -70,4 +70,4 @@
     "Password:": "Wachtwoord:",
     "Send Password": "Verzend Wachtwoord:",
     "Cancel": "Annuleren"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/pl.json b/systemvm/agent/noVNC/app/locale/pl.json
index 006ac7a..12a4656 100644
--- a/systemvm/agent/noVNC/app/locale/pl.json
+++ b/systemvm/agent/noVNC/app/locale/pl.json
@@ -66,4 +66,4 @@
     "Password:": "Hasło:",
     "Cancel": "Anuluj",
     "Canvas not supported.": "Element Canvas nie jest wspierany."
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/pt_BR.json b/systemvm/agent/noVNC/app/locale/pt_BR.json
index aa130f7..42b16b6 100644
--- a/systemvm/agent/noVNC/app/locale/pt_BR.json
+++ b/systemvm/agent/noVNC/app/locale/pt_BR.json
@@ -69,4 +69,4 @@
     "Password:": "Senha:",
     "Send Credentials": "Enviar credenciais",
     "Cancel": "Cancelar"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/ru.json b/systemvm/agent/noVNC/app/locale/ru.json
index cab9739..72bde71 100644
--- a/systemvm/agent/noVNC/app/locale/ru.json
+++ b/systemvm/agent/noVNC/app/locale/ru.json
@@ -69,4 +69,4 @@
     "Password:": "Пароль:",
     "Send Credentials": "Передача Учетных Данных",
     "Cancel": "Выход"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/sv.json b/systemvm/agent/noVNC/app/locale/sv.json
index 077ef42..91dff9e 100644
--- a/systemvm/agent/noVNC/app/locale/sv.json
+++ b/systemvm/agent/noVNC/app/locale/sv.json
@@ -77,4 +77,4 @@
     "Password:": "Lösenord:",
     "Send Credentials": "Skicka Användaruppgifter",
     "Cancel": "Avbryt"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/tr.json b/systemvm/agent/noVNC/app/locale/tr.json
index 451c1b8..b92b416 100644
--- a/systemvm/agent/noVNC/app/locale/tr.json
+++ b/systemvm/agent/noVNC/app/locale/tr.json
@@ -66,4 +66,4 @@
     "Password:": "Parola:",
     "Cancel": "Vazgeç",
     "Canvas not supported.": "Tuval desteklenmiyor."
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/zh_CN.json b/systemvm/agent/noVNC/app/locale/zh_CN.json
index f0aea9a..ee73eba 100644
--- a/systemvm/agent/noVNC/app/locale/zh_CN.json
+++ b/systemvm/agent/noVNC/app/locale/zh_CN.json
@@ -66,4 +66,4 @@
     "Connect": "连接",
     "Password:": "密码:",
     "Cancel": "取消"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/locale/zh_TW.json b/systemvm/agent/noVNC/app/locale/zh_TW.json
index 8ddf813..b0368ce 100644
--- a/systemvm/agent/noVNC/app/locale/zh_TW.json
+++ b/systemvm/agent/noVNC/app/locale/zh_TW.json
@@ -66,4 +66,4 @@
     "Connect": "連線",
     "Password:": "密碼:",
     "Cancel": "取消"
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/app/ui.js b/systemvm/agent/noVNC/app/ui.js
index c85d9c2..3a85648 100644
--- a/systemvm/agent/noVNC/app/ui.js
+++ b/systemvm/agent/noVNC/app/ui.js
@@ -704,7 +704,7 @@
 
     updateControlbarHandle() {
         // Since the control bar is fixed on the viewport and not the page,
-        // the move function expects coordinates relative the the viewport.
+        // the move function expects coordinates relative the viewport.
         const handle = document.getElementById("noVNC_control_bar_handle");
         const handleBounds = handle.getBoundingClientRect();
         UI.moveControlbarHandle(handleBounds.top);
diff --git a/systemvm/agent/noVNC/core/display.js b/systemvm/agent/noVNC/core/display.js
index bf8d5fa..b9c8d21 100644
--- a/systemvm/agent/noVNC/core/display.js
+++ b/systemvm/agent/noVNC/core/display.js
@@ -347,7 +347,7 @@
             // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
             //
             // We need to set these every time since all properties are reset
-            // when the the size is changed
+            // when the size is changed
             this._drawCtx.mozImageSmoothingEnabled = false;
             this._drawCtx.webkitImageSmoothingEnabled = false;
             this._drawCtx.msImageSmoothingEnabled = false;
diff --git a/systemvm/agent/noVNC/core/input/uskeysym.js b/systemvm/agent/noVNC/core/input/uskeysym.js
index 97c5ae4..7eda9a6 100644
--- a/systemvm/agent/noVNC/core/input/uskeysym.js
+++ b/systemvm/agent/noVNC/core/input/uskeysym.js
@@ -54,4 +54,4 @@
 
     ' ': 0x0020, /* U+0020 SPACE */
     '\n': 0xff0d
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/core/ra2.js b/systemvm/agent/noVNC/core/ra2.js
index 81a8a89..d038608 100644
--- a/systemvm/agent/noVNC/core/ra2.js
+++ b/systemvm/agent/noVNC/core/ra2.js
@@ -564,4 +564,4 @@
     set hasStarted(s) {
         this._hasStarted = s;
     }
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/core/rfb.js b/systemvm/agent/noVNC/core/rfb.js
index a8d63ff..228340a 100644
--- a/systemvm/agent/noVNC/core/rfb.js
+++ b/systemvm/agent/noVNC/core/rfb.js
@@ -883,7 +883,7 @@
                    size.w + 'x' + size.h);
     }
 
-    // Gets the the size of the available screen
+    // Gets the size of the available screen
     _screenSize() {
         let r = this._screen.getBoundingClientRect();
         return { w: r.width, h: r.height };
diff --git a/systemvm/agent/noVNC/core/util/md5.js b/systemvm/agent/noVNC/core/util/md5.js
index 49762ef..3834916 100644
--- a/systemvm/agent/noVNC/core/util/md5.js
+++ b/systemvm/agent/noVNC/core/util/md5.js
@@ -76,4 +76,4 @@
 
 function rol(d, g) {
     return d << g | d >>> 32 - g;
-}
\ No newline at end of file
+}
diff --git a/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py b/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py
index 1818037..145891b 100755
--- a/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py
+++ b/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py
@@ -97,7 +97,7 @@
     js_config.append("export default {\n")
     for keycode in dict(sorted(result_mappings.items(), key=lambda item: int(item[0]))):
         js_config.append("%10s : \"%s\",\n" % ("\"" + str(keycode) + "\"", result_mappings[keycode].strip()))
-    js_config.append("}")
+    js_config.append("}\n")
     for line in js_config:
         handle.write(line)
     handle.close()
diff --git a/systemvm/agent/noVNC/keymaps/keymap-ja-atset1.js b/systemvm/agent/noVNC/keymaps/keymap-ja-atset1.js
index 847ce2b..965a723 100644
--- a/systemvm/agent/noVNC/keymaps/keymap-ja-atset1.js
+++ b/systemvm/agent/noVNC/keymaps/keymap-ja-atset1.js
@@ -99,4 +99,4 @@
      "125" : "43         shift",
      "126" : "11         shift",
      "166" : "86         shift altgr",
-}
\ No newline at end of file
+}
diff --git a/systemvm/debian/etc/modprobe.d/pcspkr.conf b/systemvm/debian/etc/modprobe.d/pcspkr.conf
index 892b51f..433368f 100644
--- a/systemvm/debian/etc/modprobe.d/pcspkr.conf
+++ b/systemvm/debian/etc/modprobe.d/pcspkr.conf
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-blacklist pcspkr
\ No newline at end of file
+blacklist pcspkr
diff --git a/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-fcopy-daemon.service b/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-fcopy-daemon.service
index 12a0b63..c249a3a 100644
--- a/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-fcopy-daemon.service
+++ b/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-fcopy-daemon.service
@@ -6,4 +6,4 @@
 ExecStart=/usr/sbin/hv_fcopy_daemon -n
 
 [Install]
-WantedBy=multi-user.target
\ No newline at end of file
+WantedBy=multi-user.target
diff --git a/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-kvp-daemon.service b/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-kvp-daemon.service
index 534a25a..47e6834 100644
--- a/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-kvp-daemon.service
+++ b/systemvm/debian/etc/systemd/system/hyperv-daemons.hv-kvp-daemon.service
@@ -5,4 +5,4 @@
 ExecStart=/usr/sbin/hv_kvp_daemon -n
 
 [Install]
-WantedBy=multi-user.target
\ No newline at end of file
+WantedBy=multi-user.target
diff --git a/systemvm/debian/opt/cloud/bin/getRouterAlerts.sh b/systemvm/debian/opt/cloud/bin/getRouterAlerts.sh
index 3f5f4a3..f48478e 100755
--- a/systemvm/debian/opt/cloud/bin/getRouterAlerts.sh
+++ b/systemvm/debian/opt/cloud/bin/getRouterAlerts.sh
@@ -52,4 +52,4 @@
        echo $alerts
 else
        echo "No Alerts"
-fi
\ No newline at end of file
+fi
diff --git a/systemvm/debian/opt/cloud/bin/patched.sh b/systemvm/debian/opt/cloud/bin/patched.sh
index bfe0f64..8ebc1af 100644
--- a/systemvm/debian/opt/cloud/bin/patched.sh
+++ b/systemvm/debian/opt/cloud/bin/patched.sh
@@ -16,4 +16,4 @@
 # specific language governing permissions and limitations
 # under the License.
 
-ls -lrt $1
\ No newline at end of file
+ls -lrt $1
diff --git a/systemvm/debian/opt/cloud/bin/setup/cksnode.sh b/systemvm/debian/opt/cloud/bin/setup/cksnode.sh
index 0b5df04..55bd4ea 100755
--- a/systemvm/debian/opt/cloud/bin/setup/cksnode.sh
+++ b/systemvm/debian/opt/cloud/bin/setup/cksnode.sh
@@ -71,4 +71,4 @@
     fi
 }
 
-setup_k8s_node
\ No newline at end of file
+setup_k8s_node
diff --git a/systemvm/debian/opt/cloud/bin/update_interface_config.sh b/systemvm/debian/opt/cloud/bin/update_interface_config.sh
index 4b1e96b..53d81f7 100644
--- a/systemvm/debian/opt/cloud/bin/update_interface_config.sh
+++ b/systemvm/debian/opt/cloud/bin/update_interface_config.sh
@@ -60,4 +60,4 @@
 fi
 
 echo "Interface with IP ${ip} not found"
-exit 1
\ No newline at end of file
+exit 1
diff --git a/systemvm/debian/opt/cloud/testdata/vmp0001.json b/systemvm/debian/opt/cloud/testdata/vmp0001.json
index 39ee78d..de3db3b 100644
--- a/systemvm/debian/opt/cloud/testdata/vmp0001.json
+++ b/systemvm/debian/opt/cloud/testdata/vmp0001.json
@@ -1 +1 @@
-{"ip_address":"172.16.1.102","password":"fnirq_cnffjbeq","type":"vmpassword"}
\ No newline at end of file
+{"ip_address":"172.16.1.102","password":"fnirq_cnffjbeq","type":"vmpassword"}
diff --git a/systemvm/pom.xml b/systemvm/pom.xml
index c454576..8185e3d 100644
--- a/systemvm/pom.xml
+++ b/systemvm/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <properties>
diff --git a/test/integration/broken/maint/test_zone_level_local_storage_setting.py b/test/integration/broken/maint/test_zone_level_local_storage_setting.py
index 19d3ed5..c8e1946 100644
--- a/test/integration/broken/maint/test_zone_level_local_storage_setting.py
+++ b/test/integration/broken/maint/test_zone_level_local_storage_setting.py
@@ -576,7 +576,7 @@
             ispersistent='true'
         )
 
-        # 3-list netwrok offerings
+        # 3-list network offerings
         list_nw_of = NetworkOffering.list(self.apiclient,
                                           id=self.network_offering.id)
         self.assertEqual(
diff --git a/test/integration/component/maint/test_host_high_availability.py b/test/integration/component/maint/test_host_high_availability.py
index c95b5db..c335c66 100644
--- a/test/integration/component/maint/test_host_high_availability.py
+++ b/test/integration/component/maint/test_host_high_availability.py
@@ -483,7 +483,7 @@
         self.assertEqual(
             vm_response.id,
             vm.id,
-            "The virtual machine id and the the virtual machine from listVirtualMachines is not matching."
+            "The virtual machine id and the virtual machine from listVirtualMachines is not matching."
         )
 
         self.assertEqual(
diff --git a/test/integration/component/test_acl_isolatednetwork.py b/test/integration/component/test_acl_isolatednetwork.py
index 5126aff..038fd35 100644
--- a/test/integration/component/test_acl_isolatednetwork.py
+++ b/test/integration/component/test_acl_isolatednetwork.py
@@ -427,7 +427,7 @@
         self.cleanup.append(network)
         self.assertEqual(network.state.lower() == ALLOCATED.lower(),
                          True,
-                         "Admin User is not able to create a network for for other users in other domain")
+                         "Admin User is not able to create a network for other users in other domain")
 
 
     ## Test cases relating to createNetwork as domain admin user
@@ -575,7 +575,7 @@
         except Exception as e:
             self.debug("When user tries to create network for users in their domain %s" % e)
             if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.UNABLE_TO_LIST_NETWORK_ACCOUNT):
-                self.fail("Error message validation failed when when User tries to create network for other users in their domain ")
+                self.fail("Error message validation failed when User tries to create network for other users in their domain ")
 
     @attr("simulator_only", tags=["advanced"], required_hardware="false")
     def test_10_createNetwork_user_foruserinotherdomain(self):
@@ -674,7 +674,7 @@
         self.cleanup.append(vm)
         self.assertEqual(vm.state.lower() == RUNNING.lower() and vm.account == self.account_d2a.name and vm.domainid == self.account_d2a.domainid,
                          True,
-                         "Admin User is not able to deploy VM for users users in other domain")
+                         "Admin User is not able to deploy VM for users in other domain")
 
     @attr("simulator_only", tags=["advanced"], required_hardware="false")
     def test_13_1_deployvm_admin_foruserinotherdomain_crossnetwork(self):
diff --git a/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py b/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py
index 1cb445b..36b71de 100644
--- a/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py
+++ b/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py
@@ -1714,7 +1714,7 @@
                 accountid=self.account_d2a.name,
                 domainid=self.account_d2a.domainid
             )
-            self.fail("Domain admin is able able to deploy a VM for an regular user from a differnt domain in a shared network with scope=account")
+            self.fail("Domain admin is able to deploy a VM for an regular user from a differnt domain in a shared network with scope=account")
         except Exception as e:
             self.debug("When a user from different domain deploys a VM in a shared network with scope=account %s" % e)
             if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN):
diff --git a/test/integration/component/test_add_remove_network.py b/test/integration/component/test_add_remove_network.py
index 1562507..716ffa3 100644
--- a/test/integration/component/test_add_remove_network.py
+++ b/test/integration/component/test_add_remove_network.py
@@ -721,7 +721,7 @@
 
         self.debug("Created network %s" % network_2.name)
 
-        self.debug("Trying to add netwrok %s to VM %s, this should fail" %
+        self.debug("Trying to add network %s to VM %s, this should fail" %
                    (network_2.name, virtual_machine.id))
 
         with self.assertRaises(Exception) as e:
diff --git a/test/integration/component/test_affinity_groups_projects.py b/test/integration/component/test_affinity_groups_projects.py
index 1c0b4c2..07811e7 100644
--- a/test/integration/component/test_affinity_groups_projects.py
+++ b/test/integration/component/test_affinity_groups_projects.py
@@ -1065,7 +1065,7 @@
         """
         test DeployVM in anti-affinity groups with more vms than hosts.
         """
-        hosts = list_hosts(self.api_client, type="routing")
+        hosts = list_hosts(self.api_client, type="routing", zoneid=self.zone.id)
         aff_grp = self.create_aff_grp(self.account_api_client)
         vms = []
         for host in hosts:
diff --git a/test/integration/component/test_base_image_updation.py b/test/integration/component/test_base_image_updation.py
index 234a86e..bd78082 100644
--- a/test/integration/component/test_base_image_updation.py
+++ b/test/integration/component/test_base_image_updation.py
@@ -331,7 +331,7 @@
         except Exception as e:
             self.fail("Failed to reboot the virtual machines, %s" % e)
 
-        # Check if the the root disk was destroyed and recreated for isVolatile=True
+        # Check if the root disk was destroyed and recreated for isVolatile=True
         self.debug("Checking root disk of VM with isVolatile=True")
         vms = VirtualMachine.list(
                                   self.apiclient,
@@ -358,7 +358,7 @@
                             %(vm_with_reset.nic[0].ipaddress, self.vm_with_reset.nic[0].ipaddress)
                         )
 
-        # Check if the the root disk was not destroyed for isVolatile=False
+        # Check if the root disk was not destroyed for isVolatile=False
         self.debug("Checking root disk of VM with isVolatile=False")
         vms = VirtualMachine.list(
                                   self.apiclient,
@@ -473,7 +473,7 @@
                                     %(vm_with_reset.nic[0].ipaddress, self.vm_with_reset.nic[0].ipaddress)
                                 )
 
-                # Check if the the root disk was not destroyed for isVolatile=False
+                # Check if the root disk was not destroyed for isVolatile=False
                 self.debug("Checking template id of VM with isVolatile=False")
                 vms = VirtualMachine.list(
                                           self.apiclient,
@@ -606,7 +606,7 @@
         except Exception as e:
             self.fail("Failed to reboot the virtual machine. Error: %s" % e)
 
-        # Check if the the root disk was destroyed and recreated for isVolatile=True
+        # Check if the root disk was destroyed and recreated for isVolatile=True
         self.debug("Checking whether root disk of VM with isVolatile=True was destroyed")
         vms = VirtualMachine.list(
                                   self.apiclient,
diff --git a/test/integration/component/test_browse_volumes.py b/test/integration/component/test_browse_volumes.py
index a1ee938..1cf3bac 100644
--- a/test/integration/component/test_browse_volumes.py
+++ b/test/integration/component/test_browse_volumes.py
@@ -2083,9 +2083,9 @@
 
             vm4details = self.deploy_vm()
 
-            newvolumetodestoy_VM = self.browse_upload_volume()
+            newvolumetodestroy_VM = self.browse_upload_volume()
 
-            self.attach_volume(vm4details, newvolumetodestoy_VM.id)
+            self.attach_volume(vm4details, newvolumetodestroy_VM.id)
 
             self.destroy_vm(vm4details)
 
@@ -2095,7 +2095,7 @@
             self.expunge_vm(vm4details)
 
             cmd = deleteVolume.deleteVolumeCmd()
-            cmd.id = newvolumetodestoy_VM.id
+            cmd.id = newvolumetodestroy_VM.id
             self.apiclient.deleteVolume(cmd)
 
             self.debug(
diff --git a/test/integration/component/test_configdrive.py b/test/integration/component/test_configdrive.py
index 38e7534..b092273 100644
--- a/test/integration/component/test_configdrive.py
+++ b/test/integration/component/test_configdrive.py
@@ -366,7 +366,7 @@
             return None
 
     def _get_config_drive_data(self, ssh, file, name, fail_on_missing=True):
-        """Fetches the content of a file file on the config drive
+        """Fetches the content of a file on the config drive
 
         :param ssh: SSH connection to the VM
         :param file: path to the file to fetch
@@ -1613,7 +1613,7 @@
                          "List network should return a valid list"
                          )
         self.assertEqual(network.name, networks[0].name,
-                         "Name of the network should match with with the "
+                         "Name of the network should match with the "
                          "returned list data"
                          )
         if state:
@@ -1777,11 +1777,11 @@
         #    And the VM is successfully deployed and is in the "Running" state
         #    And there is no VR is deployed.
         # 4. And the user data in the ConfigDrive device is as expected
-        # 5. And the the vm password in the ConfigDrive device is as expected
+        # 5. And the vm password in the ConfigDrive device is as expected
 
         # 6. When I stop, reset the password, and start the VM
         # 7. Then I can login into the VM using the new password.
-        # 8. And the the vm password in the ConfigDrive device is the new one
+        # 8. And the vm password in the ConfigDrive device is the new one
 
         # 9. Verify various scenarios and check the data in configdriveIso
         # 10. Delete all the created objects (cleanup).
@@ -2000,11 +2000,11 @@
         #    And the VM is successfully deployed and is in the "Running" state
 
         # 4. And the user data in the ConfigDrive device is as expected
-        # 5. And the the vm password in the ConfigDrive device is as expected
+        # 5. And the vm password in the ConfigDrive device is as expected
 
         # 6. When I stop, reset the password, and start the VM
         # 7. Then I can login into the VM using the new password.
-        # 8. And the the vm password in the ConfigDrive device is the new one
+        # 8. And the vm password in the ConfigDrive device is the new one
 
         # 9. Verify various scenarios and check the data in configdriveIso
         # 10. Delete all the created objects (cleanup).
diff --git a/test/integration/component/test_escalations_instances.py b/test/integration/component/test_escalations_instances.py
index d2ff9b5..89c4f4c 100644
--- a/test/integration/component/test_escalations_instances.py
+++ b/test/integration/component/test_escalations_instances.py
@@ -1244,7 +1244,7 @@
                 status[0],
                 "Listing VM's by name and zone failed"
             )
-            # Verifying Verifying that the size of the list is 1
+            # Verifying that the size of the list is 1
             self.assertEqual(
                 1,
                 len(list_vms),
@@ -1404,7 +1404,7 @@
             status[0],
             "Listing VM's by name and zone failed"
         )
-        # Verifying Verifying that the size of the list is 1
+        # Verifying that the size of the list is 1
         self.assertEqual(
             1,
             len(list_vms),
@@ -1474,7 +1474,7 @@
             status[0],
             "Listing VM's by name, account and zone failed"
         )
-        # Verifying Verifying that the size of the list is 1
+        # Verifying that the size of the list is 1
         self.assertEqual(
             1,
             len(list_vms),
@@ -4194,7 +4194,7 @@
             "Isolated Network Offerings with sourceNat enabled are not found"
         )
         """
-        Create Isolated netwrok with ip range
+        Create Isolated network with ip range
         """
         self.services["network"]["startip"] = "10.1.1.2"
         self.services["network"]["endip"] = "10.1.1.254"
@@ -4312,7 +4312,7 @@
             "Isolated Network Offerings with sourceNat enabled are not found"
         )
         """
-        Create Isolated netwrok with ip range
+        Create Isolated network with ip range
         """
         self.services["network"]["startip"] = "10.1.1.2"
         self.services["network"]["endip"] = "10.1.1.254"
diff --git a/test/integration/component/test_ip_reservation.py b/test/integration/component/test_ip_reservation.py
index 0ea01be..838aa30 100644
--- a/test/integration/component/test_ip_reservation.py
+++ b/test/integration/component/test_ip_reservation.py
@@ -1069,7 +1069,7 @@
         # 1. update guestvmcidr of isolated network (non persistent)
         #
         # validation
-        # should throw exception as network is not in implemented state as no vm is created
+        # should succeed although network is not in implemented state
 
         networkOffering = self.isolated_network_offering
         resultSet = createIsolatedNetwork(self, networkOffering.id)
@@ -1078,9 +1078,7 @@
         else:
             isolated_network = resultSet[1]
 
-        with self.assertRaises(Exception):
-            isolated_network.update(self.apiclient, guestvmcidr="10.1.1.0/26")
-        return
+        isolated_network.update(self.apiclient, guestvmcidr="10.1.1.0/26")
 
     @attr(tags=["advanced"], required_hardware="false")
     def test_vm_create_after_reservation(self):
diff --git a/test/integration/component/test_ldap.py b/test/integration/component/test_ldap.py
index 56f844a..6c6179e 100644
--- a/test/integration/component/test_ldap.py
+++ b/test/integration/component/test_ldap.py
@@ -105,7 +105,7 @@
                 self.apiClient.deleteAccount(deleteAcct)
 
                 self.debug(
-                    "Deleted the the following account name %s:" %
+                    "Deleted the following account name %s:" %
                     acct_name)
 
                 if self.ldapconfRes == 1:
diff --git a/test/integration/component/test_ldap_auto_import.py b/test/integration/component/test_ldap_auto_import.py
index c2cc6b5..c397386 100644
--- a/test/integration/component/test_ldap_auto_import.py
+++ b/test/integration/component/test_ldap_auto_import.py
@@ -379,7 +379,7 @@
             self.apiClient.deleteAccount(deleteAcct2)
 
             self.debug(
-                "Deleted the the following account name %s:" %
+                "Deleted the following account name %s:" %
                 acct_name)
 
         except Exception as e:
diff --git a/test/integration/component/test_multiple_ips_per_nic.py b/test/integration/component/test_multiple_ips_per_nic.py
index 85cbd43..a9e1ca9 100644
--- a/test/integration/component/test_multiple_ips_per_nic.py
+++ b/test/integration/component/test_multiple_ips_per_nic.py
@@ -297,7 +297,7 @@
         # Validations:
         # 1. Step 3 should succeed
         # 2. Step 4 should fail
-        # 3. Step 5 should should fail
+        # 3. Step 5 should fail
         # 4. Step 6 should fail
 
         self.account = Account.create(
@@ -340,7 +340,7 @@
                 self.apiclient, id=(
                     virtual_machine.nic[0].id + random_gen()))
             self.fail(
-                "Adding secondary IP with wrong NIC id succeeded, it shoud have failed")
+                "Adding secondary IP with wrong NIC id succeeded, it should have failed")
         except Exception as e:
             self.debug("Failed while adding secondary IP to wrong NIC")
 
diff --git a/test/integration/component/test_multiple_nic_support.py b/test/integration/component/test_multiple_nic_support.py
index d9ae681..cba1c4e 100644
--- a/test/integration/component/test_multiple_nic_support.py
+++ b/test/integration/component/test_multiple_nic_support.py
@@ -52,11 +52,11 @@
 import random
 import time
 
-class TestMulipleNicSupport(cloudstackTestCase):
+class TestMultipleNicSupport(cloudstackTestCase):
     @classmethod
     def setUpClass(cls):
         cls.testClient = super(
-            TestMulipleNicSupport,
+            TestMultipleNicSupport,
             cls).getClsTestClient()
         cls.apiclient = cls.testClient.getApiClient()
         cls.testdata = cls.testClient.getParsedTestDataConfig()
@@ -70,7 +70,7 @@
             cls.skip = True
             return
 
-        cls.logger = logging.getLogger("TestMulipleNicSupport")
+        cls.logger = logging.getLogger("TestMultipleNicSupport")
         cls.stream_handler = logging.StreamHandler()
         cls.logger.setLevel(logging.DEBUG)
         cls.logger.addHandler(cls.stream_handler)
@@ -255,7 +255,7 @@
 
     @classmethod
     def tearDownClass(self):
-        super(TestMulipleNicSupport, self).tearDownClass()
+        super(TestMultipleNicSupport, self).tearDownClass()
 
     def setUp(self):
         if self.skip:
@@ -265,7 +265,7 @@
         return
 
     def tearDown(self):
-        super(TestMulipleNicSupport, self).tearDown()
+        super(TestMultipleNicSupport, self).tearDown()
 
     def verify_network_rules(self, vm_id):
         virtual_machine = VirtualMachine.list(
diff --git a/test/integration/component/test_multiple_physical_network_creation.py b/test/integration/component/test_multiple_physical_network_creation.py
index 7f6117f..09127b9 100644
--- a/test/integration/component/test_multiple_physical_network_creation.py
+++ b/test/integration/component/test_multiple_physical_network_creation.py
@@ -39,11 +39,11 @@
 
 import logging
 
-class TestMulipleNetworkCreation(cloudstackTestCase):
+class TestMultipleNetworkCreation(cloudstackTestCase):
     @classmethod
     def setUpClass(cls):
         cls.testClient = super(
-            TestMulipleNetworkCreation,
+            TestMultipleNetworkCreation,
             cls).getClsTestClient()
         cls.apiclient = cls.testClient.getApiClient()
         cls.testdata = cls.testClient.getParsedTestDataConfig()
@@ -54,7 +54,7 @@
         cls.template = get_template(cls.apiclient, cls.zone.id)
         cls._cleanup = []
 
-        cls.logger = logging.getLogger("TestMulipleNetworkCreation")
+        cls.logger = logging.getLogger("TestMultipleNetworkCreation")
         cls.stream_handler = logging.StreamHandler()
         cls.logger.setLevel(logging.DEBUG)
         cls.logger.addHandler(cls.stream_handler)
@@ -127,7 +127,7 @@
                 allocationstate="Enabled"
             )
             # Cleanup resources used
-            super(TestMulipleNetworkCreation, cls).tearDownClass()
+            super(TestMultipleNetworkCreation, cls).tearDownClass()
         except Exception as e:
             raise Exception("Warning: Exception during cleanup : %s" % e)
         return
@@ -139,7 +139,7 @@
         return
 
     def tearDown(self):
-        super(TestMulipleNetworkCreation, self).tearDown()
+        super(TestMultipleNetworkCreation, self).tearDown()
 
     @attr(tags=["advanced"], required_hardware="false")
     def test_01_add_traffictype_for_untagged_networks(self):
diff --git a/test/integration/component/test_region_vpc.py b/test/integration/component/test_region_vpc.py
index 6866e48..0bfe112 100644
--- a/test/integration/component/test_region_vpc.py
+++ b/test/integration/component/test_region_vpc.py
@@ -490,7 +490,7 @@
                                   vpcid=vpc.id
                                   )
 
-        self.debug("Adding NetwrokACl rules to make PF and LB accessible")
+        self.debug("Adding NetworkACl rules to make PF and LB accessible")
         NetworkACL.create(
                 self.apiclient,
                 networkid=network.id,
diff --git a/test/integration/component/test_shared_networks.py b/test/integration/component/test_shared_networks.py
index c7e2656..be6d663 100644
--- a/test/integration/component/test_shared_networks.py
+++ b/test/integration/component/test_shared_networks.py
@@ -584,7 +584,7 @@
         #  6. No checks reqd
         #  7. a. listVirtualMachines should show both VMs in running state
         #     in the user account and the admin account
-        #     b. VM's IPs shoud be in the range of the shared network ip ranges
+        #     b. VM's IPs should be in the range of the shared network ip ranges
 
         # Create admin account
         self.admin_account = Account.create(
@@ -1974,7 +1974,7 @@
         #     enabled offering
         #  4. listPhysicalNetworks should return at least one active
         #     physical network
-        #  5. network creation shoud PASS
+        #  5. network creation should PASS
         #  6. network creation should FAIL since VLAN is already used by
         #     previously created network
 
diff --git a/test/integration/component/test_snapshot_copy.py b/test/integration/component/test_snapshot_copy.py
new file mode 100644
index 0000000..7b9531a
--- /dev/null
+++ b/test/integration/component/test_snapshot_copy.py
@@ -0,0 +1,351 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 volume snapshot copy functionality
+"""
+# 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)
+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
+# Import System modules
+import math
+
+
+_multiprocess_shared_ = True
+
+
+class TestSnapshotCopy(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestSnapshotCopy, 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._cleanup = []
+        cls.logger = logging.getLogger('TestSnapshotCopy')
+        cls.testsNotSupported = False
+        cls.zones = Zone.list(cls.apiclient)
+        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:
+            self.logger.info("Unsupported")
+            return
+
+        cls.additional_zone = None
+        for z in enabled_core_zones:
+            if z.id != cls.zone.id:
+                cls.additional_zone = z
+
+        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)
+
+        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()
+
+    def create_snapshot(self, apiclient, zoneids):
+        cmd = createSnapshot.createSnapshotCmd()
+        cmd.volumeid = self.volume.id
+        cmd.account = self.account.name
+        cmd.domainid = self.account.domainid
+        if zoneids:
+            cmd.zoneids = zoneids
+        snapshot = Snapshot(apiclient.createSnapshot(cmd).__dict__)
+        self.cleanup.append(snapshot)
+        return snapshot
+
+    def delete_snapshot(self, apiclient, snapshot_id, zone_id=None):
+        cmd = deleteSnapshot.deleteSnapshotCmd()
+        cmd.id = snapshot_id
+        if zone_id:
+            cmd.zoneid = zone_id
+        apiclient.deleteSnapshot(cmd)
+
+    def copy_snapshot(self, apiclient, snapshot_id, zone_ids, source_zone_id=None):
+        cmd = copySnapshot.copySnapshotCmd()
+        cmd.id = snapshot_id
+        cmd.destzoneids = zone_ids
+        if source_zone_id:
+            cmd.sourcezoneid = source_zone_id
+        return apiclient.copySnapshot(cmd)
+
+    def create_snapshot_volume(self, apiclient, snapshot_id, zone_id=None, disk_offering_id=None):
+        cmd = createVolume.createVolumeCmd()
+        cmd.name = "-".join(["VolumeFromSnap", random_gen()])
+        cmd.snapshotid = snapshot_id
+        if zone_id:
+            cmd.zoneid = zone_id
+        if disk_offering_id:
+            cmd.diskofferingid = disk_offering_id
+        volume_from_snapshot = Volume(apiclient.createVolume(cmd).__dict__)
+        self.cleanup.append(volume_from_snapshot)
+        return volume_from_snapshot
+
+    def create_snapshot_template(self, 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:
+            # Find OSTypeId from Os type
+            sub_cmd = listOsTypes.listOsTypesCmd()
+            sub_cmd.description = services["ostype"]
+            ostypes = apiclient.listOsTypes(sub_cmd)
+
+            if not isinstance(ostypes, list):
+                self.fail("Unable to find Ostype id with desc: %s" %
+                    services["ostype"])
+            cmd.ostypeid = ostypes[0].id
+        else:
+            self.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:
+            self.fail("Unable to find created template with name %s" % name)
+        template = Template(templates[0].__dict__)
+        self.cleanup.append(template)
+        return template
+
+    def verify_snapshot_copies(self, snapshot_id, zone_ids):
+        snapshot_entries = Snapshot.list(self.userapiclient, id=snapshot_id, showunique=False, locationtype="Secondary")
+        if not isinstance(snapshot_entries, list):
+            self.fail("Unable to list snapshot for multiple zones")
+        elif len(snapshot_entries) != len(zone_ids):
+            self.fail("Undesired list snapshot size for multiple zones")
+        for zone_id in zone_ids:
+            zone_found = False
+            for entry in snapshot_entries:
+                if entry.zoneid == zone_id:
+                    zone_found = True
+                    break
+            if zone_found == False:
+                self.fail("Unable to find snapshot entry for the zone ID: %s" % zone_id)
+
+    @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 zones
+        """
+        # Validate the following:
+        # 1. Take snapshot in multiple zone
+        # 2. Verify
+
+        snapshot = self.create_snapshot(self.userapiclient, [str(self.additional_zone.id)])
+        self.snapshot_id = snapshot.id
+        self.verify_snapshot_copies(self.snapshot_id, [self.zone.id, self.additional_zone.id])
+        return
+
+    @skipTestIf("testsNotSupported")
+    @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_02_copy_snapshot_multi_zone(self):
+        """Test to take volume snapshot in a zone and then copy
+        """
+        # Validate the following:
+        # 1. Take snapshot in the native zone
+        # 2. Copy snapshot in the additional zone
+        # 3. Verify
+
+        snapshot = self.create_snapshot(self.userapiclient, None)
+        self.snapshot_id = snapshot.id
+        self.copy_snapshot(self.userapiclient, self.snapshot_id, [str(self.additional_zone.id)], self.zone.id)
+        self.verify_snapshot_copies(self.snapshot_id, [self.zone.id, self.additional_zone.id])
+        return
+
+    @skipTestIf("testsNotSupported")
+    @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_03_take_snapshot_multi_zone_delete_single_zone(self):
+        """Test to take volume snapshot in multiple zones and delete from one zone
+        """
+        # Validate the following:
+        # 1. Take snapshot in multiple zone
+        # 2. Verify
+        # 3. Delete from one zone
+        # 4. Verify
+
+        snapshot = self.create_snapshot(self.userapiclient, [str(self.additional_zone.id)])
+        self.snapshot_id = snapshot.id
+        self.verify_snapshot_copies(self.snapshot_id, [self.zone.id, self.additional_zone.id])
+        self.delete_snapshot(self.userapiclient, self.snapshot_id, self.zone.id)
+        self.verify_snapshot_copies(self.snapshot_id, [self.additional_zone.id])
+        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 in a zone, copy in another zone and delete for all
+        """
+        # Validate the following:
+        # 1. Take snapshot in the native zone
+        # 2. Copy snapshot in the additional zone
+        # 3. Verify
+        # 4. Delete for all zones
+        # 5. Verify
+
+        snapshot = self.create_snapshot(self.userapiclient, None)
+        self.snapshot_id = snapshot.id
+        self.copy_snapshot(self.userapiclient, self.snapshot_id, [str(self.additional_zone.id)], self.zone.id)
+        self.verify_snapshot_copies(self.snapshot_id, [self.zone.id, self.additional_zone.id])
+        self.delete_snapshot(self.userapiclient, self.snapshot_id)
+        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")
+        self.cleanup.remove(snapshot)
+        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 in multiple zones and create a volume in one of the additional zones
+        """
+        # Validate the following:
+        # 1. Take snapshot in multiple zone
+        # 2. Verify
+        # 3. Create volume in the additional zone
+        # 4. Verify volume zone
+
+        snapshot = self.create_snapshot(self.userapiclient, [str(self.additional_zone.id)])
+        self.snapshot_id = snapshot.id
+        self.verify_snapshot_copies(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 = self.create_snapshot_volume(self.userapiclient, self.snapshot_id, self.additional_zone.id, disk_offering_id)
+        if self.additional_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 zones and create a volume in one of the additional zones
+        """
+        # Validate the following:
+        # 1. Take snapshot in multiple zone
+        # 2. Verify
+        # 3. Create template in the additional zone
+        # 4. Verify template zone
+
+        snapshot = self.create_snapshot(self.userapiclient, [str(self.additional_zone.id)])
+        self.snapshot_id = snapshot.id
+        self.verify_snapshot_copies(self.snapshot_id, [self.zone.id, self.additional_zone.id])
+        self.template = self.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")
+        return
diff --git a/test/integration/component/test_vpc.py b/test/integration/component/test_vpc.py
index 2587d78..5870769 100644
--- a/test/integration/component/test_vpc.py
+++ b/test/integration/component/test_vpc.py
@@ -1004,7 +1004,7 @@
             vpcid=vpc.id
         )
 
-        self.debug("Adding NetwrokACl rules to make NAT rule accessible")
+        self.debug("Adding NetworkACl rules to make NAT rule accessible")
         NetworkACL.create(
             self.apiclient,
             networkid=network_1.id,
@@ -1090,7 +1090,7 @@
             vm_3.name, vm_4.name))
         lb_rule.assign(self.apiclient, [vm_3, vm_4])
 
-        self.debug("Adding NetwrokACl rules to make PF and LB accessible")
+        self.debug("Adding NetworkACl rules to make PF and LB accessible")
         NetworkACL.create(
             self.apiclient,
             networkid=network_2.id,
@@ -1350,7 +1350,7 @@
             vpcid=vpc.id
         )
 
-        self.debug("Adding NetwrokACl rules to make NAT rule accessible")
+        self.debug("Adding NetworkACl rules to make NAT rule accessible")
         NetworkACL.create(
             self.apiclient,
             networkid=network_1.id,
@@ -1436,7 +1436,7 @@
             vm_3.name, vm_4.name))
         lb_rule.assign(self.apiclient, [vm_3, vm_4])
 
-        self.debug("Adding NetwrokACl rules to make PF and LB accessible")
+        self.debug("Adding NetworkACl rules to make PF and LB accessible")
         NetworkACL.create(
             self.apiclient,
             networkid=network_2.id,
diff --git a/test/integration/component/test_vpc_distributed_routing_offering.py b/test/integration/component/test_vpc_distributed_routing_offering.py
index baa8f9b..97fcb80 100644
--- a/test/integration/component/test_vpc_distributed_routing_offering.py
+++ b/test/integration/component/test_vpc_distributed_routing_offering.py
@@ -481,7 +481,7 @@
                                   vpcid=vpc.id
                                   )
 
-        self.debug("Adding NetwrokACl rules to make PF and LB accessible")
+        self.debug("Adding NetworkACl rules to make PF and LB accessible")
         NetworkACL.create(
                 self.apiclient,
                 networkid=network.id,
diff --git a/test/integration/component/test_vpc_network.py b/test/integration/component/test_vpc_network.py
index bf3f368..db436bb 100644
--- a/test/integration/component/test_vpc_network.py
+++ b/test/integration/component/test_vpc_network.py
@@ -1001,23 +1001,23 @@
         # 1. Create a network offering with guest type=Isolated that has all
         #    supported Services(Vpn,dhcpdns,UserData, SourceNat,Static NAT,LB
         #    and PF,LB,NetworkAcl ) provided by VPCVR and conserve mode is ON
-        # 2. Create offering fails since Conserve mode ON isn't allowed within
-        #    VPC
+        # 2. Create offering should succeed since Conserve mode ON is allowed within
+        #    VPC since https://github.com/apache/cloudstack/pull/8309
         # 3. Repeat test for offering which has Netscaler as external LB
         #    provider
         """
 
         self.debug("Creating network offering with conserve mode = ON")
 
-        with self.assertRaises(Exception):
+        try:
             nw = NetworkOffering.create(
                 self.apiclient,
                 self.services[value],
                 conservemode=True
             )
             self.cleanup.append(nw)
-        self.debug(
-            "Network creation failed as VPC support nw with conserve mode OFF")
+        except Exception as e:
+            self.warn("Network creation failed in VPC with conserve mode ON")
         return
 
 
@@ -1864,7 +1864,7 @@
             "List public Ip for network should list the Ip addr"
         )
 
-        self.debug("Adding NetwrokACl rules to make PF and LB accessible")
+        self.debug("Adding NetworkACl rules to make PF and LB accessible")
         nw_acl = NetworkACL.create(
             self.apiclient,
             networkid=network_1.id,
@@ -2023,7 +2023,7 @@
         )
         self.cleanup.append(nat_rule)
 
-        self.debug("Adding NetwrokACl rules to make NAT rule accessible")
+        self.debug("Adding NetworkACl rules to make NAT rule accessible")
         nat_acl = NetworkACL.create(
             self.apiclient,
             networkid=network_1.id,
diff --git a/test/integration/component/test_vpc_offerings.py b/test/integration/component/test_vpc_offerings.py
index fbfb61f..25206cf 100644
--- a/test/integration/component/test_vpc_offerings.py
+++ b/test/integration/component/test_vpc_offerings.py
@@ -416,7 +416,7 @@
         )
         self.cleanup.append(nat_rule)
 
-        self.logger.debug("Adding NetwrokACl rules to make PF and LB accessible")
+        self.logger.debug("Adding NetworkACl rules to make PF and LB accessible")
         networkacl_1 = NetworkACL.create(
             self.apiclient,
             networkid=network.id,
diff --git a/test/integration/component/test_vpc_vm_life_cycle.py b/test/integration/component/test_vpc_vm_life_cycle.py
index 3d41dca..abe6d19 100644
--- a/test/integration/component/test_vpc_vm_life_cycle.py
+++ b/test/integration/component/test_vpc_vm_life_cycle.py
@@ -627,7 +627,7 @@
         """
 
         # Validate the following
-        # 1. Destory the virtual machines.
+        # 1. Destroy the virtual machines.
         # 2. Rules should be still configured on virtual router.
         # 3. Recover the virtual machines.
         # 4. Vm should be in stopped state. State both the instances
@@ -1751,7 +1751,7 @@
         """
 
         # Validate the following
-        # 1. Destory the virtual machines.
+        # 1. Destroy the virtual machines.
         # 2. Rules should be still configured on virtual router.
         # 3. Recover the virtual machines.
         # 4. Vm should be in stopped state. State both the instances
@@ -2466,7 +2466,7 @@
         """
 
         # Validate the following
-        # 1. Destory the virtual machines.
+        # 1. Destroy the virtual machines.
         # 2. Rules should be still configured on virtual router.
         # 3. Recover the virtual machines.
         # 4. Vm should be in stopped state. State both the instances
diff --git a/test/integration/component/test_vpc_vms_deployment.py b/test/integration/component/test_vpc_vms_deployment.py
index ded3374..4d3f934 100644
--- a/test/integration/component/test_vpc_vms_deployment.py
+++ b/test/integration/component/test_vpc_vms_deployment.py
@@ -2046,7 +2046,7 @@
             self.fail("Failed to enable static NAT on IP: %s - %s" % (
                                         public_ip_4.ipaddress.ipaddress, e))
 
-        self.debug("Adding NetwrokACl rules to make NAT rule accessible with network %s" % network_1.id)
+        self.debug("Adding NetworkACl rules to make NAT rule accessible with network %s" % network_1.id)
         NetworkACL.create(
                                          self.apiclient,
                                          networkid=network_1.id,
diff --git a/test/integration/plugins/storpool/TestTagsOnStorPool.py b/test/integration/plugins/storpool/TestTagsOnStorPool.py
index 6d13e20..a66fb35 100644
--- a/test/integration/plugins/storpool/TestTagsOnStorPool.py
+++ b/test/integration/plugins/storpool/TestTagsOnStorPool.py
@@ -218,6 +218,19 @@
             hypervisor=cls.hypervisor,
             rootdisksize=10
         )
+        cls.virtual_machine3 = VirtualMachine.create(
+            cls.apiclient,
+            {"name":"StorPool-%s" % uuid.uuid4() },
+            zoneid=cls.zone.id,
+            templateid=template.id,
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            serviceofferingid=cls.service_offering.id,
+            hypervisor=cls.hypervisor,
+            diskofferingid=cls.disk_offerings.id,
+            size=2,
+            rootdisksize=10
+        )
         cls.template = template
         cls.random_data_0 = random_gen(size=100)
         cls.test_dir = "/tmp"
@@ -270,7 +283,7 @@
             virtualmachineid = self.virtual_machine.id, listall=True
             )
 
-        self.vc_policy_tags(volumes, vm_tags, vm)
+        self.vc_policy_tags(volumes, vm_tags, vm, True)
 
 
     @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
@@ -310,7 +323,7 @@
         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)
+        self.vc_policy_tags(volumes, vm_tags, vm, True)
 
 
         self.assertEqual(volume_attached.id, self.volume.id, "Is not the same volume ")
@@ -442,7 +455,7 @@
         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)
+        self.vc_policy_tags(volumes, vm_tags, vm, True)
 
         self.assertEqual(
             self.random_data_0,
@@ -490,18 +503,17 @@
     def test_06_remove_vcpolicy_tag_when_disk_detached(self):
         """ Test remove vc-policy tag to disk detached from VM"""
         time.sleep(60)
-        volume_detached = self.virtual_machine.detach_volume(
-                self.apiclient,
-                self.volume_2
-                )
         vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True)
         vm_tags = vm[0].tags
         volumes = list_volumes(
             self.apiclient,
-            virtualmachineid = self.virtual_machine.id, listall=True
+            id= self.volume_2.id, listall=True,
             )
-
-        self.vc_policy_tags( volumes, vm_tags, vm)
+        volume_detached = self.virtual_machine.detach_volume(
+                self.apiclient,
+                self.volume_2
+                )
+        self.vc_policy_tags( volumes, vm_tags, vm, False)
 
     @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
     def test_07_delete_vcpolicy_tag(self):
@@ -538,7 +550,7 @@
             virtualmachineid = self.virtual_machine2.id, listall=True,
             type = "ROOT"
             )
-        self.vc_policy_tags(volume, vm_tags, vm)
+        self.vc_policy_tags(volume, vm_tags, vm, True)
 
         snapshot = Snapshot.create(
             self.apiclient,
@@ -560,11 +572,36 @@
         vm_tags = vm[0].tags
 
         vol = list_volumes(self.apiclient, id = snapshot.volumeid, listall=True)
-        self.vc_policy_tags(vol, vm_tags, vm)
+        self.vc_policy_tags(vol, vm_tags, vm, True)
 
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_09_remove_vm_tags_on_datadisks_attached_to_destroyed_vm(self):
+        tag = Tag.create(
+            self.apiclient,
+            resourceIds=self.virtual_machine3.id,
+            resourceType='UserVm',
+            tags={'vc-policy': 'testing_vc-policy'}
+        )
+        vm = list_virtual_machines(self.apiclient,id = self.virtual_machine3.id, listall=True)
+        vm_tags = vm[0].tags
+        volumes = list_volumes(
+            self.apiclient,
+            virtualmachineid = self.virtual_machine3.id, listall=True
+            )
 
-    def vc_policy_tags(self, volumes, vm_tags, vm):
-        flag = False
+        self.vc_policy_tags(volumes, vm_tags, vm, True)
+
+        volumes = list_volumes(
+            self.apiclient,
+            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)
+
+    def vc_policy_tags(self, volumes, vm_tags, vm, should_tags_exists=None):
+        vcPolicyTag = False
+        cvmTag = False
         for v in volumes:
             name = v.path.split("/")[3]
             spvolume = self.spapi.volumeList(volumeName="~" + name)
@@ -572,9 +609,15 @@
             for t in tags:
                 for vm_tag in vm_tags:
                     if t == vm_tag.key:
-                        flag = True
+                        vcPolicyTag = 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)
-        self.assertTrue(flag, "There aren't volumes with vm tags")
+        if should_tags_exists:
+            self.assertTrue(vcPolicyTag, "There aren't volumes with vm tags")
+            self.assertTrue(cvmTag, "There aren't volumes with vm tags")
+        else:
+            self.assertFalse(vcPolicyTag, "The tags should be removed")
+            self.assertFalse(cvmTag, "The tags should be removed")
diff --git a/test/integration/smoke/test_annotations.py b/test/integration/smoke/test_annotations.py
index 812b3dc..e527b96 100644
--- a/test/integration/smoke/test_annotations.py
+++ b/test/integration/smoke/test_annotations.py
@@ -83,6 +83,7 @@
         cls.host = list_hosts(cls.apiclient,
                                zoneid=cls.zone.id,
                                type='Routing')[0]
+        cls.mgmt_server = list_mgmt_servers(cls.apiclient)[0]
 
     @classmethod
     def tearDownClass(cls):
@@ -136,6 +137,12 @@
         self.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation1")
 
     @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_01_add_ms_annotation(self):
+        """Testing the addAnnotations API ability to add an annoatation per management server"""
+        self.addAnnotation("mgmt-server-annotation1", self.mgmt_server.id, "MANAGEMENT_SERVER")
+        self.assertEqual(self.added_annotations[-1].annotation.annotation, "mgmt-server-annotation1")
+
+    @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
     def test_02_add_multiple_annotations(self):
         """Testing the addAnnotations API ability to add an annoatation per host
         when there are annotations already.
diff --git a/test/integration/smoke/test_bucket.py b/test/integration/smoke/test_bucket.py
new file mode 100644
index 0000000..7d92ea9
--- /dev/null
+++ b/test/integration/smoke/test_bucket.py
@@ -0,0 +1,111 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 Bucket Operations"""
+
+#Import Local Modules
+from marvin.cloudstackTestCase import *
+from nose.plugins.attrib import attr
+from marvin.lib.base import (ObjectStoragePool, Bucket)
+from marvin.lib.utils import (cleanup_resources)
+
+_multiprocess_shared_ = True
+
+class TestObjectStore(cloudstackTestCase):
+
+    def setUp(self):
+        self.services = self.testClient.getParsedTestDataConfig()
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+        return
+
+    def tearDown(self):
+        try:
+            #Clean up, terminate the created resources
+            cleanup_resources(self.apiclient, self.cleanup)
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return
+
+    @attr(tags=["smoke"], required_hardware="false")
+    def test_01_create_bucket(self):
+        """Test to create bucket in object store
+
+        """
+
+        object_store = ObjectStoragePool.create(
+            self.apiclient,
+            "testOS-9",
+            "http://192.168.0.1",
+            "Simulator",
+            None
+        )
+
+        self.debug("Created Object Store with ID: %s" % object_store.id)
+
+        bucket = Bucket.create(
+            self.apiclient,
+            "mybucket",
+            object_store.id
+        )
+
+        list_buckets_response = Bucket.list(
+            self.apiclient,
+            id=bucket.id
+        )
+
+        self.assertNotEqual(
+            len(list_buckets_response),
+            0,
+            "Check List Bucket response"
+        )
+
+        bucket_response = list_buckets_response[0]
+        self.assertEqual(
+            object_store.id,
+            bucket_response.objectstorageid,
+            "Check object store id of the created Bucket"
+        )
+        self.assertEqual(
+            "mybucket",
+            bucket_response.name,
+            "Check Name of the created Bucket"
+        )
+
+        bucket.update(
+            self.apiclient,
+            quota=100
+        )
+
+        list_buckets_response_updated = Bucket.list(
+            self.apiclient,
+            id=bucket.id
+        )
+
+        bucket_response_updated = list_buckets_response_updated[0]
+
+        self.assertEqual(
+            100,
+            bucket_response_updated.quota,
+            "Check quota of the updated bucket"
+        )
+
+        self.cleanup.append(bucket)
+        self.cleanup.append(object_store)
+
+        return
diff --git a/test/integration/smoke/test_cluster_drs.py b/test/integration/smoke/test_cluster_drs.py
new file mode 100644
index 0000000..4db6654
--- /dev/null
+++ b/test/integration/smoke/test_cluster_drs.py
@@ -0,0 +1,267 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""
+Tests DRS on a cluster
+"""
+
+import logging
+import time
+
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import (Cluster, Configurations, Host, Network, NetworkOffering, ServiceOffering, VirtualMachine,
+                             Zone)
+from marvin.lib.common import (get_domain, get_zone, get_template)
+from marvin.lib.utils import wait_until
+from marvin import jsonHelper
+from nose.plugins.attrib import attr
+
+
+class TestClusterDRS(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestClusterDRS, cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+        cls.services = cls.testClient.getParsedTestDataConfig()
+
+        zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.zone = Zone(zone.__dict__)
+        cls.template = get_template(cls.apiclient, cls.zone.id)
+        cls._cleanup = []
+
+        cls.logger = logging.getLogger("TestClusterDRS")
+        cls.stream_handler = logging.StreamHandler()
+        cls.logger.setLevel(logging.DEBUG)
+        cls.logger.addHandler(cls.stream_handler)
+
+        cls.skipTests = False
+        clusters = Cluster.list(cls.apiclient, zoneid=cls.zone.id, allocationstate='Enabled')
+
+        if not clusters or not isinstance(clusters, list) or len(clusters) < 1:
+            cls.logger.debug("This test requires at least 1 (Up and Enabled) cluster in the zone")
+            cls.skipTests = True
+            return
+
+        for cluster in clusters:
+            cls.hosts = Host.list(cls.apiclient, zoneid=cls.zone.id, clusterid=cluster.id, state='Up',
+                                  resourcestate='Enabled')
+            if not cls.hosts or not isinstance(cls.hosts, list) or len(cls.hosts) < 2:
+                cls.logger.debug("This test requires at least two (Up and Enabled) hosts in the zone")
+                cls.skipTests = True
+                return
+            else:
+                cls.cluster = Cluster(jsonHelper.jsonDump.dump(cluster))
+                break
+
+        cls.domain = get_domain(cls.apiclient)
+
+        # 1. Create large service offering
+        cls.service_offering = ServiceOffering.create(cls.apiclient, cls.services["service_offerings"]["large"])
+        cls._cleanup.append(cls.service_offering)
+
+        # 2. Create a network
+        cls.services["network"]["name"] = "Test Network"
+        cls.network_offering = NetworkOffering.create(
+            cls.apiclient,
+            cls.services["l2-network_offering"]
+        )
+        cls._cleanup.append(cls.network_offering)
+        NetworkOffering.update(
+            cls.network_offering,
+            cls.apiclient,
+            id=cls.network_offering.id,
+            state="enabled"
+        )
+
+        cls.network = Network.create(
+            cls.apiclient,
+            cls.services["l2-network"],
+            networkofferingid=cls.network_offering.id,
+            zoneid=cls.zone.id,
+            accountid="admin",
+            domainid=cls.domain.id,
+        )
+        cls._cleanup.append(cls.network)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestClusterDRS, cls).tearDownClass()
+
+    def setUp(self):
+        if self.skipTests:
+            self.skipTest("This test requires at least two (Up and Enabled) hosts in the zone")
+        self.apiclient = self.testClient.getApiClient()
+        self.cleanup = []
+
+    def tearDown(self):
+        super(TestClusterDRS, self).tearDown()
+
+    @classmethod
+    def get_vm_host_id(cls, vm_id):
+        list_vms = VirtualMachine.list(cls.apiclient, id=vm_id)
+        vm = list_vms[0]
+        return vm.hostid
+
+    def wait_for_vm_start(self, vm):
+        """ Wait until vm is Running """
+        def check_vm_state():
+            vms = VirtualMachine.list(
+                self.apiclient,
+                id=vm.id,
+                listall=True
+            )
+            if isinstance(vms, list):
+                if vms[0].state == 'Running':
+                    return True, vms[0].state
+            return False, vms[0].state
+
+        res = wait_until(10, 30, check_vm_state)
+        if not res:
+            raise Exception("Failed to wait for VM %s (%s) to be Running" % (vm.name, vm.id))
+        return res
+
+    def wait_for_plan_completion(self, plan):
+        """ Wait until plan is completed """
+        def check_plan_status():
+            plans = self.cluster.listDrsPlans(self.apiclient, id=plan.id)
+            if isinstance(plans, list):
+                if plans[0].status == 'COMPLETED':
+                    return True, plans[0].status
+            return False, plans[0].status
+
+        res = wait_until(10, 30, check_plan_status)
+        if not res:
+            raise Exception("Failed to wait for completion of plan %s" % (plan.id))
+        return res
+
+    def get_migrations(self):
+        """ Wait until migrations are generated. Sometimes it takes a little bit of time for stats to get updated. We generate migrations
+        until we get at least one migration """
+        def generate_migrations():
+            drs_plan = self.cluster.generateDrsPlan(self.apiclient, migrations=4)
+            if len(drs_plan["migrations"]) > 0:
+                return True, drs_plan["migrations"]
+            return False, drs_plan["migrations"]
+
+        res, migrations = wait_until(10, 30, generate_migrations)
+        if not res:
+            raise Exception("Failed to generate drs migrations")
+        return migrations
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_01_condensed_drs_algorithm(self):
+        """ Verify DRS algorithm - condensed"""
+        # 1. Deploy vm-1 on host 1
+        # 2. Deploy vm-2 on host 2
+        # 3. Execute DRS to move all VMs on the same host
+        self.logger.debug("=== Running test_01_condensed_drs_algorithm ===")
+
+        # 1. Deploy vm-1 on host 1
+        self.services["virtual_machine"]["name"] = "virtual-machine-1"
+        self.services["virtual_machine"]["displayname"] = "virtual-machine-1"
+        self.virtual_machine_1 = VirtualMachine.create(self.apiclient, self.services["virtual_machine"],
+                                                       serviceofferingid=self.service_offering.id,
+                                                       templateid=self.template.id, zoneid=self.zone.id,
+                                                       networkids=self.network.id, hostid=self.hosts[0].id)
+        self.cleanup.append(self.virtual_machine_1)
+        vm_1_host_id = self.get_vm_host_id(self.virtual_machine_1.id)
+
+        # 2. Deploy vm-2 on host 2
+        self.services["virtual_machine"]["name"] = "virtual-machine-2"
+        self.services["virtual_machine"]["displayname"] = "virtual-machine-2"
+        self.virtual_machine_2 = VirtualMachine.create(self.apiclient, self.services["virtual_machine"],
+                                                       serviceofferingid=self.service_offering.id,
+                                                       templateid=self.template.id, zoneid=self.zone.id,
+                                                       networkids=self.network.id, hostid=self.hosts[1].id)
+        vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
+        self.cleanup.append(self.virtual_machine_2)
+
+        self.assertNotEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on different hosts")
+        self.wait_for_vm_start(self.virtual_machine_1)
+        self.wait_for_vm_start(self.virtual_machine_2)
+
+        # 3. Generate & execute DRS to move all VMs on the same host
+        Configurations.update(self.apiclient, "drs.algorithm", "condensed", clusterid=self.cluster.id)
+        Configurations.update(self.apiclient, "drs.imbalance", "1.0", clusterid=self.cluster.id)
+
+        migrations = self.get_migrations()
+        vm_to_dest_host_map = {
+            migration["virtualmachineid"]: migration["destinationhostid"] for migration in migrations
+        }
+
+        self.assertEqual(len(vm_to_dest_host_map), 1, msg="DRS plan should have 1 migrations")
+
+        executed_plan = self.cluster.executeDrsPlan(self.apiclient, vm_to_dest_host_map)
+        self.wait_for_plan_completion(executed_plan)
+
+        vm_1_host_id = self.get_vm_host_id(self.virtual_machine_1.id)
+        vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
+
+        self.assertEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on the same host")
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_02_balanced_drs_algorithm(self):
+        """ Verify DRS algorithm - balanced"""
+
+        # 1. Deploy vm-1 on host 1
+        # 2. Deploy vm-2 on host 2
+        # 3. Execute DRS to move all VMs on different hosts
+
+        self.logger.debug("=== Running test_02_balanced_drs_algorithm ===")
+        # 1. Deploy vm-1 on host 1
+        self.services["virtual_machine"]["name"] = "virtual-machine-1"
+        self.services["virtual_machine"]["displayname"] = "virtual-machine-1"
+        self.virtual_machine_1 = VirtualMachine.create(self.apiclient, self.services["virtual_machine"],
+                                                       serviceofferingid=self.service_offering.id,
+                                                       templateid=self.template.id, zoneid=self.zone.id,
+                                                       networkids=self.network.id, hostid=self.hosts[0].id)
+        self.cleanup.append(self.virtual_machine_1)
+        vm_1_host_id = self.get_vm_host_id(self.virtual_machine_1.id)
+
+        # 2. Deploy vm-2 on host 1
+        self.services["virtual_machine"]["name"] = "virtual-machine-2"
+        self.services["virtual_machine"]["displayname"] = "virtual-machine-2"
+        self.virtual_machine_2 = VirtualMachine.create(self.apiclient, self.services["virtual_machine"],
+                                                       serviceofferingid=self.service_offering.id,
+                                                       templateid=self.template.id, zoneid=self.zone.id,
+                                                       networkids=self.network.id, hostid=self.hosts[0].id)
+        vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
+        self.cleanup.append(self.virtual_machine_2)
+
+        self.assertEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on same hosts")
+        self.wait_for_vm_start(self.virtual_machine_1)
+        self.wait_for_vm_start(self.virtual_machine_2)
+
+        # 3. Execute DRS to move all VMs on different hosts
+        Configurations.update(self.apiclient, "drs.algorithm", "balanced", clusterid=self.cluster.id)
+        Configurations.update(self.apiclient, "drs.imbalance", "1.0", clusterid=self.cluster.id)
+
+        migrations = self.get_migrations()
+        vm_to_dest_host_map = {
+            migration["virtualmachineid"]: migration["destinationhostid"] for migration in migrations
+        }
+
+        self.assertEqual(len(vm_to_dest_host_map), 1, msg="DRS plan should have 1 migrations")
+
+        executed_plan = self.cluster.executeDrsPlan(self.apiclient, vm_to_dest_host_map)
+        self.wait_for_plan_completion(executed_plan)
+
+        vm_1_host_id = self.get_vm_host_id(self.virtual_machine_1.id)
+        vm_2_host_id = self.get_vm_host_id(self.virtual_machine_2.id)
+
+        self.assertNotEqual(vm_1_host_id, vm_2_host_id, msg="Both VMs should be on different hosts")
diff --git a/test/integration/smoke/test_global_acls.py b/test/integration/smoke/test_global_acls.py
new file mode 100644
index 0000000..47db858
--- /dev/null
+++ b/test/integration/smoke/test_global_acls.py
@@ -0,0 +1,245 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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.utils import cleanup_resources
+from marvin.lib.base import (Network, NetworkACLList, NetworkOffering, VpcOffering, VPC, NetworkACL)
+from marvin.lib.common import (get_domain, get_zone)
+from nose.plugins.attrib import attr
+from marvin.cloudstackException import CloudstackAPIException
+
+
+class Services:
+    """Test Global ACLs
+    """
+
+    def __init__(self):
+        self.services = {
+            "root_domain": {
+                "name": "ROOT",
+            },
+            "domain": {
+                "name": "Domain",
+            },
+            "user": {
+                "username": "user",
+                "roletype": 0,
+            },
+            "domain_admin": {
+                "username": "Domain admin",
+                "roletype": 2,
+            },
+            "root_admin": {
+                "username": "Root admin",
+                "roletype": 1,
+            },
+            "vpc": {
+                "name": "vpc-networkacl",
+                "displaytext": "vpc-networkacl",
+                "cidr": "10.1.1.0/24",
+            },
+            "vpcnetwork": {
+                "name": "vpcnetwork",
+                "displaytext": "vpcnetwork",
+            },
+            "rule": {
+                "protocol": "all",
+                "traffictype": "ingress",
+            }
+        }
+
+
+class TestGlobalACLs(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestGlobalACLs, cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+
+        cls.services = Services().services
+        cls.domain = get_domain(cls.apiclient)
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        return
+
+    def setUp(self):
+        self.user_apiclient = self.testClient.getUserApiClient(self.services["user"]["username"],
+                                                               self.services["domain"]["name"],
+                                                               self.services["user"]["roletype"])
+
+        self.domain_admin_apiclient = self.testClient.getUserApiClient(self.services["domain_admin"]["username"],
+                                                                       self.services["domain"]["name"],
+                                                                       self.services["domain_admin"]["roletype"])
+
+        self.admin_apiclient = self.testClient.getUserApiClient(self.services["root_admin"]["username"],
+                                                                self.services["root_domain"]["name"],
+                                                                self.services["root_admin"]["roletype"])
+
+        self.cleanup = []
+        return
+
+    def tearDown(self):
+        super(TestGlobalACLs, self).tearDown()
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_create_global_acl(self):
+        """ Test create global ACL as a normal user, domain admin and root admin users.
+        """
+
+        self.debug("Creating ACL list as a normal user, should raise exception.")
+        self.assertRaisesRegex(CloudstackAPIException, "Only Root Admin can create global ACLs.",
+                               NetworkACLList.create, apiclient=self.user_apiclient, services={},
+                               name="acl", description="acl")
+
+        self.debug("Creating ACL list as a domain admin, should raise exception.")
+        self.assertRaisesRegex(CloudstackAPIException, "Only Root Admin can create global ACLs.",
+                               NetworkACLList.create, apiclient=self.domain_admin_apiclient, services={},
+                               name="acl", description="acl")
+
+        self.debug("Creating ACL list as a root admin, should work.")
+        acl = NetworkACLList.create(apiclient=self.admin_apiclient, services={}, name="acl", description="acl")
+        self.cleanup.append(acl)
+        self.assertIsNotNone(acl, "A root admin user should be able to create a global ACL.")
+
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_replace_acl_of_network(self):
+        """ Test to replace ACL of a VPC as a normal user, domain admin and root admin users.
+        """
+        # Get network offering
+        networkOffering = NetworkOffering.list(self.apiclient, name="DefaultIsolatedNetworkOfferingForVpcNetworks")
+        self.assertTrue(networkOffering is not None and len(networkOffering) > 0, "No VPC network offering")
+
+        # Getting VPC offering
+        vpcOffering = VpcOffering.list(self.apiclient, name="Default VPC offering")
+        self.assertTrue(vpcOffering is not None and len(vpcOffering) > 0, "No VPC offerings found")
+
+        # Creating VPC
+        vpc = VPC.create(
+            apiclient=self.apiclient,
+            services=self.services["vpc"],
+            networkDomain="vpc.networkacl",
+            vpcofferingid=vpcOffering[0].id,
+            zoneid=self.zone.id,
+            domainid=self.domain.id
+        )
+        self.cleanup.append(vpc)
+        self.assertTrue(vpc is not None, "VPC creation failed")
+
+        # Creating ACL list
+        acl = NetworkACLList.create(apiclient=self.apiclient, services={}, name="acl", description="acl")
+
+        # Creating tier on VPC with ACL list
+        network = Network.create(
+            apiclient=self.apiclient,
+            services=self.services["vpcnetwork"],
+            accountid="Admin",
+            domainid=self.domain.id,
+            networkofferingid=networkOffering[0].id,
+            zoneid=self.zone.id,
+            vpcid=vpc.id,
+            aclid=acl.id,
+            gateway="10.1.1.1",
+            netmask="255.255.255.192"
+        )
+        self.cleanup.append(network)
+
+        # User should be able to replace ACL
+        network.replaceACLList(apiclient=self.user_apiclient, aclid=acl.id)
+        # Domain Admin should be able to replace ACL
+        network.replaceACLList(apiclient=self.domain_admin_apiclient, aclid=acl.id)
+        # Admin should be able to replace ACL
+        network.replaceACLList(apiclient=self.admin_apiclient, aclid=acl.id)
+
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_create_acl_rule(self):
+        """ Test to create ACL rule as a normal user, domain admin and root admin users.
+        """
+        # Creating ACL list
+        acl = NetworkACLList.create(apiclient=self.admin_apiclient, services={}, name="acl", description="acl")
+        self.cleanup.append(acl)
+
+        self.debug("Creating ACL rule as a user, should raise exception.")
+        self.assertRaisesRegex(CloudstackAPIException, "Only Root Admins can create rules for a global ACL.",
+                               NetworkACL.create, self.user_apiclient, services=self.services["rule"], aclid=acl.id)
+        self.debug("Creating ACL rule as a domain admin, should raise exception.")
+        self.assertRaisesRegex(CloudstackAPIException, "Only Root Admins can create rules for a global ACL.",
+                               NetworkACL.create, self.domain_admin_apiclient, services=self.services["rule"], aclid=acl.id)
+        self.debug("Creating ACL rule as a root admin, should work.")
+        acl_rule = NetworkACL.create(self.admin_apiclient, services=self.services["rule"], aclid=acl.id)
+        self.cleanup.append(acl_rule)
+
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_delete_acl_rule(self):
+        """ Test to delete ACL rule as a normal user, domain admin and root admin users.
+        """
+        # Creating ACL list
+        acl = NetworkACLList.create(apiclient=self.apiclient, services={}, name="acl", description="acl")
+        self.cleanup.append(acl)
+
+        # Creating ACL rule
+        acl_rule = NetworkACL.create(self.apiclient, services=self.services["rule"], aclid=acl.id)
+        self.cleanup.append(acl_rule)
+
+        self.debug("Deleting ACL rule as a user, should raise exception.")
+        self.assertRaisesRegex(Exception, "Only Root Admin can delete global ACL rules.",
+                               NetworkACL.delete, acl_rule, self.user_apiclient)
+        self.debug("Deleting ACL rule as a domain admin, should raise exception.")
+        self.assertRaisesRegex(Exception, "Only Root Admin can delete global ACL rules.",
+                               NetworkACL.delete, acl_rule, self.domain_admin_apiclient)
+
+        self.debug("Deleting ACL rule as a root admin, should work.")
+        NetworkACL.delete(acl_rule, self.admin_apiclient)
+        self.cleanup.remove(acl_rule)
+
+        # Verify if the number of ACL rules is equal to four, i.e. the number of rules
+        # for the default ACLs `default_allow` (2 rules) and `default_deny` (2 rules) ACLs
+        number_of_acl_rules = acl_rule.list(apiclient=self.admin_apiclient)
+        self.assertEqual(len(number_of_acl_rules), 4)
+
+        return
+
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_delete_global_acl(self):
+        """ Test delete global ACL as a normal user, domain admin and root admin users.
+        """
+
+        # Creating ACL list. Not adding to cleanup as it will be deleted in this method
+        acl = NetworkACLList.create(apiclient=self.apiclient, services={}, name="acl", description="acl")
+        self.cleanup.append(acl)
+
+        self.debug("Deleting ACL list as a normal user, should raise exception.")
+        self.assertRaisesRegex(Exception, "Only Root Admin can delete global ACLs.",
+                               NetworkACLList.delete, acl, apiclient=self.user_apiclient)
+
+        self.debug("Deleting ACL list as a domain admin, should raise exception.")
+        self.assertRaisesRegex(Exception, "Only Root Admin can delete global ACLs.",
+                               NetworkACLList.delete, acl, apiclient=self.domain_admin_apiclient)
+
+        self.debug("Deleting ACL list as a root admin, should work.")
+        acl.delete(apiclient=self.admin_apiclient)
+        self.cleanup.remove(acl)
+
+        # Verify if number of ACLs is equal to two, i.e. the number of default ACLs `default_allow` and `default_deny`
+        number_of_acls = NetworkACLList.list(apiclient=self.admin_apiclient)
+        self.assertEqual(len(number_of_acls), 2)
+
+        return
diff --git a/test/integration/smoke/test_guest_os.py b/test/integration/smoke/test_guest_os.py
new file mode 100644
index 0000000..c9d50a7
--- /dev/null
+++ b/test/integration/smoke/test_guest_os.py
@@ -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.
+#All tests inherit from cloudstackTestCase
+import unittest
+
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.cloudstackAPI import listHosts
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.lib.base import (VirtualMachine,
+                             Account,
+                             GuestOSCategory,
+                             GuestOS,
+                             GuestOsMapping,
+                             NetworkOffering,
+                             Network)
+from marvin.lib.common import get_test_template, get_zone, list_virtual_machines
+from marvin.lib.utils import (validateList, cleanup_resources)
+from nose.plugins.attrib import attr
+from marvin.codes import PASS,FAIL
+
+class Services:
+    def __init__(self):
+        self.services = {
+        }
+
+class TestGuestOS(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestGuestOS, cls)
+        cls.api_client = cls.testClient.getApiClient()
+        cls.services = Services().services
+
+        cls.hypervisor = cls.get_hypervisor_type()
+
+    @classmethod
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+
+        #build cleanup list
+        self.cleanup = []
+
+    @classmethod
+    def tearDown(self):
+        try:
+            cleanup_resources(self.apiclient, self.cleanup)
+        except Exception as e:
+            self.debug("Warning! Exception in tearDown: %s" % e)
+
+    @classmethod
+    def get_hypervisor_type(cls):
+
+        """Return the hypervisor available in setup"""
+
+        cmd = listHosts.listHostsCmd()
+        cmd.type = 'Routing'
+        cmd.listall = True
+        hosts = cls.api_client.listHosts(cmd)
+        hosts_list_validation_result = validateList(hosts)
+        assert hosts_list_validation_result[0] == PASS, "host list validation failed"
+        return hosts_list_validation_result[1]
+
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_CRUD_operations_guest_OS(self):
+        """Test add, list, update operations on guest OS
+            1. Add a guest OS
+            2. List the guest OS
+            3. Delete the added guest OS
+        """
+        list_os_categories = GuestOSCategory.list(self.apiclient, name="CentOS", listall=True)
+        self.assertNotEqual(
+            len(list_os_categories),
+            0,
+            "List OS categories was empty"
+        )
+        os_category = list_os_categories[0]
+
+        self.guestos1 = GuestOS.add(
+            self.apiclient,
+            osdisplayname="testCentOS",
+            oscategoryid=os_category.id
+        )
+        list_guestos = GuestOS.list(self.apiclient, id=self.guestos1.id, listall=True)
+        self.assertNotEqual(
+            len(list_guestos),
+            0,
+            "List guest OS was empty"
+        )
+        guestos = list_guestos[0]
+        self.assertEqual(
+            guestos.id,
+            self.guestos1.id,
+            "Guest os ids do not match"
+        )
+
+        GuestOS.remove(
+            self.apiclient,
+            id=self.guestos1.id
+        )
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_CRUD_operations_guest_OS_mapping(self):
+        """Test add, list, update operations on guest OS mapping
+            1. Add a guest OS
+            2. Add a guest OS mapping
+            3. Delete the added guest OS and mappings
+        """
+        list_os_categories = GuestOSCategory.list(self.apiclient, name="CentOS", listall=True)
+        os_category = list_os_categories[0]
+        self.guestos1 = GuestOS.add(
+            self.apiclient,
+            osdisplayname="testCentOS",
+            oscategoryid=os_category.id
+        )
+
+        if self.hypervisor.hypervisor.lower() not in ["xenserver", "vmware"]:
+            raise unittest.SkipTest("OS name check with hypervisor is supported only on XenServer and VMware")
+
+        self.guestosmapping1 = GuestOsMapping.add(
+            self.apiclient,
+            ostypeid=self.guestos1.id,
+            hypervisor=self.hypervisor.hypervisor,
+            hypervisorversion=self.hypervisor.hypervisorversion,
+            osnameforhypervisor="testOSMappingName"
+        )
+
+        list_guestos_mapping = GuestOsMapping.list(self.apiclient, id=self.guestosmapping1.id, listall=True)
+        self.assertNotEqual(
+            len(list_guestos_mapping),
+            0,
+            "List guest OS mapping was empty"
+        )
+        guestosmapping = list_guestos_mapping[0]
+        self.assertEqual(
+            guestosmapping.id,
+            self.guestosmapping1.id,
+            "Guest os mapping ids do not match"
+        )
+
+        GuestOsMapping.remove(
+            self.apiclient,
+            id=self.guestosmapping1.id
+        )
+
+        GuestOS.remove(
+            self.apiclient,
+            id=self.guestos1.id
+        )
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_guest_OS_mapping_check_with_hypervisor(self):
+        """Test add, list, update operations on guest OS mapping
+            1. Add a guest OS
+            2. Add a guest OS mapping with osmappingcheckenabled true
+            3. Delete the added guest OS and mappings
+        """
+        list_os_categories = GuestOSCategory.list(self.apiclient, name="CentOS", listall=True)
+        os_category = list_os_categories[0]
+        self.guestos1 = GuestOS.add(
+            self.apiclient,
+            osdisplayname="testOSname1",
+            oscategoryid=os_category.id
+        )
+
+        if self.hypervisor.hypervisor.lower() not in ["xenserver", "vmware"]:
+            raise unittest.SkipTest("OS name check with hypervisor is supported only on XenServer and VMware")
+
+        if self.hypervisor.hypervisor.lower() == "xenserver":
+            testosname="Debian Jessie 8.0"
+        else:
+            testosname="debian4_64Guest"
+
+        self.guestosmapping1 = GuestOsMapping.add(
+            self.apiclient,
+            ostypeid=self.guestos1.id,
+            hypervisor=self.hypervisor.hypervisor,
+            hypervisorversion=self.hypervisor.hypervisorversion,
+            osnameforhypervisor=testosname,
+            osmappingcheckenabled=True
+        )
+
+        list_guestos_mapping = GuestOsMapping.list(self.apiclient, id=self.guestosmapping1.id, listall=True)
+        self.assertNotEqual(
+            len(list_guestos_mapping),
+            0,
+            "List guest OS mapping was empty"
+        )
+        guestosmapping = list_guestos_mapping[0]
+        self.assertEqual(
+            guestosmapping.id,
+            self.guestosmapping1.id,
+            "Guest os mapping ids do not match"
+        )
+
+        GuestOsMapping.remove(
+            self.apiclient,
+            id=self.guestosmapping1.id
+        )
+
+        GuestOS.remove(
+            self.apiclient,
+            id=self.guestos1.id
+        )
+
+    @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False)
+    def test_guest_OS_mapping_check_with_hypervisor_failure(self):
+        """Test add, list, update operations on guest OS mapping
+            1. Add a guest OS
+            2. Add a guest OS mapping with osmappingcheckenabled true
+            3. Delete the added guest OS and mappings
+        """
+        list_os_categories = GuestOSCategory.list(self.apiclient, name="CentOS", listall=True)
+        os_category = list_os_categories[0]
+        self.guestos1 = GuestOS.add(
+            self.apiclient,
+            osdisplayname="testOSname2",
+            oscategoryid=os_category.id
+        )
+
+        if self.hypervisor.hypervisor.lower() not in ["xenserver", "vmware"]:
+            raise unittest.SkipTest("OS name check with hypervisor is supported only on XenServer and VMware")
+
+        testosname = "incorrectOSname"
+
+        try:
+            self.guestosmapping1 = GuestOsMapping.add(
+                self.apiclient,
+                ostypeid=self.guestos1.id,
+                hypervisor=self.hypervisor.hypervisor,
+                hypervisorversion=self.hypervisor.hypervisorversion,
+                osnameforhypervisor=testosname,
+                osmappingcheckenabled=True
+            )
+            GuestOsMapping.remove(
+                self.apiclient,
+                id=self.guestosmapping1.id
+            )
+            self.fail("Since os mapping name is wrong, this API should fail")
+        except CloudstackAPIException as e:
+            self.debug("Addition guest OS mapping failed as expected %s " % e)
+        GuestOS.remove(
+            self.apiclient,
+            id=self.guestos1.id
+        )
+        return
diff --git a/test/integration/smoke/test_host_control_state.py b/test/integration/smoke/test_host_control_state.py
index 809af7d..4b8409e 100644
--- a/test/integration/smoke/test_host_control_state.py
+++ b/test/integration/smoke/test_host_control_state.py
@@ -20,7 +20,7 @@
 Tests for host control state
 """
 
-from marvin.cloudstackAPI import updateHost
+from marvin.cloudstackAPI import (updateHost, updateConfiguration)
 from nose.plugins.attrib import attr
 from marvin.cloudstackTestCase import cloudstackTestCase
 from marvin.lib.common import (get_domain,
@@ -28,13 +28,18 @@
                                get_template,
                                list_hosts,
                                list_routers,
-                               list_ssvms)
+                               list_ssvms,
+                               list_clusters,
+                               list_hosts)
 from marvin.lib.base import (Account,
                              Domain,
                              Host,
                              ServiceOffering,
                              VirtualMachine)
 from marvin.sshClient import SshClient
+from marvin.lib.decoratorGenerators import skipTestIf
+from marvin.lib.utils import wait_until
+import logging
 import time
 
 
@@ -250,3 +255,220 @@
 
         self.enable_host(host_id)
         self.verify_router_host_control_state(router.id, "Enabled")
+
+
+class TestAutoEnableDisableHost(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestAutoEnableDisableHost, cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+        cls.services = cls.testClient.getParsedTestDataConfig()
+        # Get Zone, Domain and templates
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
+        cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__
+        if cls.hypervisor.lower() not in ['kvm']:
+            cls.hypervisorNotSupported = True
+            return
+
+        cls.logger = logging.getLogger('TestAutoEnableDisableHost')
+        return
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestAutoEnableDisableHost, cls).tearDownClass()
+
+    def tearDown(self):
+        super(TestAutoEnableDisableHost, self).tearDown()
+
+    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
+
+    def wait_until_host_is_in_state(self, hostid, resourcestate, interval=3, retries=20):
+        def check_resource_state():
+            response = Host.list(
+                self.apiclient,
+                id=hostid
+            )
+            if isinstance(response, list):
+                if response[0].resourcestate == resourcestate:
+                    self.logger.debug('Host with id %s is in resource state = %s' % (hostid, resourcestate))
+                    return True, None
+                else:
+                    self.logger.debug("Waiting for host " + hostid +
+                                      " to reach state " + resourcestate +
+                                      ", with current state " + response[0].resourcestate)
+            return False, None
+
+        done, _ = wait_until(interval, retries, check_resource_state)
+        if not done:
+            raise Exception("Failed to wait for host %s to be on resource state %s" % (hostid, resourcestate))
+        return True
+
+    def update_config(self, enable_feature):
+        cmd = updateConfiguration.updateConfigurationCmd()
+        cmd.name = "enable.kvm.host.auto.enable.disable"
+        cmd.value = enable_feature
+
+        response = self.apiclient.updateConfiguration(cmd)
+        self.debug("updated the parameter %s with value %s" % (response.name, response.value))
+
+    def update_health_check_script(self, ip_address, username, password, exit_code):
+        health_check_script_path = "/etc/cloudstack/agent/healthcheck.sh"
+        health_check_agent_property = "agent.health.check.script.path"
+        agent_properties_file_path = "/etc/cloudstack/agent/agent.properties"
+
+        ssh_client = self.get_ssh_client(ip_address, username, password)
+        ssh_client.execute("echo 'exit %s' > %s" % (exit_code, health_check_script_path))
+        ssh_client.execute("chmod +x %s" % health_check_script_path)
+        ssh_client.execute("echo '%s=%s' >> %s" % (health_check_agent_property, health_check_script_path,
+                                                   agent_properties_file_path))
+        ssh_client.execute("service cloudstack-agent restart")
+
+    def remove_host_health_check(self, ip_address, username, password):
+        health_check_script_path = "/etc/cloudstack/agent/healthcheck.sh"
+        ssh_client = self.get_ssh_client(ip_address, username, password)
+        ssh_client.execute("rm -f %s" % health_check_script_path)
+
+    def select_host_for_health_checks(self):
+        clusters = list_clusters(
+            self.apiclient,
+            zoneid=self.zone.id
+        )
+        if not clusters:
+            return None
+
+        for cluster in clusters:
+            list_hosts_response = list_hosts(
+                self.apiclient,
+                clusterid=cluster.id,
+                type="Routing",
+                resourcestate="Enabled"
+            )
+            assert isinstance(list_hosts_response, list)
+            if not list_hosts_response or len(list_hosts_response) < 1:
+                continue
+            return list_hosts_response[0]
+        return None
+
+    def update_host_allocation_state(self, id, enable):
+        cmd = updateHost.updateHostCmd()
+        cmd.id = id
+        cmd.allocationstate = "Enable" if enable else "Disable"
+        response = self.apiclient.updateHost(cmd)
+        self.assertEqual(response.resourcestate, "Enabled" if enable else "Disabled")
+
+    @attr(tags=["basic", "advanced"], required_hardware="false")
+    @skipTestIf("hypervisorNotSupported")
+    def test_01_auto_enable_disable_kvm_host(self):
+        """Test to auto-enable and auto-disable a KVM host based on health check results
+
+        # Validate the following:
+        # 1. Enable the KVM Auto Enable/Disable Feature
+        # 2. Set a health check script that fails and observe the host is Disabled
+        # 3. Make the health check script succeed and observe the host is Enabled
+        """
+
+        selected_host = self.select_host_for_health_checks()
+        if not selected_host:
+            self.skipTest("Cannot find a KVM host to test the auto-enable-disable feature")
+
+        username = self.hostConfig["username"]
+        password = self.hostConfig["password"]
+
+        # Enable the Auto Enable/Disable Configuration
+        self.update_config("true")
+
+        # Set health check script for failure
+        self.update_health_check_script(selected_host.ipaddress, username, password, 1)
+        self.wait_until_host_is_in_state(selected_host.id, "Disabled", 5, 200)
+
+        # Set health check script for success
+        self.update_health_check_script(selected_host.ipaddress, username, password, 0)
+
+        self.wait_until_host_is_in_state(selected_host.id, "Enabled", 5, 200)
+
+    @attr(tags=["basic", "advanced"], required_hardware="false")
+    @skipTestIf("hypervisorNotSupported")
+    def test_02_disable_host_overrides_auto_enable_kvm_host(self):
+        """Test to override the auto-enabling of a KVM host by an administrator
+
+        # Validate the following:
+        # 1. Enable the KVM Auto Enable/Disable Feature
+        # 2. Set a health check script that succeeds and observe the host is Enabled
+        # 3. Make the host Disabled
+        # 4. Verify the host does not get auto-enabled after the previous step
+        """
+
+        selected_host = self.select_host_for_health_checks()
+        if not selected_host:
+            self.skipTest("Cannot find a KVM host to test the auto-enable-disable feature")
+
+        username = self.hostConfig["username"]
+        password = self.hostConfig["password"]
+
+        # Enable the Auto Enable/Disable Configuration
+        self.update_config("true")
+
+        # Set health check script for failure
+        self.update_health_check_script(selected_host.ipaddress, username, password, 0)
+        self.wait_until_host_is_in_state(selected_host.id, "Enabled", 5, 200)
+
+        # Manually disable the host
+        self.update_host_allocation_state(selected_host.id, False)
+
+        # Wait for more than the ping interval
+        time.sleep(70)
+
+        # Verify the host continues on Disabled state
+        self.wait_until_host_is_in_state(selected_host.id, "Disabled", 5, 200)
+
+        # Restore the host to Enabled state
+        self.remove_host_health_check(selected_host.ipaddress, username, password)
+        self.update_host_allocation_state(selected_host.id, True)
+
+    @attr(tags=["basic", "advanced"], required_hardware="false")
+    @skipTestIf("hypervisorNotSupported")
+    def test_03_enable_host_does_not_override_auto_disable_kvm_host(self):
+        """Test to override the auto-disabling of a KVM host by an administrator
+
+        # Validate the following:
+        # 1. Enable the KVM Auto Enable/Disable Feature
+        # 2. Set a health check script that fails and observe the host is Disabled
+        # 3. Make the host Enabled
+        # 4. Verify the host does get auto-disabled after the previous step
+        """
+
+        selected_host = self.select_host_for_health_checks()
+        if not selected_host:
+            self.skipTest("Cannot find a KVM host to test the auto-enable-disable feature")
+
+        username = self.hostConfig["username"]
+        password = self.hostConfig["password"]
+
+        # Enable the Auto Enable/Disable Configuration
+        self.update_config("true")
+
+        # Set health check script for failure
+        self.update_health_check_script(selected_host.ipaddress, username, password, 1)
+        self.wait_until_host_is_in_state(selected_host.id, "Disabled", 5, 200)
+
+        # Manually enable the host
+        self.update_host_allocation_state(selected_host.id, True)
+
+        # Verify the host goes back to Disabled state
+        self.wait_until_host_is_in_state(selected_host.id, "Disabled", 5, 200)
+
+        # Restore the host to Enabled state
+        self.remove_host_health_check(selected_host.ipaddress, username, password)
+        self.update_host_allocation_state(selected_host.id, True)
diff --git a/test/integration/smoke/test_image_store_object_migration.py b/test/integration/smoke/test_image_store_object_migration.py
new file mode 100644
index 0000000..fcd10d7
--- /dev/null
+++ b/test/integration/smoke/test_image_store_object_migration.py
@@ -0,0 +1,233 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+""" Image store object migration test
+"""
+import logging
+
+#Import Local Modules
+from marvin.cloudstackException import *
+from marvin.cloudstackAPI import *
+from marvin.codes import FAILED
+from marvin.cloudstackTestCase import cloudstackTestCase
+import unittest
+from marvin.cloudstackAPI import listZones
+from marvin.lib.utils import random_gen, cleanup_resources
+from marvin.lib.base import (Template,
+                             ImageStore)
+from marvin.lib.common import (get_domain,
+                               get_zone,
+                               get_template)
+from nose.plugins.attrib import attr
+import urllib.request, urllib.parse, urllib.error
+#Import System modules
+import time
+from marvin.cloudstackAPI import (createTemplate, listOsTypes)
+
+_multiprocess_shared_ = True
+
+class TestImageStoreObjectMigration(cloudstackTestCase):
+    """Test Image Store Object migration
+    """
+    def setUp(self):
+        self.testClient = super(TestImageStoreObjectMigration, self).getClsTestClient()
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+
+        self.services = self.testClient.getParsedTestDataConfig()
+        self.unsupportedHypervisor = False
+        self.hypervisor = self.testClient.getHypervisorInfo()
+        if self.hypervisor.lower() in ['lxc']:
+            # Template creation from root volume is not supported in LXC
+            self.unsupportedHypervisor = True
+            return
+
+        # Get Zone, Domain and templates
+        self.domain = get_domain(self.apiclient)
+        self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests())
+
+        if "kvm" in self.hypervisor.lower():
+            self.test_template = registerTemplate.registerTemplateCmd()
+            self.test_template = registerTemplate.registerTemplateCmd()
+            self.test_template.checksum = "{SHA-1}" + "6952e58f39b470bd166ace11ffd20bf479bed936"
+            self.test_template.hypervisor = self.hypervisor
+            self.test_template.zoneid = self.zone.id
+            self.test_template.name = 'test sha-2333'
+            self.test_template.displaytext = 'test sha-1'
+            self.test_template.url = "http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-kvm.qcow2.bz2"
+            self.test_template.format = "QCOW2"
+            self.test_template.ostypeid = self.getOsType("Other Linux (64-bit)")
+            self.md5 = "88c60fd500ce7ced985cf845df0db9da"
+            self.sha256 = "bc4cc040bbab843000fab78db6cb4a33f3a06ae1ced2cf563d36b38c7fee3049"
+
+        if "vmware" in self.hypervisor.lower():
+            self.test_template = registerTemplate.registerTemplateCmd()
+            self.test_template = registerTemplate.registerTemplateCmd()
+            self.test_template.checksum = "{SHA-1}" + "8b82224fd3c6429b6914f32d8339e650770c7526"
+            self.test_template.hypervisor = self.hypervisor
+            self.test_template.zoneid = self.zone.id
+            self.test_template.name = 'test sha-2333'
+            self.test_template.displaytext = 'test sha-1'
+            self.test_template.url = "http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-vmware.ova"
+            self.test_template.format = "OVA"
+            self.test_template.ostypeid = self.getOsType("Other Linux (64-bit)")
+            self.md5 = "b4e8bff3882b23175974e692533b4381"
+            self.sha256 = "e1dffca3c3ab545a753cb42d838a341624cf25841d1bcf3d1e45556c9fce7cf3"
+
+        if "xen" in self.hypervisor.lower():
+            self.test_template = registerTemplate.registerTemplateCmd()
+            self.test_template = registerTemplate.registerTemplateCmd()
+            self.test_template.checksum = "{SHA-1}" + "80af2c18f96e94273188808c3d56e561a1cda717"
+            self.test_template.hypervisor = self.hypervisor
+            self.test_template.zoneid = self.zone.id
+            self.test_template.name = 'test sha-2333'
+            self.test_template.displaytext = 'test sha-1'
+            self.test_template.url = "http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-xen.vhd.bz2"
+            self.test_template.format = "VHD"
+            self.test_template.ostypeid = self.getOsType("Other Linux (64-bit)")
+            self.md5 = "1662bbf224e41bb62b1dee043d785731"
+            self.sha256 = "80fba5a7a83842ec4e5f67cc6755d61d4fca46ae170d59b0c6ed47ebf7162722"
+
+        if self.unsupportedHypervisor:
+            self.skipTest("Skipping test because unsupported hypervisor\
+                                %s" % self.hypervisor)
+        return
+
+    def tearDown(self):
+        try:
+            # Clean up the created templates
+            for temp in self.cleanup:
+                cmd = deleteTemplate.deleteTemplateCmd()
+                cmd.id = temp.id
+                cmd.zoneid = self.zone.id
+                self.apiclient.deleteTemplate(cmd)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return
+
+    @attr(tags = ["advanced", "basic", "smoke"], required_hardware="true")
+    def test_01_browser_migrate_template(self):
+        """
+        Test storage browser and template migration to another secondary storage
+        """
+        template = self.registerTemplate(self.test_template)
+        self.download(self.apiclient, template.id)
+
+        list_template_response=Template.list(
+            self.apiclient,
+            id=template.id,
+            templatefilter="all",
+            zoneid=self.zone.id)
+
+        datastoreid = list_template_response[0].downloaddetails[0].datastoreId
+
+        qresultset = self.dbclient.execute(
+            "select account_id, id from vm_template where uuid = '%s';"
+            % template.id
+        )
+
+        account_id = qresultset[0][0]
+        template_id = qresultset[0][1]
+
+        originalSecondaryStore = ImageStore({"id": datastoreid})
+
+        storeObjects = originalSecondaryStore.listObjects(self.apiclient, path="template/tmpl/" + str(account_id) + "/" + str(template_id))
+
+        self.assertGreaterEqual(len(storeObjects), 2, "Check template is uploaded on secondary storage")
+
+        # Migrate template to another secondary storage
+        secondaryStores = ImageStore.list(self.apiclient, zoneid=self.zone.id)
+
+        if len(secondaryStores) < 2:
+            self.skipTest("Only one secondary storage available hence skipping")
+
+        for store in secondaryStores:
+            if store.id != datastoreid:
+                destSecondaryStore = ImageStore({"id": store.id})
+                break
+
+        originalSecondaryStore.migrateResources(self.apiclient, destSecondaryStore.id, templateIdList=[template.id])
+
+        try:
+            originalSecondaryStore.listObjects(self.apiclient, path="template/tmpl/" + str(account_id) + "/" + str(template_id))
+        except Exception as exc:
+            self.assertTrue("template/tmpl/" + str(account_id) + "/" + str(template_id) + " doesn't exist in store" in str(exc),
+                            "Check template is deleted from original secondary storage")
+        else:
+            self.fail("Template is not deleted from original secondary storage")
+
+        storeObjects = destSecondaryStore.listObjects(self.apiclient, path="template/tmpl/" + str(account_id) + "/" + str(template_id))
+
+        self.assertGreaterEqual(len(storeObjects), 2, "Check template is uploaded on destination secondary storage")
+
+    def registerTemplate(self, cmd):
+        temp = self.apiclient.registerTemplate(cmd)[0]
+        if not temp:
+            self.cleanup.append(temp)
+        return temp
+
+    def getOsType(self, param):
+        cmd = listOsTypes.listOsTypesCmd()
+        cmd.description = param
+        return self.apiclient.listOsTypes(cmd)[0].id
+
+    def download(self, apiclient, template_id, retries=12, interval=5):
+        """Check if template download will finish in 1 minute"""
+        while retries > -1:
+            time.sleep(interval)
+            template_response = Template.list(
+                apiclient,
+                id=template_id,
+                zoneid=self.zone.id,
+                templatefilter='self'
+            )
+
+            if isinstance(template_response, list):
+                template = template_response[0]
+                if not hasattr(template, 'status') or not template or not template.status:
+                    retries = retries - 1
+                    continue
+
+                # If template is ready,
+                # template.status = Download Complete
+                # Downloading - x% Downloaded
+                # if Failed
+                # Error - Any other string
+                if 'Failed' in template.status:
+                    raise Exception(
+                        "Failed to download template: status - %s" %
+                        template.status)
+
+                elif template.status == 'Download Complete' and template.isready:
+                    return
+
+                elif 'Downloaded' in template.status:
+                    retries = retries - 1
+                    continue
+
+                elif 'Installing' not in template.status:
+                    if retries >= 0:
+                        retries = retries - 1
+                        continue
+                    raise Exception(
+                        "Error in downloading template: status - %s" %
+                        template.status)
+
+            else:
+                retries = retries - 1
+        raise Exception("Template download failed exception.")
diff --git a/test/integration/smoke/test_kubernetes_clusters.py b/test/integration/smoke/test_kubernetes_clusters.py
index 0c5b96a..488e5ef 100644
--- a/test/integration/smoke/test_kubernetes_clusters.py
+++ b/test/integration/smoke/test_kubernetes_clusters.py
@@ -33,7 +33,9 @@
                                   scaleKubernetesCluster,
                                   getKubernetesClusterConfig,
                                   destroyVirtualMachine,
-                                  deleteNetwork)
+                                  deleteNetwork,
+                                  addVirtualMachinesToKubernetesCluster,
+                                  removeVirtualMachinesFromKubernetesCluster)
 from marvin.cloudstackException import CloudstackAPIException
 from marvin.codes import PASS, FAILED
 from marvin.lib.base import (Template,
@@ -46,12 +48,14 @@
                              VpcOffering,
                              VPC,
                              NetworkACLList,
-                             NetworkACL)
+                             NetworkACL,
+                             VirtualMachine)
 from marvin.lib.utils import (cleanup_resources,
                               validateList,
                               random_gen)
 from marvin.lib.common import (get_zone,
-                               get_domain)
+                               get_domain,
+                               get_template)
 from marvin.sshClient import SshClient
 from nose.plugins.attrib import attr
 from marvin.lib.decoratorGenerators import skipTestIf
@@ -86,6 +90,7 @@
         cls._cleanup = []
         cls.kubernetes_version_ids = []
         cls.vpcAllowAllAclDetailsMap = {}
+        cls.initial_configuration_cks_enabled = None
 
         cls.k8s_version_from = cls.services["cks_kubernetes_version_upgrade_from"]
         cls.k8s_version_to = cls.services["cks_kubernetes_version_upgrade_to"]
@@ -579,7 +584,82 @@
         k8s_cluster = None
         return
 
-    def createKubernetesCluster(self, name, version_id, size=1, control_nodes=1):
+    @attr(tags=["advanced", "smoke"], required_hardware="true")
+    def test_11_test_unmanaged_cluster_lifecycle(self):
+        """Test all operations on unmanaged Kubernetes cluster
+
+        # Validate the following:
+        # 1. createKubernetesCluster should return valid info for new cluster
+        # 2. The Cloud Database contains the valid information
+        # 3. stopKubernetesCluster doesn't work
+        # 4. startKubernetesCluster doesn't work
+        # 5. upgradeKubernetesCluster doesn't work
+        # 6. Adding & removing vm from cluster works
+        # 7. deleteKubernetesCluster should delete an existing HA Kubernetes cluster
+        """
+        cluster = self.createKubernetesCluster("test-unmanaged-cluster", None,
+                                               cluster_type="ExternalManaged")
+        self.verifyKubernetesClusterState(cluster, 'Running')
+        self.debug("Stopping unmanaged Kubernetes cluster with ID: %s" % cluster.id)
+        try:
+            self.stopKubernetesCluster(cluster.id)
+            self.fail("Should not be able to stop unmanaged cluster")
+        except Exception as e:
+            self.debug("Expected exception: %s" % e)
+
+        self.debug("Starting unmanaged Kubernetes cluster with ID: %s" % cluster.id)
+        try:
+            self.startKubernetesCluster(cluster.id)
+            self.fail("Should not be able to start unmanaged cluster")
+        except Exception as e:
+            self.debug("Expected exception: %s" % e)
+
+        self.debug("Upgrading unmanaged Kubernetes cluster with ID: %s" % cluster.id)
+        try:
+            self.upgradeKubernetesCluster(cluster.id, self.kubernetes_version_1_24_0.id)
+            self.fail("Should not be able to upgrade unmanaged cluster")
+        except Exception as e:
+            self.debug("Expected exception: %s" % e)
+
+        template = get_template(self.apiclient,
+                                    self.zone.id,
+                                    self.services["ostype"])
+
+        self.services["virtual_machine"]["template"] = template.id
+        virtualMachine = VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id,
+                                                            accountid=self.account.name, domainid=self.account.domainid,
+                                                            serviceofferingid=self.cks_service_offering.id)
+        self.debug("Adding VM %s to unmanaged Kubernetes cluster with ID: %s" % (virtualMachine.id, cluster.id))
+        self.addVirtualMachinesToKubernetesCluster(cluster.id, [virtualMachine.id])
+        cluster = self.listKubernetesCluster(cluster.id)
+        self.assertEqual(virtualMachine.id, cluster.virtualmachines[0].id, "VM should be part of the kubernetes cluster")
+        self.assertEqual(1, len(cluster.virtualmachines), "Only one VM should be part of the kubernetes cluster")
+
+        self.debug("Removing VM %s from unmanaged Kubernetes cluster with ID: %s" % (virtualMachine.id, cluster.id))
+        self.removeVirtualMachinesFromKubernetesCluster(cluster.id, [virtualMachine.id])
+        cluster = self.listKubernetesCluster(cluster.id)
+        self.assertEqual(0, len(cluster.virtualmachines), "No VM should be part of the kubernetes cluster")
+
+        self.debug("Deleting unmanaged Kubernetes cluster with ID: %s" % cluster.id)
+        self.deleteKubernetesClusterAndVerify(cluster.id)
+        return
+
+    def addVirtualMachinesToKubernetesCluster(self, cluster_id, vm_list):
+        cmd = addVirtualMachinesToKubernetesCluster.addVirtualMachinesToKubernetesClusterCmd()
+        cmd.id = cluster_id
+        cmd.virtualmachineids = vm_list
+
+        return self.apiclient.addVirtualMachinesToKubernetesCluster(cmd)
+
+    def removeVirtualMachinesFromKubernetesCluster(self, cluster_id, vm_list):
+        cmd = removeVirtualMachinesFromKubernetesCluster.removeVirtualMachinesFromKubernetesClusterCmd()
+        cmd.id = cluster_id
+        cmd.virtualmachineids = vm_list
+
+        return self.apiclient.removeVirtualMachinesFromKubernetesCluster(cmd)
+
+
+    def createKubernetesCluster(self, name, version_id, size=1, control_nodes=1, cluster_type='CloudManaged'):
         createKubernetesClusterCmd = createKubernetesCluster.createKubernetesClusterCmd()
         createKubernetesClusterCmd.name = name
         createKubernetesClusterCmd.description = name + "-description"
@@ -591,11 +671,10 @@
         createKubernetesClusterCmd.noderootdisksize = 10
         createKubernetesClusterCmd.account = self.account.name
         createKubernetesClusterCmd.domainid = self.domain.id
+        createKubernetesClusterCmd.clustertype = cluster_type
         if self.default_network:
             createKubernetesClusterCmd.networkid = self.default_network.id
         clusterResponse = self.apiclient.createKubernetesCluster(createKubernetesClusterCmd)
-        if not clusterResponse:
-            self.cleanup.append(clusterResponse)
         return clusterResponse
 
     def startKubernetesCluster(self, cluster_id):
diff --git a/test/integration/smoke/test_list_accounts.py b/test/integration/smoke/test_list_accounts.py
new file mode 100644
index 0000000..1cce3ce
--- /dev/null
+++ b/test/integration/smoke/test_list_accounts.py
@@ -0,0 +1,379 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 API listing of accounts with different filters
+"""
+
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import (Account,
+                             Domain)
+from marvin.lib.common import (get_domain, list_accounts)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListAccounts(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListAccounts, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.domain = get_domain(cls.apiclient)
+        cls.account = list_accounts(cls.apiclient, name="admin")[0]
+        cls._cleanup = []
+        cls.accounts = list_accounts(cls.apiclient, listall=True)
+
+        cls.child_domain_1 = Domain.create(
+            cls.apiclient,
+            cls.services["domain"],
+            parentdomainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.child_domain_1)
+
+        cls.services["account"]["username"] = "child_account_admin"
+        cls.child_account_admin = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.child_domain_1.id
+        )
+        cls._cleanup.append(cls.child_account_admin)
+
+        cls.services["username"] = "child_account_user"
+        cls.child_account_user = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=0,
+            domainid=cls.child_domain_1.id
+        )
+        cls.child_account_user.disable(cls.apiclient)
+        cls._cleanup.append(cls.child_account_user)
+
+        cls.child_domain_2 = Domain.create(
+            cls.apiclient,
+            cls.services["domain"],
+            parentdomainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.child_domain_2)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListAccounts, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_accounts_accounttype_filter(self):
+        """Test listing accounts with accounttype filter
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            accounttype=0,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_user.name,
+            "Check for list response return valid data"
+        )
+        self.assertEqual(
+            list_account_response[0].accounttype,
+            0,
+            "Check for list response return valid data"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            accounttype=2,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_admin.name,
+            "Check for list response return valid data"
+        )
+        self.assertEqual(
+            list_account_response[0].accounttype,
+            2,
+            "Check for list response return valid data"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_accounts_domainid_filter(self):
+        """Test listing accounts with domainid filter
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            2,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            self.child_domain_1.id,
+            list_account_response[0].domainid,
+            "Check for list response return valid data"
+        )
+        self.assertEqual(
+            self.child_domain_1.id,
+            list_account_response[1].domainid,
+            "Check for list response return valid data"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            domainid=self.child_domain_2.id
+        )
+        self.assertIsNone(list_account_response, "Check for list response return valid data")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_accounts_id_filter(self):
+        """Test listing accounts with id filter
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            id=self.child_account_user.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_user.name,
+            "Expected account name and actual account name should be same"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            id=self.child_account_admin.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_admin.name,
+            "Expected account name and actual account name should be same"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_accounts_name_filter(self):
+        """Test listing accounts with name filter
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            name=self.child_account_user.name,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_user.name,
+            "Expected account name and actual account name should be same"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            name=self.child_account_admin.name,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_admin.name,
+            "Expected account name and actual account name should be same"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_accounts_state_filter(self):
+        """Test listing accounts with state filter
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            state="enabled",
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_admin.name,
+            "Expected account name and actual account name should be same"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            state="disabled",
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_account_response),
+            1,
+            "List Account response has incorrect length"
+        )
+        self.assertEqual(
+            list_account_response[0].name,
+            self.child_account_user.name,
+            "Expected account name and actual account name should be same"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_06_list_accounts_keyword_filter(self):
+        """Test listing accounts with keyword filter
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            keyword=self.child_account_user.name,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            self.child_account_user.name,
+            list_account_response[0].name,
+            "Expected account name and actual account name should be same"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            keyword=self.child_account_admin.name,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            self.child_account_admin.name,
+            list_account_response[0].name,
+            "Expected account name and actual account name should be same"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_07_list_accounts_with_listall_filters(self):
+        """Test listing accounts with listall filters
+        """
+        list_account_response = Account.list(
+            self.apiclient,
+            listall=False
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            1,
+            len(list_account_response),
+            "List Account response has incorrect length"
+        )
+
+        list_account_response = Account.list(
+            self.apiclient,
+            listall=True
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            2,
+            len(list_account_response) - len(self.accounts),
+            "List Account response has incorrect length"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_08_list_accounts_with_no_filters(self):
+        """Test listing accounts with no filters
+        """
+        list_account_response = Account.list(
+            self.apiclient
+        )
+        self.assertTrue(
+            isinstance(list_account_response, list),
+            "List Account response is not a valid list"
+        )
+        self.assertEqual(
+            1,
+            len(list_account_response),
+            "List Account response has incorrect length"
+        )
diff --git a/test/integration/smoke/test_list_disk_offerings.py b/test/integration/smoke/test_list_disk_offerings.py
new file mode 100644
index 0000000..6319ea3
--- /dev/null
+++ b/test/integration/smoke/test_list_disk_offerings.py
@@ -0,0 +1,319 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 API listing of disk offerings with different filters
+"""
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (Account,
+                             Domain,
+                             Volume,
+                             ServiceOffering,
+                             DiskOffering,
+                             VirtualMachine)
+from marvin.lib.common import (get_domain, list_accounts,
+                               list_zones, list_clusters, list_hosts)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListDiskOfferings(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListDiskOfferings, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls.domain = get_domain(cls.apiclient)
+        cls.zones = list_zones(cls.apiclient)
+        cls.zone = cls.zones[0]
+        cls.clusters = list_clusters(cls.apiclient)
+        cls.cluster = cls.clusters[0]
+        cls.hosts = list_hosts(cls.apiclient)
+        cls.account = list_accounts(cls.apiclient, name="admin")[0]
+        cls._cleanup = []
+        cls.disk_offerings = DiskOffering.list(cls.apiclient, listall=True)
+
+        cls.disk_offering = DiskOffering.create(cls.apiclient,
+                                                cls.services["disk_offering"],
+                                                domainid=cls.domain.id)
+        cls._cleanup.append(cls.disk_offering)
+
+        cls.child_domain_1 = Domain.create(
+            cls.apiclient,
+            cls.services["domain"],
+            parentdomainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.child_domain_1)
+
+        cls.account_1 = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.account_1)
+
+        cls.domainadmin_api_client = testClient.getUserApiClient(
+            UserName=cls.account_1.user[0].username,
+            DomainName=cls.domain.name,
+            type=2
+        )
+
+        cls.disk_offering_child_domain = DiskOffering.create(cls.apiclient,
+                                                             cls.services["disk_offering"],
+                                                             domainid=cls.child_domain_1.id,
+                                                             zoneid=cls.zone.id,
+                                                             encrypt=True)
+        cls._cleanup.append(cls.disk_offering_child_domain)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListDiskOfferings, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_disk_offerings_id_filter(self):
+        """ Test list disk offerings with id filter
+        """
+        # List all disk offerings
+        disk_offerings = DiskOffering.list(self.apiclient, id=self.disk_offering.id)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings),
+            1,
+            "List disk offerings response has incorrect length"
+        )
+        # Verify the id of the disk offering returned is the same as the one requested
+        self.assertEqual(
+            disk_offerings[0].id,
+            self.disk_offering.id,
+            "List disk offerings should return the disk offering requested"
+        )
+
+        disk_offerings = DiskOffering.list(self.apiclient, id=-1)
+        self.assertIsNone(disk_offerings, "List disk offerings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_disk_offerings_name_filter(self):
+        """ Test list disk offerings with name filter
+        """
+        disk_offerings = DiskOffering.list(self.apiclient, name=self.services["disk_offering"]["name"])
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings),
+            2,
+            "List disk offerings response has incorrect length"
+        )
+        # Verify the name of the disk offering returned is the same as the one requested
+        self.assertEqual(
+            disk_offerings[0].name,
+            self.services["disk_offering"]["name"],
+            "List disk offerings should return the disk offering requested"
+        )
+        self.assertEqual(
+            disk_offerings[1].name,
+            self.services["disk_offering"]["name"],
+            "List disk offerings should return the disk offering requested"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_disk_offerings_zoneid_filter(self):
+        """ Test list disk offerings with zoneid filter
+        """
+        disk_offerings_zone_1 = DiskOffering.list(self.apiclient, zoneid=self.zone.id)
+        self.assertTrue(
+            isinstance(disk_offerings_zone_1, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings_zone_1) - len(self.disk_offerings),
+            2,
+            "List disk offerings response has incorrect length"
+        )
+
+        for disk_offering in disk_offerings_zone_1:
+            self.assertTrue(
+                disk_offering.zoneid is None or disk_offering.zoneid == self.zone.id,
+                "List disk offerings should return the disk offering requested"
+            )
+
+        if len(self.zones) > 1:
+            disk_offerings_zone_2 = DiskOffering.list(self.apiclient, zoneid=self.zones[1].id)
+            self.assertTrue(
+                isinstance(disk_offerings_zone_2, list),
+                "List disk offerings response is not a valid list"
+            )
+            for disk_offering in disk_offerings_zone_2:
+                self.assertTrue(
+                    disk_offering.zoneid is None or disk_offering.zoneid == self.zones[1].id,
+                    "List disk offerings should return the disk offering requested"
+                )
+
+            self.assertEqual(len(disk_offerings_zone_1) - len(disk_offerings_zone_2), 1)
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_disk_offerings_domainid_filter(self):
+        """ Test list disk offerings with domainid filter
+        """
+        disk_offerings = DiskOffering.list(self.apiclient, domainid=self.domain.id)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings),
+            1,
+            "List disk offerings response has incorrect length"
+        )
+        self.assertEqual(
+            disk_offerings[0].domainid,
+            self.domain.id,
+            "List disk offerings should return the disk offering requested"
+        )
+
+        disk_offerings = DiskOffering.list(self.apiclient, domainid=self.child_domain_1.id)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings),
+            1,
+            "List disk offerings response has incorrect length"
+        )
+        self.assertEqual(
+            disk_offerings[0].domainid,
+            self.child_domain_1.id,
+            "List disk offerings should return the disk offering requested"
+        )
+
+        disk_offerings = DiskOffering.list(self.apiclient, domainid=-1)
+        self.assertIsNone(disk_offerings, "List disk offerings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_disk_offerings_encrypted_filter(self):
+        """ Test list disk offerings with encrypted filter
+        """
+        disk_offerings = DiskOffering.list(self.apiclient, encrypt=True)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+
+        self.assertEqual(
+            len(disk_offerings),
+            1,
+            "List disk offerings response has incorrect length"
+        )
+        self.assertTrue(
+            disk_offerings[0].encrypt,
+            "List disk offerings should return the disk offering requested"
+        )
+
+        disk_offerings = DiskOffering.list(self.apiclient, encrypt=False)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings) - len(self.disk_offerings),
+            1,
+            "List disk offerings response has incorrect length"
+        )
+        for disk_offering in disk_offerings:
+            self.assertFalse(
+                disk_offering.encrypt,
+                "List disk offerings should return the disk offering requested"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_06_list_disk_offerings_keyword_filter(self):
+        """ Test list disk offerings with keyword filter
+        """
+        disk_offerings = DiskOffering.list(self.apiclient, keyword=self.disk_offering.name)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings),
+            2,
+            "List disk offerings response has incorrect length"
+        )
+        self.assertEqual(
+            disk_offerings[0].name,
+            self.disk_offering.name,
+            "List disk offerings should return the disk offering requested"
+        )
+        self.assertEqual(
+            disk_offerings[1].name,
+            self.disk_offering.name,
+            "List disk offerings should return the disk offering requested"
+        )
+
+        disk_offerings = DiskOffering.list(self.apiclient, keyword="random")
+        self.assertIsNone(disk_offerings, "List disk offerings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_07_list_disk_offering_isrecursive_filter(self):
+        """ Test list disk offerings with isrecursive parameter
+        """
+        disk_offerings = DiskOffering.list(self.domainadmin_api_client, isrecursive=True)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings) - len(self.disk_offerings),
+            2,
+            "List disk offerings response has incorrect length"
+        )
+
+        disk_offerings = DiskOffering.list(self.domainadmin_api_client, isrecursive=False)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings) - len(self.disk_offerings),
+            1,
+            "List disk offerings response has incorrect length"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_08_list_disk_offering_no_filter(self):
+        """ Test list disk offerings with no filters
+        """
+        disk_offerings = DiskOffering.list(self.apiclient)
+        self.assertTrue(
+            isinstance(disk_offerings, list),
+            "List disk offerings response is not a valid list"
+        )
+        self.assertEqual(
+            len(disk_offerings) - len(self.disk_offerings),
+            2,
+            "List disk offerings response has incorrect length"
+        )
diff --git a/test/integration/smoke/test_list_domains.py b/test/integration/smoke/test_list_domains.py
new file mode 100644
index 0000000..546ffbb
--- /dev/null
+++ b/test/integration/smoke/test_list_domains.py
@@ -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.
+""" Tests for API listing of domains with different filters
+"""
+
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import (Account,
+                             Domain)
+from marvin.lib.common import (get_domain, list_accounts)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListDomains(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListDomains, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.domain = get_domain(cls.apiclient)
+        cls.account = list_accounts(cls.apiclient, name="admin")[0]
+        cls._cleanup = []
+
+        cls.child_domain_1 = Domain.create(
+            cls.apiclient,
+            cls.services["domain"],
+            parentdomainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.child_domain_1)
+
+        cls.child_account_1 = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.child_domain_1.id
+        )
+        cls._cleanup.append(cls.child_account_1)
+
+        cls.child_account_apiclient = testClient.getUserApiClient(cls.child_account_1.user[0]['username'], cls.child_domain_1.name, type=2)
+
+        cls.child_domain_2 = Domain.create(
+            cls.apiclient,
+            cls.services["domain"],
+            parentdomainid=cls.child_domain_1.id
+        )
+        cls._cleanup.append(cls.child_domain_2)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListDomains, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_domains_id_filter(self):
+        """ Test list domains with id filter
+        """
+        # List all domains
+        domains = Domain.list(self.apiclient, id=self.domain.id)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            1,
+            "List Domain response has incorrect length"
+        )
+        self.assertEqual(
+            domains[0].id,
+            self.domain.id,
+            "Check if list domains returns valid domain"
+        )
+
+        # List all domains with a non-existent id
+        with self.assertRaises(Exception):
+            Domain.list(self.apiclient, id=-1)
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_domains_name_filter(self):
+        """ Test list domains with name filter
+        """
+        # List all domains
+        domains = Domain.list(self.apiclient, name=self.domain.name)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            1,
+            "List Domain response has incorrect length"
+        )
+        self.assertEqual(
+            domains[0].name,
+            self.domain.name,
+            "Check if list domains returns valid domain"
+        )
+
+        domains = Domain.list(self.apiclient, name="non-existent-domain")
+        self.assertIsNone(domains, "List Domain response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_domains_listall_filter(self):
+        """ Test list domains with listall parameter
+        """
+        # List all domains
+        domains = Domain.list(self.child_account_apiclient, listall=True)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            2,
+            "List Domain response has incorrect length"
+        )
+
+        domains = Domain.list(self.child_account_apiclient, listall=False)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            1,
+            "List Domain response has incorrect length"
+        )
+        self.assertEqual(
+            domains[0].id,
+            self.child_domain_1.id,
+            "Check if list domains returns valid domain"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_domains_level_filter(self):
+        """ Test list domains with level filter
+        """
+        # List all domains
+        domains = Domain.list(self.apiclient, level=0)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            1,
+            "List Domain response has incorrect length"
+        )
+        self.assertEqual(
+            domains[0].id,
+            self.domain.id,
+            "Check if list domains returns valid domain"
+        )
+
+        domains = Domain.list(self.apiclient, level=1)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            1,
+            "List Domain response has incorrect length"
+        )
+
+        domains = Domain.list(self.apiclient, level=2)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            1,
+            "List Domain response has incorrect length"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_domains_no_filter(self):
+        """ Test list domains with no filter
+        """
+        # List all domains
+        domains = Domain.list(self.apiclient)
+        self.assertEqual(
+            isinstance(domains, list),
+            True,
+            "List Domain response is not a valid list"
+        )
+        self.assertEqual(
+            len(domains),
+            3,
+            "List Domain response has incorrect length"
+        )
diff --git a/test/integration/smoke/test_list_hosts.py b/test/integration/smoke/test_list_hosts.py
new file mode 100644
index 0000000..7bae216
--- /dev/null
+++ b/test/integration/smoke/test_list_hosts.py
@@ -0,0 +1,372 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 API listing of hosts with different filters
+"""
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (Configurations, Host)
+from marvin.lib.common import (get_domain, list_accounts,
+                               list_zones, list_clusters)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListHosts(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListHosts, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls.zones = list_zones(cls.apiclient)
+        cls.zone = cls.zones[0]
+        cls.clusters = list_clusters(cls.apiclient)
+        cls.cluster = cls.clusters[0]
+        cls.hosts = Host.list(cls.apiclient)
+
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListHosts, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_hosts_no_filter(self):
+        """Test list hosts with no filter"""
+        hosts = Host.list(self.apiclient)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should greater than 0"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_hosts_clusterid_filter(self):
+        """Test list hosts with clusterid filter"""
+        hosts = Host.list(self.apiclient, clusterid=self.cluster.id)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.clusterid,
+                self.cluster.id,
+                "Host should be in the cluster %s" % self.cluster.id
+            )
+        with self.assertRaises(Exception):
+            hosts = Host.list(self.apiclient, clusterid="invalidclusterid")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_hosts_hahost_filter(self):
+        """Test list hosts with hahost filter"""
+        configs = Configurations.list(
+            self.apiclient,
+            name='ha.tag'
+        )
+        if isinstance(configs, list) and configs[0].value != "" and configs[0].value is not None:
+            hosts = Host.list(self.apiclient, hahost=True)
+            if hosts is not None:
+                self.assertTrue(
+                    isinstance(hosts, list),
+                    "Host response type should be a valid list"
+                )
+                self.assertGreater(
+                    len(hosts),
+                    0,
+                    "Length of host response should greater than 0"
+                )
+                for host in hosts:
+                    self.assertEqual(
+                        host.hahost,
+                        True,
+                        "Host should be a HA host"
+                    )
+
+            hosts = Host.list(self.apiclient, hahost=False)
+            if hosts is not None:
+                self.assertTrue(
+                    isinstance(hosts, list),
+                    "Host response type should be a valid list"
+                )
+                self.assertGreater(
+                    len(hosts),
+                    0,
+                    "Length of host response should greater than 0"
+                )
+                for host in hosts:
+                    self.assertTrue(
+                        host.hahost is None or host.hahost is False,
+                        "Host should not be a HA host"
+                    )
+        else:
+            self.debug("HA is not enabled in the setup")
+            hosts = Host.list(self.apiclient, hahost="invalidvalue")
+            self.assertTrue(
+                isinstance(hosts, list),
+                "Host response type should be a valid list"
+            )
+            self.assertGreater(
+                len(hosts),
+                0,
+                "Length of host response should greater than 0"
+            )
+            self.assertEqual(
+                len(hosts),
+                len(self.hosts),
+                "Length of host response should be equal to the length of hosts"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_hosts_hypervisor_filter(self):
+        """Test list hosts with hypervisor filter"""
+        hosts = Host.list(self.apiclient, hypervisor=self.hypervisor)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.hypervisor.lower(),
+                self.hypervisor.lower(),
+                "Host should be a %s hypervisor" % self.hypervisor
+            )
+
+        hosts = Host.list(self.apiclient, hypervisor="invalidhypervisor")
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertEqual(
+            len(hosts),
+            len(self.hosts),
+            "Length of host response should be equal to the length of hosts"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_hosts_id_filter(self):
+        """Test list hosts with id filter"""
+        hosts = Host.list(self.apiclient, id=self.hosts[0].id)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertEqual(
+            len(hosts),
+            1,
+            "Length of host response should be 1"
+        )
+        self.assertEqual(
+            hosts[0].id,
+            self.hosts[0].id,
+            "Host id should match with the host id in the list"
+        )
+
+        with self.assertRaises(Exception):
+            hosts = Host.list(self.apiclient, id="invalidid")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_06_list_hosts_keyword_filter(self):
+        """Test list hosts with keyword filter"""
+        hosts = Host.list(self.apiclient, keyword=self.hosts[0].name)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertIn(
+                host.name,
+                self.hosts[0].name,
+                "Host name should match with the host name in the list"
+            )
+
+        hosts = Host.list(self.apiclient, keyword="invalidkeyword")
+        self.assertIsNone(
+            hosts,
+            "Host response should be None"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_07_list_hosts_name_filter(self):
+        """Test list hosts with name filter"""
+        hosts = Host.list(self.apiclient, name=self.hosts[0].name)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertIn(
+                host.name,
+                self.hosts[0].name,
+                "Host name should match with the host name in the list"
+            )
+
+        hosts = Host.list(self.apiclient, name="invalidname")
+        self.assertIsNone(
+            hosts,
+            "Host response should be None"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_08_list_hosts_podid_filter(self):
+        """Test list hosts with podid filter"""
+        hosts = Host.list(self.apiclient, podid=self.hosts[0].podid)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.podid,
+                self.hosts[0].podid,
+                "Host podid should match with the host podid in the list"
+            )
+        with self.assertRaises(Exception):
+            hosts = Host.list(self.apiclient, podid="invalidpodid")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_09_list_hosts_resourcestate_filter(self):
+        """Test list hosts with resourcestate filter"""
+        hosts = Host.list(self.apiclient, resourcestate=self.hosts[0].resourcestate)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.resourcestate,
+                self.hosts[0].resourcestate,
+                "Host resourcestate should match with the host resourcestate in the list"
+            )
+
+        hosts = Host.list(self.apiclient, resourcestate="invalidresourcestate")
+        self.assertIsNone(
+            hosts,
+            "Host response should be None"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_10_list_hosts_state_filter(self):
+        """Test list hosts with state filter"""
+        hosts = Host.list(self.apiclient, state=self.hosts[0].state)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.state,
+                self.hosts[0].state,
+                "Host state should match with the host state in the list"
+            )
+
+        hosts = Host.list(self.apiclient, state="invalidstate")
+        self.assertIsNone(
+            hosts,
+            "Host response should be None"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_11_list_hosts_type_filter(self):
+        """Test list hosts with type filter"""
+        hosts = Host.list(self.apiclient, type=self.hosts[0].type)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.type,
+                self.hosts[0].type,
+                "Host type should match with the host type in the list"
+            )
+
+        hosts = Host.list(self.apiclient, type="invalidtype")
+        self.assertIsNone(
+            hosts,
+            "Host response should be None"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_12_list_hosts_zoneid_filter(self):
+        """Test list hosts with zoneid filter"""
+        hosts = Host.list(self.apiclient, zoneid=self.zone.id)
+        self.assertTrue(
+            isinstance(hosts, list),
+            "Host response type should be a valid list"
+        )
+        self.assertGreater(
+            len(hosts),
+            0,
+            "Length of host response should be greater than 0"
+        )
+        for host in hosts:
+            self.assertEqual(
+                host.zoneid,
+                self.zone.id,
+                "Host zoneid should match with the host zoneid in the list"
+            )
+
+        with self.assertRaises(Exception):
+            hosts = Host.list(self.apiclient, zoneid="invalidzoneid")
diff --git a/test/integration/smoke/test_list_service_offerings.py b/test/integration/smoke/test_list_service_offerings.py
new file mode 100644
index 0000000..3196754
--- /dev/null
+++ b/test/integration/smoke/test_list_service_offerings.py
@@ -0,0 +1,559 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 API listing of service offerings with different filters
+"""
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (Account,
+                             Domain,
+                             Volume,
+                             ServiceOffering,
+                             DiskOffering,
+                             VirtualMachine)
+from marvin.lib.common import (get_domain, list_accounts,
+                               list_zones, list_clusters, list_hosts)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListServiceOfferings(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListServiceOfferings, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls.domain = get_domain(cls.apiclient)
+        cls.zones = list_zones(cls.apiclient)
+        cls.zone = cls.zones[0]
+        cls.clusters = list_clusters(cls.apiclient)
+        cls.cluster = cls.clusters[0]
+        cls.hosts = list_hosts(cls.apiclient)
+        cls.account = list_accounts(cls.apiclient, name="admin")[0]
+        cls._cleanup = []
+        cls.service_offerings = ServiceOffering.list(cls.apiclient)
+        cls.system_service_offerings = ServiceOffering.list(cls.apiclient, issystem=True)
+
+        cls.child_domain_1 = Domain.create(
+            cls.apiclient,
+            cls.services["domain"],
+            parentdomainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.child_domain_1)
+
+        cls.account_1 = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.account_1)
+
+        cls.domainadmin_api_client = testClient.getUserApiClient(
+            UserName=cls.account_1.user[0].username,
+            DomainName=cls.domain.name,
+            type=2
+        )
+
+        cls.system_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["tiny"],
+            issystem=True,
+            name="custom_system_offering",
+            systemvmtype="domainrouter"
+        )
+        cls._cleanup.append(cls.system_offering)
+
+        cls.service_offering_1 = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["small"],
+            cpunumber=2,
+            cpuspeed=2000,
+            domainid=cls.child_domain_1.id,
+            encryptroot=True,
+            name="custom_offering_1",
+            zoneid=cls.zone.id
+        )
+        cls._cleanup.append(cls.service_offering_1)
+
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListServiceOfferings, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_service_offerings_cpunumber_filter(self):
+        """Test list service offerings with cpunumber filter
+        """
+        # List all service offerings with cpunumber 1
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            cpunumber=1
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings) - len(self.service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertGreaterEqual(
+                service_offering.cpunumber,
+                1,
+                "List ServiceOfferings response has incorrect cpunumber"
+            )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            cpunumber=99999
+        )
+        self.assertIsNone(service_offerings, "List ServiceOfferings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_service_offerings_cpuspeed_filter(self):
+        """Test list service offerings with cpuspeed filter
+        """
+        # List all service offerings with cpuspeed 1000
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            cpuspeed=1000
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertGreaterEqual(
+                service_offering.cpuspeed,
+                1000,
+                "List ServiceOfferings response has incorrect cpuspeed"
+            )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            cpuspeed=99999
+        )
+        self.assertIsNone(service_offerings, "List ServiceOfferings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_service_offerings_memory_filter(self):
+        """Test list service offerings with memory filter
+        """
+        # List all service offerings with memory 256
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            memory=256
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertGreaterEqual(
+                service_offering.memory,
+                256,
+                "List ServiceOfferings response has incorrect memory"
+            )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            memory=99999
+        )
+        self.assertIsNone(service_offerings, "List ServiceOfferings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_service_offerings_domainid_filter(self):
+        """Test list service offerings with domainid filter
+        """
+        # List all service offerings with domainid
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            domainid=self.domain.id
+        )
+        self.assertIsNone(
+            service_offerings,
+            "List ServiceOfferings response is not None"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            domainid=self.child_domain_1.id
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertEqual(
+                service_offering.domainid,
+                self.child_domain_1.id,
+                "List ServiceOfferings response has incorrect domainid"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_service_offerings_encryptroot_filter(self):
+        """Test list service offerings with encryptroot filter
+        """
+        # List all service offerings with encryptroot True
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            encryptroot=True
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertTrue(
+                service_offering.encryptroot,
+                "List ServiceOfferings response has incorrect encryptroot"
+            )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            encryptroot=False
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertFalse(
+                service_offering.encryptroot,
+                "List ServiceOfferings response has incorrect encryptroot"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_06_list_service_offerings_id_filter(self):
+        """Test list service offerings with id filter
+        """
+        # List all service offerings with id
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            id=self.system_offering.id
+        )
+        self.assertIsNone(
+            service_offerings,
+            "List ServiceOfferings response is not None"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            id=self.service_offering_1.id
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        self.assertEqual(
+            service_offerings[0].id,
+            self.service_offering_1.id,
+            "List ServiceOfferings response has incorrect id"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            id=-1
+        )
+        self.assertIsNone(service_offerings, "List ServiceOfferings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_07_list_service_offerings_isrecursive_filter(self):
+        """Test list service offerings with isrecursive filter
+        """
+        # List all service offerings with listall True
+        service_offerings = ServiceOffering.list(
+            self.domainadmin_api_client,
+            isrecursive=True
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            len(self.service_offerings) + 1,
+            "List ServiceOfferings response is empty"
+        )
+
+        # List all service offerings with isrecursive False
+        service_offerings = ServiceOffering.list(
+            self.domainadmin_api_client,
+            isrecursive=False
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(service_offerings),
+            len(self.service_offerings),
+            "List ServiceOfferings response is empty"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_08_list_service_offerings_issystem_filter(self):
+        """Test list service offerings with issystem filter
+        """
+        # List all service offerings with issystem True
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            issystem=True
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            len(self.system_service_offerings) + 1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertTrue(
+                service_offering.issystem,
+                "List ServiceOfferings response has incorrect issystem"
+            )
+
+        # List all service offerings with issystem False
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            issystem=False
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            len(self.service_offerings) + 1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertFalse(
+                service_offering.issystem,
+                "List ServiceOfferings response has incorrect issystem"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_09_list_service_offerings_keyword_filter(self):
+        """Test list service offerings with keyword filter
+        """
+        # List all service offerings with keyword
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            keyword=self.system_offering.name
+        )
+        self.assertIsNone(
+            service_offerings,
+            "List ServiceOfferings response is not None"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            keyword=self.service_offering_1.name
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        self.assertEqual(
+            service_offerings[0].name,
+            self.service_offering_1.name,
+            "List ServiceOfferings response has incorrect name"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            keyword="invalid"
+        )
+        self.assertIsNone(service_offerings, "List ServiceOfferings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_10_list_service_offerings_name_filter(self):
+        """Test list service offerings with name filter
+        """
+        # List all service offerings with name
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            name=self.system_offering.name
+        )
+        self.assertIsNone(
+            service_offerings,
+            "List ServiceOfferings response is not None"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            name=self.system_offering.name,
+            issystem=True
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        self.assertEqual(
+            service_offerings[0].name,
+            self.system_offering.name,
+            "List ServiceOfferings response has incorrect name"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            name=self.service_offering_1.name
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        self.assertEqual(
+            service_offerings[0].name,
+            self.service_offering_1.name,
+            "List ServiceOfferings response has incorrect name"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            name="invalid"
+        )
+        self.assertIsNone(service_offerings, "List ServiceOfferings response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_11_list_service_offerings_systemvmtype_filter(self):
+        """Test list service offerings with systemvmtype filter
+        """
+        # List all service offerings with systemvmtype domainrouter
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            systemvmtype="domainrouter"
+        )
+        self.assertIsNone(
+            service_offerings,
+            "List ServiceOfferings response is not None"
+        )
+
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            systemvmtype="domainrouter",
+            issystem=True
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(service_offerings),
+            1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertEqual(
+                service_offering.systemvmtype,
+                "domainrouter",
+                "List ServiceOfferings response has incorrect systemvmtype"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_12_list_service_offerings_zoneid_filter(self):
+        """Test list service offerings with zoneid filter
+        """
+        service_offerings = ServiceOffering.list(
+            self.apiclient,
+            zoneid=self.zone.id
+        )
+        self.assertTrue(
+            isinstance(service_offerings, list),
+            "List ServiceOfferings response is not a valid list"
+        )
+        self.assertEqual(
+            len(service_offerings),
+            len(self.service_offerings) + 1,
+            "List ServiceOfferings response is empty"
+        )
+        for service_offering in service_offerings:
+            self.assertTrue(
+                service_offering.zoneid is None or service_offering.zoneid == self.zone.id,
+                "List ServiceOfferings response has incorrect zoneid"
+            )
+
+        if len(self.zones) > 1:
+            service_offerings = ServiceOffering.list(
+                self.apiclient,
+                zoneid=self.zones[1].id
+            )
+            if service_offerings is not None:
+                self.assertTrue(
+                    isinstance(service_offerings, list),
+                    "List ServiceOfferings response is not a valid list"
+                )
+                self.assertEqual(
+                    len(service_offerings),
+                    len(self.service_offerings),
+                    "List ServiceOfferings response is empty"
+                )
diff --git a/test/integration/smoke/test_list_storage_pools.py b/test/integration/smoke/test_list_storage_pools.py
new file mode 100644
index 0000000..c2c075d
--- /dev/null
+++ b/test/integration/smoke/test_list_storage_pools.py
@@ -0,0 +1,396 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 API listing of storage pools with different filters
+"""
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (StoragePool)
+from marvin.lib.common import (get_domain, list_accounts,
+                               list_zones, list_clusters, list_hosts)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListStoragePools(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListStoragePools, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls.domain = get_domain(cls.apiclient)
+        cls.zones = list_zones(cls.apiclient)
+        cls.zone = cls.zones[0]
+        cls.clusters = list_clusters(cls.apiclient)
+        cls.cluster = cls.clusters[0]
+        cls.hosts = list_hosts(cls.apiclient)
+        cls.account = list_accounts(cls.apiclient, name="admin")[0]
+        cls.storage_pools = StoragePool.list(cls.apiclient)
+
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListStoragePools, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_storage_pools_clusterid_filter(self):
+        """ Test list storage pools by clusterid filter
+        """
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            clusterid=self.cluster.id
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.clusterid,
+                self.cluster.id,
+                "Cluster id should be equal to the cluster id passed in the filter"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            clusterid="-1"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid cluster id is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_storage_pools_id_filter(self):
+        """ Test list storage pools by id filter
+        """
+        valid_id = self.storage_pools[0].id
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            id=valid_id
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertEqual(
+            len(storage_pools),
+            1,
+            "Length of storage pools should be equal to 1"
+        )
+        self.assertEqual(
+            storage_pools[0].id,
+            valid_id,
+            "Cluster id should be equal to the cluster id passed in the filter"
+        )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            id="-1"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid cluster id is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_storage_pools_ipaddress_filter(self):
+        """ Test list storage pools by ipaddress filter
+        """
+        valid_ipaddress = self.storage_pools[0].ipaddress
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            ipaddress=valid_ipaddress
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.ipaddress,
+                valid_ipaddress,
+                "IP address should be equal to the ip address passed in the filter"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            ipaddress="1.1.1.1"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid ip address is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_storage_pools_keyword_filter(self):
+        """ Test list storage pools by keyword filter
+        """
+        valid_keyword = self.storage_pools[0].name
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            keyword=valid_keyword
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertIn(
+                valid_keyword,
+                storage_pool.name,
+                "Keyword should be present in the storage pool name"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            keyword="invalid"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid keyword is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_storage_pools_name_filter(self):
+        """ Test list storage pools by name filter
+        """
+        valid_name = self.storage_pools[0].name
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            name=valid_name
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.name,
+                valid_name,
+                "Name should be equal to the name passed in the filter"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            name="invalid"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid name is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_06_list_storage_pools_path_filter(self):
+        """ Test list storage pools by path filter
+        """
+        valid_path = self.storage_pools[0].path
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            path=valid_path
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.path,
+                valid_path,
+                "Path should be equal to the path passed in the filter"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            path="invalid"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid path is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_07_list_storage_pools_podid_filter(self):
+        """ Test list storage pools by podid filter
+        """
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            podid=self.cluster.podid
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.podid,
+                self.cluster.podid,
+                "Pod id should be equal to the pod id passed in the filter"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            podid="-1"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid pod id is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_08_list_storage_pools_scope_filter(self):
+        """ Test list storage pools by scope filter
+        """
+        valid_scope = self.storage_pools[0].scope
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            scope=valid_scope
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.scope,
+                valid_scope,
+                "Scope should be equal to the scope passed in the filter"
+            )
+        with self.assertRaises(Exception):
+            storage_pools = StoragePool.list(
+                self.apiclient,
+                scope="invalid"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_09_list_storage_pools_status_filter(self):
+        """ Test list storage pools by status filter
+        """
+        valid_status = self.storage_pools[0].status
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            status=valid_status
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.status,
+                valid_status,
+                "State should be equal to the status passed in the filter"
+            )
+        with self.assertRaises(Exception):
+            storage_pools = StoragePool.list(
+                self.apiclient,
+                status="invalid"
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_10_list_storage_pools_zoneid_filter(self):
+        """ Test list storage pools by zoneid filter
+        """
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            zoneid=self.zone.id
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
+        for storage_pool in storage_pools:
+            self.assertEqual(
+                storage_pool.zoneid,
+                self.zone.id,
+                "Zone id should be equal to the zone id passed in the filter"
+            )
+
+        storage_pools = StoragePool.list(
+            self.apiclient,
+            zoneid="-1"
+        )
+        self.assertIsNone(
+            storage_pools,
+            "Response should be empty when invalid zone id is passed"
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_11_list_storage_pools_no_filter(self):
+        """ Test list storage pools with no filter
+        """
+        storage_pools = StoragePool.list(
+            self.apiclient
+        )
+        self.assertTrue(
+            isinstance(storage_pools, list),
+            "Storage pool response type should be a list"
+        )
+        self.assertGreater(
+            len(storage_pools),
+            0,
+            "Length of storage pools should greater than 0"
+        )
diff --git a/test/integration/smoke/test_list_volumes.py b/test/integration/smoke/test_list_volumes.py
new file mode 100644
index 0000000..d21ce5d
--- /dev/null
+++ b/test/integration/smoke/test_list_volumes.py
@@ -0,0 +1,615 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 API listing of volumes with different filters
+"""
+# Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (Account,
+                             Domain,
+                             Volume,
+                             ServiceOffering,
+                             Tag,
+                             DiskOffering,
+                             VirtualMachine)
+from marvin.lib.common import (get_domain, list_accounts,
+                               list_zones, list_clusters, list_hosts, get_suitable_test_template)
+# Import System modules
+from nose.plugins.attrib import attr
+
+_multiprocess_shared_ = True
+
+
+class TestListVolumes(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestListVolumes, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = testClient.getHypervisorInfo()
+        cls.domain = get_domain(cls.apiclient)
+        cls.zones = list_zones(cls.apiclient)
+        cls.zone = cls.zones[0]
+        cls.clusters = list_clusters(cls.apiclient)
+        cls.cluster = cls.clusters[0]
+        cls.hosts = list_hosts(cls.apiclient)
+        cls.account = list_accounts(cls.apiclient, name="admin")[0]
+        cls._cleanup = []
+
+        cls.service_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["tiny"]
+        )
+        cls._cleanup.append(cls.service_offering)
+
+        template = get_suitable_test_template(
+            cls.apiclient,
+            cls.zone.id,
+            cls.services["ostype"],
+            cls.hypervisor
+        )
+        if template == FAILED:
+            assert False, "get_test_template() failed to return template"
+
+        cls.services["template"]["ostypeid"] = template.ostypeid
+        cls.services["template_2"]["ostypeid"] = template.ostypeid
+        cls.services["ostypeid"] = template.ostypeid
+        cls.services["virtual_machine"]["zoneid"] = cls.zone.id
+        cls.services["mode"] = cls.zone.networktype
+
+        cls.disk_offering = DiskOffering.create(cls.apiclient,
+                                                cls.services["disk_offering"])
+        cls._cleanup.append(cls.disk_offering)
+
+        # Create VM
+        cls.virtual_machine = VirtualMachine.create(
+            cls.apiclient,
+            cls.services["virtual_machine"],
+            templateid=template.id,
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            clusterid=cls.cluster.id,
+            serviceofferingid=cls.service_offering.id,
+            mode=cls.services["mode"]
+        )
+
+        cls.child_domain = Domain.create(
+            cls.apiclient,
+            cls.services["domain"])
+        cls._cleanup.append(cls.child_domain)
+
+        cls.child_account = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.child_domain.id)
+        cls._cleanup.append(cls.child_account)
+
+        cls.vol_1 = Volume.create(cls.apiclient,
+                                  cls.services["volume"],
+                                  zoneid=cls.zone.id,
+                                  account=cls.account.name,
+                                  domainid=cls.account.domainid,
+                                  diskofferingid=cls.disk_offering.id)
+        cls._cleanup.append(cls.vol_1)
+
+        cls.vol_1 = cls.virtual_machine.attach_volume(
+            cls.apiclient,
+            cls.vol_1
+        )
+        cls._cleanup.append(cls.virtual_machine)
+
+        Tag.create(cls.apiclient, cls.vol_1.id, "Volume", {"abc": "xyz"})
+
+        cls.vol_2 = Volume.create(cls.apiclient,
+                                  cls.services["volume"],
+                                  zoneid=cls.zone.id,
+                                  account=cls.account.name,
+                                  domainid=cls.account.domainid,
+                                  diskofferingid=cls.disk_offering.id)
+
+        cls._cleanup.append(cls.vol_2)
+
+        cls.vol_3 = Volume.create(cls.apiclient,
+                                  cls.services["volume"],
+                                  zoneid=cls.zone.id,
+                                  account=cls.child_account.name,
+                                  domainid=cls.child_account.domainid,
+                                  diskofferingid=cls.disk_offering.id)
+        cls._cleanup.append(cls.vol_3)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestListVolumes, cls).tearDownClass()
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_01_list_volumes_account_domain_filter(self):
+        """Test listing Volumes with account & domain filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            3,
+            "ListVolumes response expected 3 Volumes, received %s" % len(list_volume_response)
+        )
+
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            account=self.child_account.name,
+            domainid=self.child_account.domainid
+        )
+
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 Volume, received %s" % len(list_volume_response)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_02_list_volumes_diskofferingid_filter(self):
+        """Test listing Volumes with diskofferingid filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            account=self.account.name,
+            domainid=self.account.domainid,
+            diskofferingid=self.disk_offering.id
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            2,
+            "ListVolumes response expected 2 Volumes, received %s" % len(list_volume_response)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_03_list_volumes_id_filter(self):
+        """Test listing Volumes with id filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            id=self.vol_1.id
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 Volume, received %s" % len(list_volume_response)
+        )
+        self.assertEqual(
+            list_volume_response[0].id,
+            self.vol_1.id,
+            "ListVolumes response expected Volume with id %s, received %s" % (self.vol_1.id, list_volume_response[0].id)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_04_list_volumes_ids_filter(self):
+        """Test listing Volumes with ids filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            ids=[self.vol_1.id, self.vol_2.id, self.vol_3.id]
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            2,
+            "ListVolumes response expected 2 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(list_volume_response[0].id, [self.vol_1.id, self.vol_2.id],
+                      "ListVolumes response Volume 1 not in list")
+        self.assertIn(list_volume_response[1].id, [self.vol_1.id, self.vol_2.id],
+                      "ListVolumes response Volume 2 not in list")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_05_list_volumes_isrecursive(self):
+        """Test listing Volumes with isrecursive filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            isrecursive=True,
+            domainid=self.account.domainid
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len([v for v in list_volume_response if v.state != "Destroy"]),
+            4,
+            "ListVolumes response expected 4 Volumes, received %s" % len(list_volume_response)
+        )
+
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            isrecursive=False,
+            domainid=self.account.domainid
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len([v for v in list_volume_response if v.state != "Destroy"]),
+            3,
+            "ListVolumes response expected 3 Volumes, received %s" % len(list_volume_response)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_06_list_volumes_keyword_filter(self):
+        """Test listing Volumes with keyword filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            keyword=self.services["volume"]["diskname"]
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            2,
+            "ListVolumes response expected 2 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(
+            list_volume_response[0].id, [self.vol_1.id, self.vol_2.id],
+            "ListVolumes response Volume 1 not in list")
+        self.assertIn(list_volume_response[1].id, [self.vol_1.id, self.vol_2.id],
+                      "ListVolumes response Volume 2 not in list")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_07_list_volumes_listall(self):
+        """Test listing Volumes with listall filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            listall=True
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len([v for v in list_volume_response if v.state != "Destroy"]),
+            4,
+            "ListVolumes response expected 4 Volumes, received %s" % len(list_volume_response)
+        )
+
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            listall=False
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len([v for v in list_volume_response if v.state != "Destroy"]),
+            3,
+            "ListVolumes response expected 3 Volumes, received %s" % len(list_volume_response)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_08_listsystemvms(self):
+        list_volumes_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            listsystemvms=True
+        )
+        self.assertEqual(
+            isinstance(list_volumes_response, list),
+            True,
+            "List Volume response is not a valid list"
+        )
+        self.assertGreater(
+            len(list_volumes_response),
+            3,
+            "ListVolumes response expected more than 3 Volumes, received %s" % len(list_volumes_response)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_09_list_volumes_name_filter(self):
+        """Test listing Volumes with name filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            name=self.vol_1.name
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertEqual(
+            list_volume_response[0].id,
+            self.vol_1.id,
+            "ListVolumes response expected Volume with id %s, received %s" % (self.vol_1.id, list_volume_response[0].id)
+        )
+        self.assertEqual(
+            list_volume_response[0].name,
+            self.vol_1.name,
+            "ListVolumes response expected Volume with name %s, received %s" % (
+                self.vol_1.name, list_volume_response[0].name)
+        )
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_10_list_volumes_podid_filter(self):
+        """Test listing Volumes with podid filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            podid=self.vol_1.podid
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertGreater(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected more than 1 Volume, received %s" % len(list_volume_response)
+        )
+        self.assertIn(self.vol_1.id, [volume.id for volume in list_volume_response],
+                      "ListVolumes response expected Volume with id %s" % self.vol_1.id)
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_11_list_volumes_state_filter(self):
+        """Test listing Volumes with state filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            state="Ready"
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            2,
+            "ListVolumes response expected 2 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(self.vol_1.id, [volume.id for volume in list_volume_response],
+                      "ListVolumes response expected Volume with id %s" % self.vol_1.id)
+
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            state="Allocated"
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertEqual(self.vol_2.id, list_volume_response[0].id,
+                         "ListVolumes response expected Volume with id %s" % self.vol_3.id)
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_12_list_volumes_storageid_filter(self):
+        """Test listing Volumes with storageid filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            storageid=self.vol_1.storageid
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 or more Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(self.vol_1.id, [volume.id for volume in list_volume_response],
+                      "ListVolumes response expected Volume with id %s" % self.vol_1.id)
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_13_list_volumes_type_filter(self):
+        """Test listing Volumes with type filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            type="DATADISK"
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            2,
+            "ListVolumes response expected 2 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(self.vol_1.id, [volume.id for volume in list_volume_response],
+                      "ListVolumes response expected Volume with id %s" % self.vol_1.id)
+
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            type="ROOT"
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertNotIn(list_volume_response[0].id, [self.vol_1.id, self.vol_2.id],
+                         "ListVolumes response expected ROOT Volume")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_14_list_volumes_virtualmachineid_filter(self):
+        """Test listing Volumes with virtualmachineid filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id,
+            virtualmachineid=self.vol_1.virtualmachineid
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            2,
+            "ListVolumes response expected 2 Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(self.vol_1.id, [volume.id for volume in list_volume_response],
+                      "ListVolumes response expected Volume with id %s" % self.vol_1.id)
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_15_list_volumes_zoneid_filter(self):
+        """Test listing Volumes with zoneid filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zones[0].id
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            3,
+            "ListVolumes response expected 3 Volumes, received %s" % len(list_volume_response)
+        )
+
+        if len(self.zones) > 1:
+            list_volume_response = Volume.list(
+                self.apiclient,
+                zoneid=self.zones[1].id
+            )
+            self.assertIsNone(list_volume_response, "List Volume response is not None")
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_16_list_volumes_tags_filter(self):
+        """Test listing Volumes with tags filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            tags=[{"key": "abc", "value": "xyz"}]
+        )
+
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertEqual(
+            len(list_volume_response),
+            1,
+            "ListVolumes response expected 1 or more Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertEqual(
+            list_volume_response[0].id,
+            self.vol_1.id,
+            "ListVolumes response expected Volume with id %s, received %s" % (self.vol_1.id, list_volume_response[0].id)
+        )
+        self.assertEqual(
+            list_volume_response[0].tags[0]["key"],
+            "abc",
+            "ListVolumes response expected Volume with tag key abc, received %s" % list_volume_response[0].tags[0]["key"]
+        )
+        self.assertEqual(
+            list_volume_response[0].tags[0]["value"],
+            "xyz",
+            "ListVolumes response expected Volume with tag value xyz, received %s" % list_volume_response[0].tags[0]["value"]
+        )
+
+        list_volume_response = Volume.list(
+            self.apiclient,
+            tags=[{"key": "abc", "value": "xyz1"}]
+        )
+        self.assertIsNone(list_volume_response, "List Volume response is not None")
+        with self.assertRaises(Exception):
+            list_volume_response = Volume.list(
+                self.apiclient,
+                tags=[{"key": None, "value": None}]
+            )
+
+
+    @attr(tags=["advanced", "advancedns", "smoke", "basic"], required_hardware="false")
+    def test_17_list_volumes_no_filter(self):
+        """Test listing Volumes with no filter
+        """
+        list_volume_response = Volume.list(
+            self.apiclient,
+            zoneid=self.zone.id
+        )
+        self.assertTrue(
+            isinstance(list_volume_response, list),
+            "List Volume response is not a valid list"
+        )
+        self.assertGreaterEqual(
+            len(list_volume_response),
+            3,
+            "ListVolumes response expected 3 or more Volumes, received %s" % len(list_volume_response)
+        )
+        self.assertIn(self.vol_1.id, [volume.id for volume in list_volume_response],
+                      "ListVolumes response expected Volume with id %s" % self.vol_1.id)
diff --git a/test/integration/smoke/test_metrics_api.py b/test/integration/smoke/test_metrics_api.py
index d5ad559..ab2644f 100644
--- a/test/integration/smoke/test_metrics_api.py
+++ b/test/integration/smoke/test_metrics_api.py
@@ -289,7 +289,6 @@
         self.assertTrue(hasattr(li, 'hosts'))
 
         self.assertEqual(li.hosts, len(list_hosts(self.apiclient,
-            zoneid=self.zone.id,
             type='Routing')))
 
         self.assertTrue(hasattr(li, 'imagestores'))
@@ -518,7 +517,7 @@
         self.cleanup.append(self.small_virtual_machine)
 
         currentHost = Host.list(self.apiclient, id=self.small_virtual_machine.hostid)[0]
-        if currentHost.hypervisor.lower() == "xenserver" and currentHost.hypervisorversion == "7.1.0":
+        if currentHost.hypervisor.lower() == "xenserver":
             # Skip tests as volume metrics doesn't see to work
             self.skipTest("Skipping test because volume metrics doesn't work on hypervisor\
                             %s, %s" % (currentHost.hypervisor, currentHost.hypervisorversion))
diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py
index 0c37c08..8f3f4f5 100644
--- a/test/integration/smoke/test_network.py
+++ b/test/integration/smoke/test_network.py
@@ -72,9 +72,6 @@
 
 class TestPublicIP(cloudstackTestCase):
 
-    def setUp(self):
-        self.apiclient = self.testClient.getApiClient()
-
     @classmethod
     def setUpClass(cls):
         testClient = super(TestPublicIP, cls).getClsTestClient()
@@ -85,6 +82,7 @@
         cls.domain = get_domain(cls.apiclient)
         cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
         cls.services['mode'] = cls.zone.networktype
+        cls._cleanup = []
         # Create Accounts & networks
         cls.account = Account.create(
             cls.apiclient,
@@ -92,18 +90,21 @@
             admin=True,
             domainid=cls.domain.id
         )
+        cls._cleanup.append(cls.account)
 
         cls.user = Account.create(
             cls.apiclient,
             cls.services["account"],
             domainid=cls.domain.id
         )
+        cls._cleanup.append(cls.user)
         cls.services["network"]["zoneid"] = cls.zone.id
 
         cls.network_offering = NetworkOffering.create(
             cls.apiclient,
             cls.services["network_offering"],
         )
+        cls._cleanup.append(cls.network_offering)
         # Enable Network offering
         cls.network_offering.update(cls.apiclient, state='Enabled')
 
@@ -114,17 +115,20 @@
             cls.account.name,
             cls.account.domainid
         )
+        cls._cleanup.append(cls.account_network)
         cls.user_network = Network.create(
             cls.apiclient,
             cls.services["network"],
             cls.user.name,
             cls.user.domainid
         )
+        cls._cleanup.append(cls.user_network)
 
         cls.service_offering = ServiceOffering.create(
             cls.apiclient,
             cls.services["service_offerings"]["tiny"],
         )
+        cls._cleanup.append(cls.service_offering)
 
         cls.hypervisor = testClient.getHypervisorInfo()
         cls.template = get_test_template(
@@ -146,6 +150,7 @@
             networkids=cls.account_network.id,
             serviceofferingid=cls.service_offering.id
         )
+        cls._cleanup.append(cls.account_vm)
 
         cls.user_vm = VirtualMachine.create(
             cls.apiclient,
@@ -156,6 +161,7 @@
             networkids=cls.user_network.id,
             serviceofferingid=cls.service_offering.id
         )
+        cls._cleanup.append(cls.user_vm)
 
         # Create Source NAT IP addresses
         PublicIPAddress.create(
@@ -170,25 +176,11 @@
             cls.zone.id,
             cls.user.domainid
         )
-        cls._cleanup = [
-            cls.account_vm,
-            cls.user_vm,
-            cls.account_network,
-            cls.user_network,
-            cls.account,
-            cls.user,
-            cls.network_offering
-        ]
         return
 
     @classmethod
     def tearDownClass(cls):
-        try:
-            # Cleanup resources used
-            cleanup_resources(cls.apiclient, cls._cleanup)
-        except Exception as e:
-            raise Exception("Warning: Exception during cleanup : %s" % e)
-        return
+        super(TestPublicIP, cls).tearDownClass()
 
     @attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false")
     def test_public_ip_admin_account(self):
@@ -879,7 +871,7 @@
 
     @attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false")
     def test_releaseIP(self):
-        """Test for release public IP address"""
+        """Test for release public IP address using the ID"""
 
         logger.debug("Deleting Public IP : %s" % self.ip_addr.id)
 
@@ -938,6 +930,66 @@
             )
         return
 
+    @attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false")
+    def test_releaseIP_using_IP(self):
+        """Test for release public IP address using the address"""
+
+        logger.debug("Deleting Public IP : %s" % self.ip_addr.ipaddress)
+        self.ip_address.delete_by_ip(self.apiclient)
+
+        retriesCount = 10
+        isIpAddressDisassociated = False
+        while retriesCount > 0:
+            listResponse = list_publicIP(
+                self.apiclient,
+                id=self.ip_addr.id,
+                state="Allocated"
+            )
+            if listResponse is None:
+                isIpAddressDisassociated = True
+                break
+            retriesCount -= 1
+            time.sleep(60)
+        # End while
+
+        self.assertTrue(
+            isIpAddressDisassociated,
+            "Failed to disassociate IP address")
+
+        # ListPortForwardingRules should not list
+        # associated rules with Public IP address
+        try:
+            list_nat_rule = list_nat_rules(
+                self.apiclient,
+                id=self.nat_rule.id
+            )
+            logger.debug("List NAT Rule response" + str(list_nat_rule))
+        except CloudstackAPIException:
+            logger.debug("Port Forwarding Rule is deleted")
+
+        # listLoadBalancerRules should not list
+        # associated rules with Public IP address
+        try:
+            list_lb_rule = list_lb_rules(
+                self.apiclient,
+                id=self.lb_rule.id
+            )
+            logger.debug("List LB Rule response" + str(list_lb_rule))
+        except CloudstackAPIException:
+            logger.debug("Port Forwarding Rule is deleted")
+
+        # SSH Attempt though public IP should fail
+        with self.assertRaises(Exception):
+            SshClient(
+                self.ip_addr.ipaddress,
+                self.services["natrule"]["publicport"],
+                self.virtual_machine.username,
+                self.virtual_machine.password,
+                retries=2,
+                delay=0
+            )
+        return
+
 
 class TestDeleteAccount(cloudstackTestCase):
 
diff --git a/test/integration/smoke/test_object_stores.py b/test/integration/smoke/test_object_stores.py
new file mode 100644
index 0000000..710fce6
--- /dev/null
+++ b/test/integration/smoke/test_object_stores.py
@@ -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.
+""" BVT tests for Object Storage Pool"""
+
+#Import Local Modules
+from marvin.cloudstackTestCase import cloudstackTestCase
+from nose.plugins.attrib import attr
+from marvin.lib.base import (ObjectStoragePool)
+from marvin.lib.utils import (cleanup_resources)
+
+_multiprocess_shared_ = True
+
+class TestObjectStore(cloudstackTestCase):
+
+    def setUp(self):
+        self.services = self.testClient.getParsedTestDataConfig()
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+        return
+
+    def tearDown(self):
+        try:
+            #Clean up, terminate the created resources
+            cleanup_resources(self.apiclient, self.cleanup)
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return
+
+    @attr(tags=["smoke"], required_hardware="false")
+    def test_01_create_object_store(self):
+        """Test to create object store
+        """
+
+        object_store = ObjectStoragePool.create(
+            self.apiclient,
+            "testOS-10",
+            "http://192.168.0.1",
+            "Simulator",
+            None
+        )
+
+        self.debug("Created Object Store with ID: %s" % object_store.id)
+
+        list_object_stores_response = ObjectStoragePool.list(
+            self.apiclient,
+            id=object_store.id
+        )
+
+        self.assertNotEqual(
+            len(list_object_stores_response),
+            0,
+            "Check List Object Store response"
+        )
+
+        object_store_response = list_object_stores_response[0]
+        self.assertEqual(
+            "Simulator",
+            object_store_response.providername,
+            "Check Provider of the created Object Store"
+        )
+        self.assertEqual(
+            "testOS-10",
+            object_store_response.name,
+            "Check Name of the created Object Store"
+        )
+        self.assertEqual(
+            "http://192.168.0.1",
+            object_store_response.url,
+            "Check URL of the created Object Store"
+        )
+
+        object_store.update(
+            self.apiclient,
+            name="updated_name"
+        )
+
+        list_object_stores_response_updated = ObjectStoragePool.list(
+            self.apiclient,
+            id=object_store.id
+        )
+
+        object_store_response_updated = list_object_stores_response_updated[0]
+
+        self.assertEqual(
+            "updated_name",
+            object_store_response_updated.name,
+            "Check Name of the updated Object Store name"
+        )
+
+        self.cleanup.append(object_store)
+
+        return
diff --git a/test/integration/smoke/test_private_roles.py b/test/integration/smoke/test_private_roles.py
new file mode 100644
index 0000000..32d4883
--- /dev/null
+++ b/test/integration/smoke/test_private_roles.py
@@ -0,0 +1,275 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 *
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import Account, Role, User
+from marvin.lib.utils import cleanup_resources
+from nose.plugins.attrib import attr
+
+import random
+
+
+class TestData(object):
+    """Test data object that is required to create resources
+    """
+    def __init__(self):
+        self.testdata = {
+            "accountadmin": {
+                "email": "mtu@test.cloud",
+                "firstname": "Marvin",
+                "lastname": "TestAdminAccount",
+                "username": "TestAdminAccount",
+                "password": "password"
+            },
+            "accountdomainadmin": {
+                "email": "mtu@test.cloud",
+                "firstname": "Marvin",
+                "lastname": "TestDomainAdminAccount",
+                "username": "TestDomainAdminAccount",
+                "password": "password"
+            },
+            "accountroleuser": {
+                "email": "mtu@test.cloud",
+                "firstname": "Marvin",
+                "lastname": "TestUserAccount",
+                "username": "TestUserAccount",
+                "password": "password"
+            },
+            "roleadmin": {
+                "name": "MarvinFake Admin Role ",
+                "type": "Admin",
+                "description": "Fake Admin Role created by Marvin test"
+            },
+            "roleuser": {
+                "name": "MarvinFake User Role ",
+                "type": "User",
+                "description": "Fake User Role created by Marvin test",
+                "ispublic": False
+            },
+            "publicrole": {
+                "name": "MarvinFake Public Role ",
+                "type": "User",
+                "description": "Fake Public Role created by Marvin test"
+            },
+            "importrole": {
+                "name": "MarvinFake Import Role ",
+                "type": "User",
+                "description": "Fake Import User Role created by Marvin test",
+                "ispublic": True,
+                "rules": [{"rule":"list*", "permission":"allow","description":"Listing apis"},
+                           {"rule":"get*", "permission":"allow","description":"Get apis"},
+                           {"rule":"update*", "permission":"deny","description":"Update apis"}]
+            },
+            "roledomainadmin": {
+                "name": "MarvinFake DomainAdmin Role ",
+                "type": "DomainAdmin",
+                "description": "Fake Domain-Admin Role created by Marvin test",
+                "ispublic": False
+            },
+            "apiConfig": {
+                "listApis": "allow",
+                "listAccounts": "allow",
+                "listClusters": "deny",
+                "*VM*": "allow",
+                "*Host*": "deny"
+            }
+        }
+
+
+class TestPrivateRoles(cloudstackTestCase):
+    """Tests Visibility of private and public roles
+    """
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.testdata = TestData().testdata
+
+        self.testdata["roleadmin"]["name"] += self.getRandomString()
+        self.testdata["roledomainadmin"]["name"] += self.getRandomString()
+        self.testdata["roleuser"]["name"] += self.getRandomString()
+        self.cleanup = []
+
+        self.role_admin = Role.create(
+            self.apiclient,
+            self.testdata["roleadmin"]
+        )
+        self.cleanup.append(self.role_admin)
+
+        self.role_domain_admin = Role.create(
+            self.apiclient,
+            self.testdata["roledomainadmin"]
+        )
+        self.cleanup.append(self.role_domain_admin)
+
+        self.private_role = Role.create(
+            self.apiclient,
+            self.testdata["roleuser"]
+        )
+        self.cleanup.append(self.private_role)
+
+        self.account_admin = Account.create(
+            self.apiclient,
+            self.testdata["accountadmin"],
+            roleid=self.role_admin.id
+        )
+        self.cleanup.append(self.account_admin)
+
+        self.account_domain_admin = Account.create(
+            self.apiclient,
+            self.testdata["accountdomainadmin"],
+            roleid=self.role_domain_admin.id
+        )
+        self.cleanup.append(self.account_domain_admin)
+
+        self.admin_apiclient = self.testClient.getUserApiClient(
+            UserName=self.account_admin.name,
+            DomainName='ROOT',
+            type=1
+        )
+
+        self.domain_admin_apiclient = self.testClient.getUserApiClient(
+            UserName=self.account_domain_admin.name,
+            DomainName='ROOT',
+            type=2
+        )
+
+    def tearDown(self):
+        super(TestPrivateRoles, self).tearDown()
+
+    def getRandomString(self):
+        return "".join(random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for _ in range(10))
+
+    def asserts_visibility_of_private_role(self, role_id):
+        list_roles_domain_admin = Role.list(self.domain_admin_apiclient, id=role_id)
+        self.assertEqual(
+            list_roles_domain_admin,
+            None,
+            "Domain Admins should not be able to list private roles"
+        )
+
+        list_roles_admin = Role.list(self.admin_apiclient, id=role_id)
+        self.assertNotEqual(
+            list_roles_admin,
+            None,
+            "Admins should be able to list private roles"
+        )
+
+    def asserts_visibility_of_public_role(self, role_id):
+        list_roles_domain_admin = Role.list(self.domain_admin_apiclient, id=role_id)
+        self.assertNotEqual(
+            list_roles_domain_admin,
+            None,
+            "Domain Admins should be able to list public roles"
+        )
+
+        list_roles_admin = Role.list(self.admin_apiclient, id=role_id)
+        self.assertNotEqual(
+            list_roles_admin,
+            None,
+            "Admins should be able to list public roles"
+        )
+
+    @attr(tags=['simulator', 'basic'], required_hardware=False)
+    def test_create_role(self):
+        """
+            1. Create a private role
+            2. Create a public role
+            3. Verify whether their visibility is as expected
+        """
+        self.testdata["roleuser"]["name"] += self.getRandomString()
+        self.testdata["publicrole"]["name"] += self.getRandomString()
+        private_role = Role.create(
+            self.apiclient,
+            self.testdata["roleuser"]
+        )
+        self.cleanup.append(self.private_role)
+        public_role = Role.create(
+            self.apiclient,
+            self.testdata["publicrole"]
+        )
+        self.cleanup.append(self.public_role)
+        self.asserts_visibility_of_private_role(private_role.id)
+        self.asserts_visibility_of_public_role(public_role.id)
+
+    @attr(tags=['simulator', 'basic'], required_hardware=False)
+    def test_update_role(self):
+        """
+            1. Create a public role
+            2. Check if its visibility is public
+            3. Update it to make it private
+            4. Verify if its visibility is private
+        """
+        self.testdata["publicrole"]["name"] += self.getRandomString()
+        role = Role.create(
+            self.apiclient,
+            self.testdata["publicrole"]
+        )
+        self.cleanup.append(role)
+        self.asserts_visibility_of_public_role(role.id)
+        role.update(self.apiclient, id=role.id, ispublic=False)
+        self.asserts_visibility_of_private_role(role.id)
+
+    @attr(tags=['simulator', 'basic'], required_hardware=False)
+    def test_import_role(self):
+        """
+            1. Import a public role
+            2. Import a private role
+            3. Verify their visibility
+        """
+        self.testdata["importrole"]["name"] += self.getRandomString()
+        imported_public_role = Role.importRole(
+            self.apiclient,
+            self.testdata["importrole"]
+        )
+        self.cleanup.append(imported_public_role)
+        self.testdata["importrole"]["name"] += self.getRandomString()
+        self.testdata["importrole"]["ispublic"] = False
+        imported_private_role = Role.importRole(
+            self.apiclient,
+            self.testdata["importrole"]
+        )
+        self.cleanup.append(imported_private_role)
+
+        self.asserts_visibility_of_public_role(imported_public_role.id)
+        self.asserts_visibility_of_private_role(imported_private_role.id)
+
+    @attr(tags=['simulator', 'basic'], required_hardware=False)
+    def test_login_private_role(self):
+        """
+            1. Crate a User account with a private role
+            2. Login with the created account
+            3. Verify that the login was successful
+        """
+        account_private_role = Account.create(
+            self.apiclient,
+            self.testdata["accountroleuser"],
+            roleid=self.private_role.id
+        )
+        self.cleanup.append(account_private_role)
+
+        response = User.login(
+            self.apiclient,
+            username=account_private_role.name,
+            password=self.testdata["accountroleuser"]["password"]
+        )
+
+        self.assertNotEqual(
+            response.sessionkey,
+            None,
+            "Accounts using private roles should be able to login."
+        )
diff --git a/test/integration/smoke/test_privategw_acl.py b/test/integration/smoke/test_privategw_acl.py
index da0ae6a..06a6241 100644
--- a/test/integration/smoke/test_privategw_acl.py
+++ b/test/integration/smoke/test_privategw_acl.py
@@ -850,7 +850,7 @@
             self.assertTrue(check_state == 0, "Routers private gateway interface should not be on the same state!")
             self.assertTrue(check_mac == 0, "Routers private gateway interface should not have the same mac address!")
         else:
-            self.assertTrue(check_state == 1, "Routers private gateway interface should should have been removed!")
+            self.assertTrue(check_state == 1, "Routers private gateway interface should have been removed!")
 
     def check_routers_state(self, status_to_check="PRIMARY", expected_count=1):
         routers = self.query_routers()
diff --git a/test/integration/smoke/test_projects.py b/test/integration/smoke/test_projects.py
index 79d364f..f0696b4 100644
--- a/test/integration/smoke/test_projects.py
+++ b/test/integration/smoke/test_projects.py
@@ -1820,3 +1820,116 @@
                 "VM should be in Running state after project activation"
             )
         return
+
+class TestProjectWithNameDisplayTextAction(cloudstackTestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(
+            TestProjectWithNameDisplayTextAction,
+            cls).getClsTestClient()
+        cls.api_client = cls.testClient.getApiClient()
+        cls.services = Services().services
+        # Get Zone
+        cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
+        cls.domain = get_domain(cls.api_client)
+        cls.services['mode'] = cls.zone.networktype
+        cls._cleanup = []
+
+        cls.account = Account.create(
+            cls.api_client,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.account)
+        return
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestProjectWithNameDisplayTextAction, cls).tearDownClass()
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+
+    def tearDown(self):
+        super(TestProjectWithNameDisplayTextAction, self).tearDown()
+
+    @attr(
+        tags=[
+            "advanced",
+            "basic",
+            "sg",
+            "eip",
+            "advancedns",
+            "simulator"],
+        required_hardware="false")
+    def test_11_create_project_with_empty_displayText(self):
+        """ create Project with Empty DisplayText
+        """
+        # Validate the following
+        # 1. Create a project while giving empty displayText
+        # 2. Verify displayText takes content of Project name.
+
+        self.services["project"]["displaytext"] = ""
+
+        project = Project.create(
+            self.apiclient,
+            self.services["project"],
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.cleanup.append(project)
+
+        self.assertEqual(
+            project.displaytext,
+            project.name,
+            "displayText does not matches project name"
+        )
+        return
+
+    @attr(
+        tags=[
+            "advanced",
+            "basic",
+            "sg",
+            "eip",
+            "advancedns",
+            "simulator"],
+        required_hardware="false")
+    def test_12_update_project_name_display_text(self):
+        """ Create Project and update its name
+        """
+        # Validate the following
+        # 1. Create a project
+        # 2. Update project name and display text
+        # 2. Verify name and display text for the project are updated
+
+        project = Project.create(
+            self.apiclient,
+            self.services["project"],
+            account=self.account.name,
+            domainid=self.account.domainid
+        )
+        self.cleanup.append(project)
+
+        new_name = "NewName"
+        new_display_text = "NewDisplayText"
+        project.update(self.apiclient, name=new_name, displaytext=new_display_text)
+        updated_project = Project.list(
+            self.apiclient,
+            id=project.id,
+            listall=True
+        )[0]
+        self.assertEqual(
+            updated_project.name,
+            new_name,
+            "Project name not updated"
+        )
+        self.assertEqual(
+            updated_project.displaytext,
+            new_display_text,
+            "Project displaytext not updated"
+        )
+        return
diff --git a/test/integration/smoke/test_quarantined_ips.py b/test/integration/smoke/test_quarantined_ips.py
new file mode 100644
index 0000000..42349fd
--- /dev/null
+++ b/test/integration/smoke/test_quarantined_ips.py
@@ -0,0 +1,329 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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
+
+from nose.plugins.attrib import attr
+
+from marvin.cloudstackAPI import updateConfiguration
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import Network, NetworkOffering, VpcOffering, VPC, PublicIPAddress
+from marvin.lib.common import get_domain, get_zone
+
+
+class Services:
+    """ Test Quarantine for public IPs
+    """
+
+    def __init__(self):
+        self.services = {
+            "root_domain": {
+                "name": "ROOT",
+            },
+            "domain_admin": {
+                "username": "Domain admin",
+                "roletype": 2,
+            },
+            "root_admin": {
+                "username": "Root admin",
+                "roletype": 1,
+            },
+            "domain_vpc": {
+                "name": "domain-vpc",
+                "displaytext": "domain-vpc",
+                "cidr": "10.1.1.0/24",
+            },
+            "domain_network": {
+                "name": "domain-network",
+                "displaytext": "domain-network",
+            },
+            "root_vpc": {
+                "name": "root-vpc",
+                "displaytext": "root-vpc",
+                "cidr": "10.2.1.0/24",
+            },
+            "root_network": {
+                "name": "root-network",
+                "displaytext": "root-network",
+            }
+        }
+
+
+class TestQuarantineIPs(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestQuarantineIPs, cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+
+        cls.services = Services().services
+        cls.domain = get_domain(cls.apiclient)
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        return
+
+    def setUp(self):
+        self.domain_admin_apiclient = self.testClient.getUserApiClient(self.services["domain_admin"]["username"],
+                                                                       self.services["root_domain"]["name"],
+                                                                       self.services["domain_admin"]["roletype"])
+
+        self.admin_apiclient = self.testClient.getUserApiClient(self.services["root_admin"]["username"],
+                                                                self.services["root_domain"]["name"],
+                                                                self.services["root_admin"]["roletype"])
+
+        """
+        Set public.ip.address.quarantine.duration to 60 minutes
+        """
+        update_configuration_cmd = updateConfiguration.updateConfigurationCmd()
+        update_configuration_cmd.name = "public.ip.address.quarantine.duration"
+        update_configuration_cmd.value = "1"
+        self.apiclient.updateConfiguration(update_configuration_cmd)
+
+        self.cleanup = []
+        return
+
+    def tearDown(self):
+        """
+        Reset public.ip.address.quarantine.duration to 0 minutes
+        """
+        update_configuration_cmd = updateConfiguration.updateConfigurationCmd()
+        update_configuration_cmd.name = "public.ip.address.quarantine.duration"
+        update_configuration_cmd.value = "0"
+        self.apiclient.updateConfiguration(update_configuration_cmd)
+
+        super(TestQuarantineIPs, self).tearDown()
+
+    def create_vpc(self, api_client, services):
+        # Get network offering
+        network_offering = NetworkOffering.list(api_client, name="DefaultIsolatedNetworkOfferingForVpcNetworks")
+        self.assertTrue(network_offering is not None and len(network_offering) > 0, "No VPC network offering")
+
+        # Getting VPC offering
+        vpc_offering = VpcOffering.list(api_client, name="Default VPC offering")
+        self.assertTrue(vpc_offering is not None and len(vpc_offering) > 0, "No VPC offerings found")
+
+        # Creating VPC
+        vpc = VPC.create(
+            apiclient=api_client,
+            services=services,
+            networkDomain="vpc.networkacl",
+            vpcofferingid=vpc_offering[0].id,
+            zoneid=self.zone.id,
+            domainid=self.domain.id,
+            start=False
+        )
+
+        self.cleanup.append(vpc)
+        self.assertTrue(vpc is not None, "VPC creation failed")
+        return vpc
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_only_owner_can_allocate_ip_in_quarantine_vpc(self):
+        """ Test allocate IP in quarantine to VPC.
+        """
+        # Creating Domain Admin VPC
+        domain_vpc = self.create_vpc(self.domain_admin_apiclient, self.services["domain_vpc"])
+
+        # Allocating source nat first
+        PublicIPAddress.create(self.domain_admin_apiclient,
+                               zoneid=self.zone.id,
+                               vpcid=domain_vpc.id)
+
+        # Getting available public IP address
+        ip_address = PublicIPAddress.list(self.domain_admin_apiclient, state="Free", listall=True)[0].ipaddress
+
+        self.debug(
+            f"creating public address with zone {self.zone.id} and vpc id {domain_vpc.id} and ip address {ip_address}.")
+        # Associating public IP address to Domain Admin account
+        public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
+                                           zoneid=self.zone.id,
+                                           vpcid=domain_vpc.id,
+                                           ipaddress=ip_address)
+        self.assertIsNotNone(public_ip, "Failed to Associate IP Address")
+        self.assertEqual(public_ip.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+        self.debug(f"Disassociating public IP {public_ip.ipaddress.ipaddress}.")
+        public_ip.delete(self.domain_admin_apiclient)
+
+        # Creating Root Admin VPC
+        root_vpc = self.create_vpc(self.admin_apiclient, self.services["root_vpc"])
+
+        self.debug(f"Trying to allocate the same IP address {ip_address} that is still in quarantine.")
+
+        with self.assertRaises(CloudstackAPIException) as exception:
+            PublicIPAddress.create(self.admin_apiclient,
+                                   zoneid=self.zone.id,
+                                   vpcid=root_vpc.id,
+                                   ipaddress=ip_address)
+        self.assertIn(f"Failed to allocate public IP address [{ip_address}] as it is in quarantine.",
+                      exception.exception.errorMsg)
+
+        # Owner should be able to allocate its IP in quarantine
+        public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
+                                           zoneid=self.zone.id,
+                                           vpcid=domain_vpc.id,
+                                           ipaddress=ip_address)
+        self.assertIsNotNone(public_ip, "Failed to Associate IP Address")
+        self.assertEqual(public_ip.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_another_user_can_allocate_ip_after_quarantined_has_ended_vpc(self):
+        """ Test allocate IP to VPC after quarantine has ended.
+        """
+        # Creating Domain Admin VPC
+        domain_vpc = self.create_vpc(self.domain_admin_apiclient, self.services["domain_vpc"])
+
+        # Allocating source nat first
+        PublicIPAddress.create(self.domain_admin_apiclient,
+                               zoneid=self.zone.id,
+                               vpcid=domain_vpc.id)
+
+        # Getting available public IP address
+        ip_address = PublicIPAddress.list(self.domain_admin_apiclient, state="Free", listall=True)[0].ipaddress
+
+        self.debug(
+            f"creating public address with zone {self.zone.id} and vpc id {domain_vpc.id} and ip address {ip_address}.")
+        # Associating public IP address to Domain Admin account
+        public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
+                                           zoneid=self.zone.id,
+                                           vpcid=domain_vpc.id,
+                                           ipaddress=ip_address)
+        self.assertIsNotNone(public_ip, "Failed to Associate IP Address")
+        self.assertEqual(public_ip.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+        self.debug(f"Disassociating public IP {public_ip.ipaddress.ipaddress}.")
+        public_ip.delete(self.domain_admin_apiclient)
+
+        # Creating Root Admin VPC
+        root_vpc = self.create_vpc(self.admin_apiclient, self.services["root_vpc"])
+
+        self.debug(f"Trying to allocate the same IP address {ip_address} after the quarantine duration.")
+
+        time.sleep(60)
+
+        public_ip_2 = PublicIPAddress.create(self.admin_apiclient,
+                                             zoneid=self.zone.id,
+                                             vpcid=root_vpc.id,
+                                             ipaddress=ip_address)
+        self.assertIsNotNone(public_ip_2, "Failed to Associate IP Address")
+        self.assertEqual(public_ip_2.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_only_owner_can_allocate_ip_in_quarantine_network(self):
+        """ Test allocate IP in quarantine to network.
+        """
+        network_offering = NetworkOffering.list(self.domain_admin_apiclient,
+                                                name="DefaultIsolatedNetworkOfferingWithSourceNatService")
+        domain_network = Network.create(self.domain_admin_apiclient,
+                                        zoneid=self.zone.id,
+                                        services=self.services["domain_network"],
+                                        networkofferingid=network_offering[0].id)
+        self.cleanup.append(domain_network)
+
+        # Allocating source nat first
+        PublicIPAddress.create(self.domain_admin_apiclient,
+                               zoneid=self.zone.id,
+                               networkid=domain_network.id)
+
+        # Getting available public IP address
+        ip_address = PublicIPAddress.list(self.domain_admin_apiclient, state="Free", listall=True)[0].ipaddress
+
+        self.debug(
+            f"creating public address with zone {self.zone.id} and network id {domain_network.id} and ip address {ip_address}.")
+        # Associating public IP address to Domain Admin account
+        public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
+                                           zoneid=self.zone.id,
+                                           networkid=domain_network.id,
+                                           ipaddress=ip_address)
+        self.assertIsNotNone(public_ip, "Failed to Associate IP Address")
+        self.assertEqual(public_ip.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+        self.debug(f"Disassociating public IP {public_ip.ipaddress.ipaddress}.")
+        public_ip.delete(self.domain_admin_apiclient)
+
+        # Creating Root Admin network
+        root_network = Network.create(self.admin_apiclient,
+                                      zoneid=self.zone.id,
+                                      services=self.services["root_network"],
+                                      networkofferingid=network_offering[0].id)
+        self.cleanup.append(root_network)
+        self.debug(f"Trying to allocate the same IP address {ip_address} that is still in quarantine.")
+
+        with self.assertRaises(CloudstackAPIException) as exception:
+            PublicIPAddress.create(self.admin_apiclient,
+                                   zoneid=self.zone.id,
+                                   networkid=root_network.id,
+                                   ipaddress=ip_address)
+        self.assertIn(f"Failed to allocate public IP address [{ip_address}] as it is in quarantine.",
+                      exception.exception.errorMsg)
+
+        # Owner should be able to allocate its IP in quarantine
+        public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
+                                           zoneid=self.zone.id,
+                                           networkid=domain_network.id,
+                                           ipaddress=ip_address)
+        self.assertIsNotNone(public_ip, "Failed to Associate IP Address")
+        self.assertEqual(public_ip.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_another_user_can_allocate_ip_after_quarantined_has_ended_network(self):
+        """ Test allocate IP to network after quarantine has ended.
+        """
+        network_offering = NetworkOffering.list(self.domain_admin_apiclient,
+                                                name="DefaultIsolatedNetworkOfferingWithSourceNatService")
+        domain_network = Network.create(self.domain_admin_apiclient,
+                                        zoneid=self.zone.id,
+                                        services=self.services["domain_network"],
+                                        networkofferingid=network_offering[0].id)
+        self.cleanup.append(domain_network)
+        # Allocating source nat first
+        PublicIPAddress.create(self.domain_admin_apiclient,
+                               zoneid=self.zone.id,
+                               networkid=domain_network.id)
+
+        # Getting available public IP address
+        ip_address = PublicIPAddress.list(self.domain_admin_apiclient, state="Free", listall=True)[0].ipaddress
+
+        self.debug(
+            f"creating public address with zone {self.zone.id} and network id {domain_network.id} and ip address {ip_address}.")
+        # Associating public IP address to Domain Admin account
+        public_ip = PublicIPAddress.create(self.domain_admin_apiclient,
+                                           zoneid=self.zone.id,
+                                           networkid=domain_network.id,
+                                           ipaddress=ip_address)
+        self.assertIsNotNone(public_ip, "Failed to Associate IP Address")
+        self.assertEqual(public_ip.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
+
+        self.debug(f"Disassociating public IP {public_ip.ipaddress.ipaddress}.")
+        public_ip.delete(self.domain_admin_apiclient)
+
+        # Creating Root Admin VPC
+        root_network = Network.create(self.admin_apiclient,
+                                      zoneid=self.zone.id,
+                                      services=self.services["root_network"],
+                                      networkofferingid=network_offering[0].id)
+        self.cleanup.append(root_network)
+
+        self.debug(f"Trying to allocate the same IP address {ip_address} after the quarantine duration.")
+
+        time.sleep(60)
+
+        public_ip_2 = PublicIPAddress.create(self.admin_apiclient,
+                                             zoneid=self.zone.id,
+                                             networkid=domain_network.id,
+                                             ipaddress=ip_address)
+        self.assertIsNotNone(public_ip_2, "Failed to Associate IP Address")
+        self.assertEqual(public_ip_2.ipaddress.ipaddress, ip_address, "Associated IP is not same as specified")
diff --git a/test/integration/smoke/test_register_userdata.py b/test/integration/smoke/test_register_userdata.py
index bc38cd9..c89d08e 100644
--- a/test/integration/smoke/test_register_userdata.py
+++ b/test/integration/smoke/test_register_userdata.py
@@ -31,6 +31,8 @@
 from marvin.lib.utils import (validateList, cleanup_resources)
 from nose.plugins.attrib import attr
 from marvin.codes import PASS,FAIL
+import base64
+import email
 
 
 from marvin.lib.common import (get_domain, get_template)
@@ -589,21 +591,23 @@
             2. Link a userdata to template with override policy is append
             3. Deploy a VM with that template and also by passing another userdata id
             4. Since the override policy is append, userdata passed during VM deployment will be appended to template's
-            userdata and configured to VM. Verify the same by SSH into VM.
+            userdata and configured to VM as a multipart MIME userdata. Verify the same by SSH into VM.
         """
 
+        shellscript_userdata = str("#!/bin/bash\ndate > /provisioned")
         self.apiUserdata = UserData.register(
             self.apiclient,
             name="ApiUserdata",
-            userdata="QVBJdXNlcmRhdGE=", #APIuserdata
+            userdata=base64.encodebytes(shellscript_userdata.encode()).decode(),
             account=self.account.name,
             domainid=self.account.domainid
         )
 
+        cloudconfig_userdata = str("#cloud-config\npassword: atomic\nchpasswd: { expire: False }\nssh_pwauth: True")
         self.templateUserdata = UserData.register(
             self.apiclient,
             name="TemplateUserdata",
-            userdata="VGVtcGxhdGVVc2VyRGF0YQ==", #TemplateUserData
+            userdata=base64.encodebytes(cloudconfig_userdata.encode()).decode(),
             account=self.account.name,
             domainid=self.account.domainid
         )
@@ -700,10 +704,32 @@
         cmd = "curl http://%s/latest/user-data" % vr_ip
         res = ssh.execute(cmd)
         self.debug("Verifying userdata in the VR")
-        self.assertEqual(
-            str(res[0]),
-            "TemplateUserDataAPIuserdata",
-            "Failed to match userdata"
+        self.assertTrue(
+            res is not None and len(res) > 0,
+            "Resultant userdata is not valid"
+        )
+        msg = email.message_from_string('\n'.join(res))
+        self.assertTrue(
+            msg.is_multipart(),
+            "Failed to match multipart userdata"
+        )
+        shellscript_userdata_found = False
+        cloudconfig_userdata_found = False
+        for part in msg.get_payload():
+            content_type = part.get_content_type()
+            payload = part.get_payload(decode=True).decode()
+            if "shellscript" in content_type:
+                shellscript_userdata_found = shellscript_userdata == payload
+            elif "cloud-config" in content_type:
+                cloudconfig_userdata_found = cloudconfig_userdata == payload
+
+        self.assertTrue(
+            shellscript_userdata_found,
+            "Failed to find shellscript userdata in append result"
+        )
+        self.assertTrue(
+            cloudconfig_userdata_found,
+            "Failed to find cloud-config userdata in append result"
         )
 
     @attr(tags=['advanced', 'simulator', 'basic', 'sg', 'testnow'], required_hardware=True)
diff --git a/test/integration/smoke/test_safe_shutdown.py b/test/integration/smoke/test_safe_shutdown.py
new file mode 100644
index 0000000..d757bb6
--- /dev/null
+++ b/test/integration/smoke/test_safe_shutdown.py
@@ -0,0 +1,121 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import *
+from marvin.cloudstackAPI import *
+from marvin.lib.utils import *
+from marvin.lib.base import *
+from marvin.lib.common import *
+
+class TestSafeShutdown(cloudstackTestCase):
+    """
+        Tests safely shutting down the Management Server
+    """
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.mgtSvrDetails = self.config.__dict__["mgtSvr"][0].__dict__
+        self.cleanup = []
+
+    def tearDown(self):
+        self.startServer()
+        super(TestSafeShutdown, self).tearDown()
+
+    def isServerShutdown(self):
+        sshClient = SshClient(
+            self.mgtSvrDetails["mgtSvrIp"],
+            22,
+            self.mgtSvrDetails["user"],
+            self.mgtSvrDetails["passwd"]
+        )
+
+        timeout = time.time() + 300
+        while time.time() < timeout:
+            command = "service cloudstack-management status | grep dead"
+            results = sshClient.execute(command)
+
+            if len(results) > 0 and "(dead)" in results[0] :
+                return
+            time.sleep(30)
+        return self.fail("Management server did shut down, failing")
+
+    def isManagementUp(self):
+        try:
+            self.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd())
+            return True
+        except Exception:
+            return False
+
+    def startServer(self):
+        """Start management server"""
+
+        sshClient = SshClient(
+                    self.mgtSvrDetails["mgtSvrIp"],
+            22,
+            self.mgtSvrDetails["user"],
+            self.mgtSvrDetails["passwd"]
+        )
+
+        command = "service cloudstack-management start"
+        sshClient.execute(command)
+
+        #Waits for management to come up in 5 mins, when it's up it will continue
+        timeout = time.time() + 300
+        while time.time() < timeout:
+            if self.isManagementUp() is True: return
+            time.sleep(5)
+        return self.fail("Management server did not come up, failing")
+
+    def run_async_cmd(self) :
+        return Project.create(
+            self.apiclient,
+            {"name": "test", "displaytext": "test"}
+        )
+
+    @attr(tags=["advanced", "smoke"])
+    def test_01_prepare_and_cancel_shutdown(self):
+        try :
+            prepare_for_shutdown_cmd = prepareForShutdown.prepareForShutdownCmd()
+            prepare_for_shutdown_cmd.managementserverid = 1
+            self.apiclient.prepareForShutdown(prepare_for_shutdown_cmd)
+            try :
+                self.run_async_cmd()
+            except Exception as e:
+                self.debug("Prepare for shutdown check successful, API failure: %s" % e)
+        finally :
+            cancel_shutdown_cmd = cancelShutdown.cancelShutdownCmd()
+            cancel_shutdown_cmd.managementserverid = 1
+            response = self.apiclient.cancelShutdown(cancel_shutdown_cmd)
+            self.assertEqual(
+                response.shutdowntriggered,
+                False,
+                "Failed to cancel shutdown"
+            )
+            ## Just to be sure, run another async command
+            project = self.run_async_cmd()
+            self.cleanup.append(project)
+
+    @attr(tags=["advanced", "smoke"])
+    def test_02_trigger_shutdown(self):
+        try :
+            cmd = triggerShutdown.triggerShutdownCmd()
+            cmd.managementserverid = 1
+            self.apiclient.triggerShutdown(cmd)
+            self.isServerShutdown()
+        finally :
+            self.startServer()
diff --git a/test/integration/smoke/test_secondary_storage.py b/test/integration/smoke/test_secondary_storage.py
index 5b339ae..4b26950 100644
--- a/test/integration/smoke/test_secondary_storage.py
+++ b/test/integration/smoke/test_secondary_storage.py
@@ -340,7 +340,7 @@
         # 1. Try complete migration from a storage with more (or equal) free space - migration should be refused
 
         storages = self.list_secondary_storages(self.apiclient)
-        if (len(storages)) < 2:
+        if (len(storages)) < 2 or (storages[0]['zoneid'] != storages[1]['zoneid']):
             self.skipTest(
                 "This test requires more than one secondary storage")
 
diff --git a/test/integration/smoke/test_set_sourcenat.py b/test/integration/smoke/test_set_sourcenat.py
new file mode 100644
index 0000000..e89af9f
--- /dev/null
+++ b/test/integration/smoke/test_set_sourcenat.py
@@ -0,0 +1,274 @@
+# Licensed 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
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 Network Life Cycle
+"""
+# Import Local Modules
+from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE,
+                          NAT_RULE, PASS)
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import (Account,
+                             VPC,
+                             VpcOffering,
+                             ServiceOffering,
+                             PublicIPAddress,
+                             Network,
+                             NetworkOffering)
+from marvin.lib.common import (get_domain,
+                               get_free_vlan,
+                               get_zone,
+                               get_template,
+                               get_test_template,
+                               list_publicIP)
+
+from nose.plugins.attrib import attr
+# Import System modules
+import time
+import logging
+
+_multiprocess_shared_ = True
+
+logger = logging.getLogger('TestSetSourceNatIp')
+stream_handler = logging.StreamHandler()
+logger.setLevel(logging.DEBUG)
+logger.addHandler(stream_handler)
+
+
+class TestSetSourceNatIp(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestSetSourceNatIp, 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._cleanup = []
+        # Create Accounts & networks
+        cls.account = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.account)
+
+        cls.services["network"]["zoneid"] = cls.zone.id
+
+        cls.vpc_offering = VpcOffering.create(
+            cls.apiclient,
+            cls.services["vpc_offering"],
+        )
+        cls._cleanup.append(cls.vpc_offering)
+        cls.vpc_offering.update(cls.apiclient, state='Enabled')
+        cls.services["vpc"]["vpcoffering"] = cls.vpc_offering.id
+
+        cls.network_offering = NetworkOffering.create(
+            cls.apiclient,
+            cls.services["network_offering"],
+        )
+        cls._cleanup.append(cls.network_offering)
+        # Enable Network offering
+        cls.network_offering.update(cls.apiclient, state='Enabled')
+
+        cls.services["network"]["networkoffering"] = cls.network_offering.id
+
+        cls.service_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["tiny"],
+        )
+        cls._cleanup.append(cls.service_offering)
+
+        cls.hypervisor = testClient.getHypervisorInfo()
+        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.services["virtual_machine"]["zoneid"] = cls.zone.id
+        network = Network.create(
+            cls.apiclient,
+            cls.services["network"],
+            cls.account.name,
+            cls.account.domainid
+        )
+        cls._cleanup.append(network)
+        ip_address = PublicIPAddress.create(
+            cls.apiclient,
+            cls.account.name,
+            cls.zone.id,
+            cls.account.domainid
+        )
+        cls._cleanup.append(ip_address)
+        cls.ip_to_want = ip_address.ipaddress.ipaddress
+        cls.debug(f'==== my local ip: {cls.ip_to_want}')
+        ip_address.delete(cls.apiclient)
+        cls._cleanup.remove(ip_address)
+        network.delete(cls.apiclient)
+        cls._cleanup.remove(network)
+        return
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestSetSourceNatIp, cls).tearDownClass()
+
+    def setUp(self):
+        self.cleanup = []
+
+    def tearDown(self):
+        super(TestSetSourceNatIp, self).tearDown()
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
+    def test_01_create_network_with_specified_source_nat_ip_address(self):
+        """
+        For creation of network witjh a specified address
+        """
+
+
+        self.services["network"]["networkoffering"] = self.network_offering.id
+        network = Network.create(
+            self.apiclient,
+            self.services["network"],
+            self.account.name,
+            self.account.domainid,
+            sourcenatipaddress = self.ip_to_want
+        )
+        self.cleanup.append(network)
+
+        self.validate_source_nat(network=network, ip=self.ip_to_want)
+
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
+    def test_02_change_source_nat_ip_address_for_network(self):
+        """
+        Test changing a networks source NAT IP address
+        """
+        network = Network.create(
+            self.apiclient,
+            self.services["network"],
+            self.account.name,
+            self.account.domainid,
+            sourcenatipaddress = self.ip_to_want
+        )
+        self.cleanup.append(network)
+        second_ip = PublicIPAddress.create(
+            self.apiclient,
+            self.account.name,
+            self.zone.id,
+            self.account.domainid,
+            networkid=network.id
+        )
+        self.cleanup.append(second_ip)
+        self.debug(f'==== second ip: {second_ip.ipaddress.ipaddress}')
+
+        self.validate_source_nat(network=network, ip=self.ip_to_want)
+
+        network.update(self.apiclient, sourcenatipaddress=second_ip.ipaddress.ipaddress)
+
+        self.validate_source_nat(network=network, ip=second_ip.ipaddress.ipaddress)
+
+        return
+
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
+    def test_03_create_vpc_with_specified_source_nat_ip_address(self):
+        """
+        Test for creation of a VPC with a specified address
+        """
+
+        vpc = VPC.create(
+            self.apiclient,
+            self.services["vpc"],
+            self.vpc_offering.id,
+            self.zone.id,
+            sourcenatipaddress = self.ip_to_want
+        )
+        self.cleanup.append(vpc)
+
+        self.validate_source_nat(vpc=vpc, ip=self.ip_to_want)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
+    def test_04_change_source_nat_ip_address_for_vpc(self):
+        """
+        Test changing a networks source NAT IP address
+        """
+        vpc = VPC.create(
+            self.apiclient,
+            self.services["vpc"],
+            self.vpc_offering.id,
+            self.zone.id,
+            account=self.account.name,
+            domainid = self.account.domainid,
+            sourcenatipaddress = self.ip_to_want
+        )
+        self.cleanup.append(vpc)
+        second_ip = PublicIPAddress.create(
+            self.apiclient,
+            self.account.name,
+            self.zone.id,
+            self.account.domainid,
+            vpcid=vpc.id
+        )
+        self.debug(f'==== second ip: {second_ip.ipaddress.ipaddress}')
+
+        self.validate_source_nat(vpc=vpc, ip=self.ip_to_want)
+
+        vpc.update(self.apiclient, sourcenatipaddress=second_ip.ipaddress.ipaddress)
+
+        self.validate_source_nat(vpc=vpc, ip=second_ip.ipaddress.ipaddress)
+
+        return
+
+
+    def validate_source_nat(self, network=None, vpc=None, ip=None):
+        list_pub_ip_addr_resp = None
+        if network:
+            list_pub_ip_addr_resp = list_publicIP(
+                self.apiclient,
+                associatednetworkid=network.id,
+                listall=True,
+                issourcenat=True
+            )
+        elif vpc:
+            list_pub_ip_addr_resp = list_publicIP(
+                self.apiclient,
+                vpcid=vpc.id,
+                listall=True,
+                issourcenat=True
+            )
+        self.assertEqual(
+            isinstance(list_pub_ip_addr_resp, list),
+            True,
+            "Check list response returns a valid list"
+        )
+        self.assertNotEqual(
+            len(list_pub_ip_addr_resp),
+            0,
+            "Check if new IP Address is associated"
+        )
+        self.debug(f'==== my result {list_pub_ip_addr_resp[0]}')
+        self.assertEqual(
+            list_pub_ip_addr_resp[0].ipaddress,
+            ip,
+            f"Check Correct IP Address is returned in the List, expected {ip} but got {list_pub_ip_addr_resp[0].ipaddress}"
+        )
diff --git a/test/integration/smoke/test_ssvm.py b/test/integration/smoke/test_ssvm.py
index cdbc7d2..ad03c3d 100644
--- a/test/integration/smoke/test_ssvm.py
+++ b/test/integration/smoke/test_ssvm.py
@@ -121,7 +121,7 @@
         #    should return only ONE SSVM per zone
         # 2. The returned SSVM should be in Running state
         # 3. listSystemVM for secondarystoragevm should list publicip,
-        #    privateip and link-localip
+        #    privateip, link-localip and service offering id/name
         # 4. The gateway programmed on the ssvm by listSystemVm should be
         #    the same as the gateway returned by listVlanIpRanges
         # 5. DNS entries must match those given for the zone
@@ -188,6 +188,18 @@
                 "Check whether SSVM has public IP field"
             )
 
+            self.assertEqual(
+                hasattr(ssvm, 'serviceofferingid'),
+                True,
+                "Check whether SSVM has service offering id field"
+            )
+
+            self.assertEqual(
+                hasattr(ssvm, 'serviceofferingname'),
+                True,
+                "Check whether SSVM has service offering name field"
+            )
+
             # Fetch corresponding ip ranges information from listVlanIpRanges
             ipranges_response = list_vlan_ipranges(
                 self.apiclient,
@@ -261,8 +273,8 @@
         # 1. listSystemVM (systemvmtype=consoleproxy) should return
         #    at least ONE CPVM per zone
         # 2. The returned ConsoleProxyVM should be in Running state
-        # 3. listSystemVM for console proxy should list publicip, privateip
-        #    and link-localip
+        # 3. listSystemVM for console proxy should list publicip, privateip,
+        #    link-localip and service offering id/name
         # 4. The gateway programmed on the console proxy should be the same
         #    as the gateway returned by listZones
         # 5. DNS entries must match those given for the zone
@@ -327,6 +339,18 @@
                 True,
                 "Check whether CPVM has public IP field"
             )
+
+            self.assertEqual(
+                hasattr(cpvm, 'serviceofferingid'),
+                True,
+                "Check whether CPVM has service offering id field"
+            )
+
+            self.assertEqual(
+                hasattr(cpvm, 'serviceofferingname'),
+                True,
+                "Check whether CPVM has service offering name field"
+            )
             # Fetch corresponding ip ranges information from listVlanIpRanges
             ipranges_response = list_vlan_ipranges(
                 self.apiclient,
diff --git a/test/integration/smoke/test_templates.py b/test/integration/smoke/test_templates.py
index 66008b1..2696db8 100644
--- a/test/integration/smoke/test_templates.py
+++ b/test/integration/smoke/test_templates.py
@@ -1024,17 +1024,17 @@
                 cls.services["disk_offering"]
             )
             cls._cleanup.append(cls.disk_offering)
-            template = get_template(
+            cls.template = get_template(
                 cls.apiclient,
                 cls.zone.id,
                 cls.services["ostype"]
             )
-            if template == FAILED:
+            if cls.template == FAILED:
                 assert False, "get_template() failed to return template with description %s" % cls.services["ostype"]
 
-            cls.services["template"]["ostypeid"] = template.ostypeid
-            cls.services["template_2"]["ostypeid"] = template.ostypeid
-            cls.services["ostypeid"] = template.ostypeid
+            cls.services["template"]["ostypeid"] = cls.template.ostypeid
+            cls.services["template_2"]["ostypeid"] = cls.template.ostypeid
+            cls.services["ostypeid"] = cls.template.ostypeid
 
             cls.services["virtual_machine"]["zoneid"] = cls.zone.id
             cls.services["volume"]["diskoffering"] = cls.disk_offering.id
@@ -1055,7 +1055,7 @@
             cls.virtual_machine = VirtualMachine.create(
                 cls.apiclient,
                 cls.services["virtual_machine"],
-                templateid=template.id,
+                templateid=cls.template.id,
                 accountid=cls.account.name,
                 domainid=cls.account.domainid,
                 serviceofferingid=cls.service_offering.id,
@@ -1104,7 +1104,7 @@
             raise Exception("Warning: Exception during cleanup : %s" % e)
         return
 
-    @attr(tags=["advanced", "advancedns"], required_hardware="false")
+    @attr(tags=["advanced", "advancedns"], required_hardware="true")
     def test_09_copy_delete_template(self):
         cmd = listZones.listZonesCmd()
         zones = self.apiclient.listZones(cmd)
@@ -1156,7 +1156,7 @@
 
         list_template_response = Template.list(
             self.apiclient,
-            templatefilter=self.services["template"]["templatefilter"],
+            templatefilter=self.services["templatefilter"],
             id=self.template.id,
             zoneid=self.destZone.id
         )
diff --git a/test/integration/smoke/test_vm_autoscaling.py b/test/integration/smoke/test_vm_autoscaling.py
index 316f94f..7ae61ce 100644
--- a/test/integration/smoke/test_vm_autoscaling.py
+++ b/test/integration/smoke/test_vm_autoscaling.py
@@ -204,7 +204,10 @@
         cls.apiUserdata = UserData.register(
             cls.apiclient,
             name="ApiUserdata",
-            userdata="QVBJdXNlcmRhdGE=", #APIuserdata
+            userdata="IyEvYmluL2Jhc2gKCmVjaG8gIkFQSVVzZXJkYXRhIgoK",
+            #   #!/bin/bash
+            #
+            #   echo "APIUserData"
             account=cls.regular_user.name,
             domainid=cls.regular_user.domainid
         )
@@ -327,7 +330,10 @@
             serviceofferingid=cls.service_offering.id,
             zoneid=cls.zone.id,
             templateid=cls.template.id,
-            userdata="VGVzdFVzZXJEYXRh", #TestUserData
+            userdata="IyEvYmluL2Jhc2gKCmVjaG8gIlRlc3RVc2VyRGF0YSIKCg==",
+            #   #!/bin/bash
+            #
+            #   echo "TestUserData"
             expungevmgraceperiod=DEFAULT_EXPUNGE_VM_GRACE_PERIOD,
             otherdeployparams=cls.otherdeployparams
         )
diff --git a/test/integration/smoke/test_vm_deployment_planner.py b/test/integration/smoke/test_vm_deployment_planner.py
index 636eaac..e8d24cb 100644
--- a/test/integration/smoke/test_vm_deployment_planner.py
+++ b/test/integration/smoke/test_vm_deployment_planner.py
@@ -69,8 +69,7 @@
         cmd = deployVirtualMachine.deployVirtualMachineCmd()
         template = get_template(
             self.apiclient,
-            self.zone.id,
-            hypervisor=self.hypervisor
+            self.zone.id
         )
         cmd.zoneid = self.zone.id
         cmd.templateid = template.id
diff --git a/test/integration/smoke/test_vm_schedule.py b/test/integration/smoke/test_vm_schedule.py
new file mode 100644
index 0000000..ee1354f
--- /dev/null
+++ b/test/integration/smoke/test_vm_schedule.py
@@ -0,0 +1,613 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+""" P1 tests for VM Schedule
+"""
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import Account, ServiceOffering, VirtualMachine, VMSchedule
+from marvin.lib.common import get_domain, get_zone, get_template
+from marvin.lib.utils import cleanup_resources
+
+# Import Local Modules
+from nose.plugins.attrib import attr
+
+import datetime
+import time
+
+
+class Services:
+    """Test Snapshots Services"""
+
+    def __init__(self):
+        self.services = {
+            "account": {
+                "email": "test@test.com",
+                "firstname": "Test",
+                "lastname": "User",
+                "username": "test",
+                # Random characters are appended for unique
+                # username
+                "password": "password",
+            },
+            "service_offering": {
+                "name": "Tiny Instance",
+                "displaytext": "Tiny Instance",
+                "cpunumber": 1,
+                "cpuspeed": 200,  # in MHz
+                "memory": 256,  # In MBs
+            },
+            "disk_offering": {
+                "displaytext": "Small Disk",
+                "name": "Small Disk",
+                "disksize": 1,
+            },
+            "server": {
+                "displayname": "TestVM",
+                "username": "root",
+                "password": "password",
+                "ssh_port": 22,
+                "privateport": 22,
+                "publicport": 22,
+                "protocol": "TCP",
+            },
+            "mgmt_server": {
+                "ipaddress": "192.168.100.21",
+                "username": "root",
+                "password": "password",
+                "port": 22,
+            },
+            "templates": {
+                "displaytext": "Template",
+                "name": "Template",
+                "ostype": "CentOS 5.3 (64-bit)",
+                "templatefilter": "self",
+            },
+            "ostype": "CentOS 5.3 (64-bit)",
+        }
+
+
+class TestVMSchedule(cloudstackTestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestVMSchedule, cls).getClsTestClient()
+        cls.api_client = cls.testClient.getApiClient()
+
+        cls._cleanup = []
+
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
+
+        cls.services = Services().services
+        # Get Zone, Domain and templates
+        cls.domain = get_domain(cls.api_client)
+        cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
+        cls.services["mode"] = cls.zone.networktype
+        template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"])
+
+        cls.services["domainid"] = cls.domain.id
+        cls.services["server"]["zoneid"] = cls.zone.id
+
+        cls.services["templates"]["ostypeid"] = template.ostypeid
+        cls.services["zoneid"] = cls.zone.id
+
+        # Create VMs, NAT Rules etc
+        cls.account = Account.create(
+            cls.api_client, cls.services["account"], domainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.account)
+
+        cls.services["account"] = cls.account.name
+
+        cls.service_offering = ServiceOffering.create(
+            cls.api_client, cls.services["service_offering"]
+        )
+        cls._cleanup.append(cls.service_offering)
+        cls.virtual_machine = VirtualMachine.create(
+            cls.api_client,
+            cls.services["server"],
+            templateid=template.id,
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            serviceofferingid=cls.service_offering.id,
+        )
+        return
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestVMSchedule, cls).tearDownClass()
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+        return
+
+    def tearDown(self):
+        super(TestVMSchedule, self).tearDown()
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_01_vmschedule_create(self):
+        """Test VM Schedule Creation in cron format and validate responses"""
+
+        # Validate the following
+        # 1. Create VM Schedule in cron format
+        # 2. List VM Schedule and verify the response
+        # 3. Delete VM Schedule and verify the response
+
+        # Create VM Schedule
+        schedule = "0 0 1 * *"
+        vmschedule = VMSchedule.create(
+            self.apiclient,
+            self.virtual_machine.id,
+            "start",
+            schedule,
+            datetime.datetime.now().astimezone().tzinfo,
+            # Current date minutes in format "2014-01-01 00:00:00"
+            (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                "%Y-%m-%d %H:%M:%S"
+            ),
+            enabled=True,
+        )
+
+        self.cleanup.append(vmschedule)
+
+        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
+        )
+
+        self.assertEqual(
+            isinstance(vmschedules, list),
+            True,
+            "Check list response returns a valid list",
+        )
+
+        self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule list")
+
+        self.debug("List VM Schedule response: %s" % vmschedules[0].__dict__)
+
+        self.assertEqual(
+            vmschedules[0].id,
+            vmschedule.id,
+            "Check VM Schedule ID in list resources call",
+        )
+
+        self.assertEqual(
+            vmschedules[0].virtualmachineid,
+            self.virtual_machine.id,
+            "Check VM ID in list resources call",
+        )
+
+        self.assertEqual(
+            vmschedules[0].schedule,
+            schedule,
+            "Check VM Schedule in list resources call",
+        )
+
+        self.assertEqual(
+            vmschedules[0].timezone,
+            str(datetime.datetime.now().astimezone().tzinfo),
+            "Check VM Schedule timezone in list resources call",
+        )
+
+        # Check for entry in vm_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')"
+            % vmschedule.id,
+            db="cloud",
+        )
+
+        self.assertIsInstance(
+            vmscheduled_job,
+            list,
+            "Check if VM Schedule exists in vm_scheduled_job table",
+        )
+
+        self.assertGreater(
+            len(vmscheduled_job),
+            0,
+            "Check if VM Schedule exists in vm_scheduled_job table",
+        )
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_02_vmschedule_create_parameter_exceptions(self):
+        """Test VM Schedule Creation exceptions with invalid parameters"""
+
+        # Validate the following
+        # 1. Create VM Schedule with invalid virtual machine ID
+        # 2. Create VM Schedule with invalid schedule
+        # 3. Create VM Schedule with invalid start date
+        # 5. Create VM Schedule with invalid action
+        # 6. Create VM Schedule with invalid end date
+
+        # Create VM Schedule with invalid virtual machine ID
+        with self.assertRaises(Exception):
+            VMSchedule.create(
+                self.apiclient,
+                "invalid",
+                "start",
+                "0 0 1 * *",
+                datetime.datetime.now().astimezone().tzinfo,
+                # Current date minutes in format "2014-01-01 00:00:00"
+                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                    "%Y-%m-%d %H:%M:%S"
+                ),
+            )
+
+        # Create VM Schedule with invalid schedule
+        with self.assertRaises(Exception):
+            VMSchedule.create(
+                self.apiclient,
+                self.virtual_machine.id,
+                "start",
+                "invalid",
+                datetime.datetime.now().astimezone().tzinfo,
+                # Current date minutes in format "2014-01-01 00:00:00"
+                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                    "%Y-%m-%d %H:%M:%S"
+                ),
+            )
+
+        # Create VM Schedule with invalid start date
+        with self.assertRaises(Exception):
+            VMSchedule.create(
+                self.apiclient,
+                self.virtual_machine.id,
+                "start",
+                "0 0 1 * *",
+                datetime.datetime.now().astimezone().tzinfo,
+                # Current date minutes in format "2014-01-01 00:00:00"
+                "invalid",
+            )
+
+        # Create VM Schedule with invalid action
+        with self.assertRaises(Exception):
+            VMSchedule.create(
+                self.apiclient,
+                self.virtual_machine.id,
+                "invalid",
+                "0 0 1 * *",
+                datetime.datetime.now().astimezone().tzinfo,
+                # Current date minutes in format "2014-01-01 00:00:00"
+                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                    "%Y-%m-%d %H:%M:%S"
+                ),
+            )
+
+        # test invalid end date
+        with self.assertRaises(Exception):
+            VMSchedule.create(
+                self.apiclient,
+                self.virtual_machine.id,
+                "start",
+                "0 0 1 * *",
+                datetime.datetime.now().astimezone().tzinfo,
+                # Current date minutes in format "2014-01-01 00:00:00"
+                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                    "%Y-%m-%d %H:%M:%S"
+                ),
+                enddate="invalid",
+            )
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_03_vmschedule_update(self):
+        """Test VM Schedule Update in cron format and validate responses"""
+
+        # Validate the following
+        # 1. Create VM Schedule in cron format
+        # 2. Update VM Schedule and verify the response
+
+        # Create VM Schedule
+        schedule = "0 0 1 * *"
+        vmschedule = VMSchedule.create(
+            self.apiclient,
+            self.virtual_machine.id,
+            "start",
+            schedule,
+            datetime.datetime.now().astimezone().tzinfo,
+            # Current date minutes in format "2014-01-01 00:00:00"
+            (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                "%Y-%m-%d %H:%M:%S"
+            ),
+        )
+
+        self.cleanup.append(vmschedule)
+
+        self.debug("Created VM Schedule with ID: %s" % vmschedule.id)
+
+        # Update VM Schedule
+        new_schedule = "0 0 2 * *"
+        vmschedule.update(
+            self.apiclient,
+            id=vmschedule.id,
+            virtualmachineid=self.virtual_machine.id,
+            description="TestVM",
+            schedule=new_schedule,
+            timezone=datetime.datetime.now().astimezone().tzinfo,
+            startdate=(
+                datetime.datetime.now() + datetime.timedelta(minutes=10)
+            ).strftime("%Y-%m-%d %H:%M:%S"),
+            enddate=(datetime.datetime.now() + datetime.timedelta(hours=10)).strftime(
+                "%Y-%m-%d %H:%M:%S"
+            ),
+        )
+
+        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
+        )
+        self.assertEqual(
+            isinstance(vmschedules, list),
+            True,
+            "Check list response returns a valid list",
+        )
+
+        self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule list")
+
+        self.debug("List VM Schedule response: %s" % vmschedules[0].__dict__)
+
+        self.assertEqual(
+            vmschedules[0].id,
+            vmschedule.id,
+            "Check VM Schedule ID in list resources call",
+        )
+
+        self.assertEqual(
+            vmschedules[0].virtualmachineid,
+            self.virtual_machine.id,
+            "Check VM ID in list resources call",
+        )
+
+        self.assertEqual(
+            vmschedules[0].schedule,
+            new_schedule,
+            "Check VM Schedule in list resources call",
+        )
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_04_vmschedule_update_parameter_exceptions(self):
+        """Test VM Schedule Update exceptions with invalid parameters"""
+
+        # Validate the following
+        # 1. Update VM Schedule with invalid schedule
+        # 2. Update VM Schedule with invalid start date
+        # 3. Update VM Schedule with invalid ID
+        # 4. Update VM Schedule with invalid end date
+
+        # Create VM Schedule
+        schedule = "0 0 1 * *"
+        vmschedule = VMSchedule.create(
+            self.apiclient,
+            self.virtual_machine.id,
+            "start",
+            schedule,
+            datetime.datetime.now().astimezone().tzinfo,
+            # Current date minutes in format "2014-01-01 00:00:00"
+            (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
+                "%Y-%m-%d %H:%M:%S"
+            ),
+        )
+
+        self.cleanup.append(vmschedule)
+
+        self.debug("Created VM Schedule with ID: %s" % vmschedule.id)
+
+        # Update VM Schedule with invalid schedule
+        with self.assertRaises(Exception):
+            vmschedule.update(
+                self.apiclient,
+                id=vmschedule.id,
+                virtualmachineid=self.virtual_machine.id,
+                description="TestVM",
+                schedule="invalid",
+                timezone=datetime.datetime.now().astimezone().tzinfo,
+                startdate=(
+                    datetime.datetime.now() + datetime.timedelta(minutes=5)
+                ).strftime("%Y-%m-%d %H:%M:%S"),
+            )
+
+        # Update VM Schedule with invalid start date
+        with self.assertRaises(Exception):
+            vmschedule.update(
+                self.apiclient,
+                id=vmschedule.id,
+                virtualmachineid=self.virtual_machine.id,
+                description="TestVM",
+                schedule=schedule,
+                timezone=datetime.datetime.now().astimezone().tzinfo,
+                startdate=(
+                    datetime.datetime.now() - datetime.timedelta(days=1)
+                ).strftime("%Y-%m-%d %H:%M:%S"),
+            )
+
+        # Update VM Schedule with invalid ID
+        with self.assertRaises(Exception):
+            vmschedule.update(
+                self.apiclient,
+                id="invalid",
+                virtualmachineid=self.virtual_machine.id,
+                description="TestVM",
+                schedule=schedule,
+                timezone=datetime.datetime.now().astimezone().tzinfo,
+                startdate=(
+                    datetime.datetime.now() + datetime.timedelta(minutes=5)
+                ).strftime("%Y-%m-%d %H:%M:%S"),
+            )
+
+        # Update VM Schedule with invalid end date
+        with self.assertRaises(Exception):
+            vmschedule.update(
+                self.apiclient,
+                id=vmschedule.id,
+                virtualmachineid=self.virtual_machine.id,
+                description="TestVM",
+                schedule=schedule,
+                timezone=datetime.datetime.now().astimezone().tzinfo,
+                startdate=(
+                    datetime.datetime.now() + datetime.timedelta(minutes=5)
+                ).strftime("%Y-%m-%d %H:%M:%S"),
+                enddate=(
+                    datetime.datetime.now() - datetime.timedelta(minutes=5)
+                ).strftime("%Y-%m-%d %H:%M:%S"),
+            )
+
+        return
+
+    @attr(tags=["advanced", "basic"], required_hardware="false")
+    def test_05_vmschedule_test_e2e(self):
+        # Validate the following
+        # 1. Create 2 VM Schedules - start and stop
+        # 2. Verify VM Schedule is created
+        # 3. Verify VM is stopped after schedule time
+        # 4. Verify VM is started after schedule time
+        # 5. Delete VM Schedule
+        # 6. Verify VM Schedule is deleted
+        # 7. Verify VM is not stopped after schedule time
+        # 8. Verify VM is not started after schedule time
+
+        # Create VM Schedule - start
+        start_schedule = "*/2 * * * *"
+        start_vmschedule = VMSchedule.create(
+            self.apiclient,
+            self.virtual_machine.id,
+            "start",
+            start_schedule,
+            datetime.datetime.now().astimezone().tzinfo,
+            # Current date minutes in format "2014-01-01 00:00:00"
+            (datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime(
+                "%Y-%m-%d %H:%M:%S"
+            ),
+            enabled=True,
+        )
+
+        self.debug("Created VM Schedule with ID: %s" % start_vmschedule.id)
+
+        # Create VM Schedule - stop
+        stop_schedule = "*/1 * * * *"
+        stop_vmschedule = VMSchedule.create(
+            self.apiclient,
+            self.virtual_machine.id,
+            "stop",
+            stop_schedule,
+            datetime.datetime.now().astimezone().tzinfo,
+            # Current date minutes in format "2014-01-01 00:00:00"
+            (datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime(
+                "%Y-%m-%d %H:%M:%S"
+            ),
+            enabled=True,
+        )
+
+        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
+        )
+
+        self.assertEqual(
+            isinstance(vmschedules, list),
+            True,
+            "Check list response returns a valid list",
+        )
+        self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule is created")
+
+        # poll every 10 seconds (max waiting time is 6 minutes) and check VM's state for changes
+        previous_state = self.virtual_machine.state
+        self.debug("VM state: %s" % self.virtual_machine.state)
+        is_stop_schedule_working = False
+        is_start_schedule_working = False
+        for i in range(0, 36):
+            time.sleep(10)
+            current_state = self.virtual_machine.update(self.apiclient).state
+            self.debug("Polling VM state: %s" % current_state)
+            if previous_state in ("Running", "Starting") and current_state in (
+                "Stopped",
+                "Stopping",
+            ):
+                is_stop_schedule_working = True
+            elif previous_state in ("Stopped", "Stopping") and current_state in (
+                "Running",
+                "Starting",
+            ):
+                is_start_schedule_working = True
+            if is_start_schedule_working and is_stop_schedule_working:
+                break
+            previous_state = current_state
+
+        self.debug("Is stop schedule working: %s" % is_stop_schedule_working)
+        self.debug("Is start schedule working: %s" % is_start_schedule_working)
+
+        self.assertTrue(
+            is_stop_schedule_working,
+            "VM switched states from Running to Stopped at least once",
+        )
+
+        self.assertTrue(
+            is_start_schedule_working,
+            "VM switched states from Stopped to Running at least once",
+        )
+
+        # Delete VM Schedule
+        start_vmschedule.delete(self.apiclient)
+        stop_vmschedule.delete(self.apiclient)
+
+        # To ensure that all vm schedules have been deleted and all of their jobs have been completed
+        time.sleep(60)
+
+        # Verify VM Schedule is deleted
+        self.assertEqual(
+            VMSchedule.list(
+                self.apiclient, 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
+            ),
+            None,
+            "Check VM Schedule is deleted",
+        )
+
+        # Verify VM does not switch states after deleting schedules at least for 2 minutes
+        previous_state = self.virtual_machine.update(self.apiclient).state
+        state_changed = False
+        for i in range(0, 4):
+            time.sleep(30)
+            current_state = self.virtual_machine.update(self.apiclient).state
+            if previous_state != current_state:
+                # Add these checks because VMs can take some time to start or stop
+                if (previous_state == 'Starting' and current_state in ('Starting', 'Running')) or (
+                        previous_state == 'Stopping' and current_state in ('Stopping', 'Stopped')):
+                    continue
+                self.debug(
+                    "VM changed state from %s to %s" % (previous_state, current_state)
+                )
+                state_changed = True
+                break
+
+        self.assertFalse(
+            state_changed,
+            "VM did not switch states after schedule time",
+        )
+        return
diff --git a/test/integration/smoke/test_vnf_templates.py b/test/integration/smoke/test_vnf_templates.py
new file mode 100644
index 0000000..f963ce4
--- /dev/null
+++ b/test/integration/smoke/test_vnf_templates.py
@@ -0,0 +1,341 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 VNF templates/appliances
+"""
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.base import (Account,
+                             Domain,
+                             Configurations,
+                             ServiceOffering,
+                             VirtualMachine,
+                             Network,
+                             NetworkOffering,
+                             VnfAppliance,
+                             VnfTemplate,
+                             Zone)
+from marvin.lib.common import get_zone, get_template
+from nose.plugins.attrib import attr
+
+import time
+
+VNF_NICS = [{"deviceid": "0", "name": "WAN", "required": "true", "description": "Public WAN"},
+          {"deviceid": "1", "name": "LAN-1", "required": "true", "description": "Private LAN-1"}]
+NEW_VNF_NICS = [{"deviceid": "0", "name": "WAN", "required": "true", "description": "Public WAN"},
+          {"deviceid": "1", "name": "LAN-1", "required": "true", "description": "Private LAN-1"},
+          {"deviceid": "2", "name": "LAN-2", "required": "false", "description": "Private LAN-2"}]
+VNF_DETAILS = [{"access_methods": "console,https,http", "username": "root"}]
+NEW_VNF_DETAILS = [{"access_methods": "console,https,http", "username": "root", "password": "cloudstack"}]
+
+class TestVnfTemplates(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+
+        testClient = super(TestVnfTemplates, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls._cleanup = []
+        cls.services = testClient.getParsedTestDataConfig()
+
+        # Get Zone, Domain and templates
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
+        zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.zone = Zone(zone.__dict__)
+        cls.template = get_template(cls.apiclient, cls.zone.id)
+
+        cls.domain = Domain.create(
+            cls.apiclient,
+            cls.services["domain"]
+        )
+        cls._cleanup.append(cls.domain)
+
+        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.user_apiclient = cls.testClient.getUserApiClient(
+            cls.user.username, cls.domain.name
+        )
+
+        cls.service_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["big"]
+        )
+        cls._cleanup.append(cls.service_offering)
+
+        cls.vnf_template_config = {
+            "name": "pfsense",
+            "displaytext": "pfsense",
+            "format": cls.template.format,
+            "url": cls.template.url,
+            "requireshvm": "True",
+            "ispublic": "True",
+            "isextractable": "True",
+            "hypervisor": cls.hypervisor,
+            "zoneid": cls.zone.id,
+            "ostype": "FreeBSD 12 (64-bit)",
+            "directdownload": False
+        }
+
+        cls.initial_setting = Configurations.list(
+            cls.apiclient,
+            name="vnf.template.appliance.enabled")[0].value
+
+        Configurations.update(cls.apiclient, "vnf.template.appliance.enabled", "true")
+
+        cls.vnf_templates = []
+
+    @classmethod
+    def tearDownClass(cls):
+        Configurations.update(cls.apiclient, "vnf.template.appliance.enabled", cls.initial_setting)
+        if len(cls.vnf_templates) > 0:
+            for vnf_template in cls.vnf_templates:
+                vnf_template.delete(cls.user_apiclient)
+        super(TestVnfTemplates, cls).tearDownClass()
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+
+    def tearDown(self):
+        super(TestVnfTemplates, self).tearDown()
+
+    def ensureVnfTemplateExists(self):
+        if len(self.vnf_templates) == 0:
+            self.vnf_template = VnfTemplate.register(self.user_apiclient,
+                                                     self.vnf_template_config,
+                                                     zoneid=self.zone.id,
+                                                     hypervisor=self.hypervisor,
+                                                     vnfnics=VNF_NICS,
+                                                     vnfdetails=VNF_DETAILS)
+            self.vnf_templates.append(self.vnf_template)
+        else:
+            self.vnf_template = self.vnf_templates[0]
+
+    def ensureVnfTemplateDownloaded(self):
+        """Check if template download will finish in 5 minutes"""
+        retries = 30
+        interval = 10
+        while retries > -1:
+            time.sleep(interval)
+            templates_response = VnfTemplate.list(
+                self.user_apiclient,
+                id=self.vnf_template.id,
+                zoneid=self.zone.id,
+                templatefilter='self'
+            )
+            template = templates_response[0]
+
+            if not hasattr(template, 'status') or not template or not template.status:
+                retries = retries - 1
+                continue
+
+            if 'Failed' in template.status:
+                raise Exception(
+                    "Failed to download template: status - %s" %
+                    template.status)
+            elif template.status == 'Download Complete' and template.isready:
+                return
+            elif 'Downloaded' in template.status:
+                retries = retries - 1
+                continue
+            elif 'Installing' not in template.status:
+                if retries >= 0:
+                    retries = retries - 1
+                    continue
+                raise Exception(
+                    "Error in downloading template: status - %s" %
+                    template.status)
+            else:
+                retries = retries - 1
+        raise Exception("Template download failed exception.")
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_01_register_vnf_template(self):
+        """Test register VNF template
+        """
+        self.ensureVnfTemplateExists()
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_02_list_vnf_template(self):
+        """Test list VNF template
+        """
+        self.ensureVnfTemplateExists()
+
+        templates_response = VnfTemplate.list(
+            self.user_apiclient,
+            id=self.vnf_template.id,
+            zoneid=self.zone.id,
+            templatefilter='self'
+        )
+
+        if isinstance(templates_response, list) and len(templates_response) > 0:
+            template = templates_response[0]
+            self.assertEqual("VNF", template.templatetype,
+                             "The template type of VNF template should be VNF but actually it is %s" % template.templatetype)
+            self.assertTrue(isinstance(template.vnfnics, list), "The template vnfnics must be a list")
+            self.assertEqual(2, len(template.vnfnics), "The VNF template should have 2 VNF nics")
+            self.assertEqual(2, len(template.vnfdetails.__dict__), "The VNF template should have 2 VNF details")
+        else:
+            self.fail("Failed to get VNF templates by listVnfTemplates API")
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_03_edit_vnf_template(self):
+        """Test edit VNF template
+        """
+        self.ensureVnfTemplateExists()
+
+        self.vnf_template.update(
+            self.user_apiclient,
+            id=self.vnf_template.id,
+            vnfnics=NEW_VNF_NICS,
+            vnfdetails=NEW_VNF_DETAILS
+        )
+
+        templates_response = VnfTemplate.list(
+            self.user_apiclient,
+            id=self.vnf_template.id,
+            zoneid=self.zone.id,
+            templatefilter='self'
+        )
+
+        if isinstance(templates_response, list) and len(templates_response) > 0:
+            template = templates_response[0]
+            self.assertEqual("VNF", template.templatetype,
+                             "The template type of VNF template should be VNF but actually it is %s" % template.templatetype)
+            self.assertEqual(3, len(template.vnfnics), "The VNF template should have 2 VNF nics")
+            self.assertEqual(3, len(template.vnfdetails.__dict__), "The VNF template should have 3 VNF details")
+        else:
+            self.fail("Failed to get VNF templates by listVnfTemplates API")
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_04_deploy_vnf_appliance(self):
+        """Test deploy VNF appliance
+        """
+        self.ensureVnfTemplateExists()
+        self.ensureVnfTemplateDownloaded()
+
+        templates_response = VnfTemplate.list(
+            self.user_apiclient,
+            id=self.vnf_template.id,
+            zoneid=self.zone.id,
+            templatefilter='self'
+        )
+
+        if isinstance(templates_response, list) and len(templates_response) > 0:
+            template = templates_response[0]
+            if not template.isready:
+                self.fail("VNF template is not Ready")
+        else:
+            self.fail("Failed to find VNF template")
+
+        # Create network offerings
+        self.isolated_network_offering = NetworkOffering.create(
+            self.apiclient,
+            self.services["isolated_network_offering"])
+        self.cleanup.append(self.isolated_network_offering)
+        self.isolated_network_offering.update(
+            self.apiclient,
+            state='Enabled')
+
+        self.l2_network_offering = NetworkOffering.create(
+            self.apiclient,
+            self.services["l2-network_offering"])
+        self.cleanup.append(self.l2_network_offering)
+        self.l2_network_offering.update(
+            self.apiclient,
+            state='Enabled')
+
+        # Create networks
+        isolated_network = Network.create(
+            self.user_apiclient,
+            self.services["network"],
+            networkofferingid=self.isolated_network_offering.id,
+            zoneid=self.zone.id
+        )
+        self.cleanup.append(isolated_network)
+
+        l2_network_1 = Network.create(
+            self.user_apiclient,
+            self.services["l2-network"],
+            networkofferingid=self.l2_network_offering.id,
+            zoneid=self.zone.id
+        )
+        self.cleanup.append(l2_network_1)
+
+        l2_network_2 = Network.create(
+            self.user_apiclient,
+            self.services["l2-network"],
+            networkofferingid=self.l2_network_offering.id,
+            zoneid=self.zone.id
+        )
+        self.cleanup.append(l2_network_2)
+
+        # failed deployment
+        try:
+            self.virtual_machine = VirtualMachine.create(
+                self.user_apiclient,
+                self.services["virtual_machine"],
+                zoneid=self.zone.id,
+                templateid=self.vnf_template.id,
+                accountid=self.account.name,
+                domainid=self.account.domainid,
+                serviceofferingid=self.service_offering.id,
+                networkids=[isolated_network.id]
+            )
+            self.cleanup.append(self.virtual_machine)
+            self.fail("The deployment should fail")
+        except Exception as e:
+            pass
+
+        # success deployment
+        self.vnf_appliance = VnfAppliance.create(
+            self.user_apiclient,
+            self.services["virtual_machine"],
+            zoneid=self.zone.id,
+            templateid=self.vnf_template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            networkids=[isolated_network.id, l2_network_1.id, l2_network_2.id],
+            vnfconfiguremanagement='true'
+        )
+        self.cleanup.append(self.vnf_appliance)
+
+    @attr(tags=["advanced"], required_hardware="false")
+    def test_05_delete_vnf_template(self):
+        """Test delete VNF template
+        """
+        self.ensureVnfTemplateExists()
+
+        self.vnf_template.delete(self.user_apiclient)
+
+        templates_response = VnfTemplate.list(
+            self.user_apiclient,
+            id=self.vnf_template.id,
+            zoneid=self.zone.id,
+            templatefilter='self'
+        )
+        self.assertIsNone(templates_response, "The VNF template should be removed")
+
+        self.vnf_templates.remove(self.vnf_template)
diff --git a/test/integration/testpaths/testpath_multiple_snapshot.py b/test/integration/testpaths/testpath_multiple_snapshot.py
index 50cccff..f4bc414 100644
--- a/test/integration/testpaths/testpath_multiple_snapshot.py
+++ b/test/integration/testpaths/testpath_multiple_snapshot.py
@@ -157,7 +157,7 @@
                 storage pools available in the setup")
             if len(list(storagePool for storagePool in self.pools
                         if storagePool.scope == "CLUSTER")) < 2:
-                self.skipTest("There must be at at least two cluster wide\
+                self.skipTest("There must be at least two cluster wide\
                 storage pools available in the setup")
         except Exception as e:
             self.skipTest(e)
diff --git a/test/integration/testpaths/testpath_restore_vm.py b/test/integration/testpaths/testpath_restore_vm.py
index 32ac214..956e411 100644
--- a/test/integration/testpaths/testpath_restore_vm.py
+++ b/test/integration/testpaths/testpath_restore_vm.py
@@ -141,7 +141,7 @@
                 "Check: Failed to list  cluster wide storage pools")
 
             if len(self.pools) < 2:
-                self.skipTest("There must be at at least two cluster wide\
+                self.skipTest("There must be at least two cluster wide\
                 storage pools available in the setup")
 
         except Exception as e:
diff --git a/test/integration/testpaths/testpath_usage.py b/test/integration/testpaths/testpath_usage.py
index 3774bf8..7726116 100644
--- a/test/integration/testpaths/testpath_usage.py
+++ b/test/integration/testpaths/testpath_usage.py
@@ -432,7 +432,7 @@
     def test_01_positive_tests_usage(self):
         """ Positive test for usage test path
 
-        # 1.  Register a template and verify that usage usage is generated
+        # 1.  Register a template and verify that usage is generated
               for correct size of template
         # 2.  Register an ISO, verify usage is generate for the correct size
               of ISO
diff --git a/test/integration/testpaths/testpath_volumelifecycle.py b/test/integration/testpaths/testpath_volumelifecycle.py
index 42dd7c0..3b8350c 100644
--- a/test/integration/testpaths/testpath_volumelifecycle.py
+++ b/test/integration/testpaths/testpath_volumelifecycle.py
@@ -732,7 +732,7 @@
         self.assertGreater(
             len(list_pool),
             0,
-            "Check the list list storagepoolresponse for vm id:  %s" %
+            "Check the list storagepoolresponse for vm id:  %s" %
             list_volume[0].storageid)
         list_pools = StoragePool.list(self.apiclient,
                                       scope=list_pool[0].scope
diff --git a/test/metadata/func/commands b/test/metadata/func/commands
index 86754eb..53bebed 100644
--- a/test/metadata/func/commands
+++ b/test/metadata/func/commands
@@ -1,191 +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.

-

-updateAccount=no

-listAccounts=no

-

-createUser=no

-updateUser=no

-deleteUser=no

-listUsers=no

-disableUser=yes

-enableUser=no

-disableAccount=yes

-enableAccount=no

-lockUser=no

-lockAccount=no

-deleteAccount=yes

-

-createDomain=no

-updateDomain=no

-deleteDomain=yes

-listDomains=no

-listDomainChildren=no

-

-updateResourceLimit=no

-deleteResourceLimit=no

-listResourceLimits=no

-

-deployVirtualMachine=yes

-destroyVirtualMachine=yes

-rebootVirtualMachine=yes

-startVirtualMachine=yes

-stopVirtualMachine=yes

-resetPasswordForVirtualMachine=yes

-changeServiceForVirtualMachine=no

-updateVirtualMachine=no

-recoverVirtualMachine=no

-listVirtualMachines=no

-

-createSnapshot=yes

-deleteSnapshot=yes

-listSnapshots=no

-listRecurringSnapshotSchedule=no

-createVolumeFromSnapshot=yes

-createSnapshotPolicy=no

-deleteSnapshotPolicies=no

-listSnapshotPolicies=no

-

-createTemplate=yes

-registerTemplate=no

-updateTemplate=no

-deleteTemplate=no

-listTemplates=no

-updateTemplatePermissions=no

-listTemplatePermissions=no

-copyTemplate=yes

-

-attachIso=yes

-detachIso=yes

-listIsos=no

-registerIso=no

-updateIso=no

-deleteIso=yes

-copyIso=yes

-

-listOSTypes=no

-

-createServiceOffering=no

-deleteServiceOffering=no

-updateServiceOffering=no

-listServiceOfferings=no

-

-createDiskOffering=no

-updateDiskOffering=no

-deleteDiskOffering=no

-listDiskOfferings=no

-

-createVlan=no

-deleteVlan=no

-

-createVlanIpRange=no

-deleteVlanIpRange=no

-listVlanIpRanges=no

-

-createVlanRange=no

-deleteVlanRange=no

-listVlanRanges=no

-

-associateIpAddress=yes

-disassociateIpAddress=yes

-listPublicIpAddresses=no

-listPrivateIpAddresses=no

-updatePublicIpRange=no

-updatePrivateIpRange=no

-

-createPortForwardingServiceRule=yes

-deletePortForwardingServiceRule=yes

-listPortForwardingServiceRules=no

-createPortForwardingService=no

-deletePortForwardingService=yes

-assignPortForwardingService=yes

-removePortForwardingService=yes

-listPortForwardingServices=no

-listPortForwardingServicesByVm=no

-createPortForwardingRule=no

-deletePortForwardingRule=no

-listPortForwardingRules=no

-updatePortForwardingRule=no

-

-createIpForwardingRule=yes

-deleteIpForwardingRule=yes

-listIpForwardingRules=no

-

-createLoadBalancerRule=no

-deleteLoadBalancerRule=yes

-removeFromLoadBalancerRule=yes

-assignToLoadBalancerRule=yes

-listLoadBalancerRules=no

-listLoadBalancerRuleInstances=no

-

-startRouter=yes

-rebootRouter=yes

-stopRouter=yes

-listRouters=no

-

-startSystemVm=yes

-rebootSystemVm=yes

-stopSystemVm=yes

-listSystemVms=no

-

-updateConfiguration=no

-listConfigurations=no

-

-createPod=no

-updatePod=no

-deletePod=no

-listPods=no

-

-createZone=no

-updateZone=no

-deleteZone=no

-listZones=no

-

-listEvents=no

-

-listAlerts=no

-

-listCapacity=no

-

-addHost=no

-reconnectHost=yes

-deleteHost=no

-prepareHostForMaintenance=yes

-cancelHostMaintenance=yes

-listHosts=no

-addSecondaryStorage=no

-

-attachVolume=yes

-detachVolume=yes

-createVolume=yes

-deleteVolume=no

-listVolumes=no

-

-registerUserKeys=no

-

-queryAsyncJobResult=no

-

-listStoragePools=no

-listStoragePoolsAndHosts=no

-createStoragePool=no

-

-

-createNetworkGroup=no

-revokeNetworkGroupIngress=yes

-authorizeNetworkGroupIngress=yes

-

-createNetwork=no

+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+updateAccount=no
+listAccounts=no
+
+createUser=no
+updateUser=no
+deleteUser=no
+listUsers=no
+disableUser=yes
+enableUser=no
+disableAccount=yes
+enableAccount=no
+lockUser=no
+lockAccount=no
+deleteAccount=yes
+
+createDomain=no
+updateDomain=no
+deleteDomain=yes
+listDomains=no
+listDomainChildren=no
+
+updateResourceLimit=no
+deleteResourceLimit=no
+listResourceLimits=no
+
+deployVirtualMachine=yes
+destroyVirtualMachine=yes
+rebootVirtualMachine=yes
+startVirtualMachine=yes
+stopVirtualMachine=yes
+resetPasswordForVirtualMachine=yes
+changeServiceForVirtualMachine=no
+updateVirtualMachine=no
+recoverVirtualMachine=no
+listVirtualMachines=no
+
+createSnapshot=yes
+deleteSnapshot=yes
+listSnapshots=no
+listRecurringSnapshotSchedule=no
+createVolumeFromSnapshot=yes
+createSnapshotPolicy=no
+deleteSnapshotPolicies=no
+listSnapshotPolicies=no
+
+createTemplate=yes
+registerTemplate=no
+updateTemplate=no
+deleteTemplate=no
+listTemplates=no
+updateTemplatePermissions=no
+listTemplatePermissions=no
+copyTemplate=yes
+
+attachIso=yes
+detachIso=yes
+listIsos=no
+registerIso=no
+updateIso=no
+deleteIso=yes
+copyIso=yes
+
+listOSTypes=no
+
+createServiceOffering=no
+deleteServiceOffering=no
+updateServiceOffering=no
+listServiceOfferings=no
+
+createDiskOffering=no
+updateDiskOffering=no
+deleteDiskOffering=no
+listDiskOfferings=no
+
+createVlan=no
+deleteVlan=no
+
+createVlanIpRange=no
+deleteVlanIpRange=no
+listVlanIpRanges=no
+
+createVlanRange=no
+deleteVlanRange=no
+listVlanRanges=no
+
+associateIpAddress=yes
+disassociateIpAddress=yes
+listPublicIpAddresses=no
+listPrivateIpAddresses=no
+updatePublicIpRange=no
+updatePrivateIpRange=no
+
+createPortForwardingServiceRule=yes
+deletePortForwardingServiceRule=yes
+listPortForwardingServiceRules=no
+createPortForwardingService=no
+deletePortForwardingService=yes
+assignPortForwardingService=yes
+removePortForwardingService=yes
+listPortForwardingServices=no
+listPortForwardingServicesByVm=no
+createPortForwardingRule=no
+deletePortForwardingRule=no
+listPortForwardingRules=no
+updatePortForwardingRule=no
+
+createIpForwardingRule=yes
+deleteIpForwardingRule=yes
+listIpForwardingRules=no
+
+createLoadBalancerRule=no
+deleteLoadBalancerRule=yes
+removeFromLoadBalancerRule=yes
+assignToLoadBalancerRule=yes
+listLoadBalancerRules=no
+listLoadBalancerRuleInstances=no
+
+startRouter=yes
+rebootRouter=yes
+stopRouter=yes
+listRouters=no
+
+startSystemVm=yes
+rebootSystemVm=yes
+stopSystemVm=yes
+listSystemVms=no
+
+updateConfiguration=no
+listConfigurations=no
+
+createPod=no
+updatePod=no
+deletePod=no
+listPods=no
+
+createZone=no
+updateZone=no
+deleteZone=no
+listZones=no
+
+listEvents=no
+
+listAlerts=no
+
+listCapacity=no
+
+addHost=no
+reconnectHost=yes
+deleteHost=no
+prepareHostForMaintenance=yes
+cancelHostMaintenance=yes
+listHosts=no
+addSecondaryStorage=no
+
+attachVolume=yes
+detachVolume=yes
+createVolume=yes
+deleteVolume=no
+listVolumes=no
+
+registerUserKeys=no
+
+queryAsyncJobResult=no
+
+listStoragePools=no
+listStoragePoolsAndHosts=no
+createStoragePool=no
+
+
+createNetworkGroup=no
+revokeNetworkGroupIngress=yes
+authorizeNetworkGroupIngress=yes
+
+createNetwork=no
diff --git a/test/metadata/func/error_events.properties b/test/metadata/func/error_events.properties
index a7c7633..4d440e6 100644
--- a/test/metadata/func/error_events.properties
+++ b/test/metadata/func/error_events.properties
@@ -1,32 +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.

-

-# All the ERROR level events that need to be checked in EventsApiTest

-

-VM.CREATE=1

-VM.START=1

-VM.STOP=1

-VM.REBOOT=1

-VM.UPGRADE=1

-VM.RESETPASSWORD=1

-ROUTER.CREATE=1

-ROUTER.START=1

-ROUTER.STOP=1

-ROUTER.REBOOT=1

-NET.IPASSIGN=1

-NET.RULEADD=2

-NET.RULEDELETE=2

+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# All the ERROR level events that need to be checked in EventsApiTest
+
+VM.CREATE=1
+VM.START=1
+VM.STOP=1
+VM.REBOOT=1
+VM.UPGRADE=1
+VM.RESETPASSWORD=1
+ROUTER.CREATE=1
+ROUTER.START=1
+ROUTER.STOP=1
+ROUTER.REBOOT=1
+NET.IPASSIGN=1
+NET.RULEADD=2
+NET.RULEDELETE=2
diff --git a/test/metadata/func/regression_events.properties b/test/metadata/func/regression_events.properties
index 456574c..3315363 100644
--- a/test/metadata/func/regression_events.properties
+++ b/test/metadata/func/regression_events.properties
@@ -1,43 +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.

-

-# All the events that need to be created as a result of execution of Regression test

-VM.CREATE=2

-VM.DESTROY=2

-VM.START=2

-VM.STOP=2

-VM.REBOOT=1

-VM.UPGRADE=1

-VM.RESETPASSWORD=1

-ROUTER.CREATE=1

-ROUTER.DESTROY=1

-ROUTER.START=2

-ROUTER.STOP=2

-ROUTER.REBOOT=1

-NET.IPASSIGN=2

-NET.IPRELEASE=2

-SNAPSHOT.CREATE=1

-SNAPSHOT.DELETE=1

-PF.SERVICE.APPLY=1

-PF.SERVICE.REMOVE=1

-TEMPLATE.CREATE=2

-NET.RULEADD=4

-NET.RULEDELETE=4

-VOLUME.CREATE=6

-VOLUME.DELETE=6

-VOLUME.ATTACH=1

-VOLUME.DETACH=1

+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# All the events that need to be created as a result of execution of Regression test
+VM.CREATE=2
+VM.DESTROY=2
+VM.START=2
+VM.STOP=2
+VM.REBOOT=1
+VM.UPGRADE=1
+VM.RESETPASSWORD=1
+ROUTER.CREATE=1
+ROUTER.DESTROY=1
+ROUTER.START=2
+ROUTER.STOP=2
+ROUTER.REBOOT=1
+NET.IPASSIGN=2
+NET.IPRELEASE=2
+SNAPSHOT.CREATE=1
+SNAPSHOT.DELETE=1
+PF.SERVICE.APPLY=1
+PF.SERVICE.REMOVE=1
+TEMPLATE.CREATE=2
+NET.RULEADD=4
+NET.RULEDELETE=4
+VOLUME.CREATE=6
+VOLUME.DELETE=6
+VOLUME.ATTACH=1
+VOLUME.DETACH=1
diff --git a/test/metadata/func/templates_sync.xml b/test/metadata/func/templates_sync.xml
index a03e4bf..96d6362 100644
--- a/test/metadata/func/templates_sync.xml
+++ b/test/metadata/func/templates_sync.xml
@@ -1058,6 +1058,3 @@
 

 

 </templatesync>

-

-

-

diff --git a/test/pom.xml b/test/pom.xml
index f42af84..4fcf84e 100644
--- a/test/pom.xml
+++ b/test/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
diff --git a/test/scripts/bootstrap-regression.sh b/test/scripts/bootstrap-regression.sh
index 8328d10..ff219dd 100755
--- a/test/scripts/bootstrap-regression.sh
+++ b/test/scripts/bootstrap-regression.sh
@@ -34,4 +34,4 @@
 ./cleanparallel.sh
 ./deploy.sh  -b ../cloud-management-dist -d -r
 sleep 300
-./regression.sh
\ No newline at end of file
+./regression.sh
diff --git a/test/scripts/build-env.sh b/test/scripts/build-env.sh
index 2a9a586..299b6b6 100755
--- a/test/scripts/build-env.sh
+++ b/test/scripts/build-env.sh
@@ -27,5 +27,3 @@
 
 CP=commons-httpclient-3.1.jar${PATHSEP}commons-logging-1.1.1.jar${PATHSEP}commons-codec-1.3.jar${PATHSEP}testclient.jar${PATHSEP}log4j-1.2.15.jar${PATHSEP}utils.jar${PATHSEP}./conf
 java -cp $CP com.vmops.test.longrun.BuildGuestNetwork $*
-
-
diff --git a/test/scripts/checkOutOfMemory.sh b/test/scripts/checkOutOfMemory.sh
index 08dccda..cb5b595 100755
--- a/test/scripts/checkOutOfMemory.sh
+++ b/test/scripts/checkOutOfMemory.sh
@@ -34,4 +34,4 @@
 sleep 1800
 done
 fi
-done
\ No newline at end of file
+done
diff --git a/test/scripts/cleanparallel.sh b/test/scripts/cleanparallel.sh
index fe72da7..0ccb546 100755
--- a/test/scripts/cleanparallel.sh
+++ b/test/scripts/cleanparallel.sh
@@ -110,6 +110,3 @@
   echo "Starting cleanup on secondary storage $i";
   cleanup_storage $i $SECONDARY_STORAGE_DIR
 done
-
-
-
diff --git a/test/scripts/deploy-and-run-regression.sh b/test/scripts/deploy-and-run-regression.sh
index 5c2ad98..691a930 100755
--- a/test/scripts/deploy-and-run-regression.sh
+++ b/test/scripts/deploy-and-run-regression.sh
@@ -45,4 +45,3 @@
 scp -o BatchMode\ yes -r "$srcbootstrapper" $host:$destbootstrapper
 
 ssh -o BatchMode\ yes $host $destbootstrapper
-
diff --git a/test/scripts/deploycluster.sh b/test/scripts/deploycluster.sh
index 636a56b..7d7e168 100755
--- a/test/scripts/deploycluster.sh
+++ b/test/scripts/deploycluster.sh
@@ -49,7 +49,7 @@
   echo "Deploying management server software on remote machine $1"
 
   rsync -avzh -e ssh $path/cloudstack-oss root@$1:/root/
-  if [ $? -gt 0 ]; then echo "failed to rsync software between $path/cloudstack-oss and and remote machine $1"; return 2; fi
+  if [ $? -gt 0 ]; then echo "failed to rsync software between $path/cloudstack-oss and remote machine $1"; return 2; fi
 
   ssh root@$1 "cd /root/cloudstack-oss && ant clean build-all deploy-server"
   if [ $? -gt 0 ]; then echo "failed to deploy cluster on $1"; return 2; fi
@@ -70,8 +70,3 @@
   echo "$ip"
   deploy_server $ip
 done
-
-
-
-
-
diff --git a/test/scripts/executeUserAPI.sh b/test/scripts/executeUserAPI.sh
index 55107bc..caf54b1 100755
--- a/test/scripts/executeUserAPI.sh
+++ b/test/scripts/executeUserAPI.sh
@@ -30,5 +30,3 @@
 
 CP=${DST}commons-httpclient-3.1.jar${PATHSEP}${DST}commons-logging-1.1.1.jar${PATHSEP}${DST}commons-codec-1.4.jar${PATHSEP}${DST}testclient.jar${PATHSEP}.././conf
 java -cp $CP com.cloud.sample.UserCloudAPIExecutor
-
-
diff --git a/test/scripts/run.sh b/test/scripts/run.sh
index 987f70e..de1dc22 100755
--- a/test/scripts/run.sh
+++ b/test/scripts/run.sh
@@ -31,4 +31,3 @@
 CP=${DST}commons-httpclient-3.1.jar${PATHSEP}${DST}commons-logging-1.1.1.jar${PATHSEP}${DST}commons-codec-1.4.jar${PATHSEP}${DST}cloud-test.jar${PATHSEP}${DST}log4j.jar${PATHSEP}${DST}trilead-ssh2-build213.jar${PATHSEP}${DST}cloud-utils.jar${PATHSEP}.././conf
 java -cp $CP com.cloud.test.stress.TestClientWithAPI $*
 #java -cp $CP com.cloud.test.stress.StressTestDirectAttach $*
-
diff --git a/test/scripts/usage/allocated.sh b/test/scripts/usage/allocated.sh
index 7ae5737..bb7fa1b 100755
--- a/test/scripts/usage/allocated.sh
+++ b/test/scripts/usage/allocated.sh
@@ -83,4 +83,3 @@
 echo "Skipping verification for account $i (the account either a) misses the second VM b) VM wasn't stopped 3) VM Stop failed "
 fi
 done
-
diff --git a/test/scripts/usage/volume_usage.sh b/test/scripts/usage/volume_usage.sh
index c18493b..a3b21d6 100755
--- a/test/scripts/usage/volume_usage.sh
+++ b/test/scripts/usage/volume_usage.sh
@@ -157,5 +157,3 @@
 echo "Skipping verification for account $i (the account either a) misses root volume $volume_name b) volume wasn't deleted 3) Delete volume failed "
 fi
 done
-
-
diff --git a/test/scripts/xen/corrupttemplate.sh b/test/scripts/xen/corrupttemplate.sh
index 528dcf1..d26097f 100755
--- a/test/scripts/xen/corrupttemplate.sh
+++ b/test/scripts/xen/corrupttemplate.sh
@@ -48,4 +48,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/createfaketemplate.sh b/test/scripts/xen/createfaketemplate.sh
index a68a0c3..cc91122 100755
--- a/test/scripts/xen/createfaketemplate.sh
+++ b/test/scripts/xen/createfaketemplate.sh
@@ -50,4 +50,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/killvm.sh b/test/scripts/xen/killvm.sh
index 43b81c9..5aa9b1e 100755
--- a/test/scripts/xen/killvm.sh
+++ b/test/scripts/xen/killvm.sh
@@ -49,4 +49,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/listtemplate.sh b/test/scripts/xen/listtemplate.sh
index bf3c589..4b76b4a 100755
--- a/test/scripts/xen/listtemplate.sh
+++ b/test/scripts/xen/listtemplate.sh
@@ -44,4 +44,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/listvdi.sh b/test/scripts/xen/listvdi.sh
index cf20bc6..9125b75 100755
--- a/test/scripts/xen/listvdi.sh
+++ b/test/scripts/xen/listvdi.sh
@@ -55,4 +55,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/listvm.sh b/test/scripts/xen/listvm.sh
index cd58e78..0a4e8b2 100755
--- a/test/scripts/xen/listvm.sh
+++ b/test/scripts/xen/listvm.sh
@@ -55,4 +55,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/ms.sh b/test/scripts/xen/ms.sh
index c06b119..d4a6cbc 100755
--- a/test/scripts/xen/ms.sh
+++ b/test/scripts/xen/ms.sh
@@ -37,4 +37,4 @@
 sleep 30
 
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/removetemplate.sh b/test/scripts/xen/removetemplate.sh
index dc7a63a..dd632cf 100755
--- a/test/scripts/xen/removetemplate.sh
+++ b/test/scripts/xen/removetemplate.sh
@@ -48,4 +48,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/shutdown.sh b/test/scripts/xen/shutdown.sh
index 69c51fe..c5621b1b 100755
--- a/test/scripts/xen/shutdown.sh
+++ b/test/scripts/xen/shutdown.sh
@@ -49,4 +49,4 @@
 exit 2
 else
 exit 0
-fi
\ No newline at end of file
+fi
diff --git a/test/scripts/xen/sleep.sh b/test/scripts/xen/sleep.sh
index f06798d..68bde07 100755
--- a/test/scripts/xen/sleep.sh
+++ b/test/scripts/xen/sleep.sh
@@ -30,4 +30,4 @@
   esac
 done
 
-sleep $sleep
\ No newline at end of file
+sleep $sleep
diff --git a/test/src-not-used/main/java/com/cloud/test/regression/TestCase.java b/test/src-not-used/main/java/com/cloud/test/regression/TestCase.java
index cb7395c..2bbf1bb 100644
--- a/test/src-not-used/main/java/com/cloud/test/regression/TestCase.java
+++ b/test/src-not-used/main/java/com/cloud/test/regression/TestCase.java
@@ -85,7 +85,7 @@
         this.conn = null;
         try {
             Class.forName("com.mysql.jdbc.Driver");
-            this.conn = DriverManager.getConnection("jdbc:mysql://" + param.get("db") + "/cloud", "root", dbPassword);
+            this.conn = DriverManager.getConnection("jdbc:mysql://" + param.get("db") + "/cloud?" + TransactionLegacy.CONNECTION_PARAMS, "root", dbPassword);
             if (!this.conn.isValid(0)) {
                 s_logger.error("Connection to DB failed to establish");
             }
diff --git a/test/systemvm/test_update_config.py b/test/systemvm/test_update_config.py
index 6b78bfb..5bdfb05 100644
--- a/test/systemvm/test_update_config.py
+++ b/test/systemvm/test_update_config.py
@@ -280,7 +280,7 @@
             unique["eth%s" % ips["nic_dev_id"]] = 1
 
         # If this is the first run, the drops will not be there yet
-        # this is so I can get get a true count of what is explicitly added
+        # this is so I can get a true count of what is explicitly added
         drops = len(unique)
         for dev in unique:
             drops -= ip.count_fw_rules('ACL_INBOUND_%s -j DROP' % dev)
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index 1352800..b971d24 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -50,6 +50,7 @@
     'SystemVm': 'System VM',
     'VirtualMachine': 'Virtual Machine',
     'VM': 'Virtual Machine',
+    'Vnf': 'Virtual Network Functions',
     'Domain': 'Domain',
     'Template': 'Template',
     'Iso': 'ISO',
@@ -157,6 +158,11 @@
     'listIdps': 'Authentication',
     'authorizeSamlSso': 'Authentication',
     'listSamlAuthorization': 'Authentication',
+    'oauthlogin': 'Authentication',
+    'deleteOauthProvider': 'Oauth',
+    'listOauthProvider': 'Oauth',
+    'registerOauthProvider': 'Oauth',
+    'updateOauthProvider': 'Oauth',
     'quota': 'Quota',
     'emailTemplate': 'Quota',
     'Capacity': 'System Capacity',
@@ -204,6 +210,7 @@
     'deleteSecondaryStagingStore': 'Image Store',
     'listSecondaryStagingStores': 'Image Store',
     'updateImageStore': 'Image Store',
+    'downloadImageStoreObject': 'Image Store',
     'InternalLoadBalancer': 'Internal LB',
 	'DeploymentPlanners': 'Configuration',
 	'ObjectStore': 'Image Store',
@@ -248,7 +255,23 @@
     'Rolling': 'Rolling Maintenance',
     'importVsphereStoragePolicies' : 'vSphere storage policies',
     'listVsphereStoragePolicies' : 'vSphere storage policies',
-    'ConsoleEndpoint': 'Console Endpoint'
+    'ConsoleEndpoint': 'Console Endpoint',
+    'Shutdown': 'Shutdown',
+    'importVm': 'Virtual Machine',
+    'listQuarantinedIp': 'IP Quarantine',
+    'updateQuarantinedIp': 'IP Quarantine',
+    'removeQuarantinedIp': 'IP Quarantine',
+    'Shutdown': 'Shutdown',
+    'addObjectStoragePool': 'Object Store',
+    'listObjectStoragePools': 'Object Store',
+    'deleteObjectStoragePool': 'Object Store',
+    'updateObjectStoragePool': 'Object Store',
+    'createBucket': 'Object Store',
+    'updateBucket': 'Object Store',
+    'deleteBucket': 'Object Store',
+    'listBuckets': 'Object Store',
+    'listVmsForImport': 'Virtual Machine',
+    'importVm': 'Virtual Machine'
 }
 
 
diff --git a/tools/apidoc/generatecommand.xsl b/tools/apidoc/generatecommand.xsl
index a54170a..e610d09 100644
--- a/tools/apidoc/generatecommand.xsl
+++ b/tools/apidoc/generatecommand.xsl
@@ -192,4 +192,3 @@
 </html>
 </xsl:template>
 </xsl:stylesheet>
-
diff --git a/tools/apidoc/generatecustomcommand.xsl b/tools/apidoc/generatecustomcommand.xsl
index d03cf56..ae480bc 100644
--- a/tools/apidoc/generatecustomcommand.xsl
+++ b/tools/apidoc/generatecustomcommand.xsl
@@ -62,4 +62,3 @@
 </body></html>
 </xsl:template>
 </xsl:stylesheet>
-
diff --git a/tools/apidoc/generategenericcommand.xsl b/tools/apidoc/generategenericcommand.xsl
index 0d5dd14..0e93667 100644
--- a/tools/apidoc/generategenericcommand.xsl
+++ b/tools/apidoc/generategenericcommand.xsl
@@ -54,4 +54,3 @@
 </body></html>
 </xsl:template>
 </xsl:stylesheet>
-
diff --git a/tools/apidoc/generatetoc.xsl b/tools/apidoc/generatetoc.xsl
index 687e626..72be6a1 100644
--- a/tools/apidoc/generatetoc.xsl
+++ b/tools/apidoc/generatetoc.xsl
@@ -1143,4 +1143,3 @@
 </body></html>
 </xsl:template>
 </xsl:stylesheet>
-
diff --git a/tools/apidoc/pom.xml b/tools/apidoc/pom.xml
index 926f157..6a63da4 100644
--- a/tools/apidoc/pom.xml
+++ b/tools/apidoc/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-tools</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <properties>
diff --git a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh
index f80d62f..27a1ead 100644
--- a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh
+++ b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh
@@ -19,7 +19,7 @@
 set -e
 set -x
 
-CLOUDSTACK_RELEASE=4.18.1
+CLOUDSTACK_RELEASE=4.19.0
 
 function configure_apache2() {
    # Enable ssl, rewrite and auth
diff --git a/tools/appliance/systemvmtemplate/template.json b/tools/appliance/systemvmtemplate/template.json
index b9d3632..ba3a843 100644
--- a/tools/appliance/systemvmtemplate/template.json
+++ b/tools/appliance/systemvmtemplate/template.json
@@ -27,8 +27,8 @@
       "format": "qcow2",
       "headless": true,
       "http_directory": "http",
-      "iso_checksum": "sha512:4460ef6470f6d8ae193c268e213d33a6a5a0da90c2d30c1024784faa4e4473f0c9b546a41e2d34c43fbbd43542ae4fb93cfd5cb6ac9b88a476f1a6877c478674",
-      "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/11.7.0/amd64/iso-cd/debian-11.7.0-amd64-netinst.iso",
+      "iso_checksum": "sha512:da7e7867ed043b784f5ae7e4adaaf4f023b5235f0fa2ead1279dc93f74bc17801ed906d330e3cd68ee8d3e96b697d21d23cfe2b755f5a9eb555bd5390a8c4dac",
+      "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/11.8.0/amd64/iso-cd/debian-11.8.0-amd64-netinst.iso",
       "net_device": "virtio-net",
       "output_directory": "../dist",
       "qemuargs": [
diff --git a/tools/checkstyle/pom.xml b/tools/checkstyle/pom.xml
index 71f9762..4819c42 100644
--- a/tools/checkstyle/pom.xml
+++ b/tools/checkstyle/pom.xml
@@ -22,7 +22,7 @@
     <name>Apache CloudStack Developer Tools - Checkstyle Configuration</name>
     <groupId>org.apache.cloudstack</groupId>
     <artifactId>checkstyle</artifactId>
-    <version>4.18.3.0-SNAPSHOT</version>
+    <version>4.19.1.0-SNAPSHOT</version>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/tools/devcloud-kvm/pom.xml b/tools/devcloud-kvm/pom.xml
index 3aac4f7..818ad62 100644
--- a/tools/devcloud-kvm/pom.xml
+++ b/tools/devcloud-kvm/pom.xml
@@ -25,13 +25,13 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-tools</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -80,8 +80,8 @@
                         <dependencies>
                             <!-- specify the dependent jdbc driver here -->
                             <dependency>
-                                <groupId>mysql</groupId>
-                                <artifactId>mysql-connector-java</artifactId>
+                                <groupId>com.mysql</groupId>
+                                <artifactId>mysql-connector-j</artifactId>
                                 <version>${cs.mysql.version}</version>
                             </dependency>
                         </dependencies>
diff --git a/tools/devcloud4/.gitignore b/tools/devcloud4/.gitignore
index 3969b1b..107499d 100644
--- a/tools/devcloud4/.gitignore
+++ b/tools/devcloud4/.gitignore
@@ -1,4 +1,4 @@
 tmp
 cookbooks
 *.lock
-.vagrant
\ No newline at end of file
+.vagrant
diff --git a/tools/devcloud4/binary-installation-advanced/Berksfile b/tools/devcloud4/binary-installation-advanced/Berksfile
index 7ad09e1..918a6d7 100644
--- a/tools/devcloud4/binary-installation-advanced/Berksfile
+++ b/tools/devcloud4/binary-installation-advanced/Berksfile
@@ -23,4 +23,4 @@
 cookbook 'selinux'
 cookbook 'nat-router', git: 'http://github.com/imduffy15/cookbook_nat-router'
 cookbook 'cloudstack', git: 'https://github.com/imduffy15/cookbook_cloudstack-1'
-cookbook 'binary-installation', path: '../common/binary-installation'
\ No newline at end of file
+cookbook 'binary-installation', path: '../common/binary-installation'
diff --git a/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb b/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb
index 6c847fb..bd65d3d 100644
--- a/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb
+++ b/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb
@@ -120,4 +120,4 @@
         "passwd": "<%= @database_password %>",
         "db": "<%= @database %>"
     }
-}
\ No newline at end of file
+}
diff --git a/tools/devcloud4/binary-installation-basic/Berksfile b/tools/devcloud4/binary-installation-basic/Berksfile
index 7ad09e1..918a6d7 100644
--- a/tools/devcloud4/binary-installation-basic/Berksfile
+++ b/tools/devcloud4/binary-installation-basic/Berksfile
@@ -23,4 +23,4 @@
 cookbook 'selinux'
 cookbook 'nat-router', git: 'http://github.com/imduffy15/cookbook_nat-router'
 cookbook 'cloudstack', git: 'https://github.com/imduffy15/cookbook_cloudstack-1'
-cookbook 'binary-installation', path: '../common/binary-installation'
\ No newline at end of file
+cookbook 'binary-installation', path: '../common/binary-installation'
diff --git a/tools/devcloud4/binary-installation-basic/marvin.cfg.erb b/tools/devcloud4/binary-installation-basic/marvin.cfg.erb
index 62415c9..721fc07 100644
--- a/tools/devcloud4/binary-installation-basic/marvin.cfg.erb
+++ b/tools/devcloud4/binary-installation-basic/marvin.cfg.erb
@@ -106,4 +106,4 @@
         "passwd": "<%= @database_password %>",
         "db": "<%= @database %>"
     }
-}
\ No newline at end of file
+}
diff --git a/tools/devcloud4/common/binary-installation/attributes/default.rb b/tools/devcloud4/common/binary-installation/attributes/default.rb
index 3b3eba0..94662ce 100644
--- a/tools/devcloud4/common/binary-installation/attributes/default.rb
+++ b/tools/devcloud4/common/binary-installation/attributes/default.rb
@@ -35,4 +35,4 @@
 default['cloudstack']['primary']['mgt_path'] = node['cloudstack']['primary']['path']
 
 
-default['cloudstack']['configuration'] = '/vagrant/marvin.cfg.erb'
\ No newline at end of file
+default['cloudstack']['configuration'] = '/vagrant/marvin.cfg.erb'
diff --git a/tools/devcloud4/common/binary-installation/recipes/database_server.rb b/tools/devcloud4/common/binary-installation/recipes/database_server.rb
index 28a374c..b9341a2 100644
--- a/tools/devcloud4/common/binary-installation/recipes/database_server.rb
+++ b/tools/devcloud4/common/binary-installation/recipes/database_server.rb
@@ -21,4 +21,3 @@
 include_recipe 'mysql::client'
 
 include_recipe 'cloudstack::mysql_conf'
-
diff --git a/tools/devcloud4/common/binary-installation/recipes/default.rb b/tools/devcloud4/common/binary-installation/recipes/default.rb
index 6dcd47c..600ec98 100644
--- a/tools/devcloud4/common/binary-installation/recipes/default.rb
+++ b/tools/devcloud4/common/binary-installation/recipes/default.rb
@@ -24,4 +24,4 @@
 
 include_recipe 'binary-installation::nfsshares'
 include_recipe 'binary-installation::database_server'
-include_recipe 'binary-installation::management_server'
\ No newline at end of file
+include_recipe 'binary-installation::management_server'
diff --git a/tools/devcloud4/common/development-installation/attributes/default.rb b/tools/devcloud4/common/development-installation/attributes/default.rb
index 705423b..f2c5712 100644
--- a/tools/devcloud4/common/development-installation/attributes/default.rb
+++ b/tools/devcloud4/common/development-installation/attributes/default.rb
@@ -26,4 +26,4 @@
 default['cloudstack']['primary']['mgt_path'] = node['cloudstack']['primary']['path']
 
 default['cloudstack']['cloud-install-sys-tmplt'] = "#{Chef::Config['file_cache_path']}/cloud-install-sys-tmplt"
-default['cloudstack']['createtmplt'] = "#{Chef::Config['file_cache_path']}/createtmplt.sh"
\ No newline at end of file
+default['cloudstack']['createtmplt'] = "#{Chef::Config['file_cache_path']}/createtmplt.sh"
diff --git a/tools/devcloud4/common/development-installation/recipes/database_server.rb b/tools/devcloud4/common/development-installation/recipes/database_server.rb
index 28a374c..b9341a2 100644
--- a/tools/devcloud4/common/development-installation/recipes/database_server.rb
+++ b/tools/devcloud4/common/development-installation/recipes/database_server.rb
@@ -21,4 +21,3 @@
 include_recipe 'mysql::client'
 
 include_recipe 'cloudstack::mysql_conf'
-
diff --git a/tools/devcloud4/common/development-installation/recipes/default.rb b/tools/devcloud4/common/development-installation/recipes/default.rb
index 617acc7..188d923 100644
--- a/tools/devcloud4/common/development-installation/recipes/default.rb
+++ b/tools/devcloud4/common/development-installation/recipes/default.rb
@@ -24,4 +24,4 @@
 
 include_recipe 'development-installation::nfsshares'
 include_recipe 'development-installation::system_templates'
-include_recipe 'development-installation::database_server'
\ No newline at end of file
+include_recipe 'development-installation::database_server'
diff --git a/tools/devcloud4/common/development-installation/recipes/system_templates.rb b/tools/devcloud4/common/development-installation/recipes/system_templates.rb
index 7723f26..9601e7b 100644
--- a/tools/devcloud4/common/development-installation/recipes/system_templates.rb
+++ b/tools/devcloud4/common/development-installation/recipes/system_templates.rb
@@ -35,4 +35,4 @@
   nfs_server node['cloudstack']['secondary']['host']
   url node['cloudstack']['hypervisor_tpl']['xenserver']
   action :create
-end
\ No newline at end of file
+end
diff --git a/tools/devcloud4/pom.xml b/tools/devcloud4/pom.xml
index a3ce883..4173a7a 100644
--- a/tools/devcloud4/pom.xml
+++ b/tools/devcloud4/pom.xml
@@ -25,13 +25,13 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-tools</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -80,8 +80,8 @@
                         <dependencies>
                             <!-- specify the dependent jdbc driver here -->
                             <dependency>
-                                <groupId>mysql</groupId>
-                                <artifactId>mysql-connector-java</artifactId>
+                                <groupId>com.mysql</groupId>
+                                <artifactId>mysql-connector-j</artifactId>
                                 <version>${cs.mysql.version}</version>
                             </dependency>
                         </dependencies>
diff --git a/tools/devcloud4/prefill.sql b/tools/devcloud4/prefill.sql
index d06de50..4213cd9 100644
--- a/tools/devcloud4/prefill.sql
+++ b/tools/devcloud4/prefill.sql
@@ -31,4 +31,4 @@
 REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'expunge.interval', '60');
 REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'management.network.cidr', '0.0.0.0/0');
 REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'secstorage.allowed.internal.sites', '0.0.0.0/0');
-UPDATE `cloud`.`vm_template` SET unique_name="Macchinina",name="Macchinina",url="http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-xen.vhd.bz2",checksum="30985504bc31bf0cd3b9d2c6ca7944d3",display_text="Macchinina" where id=5;
\ No newline at end of file
+UPDATE `cloud`.`vm_template` SET unique_name="Macchinina",name="Macchinina",url="http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-xen.vhd.bz2",checksum="30985504bc31bf0cd3b9d2c6ca7944d3",display_text="Macchinina" where id=5;
diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile
index 319e8f9..f010a7c 100644
--- a/tools/docker/Dockerfile
+++ b/tools/docker/Dockerfile
@@ -20,7 +20,7 @@
 FROM ubuntu:22.04
 
 MAINTAINER "Apache CloudStack" <dev@cloudstack.apache.org>
-LABEL Vendor="Apache.org" License="ApacheV2" Version="4.18.3.0-SNAPSHOT"
+LABEL Vendor="Apache.org" License="ApacheV2" Version="4.19.1.0-SNAPSHOT"
 
 ARG DEBIAN_FRONTEND=noninteractive
 
diff --git a/tools/docker/Dockerfile.marvin b/tools/docker/Dockerfile.marvin
index 52bdd6a..5d38c5d 100644
--- a/tools/docker/Dockerfile.marvin
+++ b/tools/docker/Dockerfile.marvin
@@ -20,11 +20,11 @@
 FROM python:2
 
 MAINTAINER "Apache CloudStack" <dev@cloudstack.apache.org>
-LABEL Vendor="Apache.org" License="ApacheV2" Version="4.18.3.0-SNAPSHOT"
+LABEL Vendor="Apache.org" License="ApacheV2" Version="4.19.1.0-SNAPSHOT"
 
 ENV WORK_DIR=/marvin
 
-ENV PKG_URL=https://builds.cloudstack.org/job/build-master-marvin/lastSuccessfulBuild/artifact/tools/marvin/dist/Marvin-4.18.3.0-SNAPSHOT.tar.gz
+ENV PKG_URL=https://builds.cloudstack.org/job/build-master-marvin/lastSuccessfulBuild/artifact/tools/marvin/dist/Marvin-4.19.1.0-SNAPSHOT.tar.gz
 
 RUN apt-get update && apt-get install -y vim
 RUN pip install --upgrade paramiko nose requests
diff --git a/tools/docker/Dockerfile.smokedev b/tools/docker/Dockerfile.smokedev
index 881e100..4476f6a 100644
--- a/tools/docker/Dockerfile.smokedev
+++ b/tools/docker/Dockerfile.smokedev
@@ -140,4 +140,4 @@
 #
 # cat /root/docker_run_tests.sh
 # for instructions
-#
\ No newline at end of file
+#
diff --git a/tools/eclipse/eclipse.epf b/tools/eclipse/eclipse.epf
index 6b42098..562a29c 100644
--- a/tools/eclipse/eclipse.epf
+++ b/tools/eclipse/eclipse.epf
@@ -15,416 +15,416 @@
 # specific language governing permissions and limitations
 # under the License.
 
-/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_parentheses_in_expressions=false

-/instance/org.eclipse.team.ui/org.eclipse.team.ui.first_time=false

-/instance/org.eclipse.jdt.ui/cleanup.add_default_serial_version_id=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line

-/instance/org.eclipse.ui.workbench/resourcetypes=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<editors version\="3.1">\r\n<info extension\="pyw" name\="*">\r\n<editor id\="org.python.pydev.editor.PythonEditor"/>\r\n<defaultEditor id\="org.python.pydev.editor.PythonEditor"/>\r\n</info>\r\n<info extension\="py" name\="*">\r\n<editor id\="org.python.pydev.editor.PythonEditor"/>\r\n<defaultEditor id\="org.python.pydev.editor.PythonEditor"/>\r\n</info>\r\n<info extension\="java" name\="*">\r\n<editor id\="org.eclipse.wb.core.guiEditor"/>\r\n</info>\r\n<info extension\="pyx" name\="*">\r\n<editor id\="org.python.pydev.editor.PythonEditor"/>\r\n<defaultEditor id\="org.python.pydev.editor.PythonEditor"/>\r\n</info>\r\n<info extension\="class without source" name\="*">\r\n<editor id\="org.eclipse.jdt.ui.ClassFileEditorNoSource"/>\r\n</info>\r\n<info extension\="html" name\="*">\r\n<editor id\="org.eclipse.ui.browser.editorSupport"/>\r\n</info>\r\n<info extension\="htm" name\="*">\r\n<editor id\="org.eclipse.ui.browser.editorSupport"/>\r\n</info>\r\n<info extension\="sql" name\="*">\r\n<editor id\="org.eclipse.ui.DefaultTextEditor"/>\r\n<defaultEditor id\="org.eclipse.ui.DefaultTextEditor"/>\r\n</info>\r\n<info extension\="jardesc" name\="*">\r\n<editor id\="org.eclipse.jdt.ui.JARDescEditor"/>\r\n<defaultEditor id\="org.eclipse.jdt.ui.JARDescEditor"/>\r\n</info>\r\n<info extension\="jpage" name\="*">\r\n<editor id\="org.eclipse.jdt.debug.ui.SnippetEditor"/>\r\n</info>\r\n<info extension\="shtml" name\="*">\r\n<editor id\="org.eclipse.ui.browser.editorSupport"/>\r\n</info>\r\n</editors>

-/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true

-/instance/org.eclipse.wst.sse.ui/useQuickDiffPrefPage=true

-/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_method_access=false

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_private_constructors=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation=2

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true

-/instance/org.eclipse.wst.validation/confirmDialog=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1

-/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debugdefault_watchpoint_suspend_policy=0

-/instance/org.eclipse.jdt.ui/cleanup.add_missing_nls_tags=false

-@org.eclipse.wst.sse.core=1.1.702.v201301241617

-/instance/org.eclipse.mylyn.context.core/mylyn.attention.migrated=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false

-/instance/org.eclipse.jdt.ui/useAnnotationsPrefPage=true

-/instance/org.eclipse.jdt.ui/formatter_profile=_Alex

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_override_annotations=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_package=0

-/instance/org.eclipse.debug.ui/preferredDetailPanes=DefaultDetailPane\:DefaultDetailPane|org.eclipse.jdt.debug.ui.DETAIL_PANE_LINE_BREAKPOINT\:org.eclipse.jdt.debug.ui.DETAIL_PANE_LINE_BREAKPOINT|org.eclipse.jdt.debug.ui.DETAIL_PANE_EXCEPTION_BREAKPOINT\:org.eclipse.jdt.debug.ui.DETAIL_PANE_EXCEPTION_BREAKPOINT|

-/instance/org.eclipse.wst.validation/override=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_method_accesses_with_declaring_class=false

-@org.eclipse.jdt.core=3.8.3.v20130121-145325

-/instance/org.eclipse.jdt.ui/cleanup.make_local_variable_final=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false

-/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.sort_members=false

-/instance/org.eclipse.jdt.ui/cleanup.remove_unused_imports=true

-/instance/org.eclipse.jdt.ui/cleanup.add_serial_version_id=false

-/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_nls_tags=true

-/instance/org.eclipse.debug.ui/preferredTargets=default\:default|

-/instance/org.eclipse.jdt.debug.ui/org.eclipse.jdt.debug.ui.javaDebug.alertHCRFailed=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_types=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JRE_SRC=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert

-/instance/org.eclipse.jdt.ui/cleanup_profile=_Alex

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.format_source_code_changes_only=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert

-/instance/org.eclipse.ui.intro/org.eclipse.epp.package.java.product_fontStyle=relative

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert

-@org.eclipse.e4.ui.css.swt.theme=0.9.4.v20130123-162658

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert

-/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.default_breakpoint_suspend_policy=2

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.editor.tab.width=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.make_variable_declarations_final=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert

-/instance/org.eclipse.core.resources/version=1

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.add_missing_deprecated_annotations=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.javadoclocations.migrated=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_imports=true

-/instance/org.eclipse.jdt.ui/content_assist_favorite_static_members=

-/instance/org.eclipse.jdt.ui/sp_cleanup.make_type_abstract_if_missing_method=false

-/instance/org.eclipse.jdt.ui/cleanup.remove_private_constructors=true

-/instance/org.eclipse.core.runtime/line.separator=\n

-/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true

-/instance/org.eclipse.jdt.ui/fontPropagated=true

-/instance/org.eclipse.wst.validation/saveAuto=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_wrapped_lines=false

-/instance/org.eclipse.ui.workbench/ENABLED_DECORATORS=de.tobject.findbugs.decorators.WorkingSetBugCountDecorator\:true,de.tobject.findbugs.decorators.ProjectBugCountDecorator\:true,de.tobject.findbugs.decorators.FolderBugCountDecorator\:true,de.tobject.findbugs.decorators.FileBugCountDecorator\:true,org.eclipse.m2e.core.mavenVersionDecorator\:false,org.eclipse.egit.ui.internal.decorators.GitLightweightDecorator\:true,org.eclipse.jdt.ui.override.decorator\:true,org.eclipse.jdt.ui.interface.decorator\:false,org.eclipse.jdt.ui.buildpath.decorator\:true,org.eclipse.jubula.client.teststyle.tsGuiNodeDecorator\:true,org.eclipse.jubula.client.teststyle.tsTestresultDecorator\:true,org.eclipse.jubula.client.core.model.TestResultNode\:true,org.eclipse.jubula.client.ui.rcp.decorators.resultDurationDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.resultParameterDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.completenessCheckDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.MissingReferenceDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.excelDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.activeElementDecorator\:true,org.eclipse.m2e.core.maven2decorator\:true,org.eclipse.mylyn.context.ui.decorator.interest\:true,org.eclipse.mylyn.tasks.ui.decorators.task\:true,org.eclipse.mylyn.team.ui.changeset.decorator\:true,org.eclipse.team.cvs.ui.decorator\:true,org.eclipse.ui.LinkedResourceDecorator\:true,org.eclipse.ui.VirtualResourceDecorator\:true,org.eclipse.ui.ContentTypeDecorator\:true,org.eclipse.ui.ResourceFilterDecorator\:false,org.python.pydev.navigator.decorator.problemsLabelDecorator\:true,

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert

-@org.eclipse.ui=3.104.0.v20121024-145224

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ondemandthreshold=99

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ignorelowercasenames=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16

-/instance/org.eclipse.debug.core/org.eclipse.debug.core.PREF_BREAKPOINT_MANAGER_ENABLED_STATE=true

-/instance/org.eclipse.mylyn.java.ui/org.eclipse.mylyn.java.ui.run.count.3_1_0=1

-/instance/org.eclipse.ui.ide/IMPORT_FILES_AND_FOLDERS_MODE=prompt

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert

-/instance/org.eclipse.jdt.ui/spelling_ignore_digits=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JUNIT_HOME=C\:/bin/Eclipse/current/plugins/org.junit_3.8.2.v3_8_2_v20100427-1100/

-/instance/org.eclipse.jdt.junit/org.eclipse.jdt.junit.show_in_all_views=false

-/instance/org.eclipse.jdt.ui/sp_cleanup.make_private_fields_final=true

-@org.eclipse.jdt.junit=3.7.100.v20120523-1543

-/instance/org.eclipse.jdt.ui/proposalOrderMigrated=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert

-/instance/org.eclipse.wst.validation/vf.version=3

-/instance/org.eclipse.jdt.ui/spelling_ignore_single_letters=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert

-/instance/org.eclipse.m2e.core/eclipse.m2.userSettingsFile=c\:\\bin\\Maven\\current\\conf\\settings.xml

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_imports=1

-/instance/org.eclipse.ui.ide/IMPORT_FILES_AND_FOLDERS_VIRTUAL_FOLDER_MODE=prompt

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<profiles version\="2">\r\n<profile kind\="CleanUpProfile" name\="Alex" version\="2">\r\n<setting id\="cleanup.remove_unused_private_fields" value\="true"/>\r\n<setting id\="cleanup.always_use_parentheses_in_expressions" value\="false"/>\r\n<setting id\="cleanup.never_use_blocks" value\="false"/>\r\n<setting id\="cleanup.add_missing_deprecated_annotations" value\="true"/>\r\n<setting id\="cleanup.remove_unused_private_methods" value\="true"/>\r\n<setting id\="cleanup.convert_to_enhanced_for_loop" value\="false"/>\r\n<setting id\="cleanup.remove_unnecessary_nls_tags" value\="true"/>\r\n<setting id\="cleanup.sort_members" value\="false"/>\r\n<setting id\="cleanup.remove_unused_local_variables" value\="false"/>\r\n<setting id\="cleanup.remove_unused_private_members" value\="false"/>\r\n<setting id\="cleanup.never_use_parentheses_in_expressions" value\="true"/>\r\n<setting id\="cleanup.remove_unnecessary_casts" value\="true"/>\r\n<setting id\="cleanup.make_parameters_final" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_field_access" value\="true"/>\r\n<setting id\="cleanup.use_blocks" value\="false"/>\r\n<setting id\="cleanup.remove_private_constructors" value\="true"/>\r\n<setting id\="cleanup.always_use_this_for_non_static_method_access" value\="false"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces_all" value\="false"/>\r\n<setting id\="cleanup.always_use_this_for_non_static_field_access" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_field_access_only_if_necessary" value\="true"/>\r\n<setting id\="cleanup.add_default_serial_version_id" value\="true"/>\r\n<setting id\="cleanup.make_type_abstract_if_missing_method" value\="false"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.make_variable_declarations_final" value\="false"/>\r\n<setting id\="cleanup.add_missing_nls_tags" value\="false"/>\r\n<setting id\="cleanup.format_source_code" value\="false"/>\r\n<setting id\="cleanup.add_missing_override_annotations" value\="true"/>\r\n<setting id\="cleanup.qualify_static_method_accesses_with_declaring_class" value\="false"/>\r\n<setting id\="cleanup.remove_unused_private_types" value\="true"/>\r\n<setting id\="cleanup.make_local_variable_final" value\="true"/>\r\n<setting id\="cleanup.add_missing_methods" value\="false"/>\r\n<setting id\="cleanup.add_missing_override_annotations_interface_methods" value\="true"/>\r\n<setting id\="cleanup.correct_indentation" value\="true"/>\r\n<setting id\="cleanup.remove_unused_imports" value\="true"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces_ignore_empty" value\="true"/>\r\n<setting id\="cleanup.make_private_fields_final" value\="true"/>\r\n<setting id\="cleanup.add_generated_serial_version_id" value\="false"/>\r\n<setting id\="cleanup.organize_imports" value\="true"/>\r\n<setting id\="cleanup.sort_members_all" value\="false"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces" value\="true"/>\r\n<setting id\="cleanup.use_blocks_only_for_return_and_throw" value\="false"/>\r\n<setting id\="cleanup.use_parentheses_in_expressions" value\="false"/>\r\n<setting id\="cleanup.add_missing_annotations" value\="true"/>\r\n<setting id\="cleanup.qualify_static_field_accesses_with_declaring_class" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_method_access_only_if_necessary" value\="true"/>\r\n<setting id\="cleanup.use_this_for_non_static_method_access" value\="true"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_through_instances_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.add_serial_version_id" value\="false"/>\r\n<setting id\="cleanup.always_use_blocks" value\="true"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.format_source_code_changes_only" value\="false"/>\r\n</profile>\r\n</profiles>\r\n

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0

-@org.python.pydev=2.7.5.2013052819

-/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_this_for_non_static_field_access=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert

-/instance/org.eclipse.ui.intro.universal/org.eclipse.epp.package.java.product_INTRO_DATA=<?xml version\="1.0" encoding\="utf-8" ?>\r\n<extensions>\r\n   <page id\="webresources">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="GDWelcome-webresourcesExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n      </group>\r\n   </page>\r\n   <page id\="tutorials">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n         <extension id\="org.eclipse.team" importance\="low"/>\r\n         <extension id\="GDWelcome-tutorialsExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="org.eclipse.mylyn" importance\="low"/>\r\n         <extension id\="org.eclipse.egit" importance\="low"/>\r\n         <extension id\="org.eclipse.mat.tutorials" importance\="low"/>\r\n      </group>\r\n   </page>\r\n   <page id\="migrate">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="GDWelcome-migrateExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n      </group>\r\n   </page>\r\n   <page id\="whatsnew">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench.news" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench.migration" importance\="low"/>\r\n         <extension id\="GDWelcome-whatsnewExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="org.eclipse.mylyn" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench" importance\="low"/>\r\n         <extension id\="org.eclipse.egit" importance\="low"/>\r\n      </group>\r\n   </page>\r\n   <page id\="samples">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="GDWelcome-samplesExtension" importance\="low"/>\r\n      </group>\r\n   </page>\r\n   <page id\="firststeps">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="GDWelcome-firststepsExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n      </group>\r\n   </page>\r\n   <page id\="overview">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.epp.mpc.ui" importance\="low"/>\r\n         <extension id\="org.eclipse.mylyn" importance\="low"/>\r\n         <extension id\="org.eclipse.egit" importance\="low"/>\r\n         <extension id\="org.eclipse.m2e" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench" importance\="low"/>\r\n         <extension id\="GDWelcome-overviewExtension" importance\="low"/>\r\n         <extension id\="org.eclipse.mat.overview" importance\="low"/>\r\n      </group>\r\n   </page>\r\n</extensions>\r\n

-/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.DebugVieworg.eclipse.debug.ui.DebugView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<DebugViewMemento org.eclipse.debug.ui.BREADCRUMB_DROPDOWN_AUTO_EXPAND\="false"/>

-/instance/org.eclipse.jdt.ui/spelling_ignore_non_letters=true

-/instance/org.python.pydev/PYDEV_FUNDING_SHOWN=true

-/instance/org.eclipse.jdt.ui/hoverModifierMasks=org.eclipse.jdt.ui.BestMatchHover;0;org.eclipse.jdt.internal.debug.ui.JavaDebugHover;0;org.eclipse.jdt.ui.ProblemHover;0;org.eclipse.jdt.ui.NLSStringHover;327680;org.eclipse.jdt.ui.JavadocHover;393216;org.eclipse.jdt.ui.AnnotationHover;0;org.eclipse.jdt.ui.JavaSourceHover;131072;

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_override_annotations_interface_methods=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=

-/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.suspend_for_breakpoints_during_evaluation=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces_all=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert

-/instance/org.eclipse.jdt.ui/cleanup.always_use_blocks=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.enumIdentifier=error

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.code_templates_migrated=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.size=4

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false

-/instance/org.eclipse.jdt.ui/useQuickDiffPrefPage=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert

-@org.eclipse.egit.core=2.3.1.201302201838-r

-/configuration/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.exception.name=e

-/instance/org.eclipse.wst.validation/USER_BUILD_PREFERENCE=enabledBuildValidatorList

-/instance/org.eclipse.jdt.ui/cleanup.add_missing_methods=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.never_use_blocks=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.align_type_members_on_columns=false

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_serial_version_id=false

-/instance/org.eclipse.jdt.ui/cleanup_settings_version=2

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_field=0

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert

-/instance/org.eclipse.ui.intro.universal/org.eclipse.epp.package.java.product_INTRO_ROOT_PAGES=overview,tutorials,samples,whatsnew

-/instance/org.eclipse.e4.ui.css.swt.theme/themeid=org.eclipse.e4.ui.css.theme.e4_classic

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert

-/instance/org.eclipse.jdt.ui/hoverModifiers=org.eclipse.jdt.ui.BestMatchHover;0;org.eclipse.jdt.internal.debug.ui.JavaDebugHover;\!0;org.eclipse.jdt.ui.ProblemHover;\!0;org.eclipse.jdt.ui.NLSStringHover;Ctrl+Alt;org.eclipse.jdt.ui.JavadocHover;Ctrl+Shift;org.eclipse.jdt.ui.AnnotationHover;\!0;org.eclipse.jdt.ui.JavaSourceHover;Shift;

-/instance/org.eclipse.jdt.ui/cleanup.make_type_abstract_if_missing_method=false

-@org.eclipse.wst.validation=1.2.402.v201212031633

-/instance/org.eclipse.wst.validation/USER_PREFERENCE=saveAutomaticallyfalseprojectsCanOverridetruedisableAllValidationfalseversion1.2.402.v201212031633

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indentation.size=4

-/instance/org.eclipse.jdt.ui/cleanup.sort_members_all=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert

-\!/=

-/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_with_declaring_class=true

-/instance/org.eclipse.jdt.ui/cleanup.organize_imports=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert

-/instance/org.eclipse.jdt.ui/spelling_ignore_mixed=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_lines_in_comments=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true

-/instance/org.eclipse.jdt.ui/spelling_ignore_upper=true

-/instance/org.eclipse.jdt.ui/content_assist_proposals_foreground=0,0,0

-@org.eclipse.ui.editors=3.8.0.v20120523-1540

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.add_missing_annotations=true

-/instance/org.eclipse.jdt.ui/sourceHoverBackgroundColor=255,255,225

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_nls_tags=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.assertIdentifier=error

-@org.eclipse.mylyn.java.ui=3.8.3.v20130107-0100

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert

-/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_ignore_empty=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert

-/instance/org.eclipse.ui.ide/platformState=1364576568717

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert

-@org.eclipse.team.cvs.ui=3.3.500.v20120522-1148

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_method_access=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_this_for_non_static_method_access=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert

-/instance/org.eclipse.jdt.ui/cleanup.make_private_fields_final=true

-/instance/org.eclipse.ui.editors/lineNumberRuler=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=

-/instance/org.eclipse.jdt.ui/sp_cleanup.format_source_code_changes_only=true

-/configuration/org.eclipse.ui.ide/RECENT_WORKSPACES_PROTOCOL=3

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JRE_SRCROOT=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_line_comments=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_root_tags=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.visibilityCheck=enabled

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false

-/instance/org.eclipse.debug.ui/org.eclipse.debug.ui.PREF_LAUNCH_PERSPECTIVES=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<launchPerspectives/>\r\n

-/instance/org.eclipse.m2e.editor.xml/org.eclipse.m2e.editor.xml.templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>

-/instance/org.eclipse.epp.mpc.ui/CatalogDescriptor=http\://marketplace.eclipse.org

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_local_variables=false

-/instance/org.eclipse.jdt.ui/cleanup.convert_to_enhanced_for_loop=false

-@org.eclipse.team.core=3.6.100.v20120524-0627

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_members=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_package=1

-/instance/org.eclipse.egit.ui/resourcehistory_rev_split=700,300

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.gettersetter.use.is=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.format_source_code=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16

-@org.eclipse.ui.intro.universal=3.2.600.v20120912-155524

-/instance/org.eclipse.jdt.ui/spelling_locale_initialized=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.convert_to_enhanced_for_loop=false

-/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations=true

-/instance/org.eclipse.jdt.launching/org.eclipse.jdt.launching.PREF_VM_XML=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<vmSettings defaultVM\="57,org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType13,1364578133326" defaultVMConnector\="">\r\n<vmType id\="org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType">\r\n<vm id\="1364578133326" name\="jre7" path\="C\:\\bin\\Java\\jre7"/>\r\n</vmType>\r\n</vmSettings>\r\n

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert

-/configuration/org.eclipse.ui.ide/SHOW_WORKSPACE_SELECTION_DIALOG=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert

-/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.ExpressionView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<VariablesViewMemento org.eclipse.debug.ui.SASH_DETAILS_PART\="315" org.eclipse.debug.ui.SASH_VIEW_PART\="684">\r\n<PRESENTATION_CONTEXT_PROPERTIES IMemento.internal.id\="org.eclipse.debug.ui.ExpressionView"/>\r\n</VariablesViewMemento>

-/instance/org.eclipse.jdt.ui/cleanup.use_parentheses_in_expressions=false

-/instance/org.python.pydev.debug/INITIAL_INTERPRETER_CMDS=import sys; print('%s %s' % (sys.executable or sys.platform, sys.version))\r\n

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_assignment=0

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_deprecated_annotations=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access_only_if_necessary=true

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.templates_migrated=true

-/instance/org.eclipse.egit.ui/merge_mode=0

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_field_accesses_with_declaring_class=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert

-/instance/org.eclipse.wst.sse.ui/useAnnotationsPrefPage=true

-@org.eclipse.m2e.discovery=1.3.1.20130219-1424

-@org.eclipse.wst.sse.ui=1.3.102.v201301162301

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert

-/instance/org.eclipse.jdt.launching/org.eclipse.jdt.launching.PREF_CONNECT_TIMEOUT=20000

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access_only_if_necessary=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert

-/instance/org.eclipse.ui.workbench/org.eclipse.ui.commands=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<org.eclipse.ui.commands/>

-/instance/org.eclipse.wst.xml.core/indentationChar=space

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_methods=true

-/instance/org.eclipse.wst.sse.ui/content_assist_number_of_computers=2

-/instance/org.eclipse.team.cvs.ui/pref_first_startup=false

-/instance/org.eclipse.egit.ui/Blame_IgnoreWhitespace=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_field_access=true

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.timeoutForParameterNameFromAttachedJavadoc=50

-/instance/org.eclipse.jdt.ui/formatter_settings_version=12

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert

-/instance/org.eclipse.jdt.ui/spelling_ignore_sentence=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16

-/instance/org.eclipse.jdt.ui/sp_cleanup.never_use_parentheses_in_expressions=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert

-/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations_interface_methods=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert

-/instance/org.eclipse.jdt.ui/spelling_ignore_java_strings=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16

-/instance/org.eclipse.jdt.ui/tabWidthPropagated=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<profiles version\="12">\r\n<profile kind\="CodeFormatterProfile" name\="Alex" version\="12">\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.disabling_tag" value\="@formatter\:off"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_field" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.use_on_off_tags" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_package" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation" value\="2"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_package" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.source" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_line_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.join_wrapped_lines" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.lineSplit" value\="180"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indentation.size" value\="4"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.enabling_tag" value\="@formatter\:on"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_assignment" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value\="error"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.char" value\="space"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_method" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_switch" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value\="error"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.compact_else_if" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.size" value\="4"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_empty_lines" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.compliance" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value\="2"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value\="enabled"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.line_length" value\="120"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.join_lines_in_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_html" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_source_code" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_header" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_block_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value\="false"/>\r\n</profile>\r\n</profiles>\r\n

-/instance/org.eclipse.wst.xml.core/lineWidth=80

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16

-/instance/org.eclipse.jdt.ui/cleanup.make_parameters_final=false

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_blocks=false

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true

-/instance/org.python.pydev/IRONPYTHON_INTERPRETER_PATH=<xml>\n<name>C\:\\Program Files (x86)\\IronPython 2.7\\ipy64.exe</name>\n<version>2.7</version>\n<executable>C\:\\Program Files (x86)\\IronPython 2.7\\ipy64.exe</executable>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7\\Lib</lib>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7\\DLLs</lib>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7</lib>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7\\lib\\site-packages</lib>\n<forced_lib>IEHost.Execute</forced_lib>\n<forced_lib>Microsoft</forced_lib>\n<forced_lib>Microsoft.Aspnet.Snapin</forced_lib>\n<forced_lib>Microsoft.Build.BuildEngine</forced_lib>\n<forced_lib>Microsoft.Build.Conversion</forced_lib>\n<forced_lib>Microsoft.Build.Framework</forced_lib>\n<forced_lib>Microsoft.Build.Tasks</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Deployment.Bootstrapper</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Deployment.ManifestUtilities</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Hosting</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Windows</forced_lib>\n<forced_lib>Microsoft.Build.Utilities</forced_lib>\n<forced_lib>Microsoft.CLRAdmin</forced_lib>\n<forced_lib>Microsoft.CSharp</forced_lib>\n<forced_lib>Microsoft.Data.Entity.Build.Tasks</forced_lib>\n<forced_lib>Microsoft.IE</forced_lib>\n<forced_lib>Microsoft.Ink</forced_lib>\n<forced_lib>Microsoft.Ink.TextInput</forced_lib>\n<forced_lib>Microsoft.JScript</forced_lib>\n<forced_lib>Microsoft.JScript.Vsa</forced_lib>\n<forced_lib>Microsoft.ManagementConsole</forced_lib>\n<forced_lib>Microsoft.ManagementConsole.Advanced</forced_lib>\n<forced_lib>Microsoft.ManagementConsole.Internal</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail.ExchangeWebService</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail.ExchangeWebService.Exchange2007</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail.WindowsMobile</forced_lib>\n<forced_lib>Microsoft.SqlServer.Server</forced_lib>\n<forced_lib>Microsoft.StylusInput</forced_lib>\n<forced_lib>Microsoft.StylusInput.PluginData</forced_lib>\n<forced_lib>Microsoft.VisualBasic</forced_lib>\n<forced_lib>Microsoft.VisualBasic.ApplicationServices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Compatibility.VB6</forced_lib>\n<forced_lib>Microsoft.VisualBasic.CompilerServices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Devices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.FileIO</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Logging</forced_lib>\n<forced_lib>Microsoft.VisualBasic.MyServices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.MyServices.Internal</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Vsa</forced_lib>\n<forced_lib>Microsoft.VisualC</forced_lib>\n<forced_lib>Microsoft.VisualC.StlClr</forced_lib>\n<forced_lib>Microsoft.VisualC.StlClr.Generic</forced_lib>\n<forced_lib>Microsoft.Vsa</forced_lib>\n<forced_lib>Microsoft.Vsa.Vb.CodeDOM</forced_lib>\n<forced_lib>Microsoft.Win32</forced_lib>\n<forced_lib>Microsoft.Win32.SafeHandles</forced_lib>\n<forced_lib>Microsoft.Windows.Themes</forced_lib>\n<forced_lib>Microsoft.WindowsCE.Forms</forced_lib>\n<forced_lib>Microsoft.WindowsMobile.DirectX</forced_lib>\n<forced_lib>Microsoft.WindowsMobile.DirectX.Direct3D</forced_lib>\n<forced_lib>Microsoft_VsaVb</forced_lib>\n<forced_lib>RegCode</forced_lib>\n<forced_lib>System</forced_lib>\n<forced_lib>System.AddIn</forced_lib>\n<forced_lib>System.AddIn.Contract</forced_lib>\n<forced_lib>System.AddIn.Contract.Automation</forced_lib>\n<forced_lib>System.AddIn.Contract.Collections</forced_lib>\n<forced_lib>System.AddIn.Hosting</forced_lib>\n<forced_lib>System.AddIn.Pipeline</forced_lib>\n<forced_lib>System.CodeDom</forced_lib>\n<forced_lib>System.CodeDom.Compiler</forced_lib>\n<forced_lib>System.Collections</forced_lib>\n<forced_lib>System.Collections.Generic</forced_lib>\n<forced_lib>System.Collections.ObjectModel</forced_lib>\n<forced_lib>System.Collections.Specialized</forced_lib>\n<forced_lib>System.ComponentModel</forced_lib>\n<forced_lib>System.ComponentModel.DataAnnotations</forced_lib>\n<forced_lib>System.ComponentModel.Design</forced_lib>\n<forced_lib>System.ComponentModel.Design.Data</forced_lib>\n<forced_lib>System.ComponentModel.Design.Serialization</forced_lib>\n<forced_lib>System.Configuration</forced_lib>\n<forced_lib>System.Configuration.Assemblies</forced_lib>\n<forced_lib>System.Configuration.Install</forced_lib>\n<forced_lib>System.Configuration.Internal</forced_lib>\n<forced_lib>System.Configuration.Provider</forced_lib>\n<forced_lib>System.Data</forced_lib>\n<forced_lib>System.Data.Common</forced_lib>\n<forced_lib>System.Data.Common.CommandTrees</forced_lib>\n<forced_lib>System.Data.Design</forced_lib>\n<forced_lib>System.Data.Entity.Design</forced_lib>\n<forced_lib>System.Data.Entity.Design.AspNet</forced_lib>\n<forced_lib>System.Data.EntityClient</forced_lib>\n<forced_lib>System.Data.Linq</forced_lib>\n<forced_lib>System.Data.Linq.Mapping</forced_lib>\n<forced_lib>System.Data.Linq.SqlClient</forced_lib>\n<forced_lib>System.Data.Linq.SqlClient.Implementation</forced_lib>\n<forced_lib>System.Data.Mapping</forced_lib>\n<forced_lib>System.Data.Metadata.Edm</forced_lib>\n<forced_lib>System.Data.Objects</forced_lib>\n<forced_lib>System.Data.Objects.DataClasses</forced_lib>\n<forced_lib>System.Data.Odbc</forced_lib>\n<forced_lib>System.Data.OleDb</forced_lib>\n<forced_lib>System.Data.OracleClient</forced_lib>\n<forced_lib>System.Data.Services</forced_lib>\n<forced_lib>System.Data.Services.Client</forced_lib>\n<forced_lib>System.Data.Services.Common</forced_lib>\n<forced_lib>System.Data.Services.Design</forced_lib>\n<forced_lib>System.Data.Services.Internal</forced_lib>\n<forced_lib>System.Data.Sql</forced_lib>\n<forced_lib>System.Data.SqlClient</forced_lib>\n<forced_lib>System.Data.SqlTypes</forced_lib>\n<forced_lib>System.Deployment.Application</forced_lib>\n<forced_lib>System.Deployment.Internal</forced_lib>\n<forced_lib>System.Diagnostics</forced_lib>\n<forced_lib>System.Diagnostics.CodeAnalysis</forced_lib>\n<forced_lib>System.Diagnostics.Design</forced_lib>\n<forced_lib>System.Diagnostics.Eventing</forced_lib>\n<forced_lib>System.Diagnostics.Eventing.Reader</forced_lib>\n<forced_lib>System.Diagnostics.PerformanceData</forced_lib>\n<forced_lib>System.Diagnostics.SymbolStore</forced_lib>\n<forced_lib>System.DirectoryServices</forced_lib>\n<forced_lib>System.DirectoryServices.AccountManagement</forced_lib>\n<forced_lib>System.DirectoryServices.ActiveDirectory</forced_lib>\n<forced_lib>System.DirectoryServices.Protocols</forced_lib>\n<forced_lib>System.Drawing</forced_lib>\n<forced_lib>System.Drawing.Design</forced_lib>\n<forced_lib>System.Drawing.Drawing2D</forced_lib>\n<forced_lib>System.Drawing.Imaging</forced_lib>\n<forced_lib>System.Drawing.Printing</forced_lib>\n<forced_lib>System.Drawing.Text</forced_lib>\n<forced_lib>System.EnterpriseServices</forced_lib>\n<forced_lib>System.EnterpriseServices.CompensatingResourceManager</forced_lib>\n<forced_lib>System.EnterpriseServices.Internal</forced_lib>\n<forced_lib>System.Globalization</forced_lib>\n<forced_lib>System.IO</forced_lib>\n<forced_lib>System.IO.Compression</forced_lib>\n<forced_lib>System.IO.IsolatedStorage</forced_lib>\n<forced_lib>System.IO.Log</forced_lib>\n<forced_lib>System.IO.Packaging</forced_lib>\n<forced_lib>System.IO.Pipes</forced_lib>\n<forced_lib>System.IO.Ports</forced_lib>\n<forced_lib>System.IdentityModel.Claims</forced_lib>\n<forced_lib>System.IdentityModel.Policy</forced_lib>\n<forced_lib>System.IdentityModel.Selectors</forced_lib>\n<forced_lib>System.IdentityModel.Tokens</forced_lib>\n<forced_lib>System.Linq</forced_lib>\n<forced_lib>System.Linq.Expressions</forced_lib>\n<forced_lib>System.Management</forced_lib>\n<forced_lib>System.Management.Instrumentation</forced_lib>\n<forced_lib>System.Media</forced_lib>\n<forced_lib>System.Messaging</forced_lib>\n<forced_lib>System.Messaging.Design</forced_lib>\n<forced_lib>System.Net</forced_lib>\n<forced_lib>System.Net.Cache</forced_lib>\n<forced_lib>System.Net.Configuration</forced_lib>\n<forced_lib>System.Net.Mail</forced_lib>\n<forced_lib>System.Net.Mime</forced_lib>\n<forced_lib>System.Net.NetworkInformation</forced_lib>\n<forced_lib>System.Net.PeerToPeer</forced_lib>\n<forced_lib>System.Net.PeerToPeer.Collaboration</forced_lib>\n<forced_lib>System.Net.Security</forced_lib>\n<forced_lib>System.Net.Sockets</forced_lib>\n<forced_lib>System.Printing</forced_lib>\n<forced_lib>System.Printing.IndexedProperties</forced_lib>\n<forced_lib>System.Printing.Interop</forced_lib>\n<forced_lib>System.Reflection</forced_lib>\n<forced_lib>System.Reflection.Emit</forced_lib>\n<forced_lib>System.Resources</forced_lib>\n<forced_lib>System.Resources.Tools</forced_lib>\n<forced_lib>System.Runtime</forced_lib>\n<forced_lib>System.Runtime.CompilerServices</forced_lib>\n<forced_lib>System.Runtime.ConstrainedExecution</forced_lib>\n<forced_lib>System.Runtime.Hosting</forced_lib>\n<forced_lib>System.Runtime.InteropServices</forced_lib>\n<forced_lib>System.Runtime.InteropServices.ComTypes</forced_lib>\n<forced_lib>System.Runtime.InteropServices.CustomMarshalers</forced_lib>\n<forced_lib>System.Runtime.InteropServices.Expando</forced_lib>\n<forced_lib>System.Runtime.Remoting</forced_lib>\n<forced_lib>System.Runtime.Remoting.Activation</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels.Http</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels.Ipc</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels.Tcp</forced_lib>\n<forced_lib>System.Runtime.Remoting.Contexts</forced_lib>\n<forced_lib>System.Runtime.Remoting.Lifetime</forced_lib>\n<forced_lib>System.Runtime.Remoting.Messaging</forced_lib>\n<forced_lib>System.Runtime.Remoting.Metadata</forced_lib>\n<forced_lib>System.Runtime.Remoting.MetadataServices</forced_lib>\n<forced_lib>System.Runtime.Remoting.Proxies</forced_lib>\n<forced_lib>System.Runtime.Remoting.Services</forced_lib>\n<forced_lib>System.Runtime.Serialization</forced_lib>\n<forced_lib>System.Runtime.Serialization.Configuration</forced_lib>\n<forced_lib>System.Runtime.Serialization.Formatters</forced_lib>\n<forced_lib>System.Runtime.Serialization.Formatters.Binary</forced_lib>\n<forced_lib>System.Runtime.Serialization.Formatters.Soap</forced_lib>\n<forced_lib>System.Runtime.Serialization.Json</forced_lib>\n<forced_lib>System.Runtime.Versioning</forced_lib>\n<forced_lib>System.Security</forced_lib>\n<forced_lib>System.Security.AccessControl</forced_lib>\n<forced_lib>System.Security.Authentication</forced_lib>\n<forced_lib>System.Security.Authentication.ExtendedProtection</forced_lib>\n<forced_lib>System.Security.Authentication.ExtendedProtection.Configuration</forced_lib>\n<forced_lib>System.Security.Cryptography</forced_lib>\n<forced_lib>System.Security.Cryptography.Pkcs</forced_lib>\n<forced_lib>System.Security.Cryptography.X509Certificates</forced_lib>\n<forced_lib>System.Security.Cryptography.Xml</forced_lib>\n<forced_lib>System.Security.Permissions</forced_lib>\n<forced_lib>System.Security.Policy</forced_lib>\n<forced_lib>System.Security.Principal</forced_lib>\n<forced_lib>System.Security.RightsManagement</forced_lib>\n<forced_lib>System.ServiceModel</forced_lib>\n<forced_lib>System.ServiceModel.Activation</forced_lib>\n<forced_lib>System.ServiceModel.Activation.Configuration</forced_lib>\n<forced_lib>System.ServiceModel.Channels</forced_lib>\n<forced_lib>System.ServiceModel.ComIntegration</forced_lib>\n<forced_lib>System.ServiceModel.Configuration</forced_lib>\n<forced_lib>System.ServiceModel.Description</forced_lib>\n<forced_lib>System.ServiceModel.Diagnostics</forced_lib>\n<forced_lib>System.ServiceModel.Dispatcher</forced_lib>\n<forced_lib>System.ServiceModel.Install.Configuration</forced_lib>\n<forced_lib>System.ServiceModel.Internal</forced_lib>\n<forced_lib>System.ServiceModel.MsmqIntegration</forced_lib>\n<forced_lib>System.ServiceModel.PeerResolvers</forced_lib>\n<forced_lib>System.ServiceModel.Persistence</forced_lib>\n<forced_lib>System.ServiceModel.Security</forced_lib>\n<forced_lib>System.ServiceModel.Security.Tokens</forced_lib>\n<forced_lib>System.ServiceModel.Syndication</forced_lib>\n<forced_lib>System.ServiceModel.Web</forced_lib>\n<forced_lib>System.ServiceProcess</forced_lib>\n<forced_lib>System.ServiceProcess.Design</forced_lib>\n<forced_lib>System.Speech.AudioFormat</forced_lib>\n<forced_lib>System.Speech.Recognition</forced_lib>\n<forced_lib>System.Speech.Recognition.SrgsGrammar</forced_lib>\n<forced_lib>System.Speech.Synthesis</forced_lib>\n<forced_lib>System.Speech.Synthesis.TtsEngine</forced_lib>\n<forced_lib>System.Text</forced_lib>\n<forced_lib>System.Text.RegularExpressions</forced_lib>\n<forced_lib>System.Threading</forced_lib>\n<forced_lib>System.Timers</forced_lib>\n<forced_lib>System.Transactions</forced_lib>\n<forced_lib>System.Transactions.Configuration</forced_lib>\n<forced_lib>System.Web</forced_lib>\n<forced_lib>System.Web.ApplicationServices</forced_lib>\n<forced_lib>System.Web.Caching</forced_lib>\n<forced_lib>System.Web.ClientServices</forced_lib>\n<forced_lib>System.Web.ClientServices.Providers</forced_lib>\n<forced_lib>System.Web.Compilation</forced_lib>\n<forced_lib>System.Web.Configuration</forced_lib>\n<forced_lib>System.Web.Configuration.Internal</forced_lib>\n<forced_lib>System.Web.DynamicData</forced_lib>\n<forced_lib>System.Web.DynamicData.Design</forced_lib>\n<forced_lib>System.Web.DynamicData.ModelProviders</forced_lib>\n<forced_lib>System.Web.Handlers</forced_lib>\n<forced_lib>System.Web.Hosting</forced_lib>\n<forced_lib>System.Web.Mail</forced_lib>\n<forced_lib>System.Web.Management</forced_lib>\n<forced_lib>System.Web.Mobile</forced_lib>\n<forced_lib>System.Web.Profile</forced_lib>\n<forced_lib>System.Web.Query.Dynamic</forced_lib>\n<forced_lib>System.Web.RegularExpressions</forced_lib>\n<forced_lib>System.Web.Routing</forced_lib>\n<forced_lib>System.Web.Script.Serialization</forced_lib>\n<forced_lib>System.Web.Script.Services</forced_lib>\n<forced_lib>System.Web.Security</forced_lib>\n<forced_lib>System.Web.Services</forced_lib>\n<forced_lib>System.Web.Services.Configuration</forced_lib>\n<forced_lib>System.Web.Services.Description</forced_lib>\n<forced_lib>System.Web.Services.Discovery</forced_lib>\n<forced_lib>System.Web.Services.Protocols</forced_lib>\n<forced_lib>System.Web.SessionState</forced_lib>\n<forced_lib>System.Web.UI</forced_lib>\n<forced_lib>System.Web.UI.Adapters</forced_lib>\n<forced_lib>System.Web.UI.Design</forced_lib>\n<forced_lib>System.Web.UI.Design.MobileControls</forced_lib>\n<forced_lib>System.Web.UI.Design.MobileControls.Converters</forced_lib>\n<forced_lib>System.Web.UI.Design.WebControls</forced_lib>\n<forced_lib>System.Web.UI.Design.WebControls.WebParts</forced_lib>\n<forced_lib>System.Web.UI.MobileControls</forced_lib>\n<forced_lib>System.Web.UI.MobileControls.Adapters</forced_lib>\n<forced_lib>System.Web.UI.MobileControls.Adapters.XhtmlAdapters</forced_lib>\n<forced_lib>System.Web.UI.WebControls</forced_lib>\n<forced_lib>System.Web.UI.WebControls.Adapters</forced_lib>\n<forced_lib>System.Web.UI.WebControls.WebParts</forced_lib>\n<forced_lib>System.Web.Util</forced_lib>\n<forced_lib>System.Windows</forced_lib>\n<forced_lib>System.Windows.Annotations</forced_lib>\n<forced_lib>System.Windows.Annotations.Storage</forced_lib>\n<forced_lib>System.Windows.Automation</forced_lib>\n<forced_lib>System.Windows.Automation.Peers</forced_lib>\n<forced_lib>System.Windows.Automation.Provider</forced_lib>\n<forced_lib>System.Windows.Automation.Text</forced_lib>\n<forced_lib>System.Windows.Controls</forced_lib>\n<forced_lib>System.Windows.Controls.Primitives</forced_lib>\n<forced_lib>System.Windows.Converters</forced_lib>\n<forced_lib>System.Windows.Data</forced_lib>\n<forced_lib>System.Windows.Documents</forced_lib>\n<forced_lib>System.Windows.Documents.Serialization</forced_lib>\n<forced_lib>System.Windows.Forms</forced_lib>\n<forced_lib>System.Windows.Forms.ComponentModel.Com2Interop</forced_lib>\n<forced_lib>System.Windows.Forms.Design</forced_lib>\n<forced_lib>System.Windows.Forms.Design.Behavior</forced_lib>\n<forced_lib>System.Windows.Forms.Integration</forced_lib>\n<forced_lib>System.Windows.Forms.Layout</forced_lib>\n<forced_lib>System.Windows.Forms.PropertyGridInternal</forced_lib>\n<forced_lib>System.Windows.Forms.VisualStyles</forced_lib>\n<forced_lib>System.Windows.Ink</forced_lib>\n<forced_lib>System.Windows.Ink.AnalysisCore</forced_lib>\n<forced_lib>System.Windows.Input</forced_lib>\n<forced_lib>System.Windows.Input.StylusPlugIns</forced_lib>\n<forced_lib>System.Windows.Interop</forced_lib>\n<forced_lib>System.Windows.Markup</forced_lib>\n<forced_lib>System.Windows.Markup.Localizer</forced_lib>\n<forced_lib>System.Windows.Markup.Primitives</forced_lib>\n<forced_lib>System.Windows.Media</forced_lib>\n<forced_lib>System.Windows.Media.Animation</forced_lib>\n<forced_lib>System.Windows.Media.Converters</forced_lib>\n<forced_lib>System.Windows.Media.Effects</forced_lib>\n<forced_lib>System.Windows.Media.Imaging</forced_lib>\n<forced_lib>System.Windows.Media.Media3D</forced_lib>\n<forced_lib>System.Windows.Media.Media3D.Converters</forced_lib>\n<forced_lib>System.Windows.Media.TextFormatting</forced_lib>\n<forced_lib>System.Windows.Navigation</forced_lib>\n<forced_lib>System.Windows.Resources</forced_lib>\n<forced_lib>System.Windows.Shapes</forced_lib>\n<forced_lib>System.Windows.Threading</forced_lib>\n<forced_lib>System.Windows.Xps</forced_lib>\n<forced_lib>System.Windows.Xps.Packaging</forced_lib>\n<forced_lib>System.Windows.Xps.Serialization</forced_lib>\n<forced_lib>System.Workflow.Activities</forced_lib>\n<forced_lib>System.Workflow.Activities.Configuration</forced_lib>\n<forced_lib>System.Workflow.Activities.Rules</forced_lib>\n<forced_lib>System.Workflow.Activities.Rules.Design</forced_lib>\n<forced_lib>System.Workflow.ComponentModel</forced_lib>\n<forced_lib>System.Workflow.ComponentModel.Compiler</forced_lib>\n<forced_lib>System.Workflow.ComponentModel.Design</forced_lib>\n<forced_lib>System.Workflow.ComponentModel.Serialization</forced_lib>\n<forced_lib>System.Workflow.Runtime</forced_lib>\n<forced_lib>System.Workflow.Runtime.Configuration</forced_lib>\n<forced_lib>System.Workflow.Runtime.DebugEngine</forced_lib>\n<forced_lib>System.Workflow.Runtime.Hosting</forced_lib>\n<forced_lib>System.Workflow.Runtime.Tracking</forced_lib>\n<forced_lib>System.Xml</forced_lib>\n<forced_lib>System.Xml.Linq</forced_lib>\n<forced_lib>System.Xml.Schema</forced_lib>\n<forced_lib>System.Xml.Serialization</forced_lib>\n<forced_lib>System.Xml.Serialization.Advanced</forced_lib>\n<forced_lib>System.Xml.Serialization.Configuration</forced_lib>\n<forced_lib>System.Xml.XPath</forced_lib>\n<forced_lib>System.Xml.Xsl</forced_lib>\n<forced_lib>System.Xml.Xsl.Runtime</forced_lib>\n<forced_lib>UIAutomationClientsideProviders</forced_lib>\n<forced_lib>__builtin__</forced_lib>\n<forced_lib>_ast</forced_lib>\n<forced_lib>_bisect</forced_lib>\n<forced_lib>_codecs</forced_lib>\n<forced_lib>_collections</forced_lib>\n<forced_lib>_csv</forced_lib>\n<forced_lib>_ctypes</forced_lib>\n<forced_lib>_ctypes_test</forced_lib>\n<forced_lib>_functools</forced_lib>\n<forced_lib>_heapq</forced_lib>\n<forced_lib>_io</forced_lib>\n<forced_lib>_locale</forced_lib>\n<forced_lib>_md5</forced_lib>\n<forced_lib>_random</forced_lib>\n<forced_lib>_sha</forced_lib>\n<forced_lib>_sha256</forced_lib>\n<forced_lib>_sha512</forced_lib>\n<forced_lib>_sqlite3</forced_lib>\n<forced_lib>_sre</forced_lib>\n<forced_lib>_ssl</forced_lib>\n<forced_lib>_struct</forced_lib>\n<forced_lib>_subprocess</forced_lib>\n<forced_lib>_warnings</forced_lib>\n<forced_lib>_weakref</forced_lib>\n<forced_lib>_winreg</forced_lib>\n<forced_lib>array</forced_lib>\n<forced_lib>binascii</forced_lib>\n<forced_lib>bz2</forced_lib>\n<forced_lib>cPickle</forced_lib>\n<forced_lib>cStringIO</forced_lib>\n<forced_lib>clr</forced_lib>\n<forced_lib>cmath</forced_lib>\n<forced_lib>copy_reg</forced_lib>\n<forced_lib>datetime</forced_lib>\n<forced_lib>email</forced_lib>\n<forced_lib>errno</forced_lib>\n<forced_lib>exceptions</forced_lib>\n<forced_lib>future_builtins</forced_lib>\n<forced_lib>gc</forced_lib>\n<forced_lib>hashlib</forced_lib>\n<forced_lib>imp</forced_lib>\n<forced_lib>itertools</forced_lib>\n<forced_lib>marshal</forced_lib>\n<forced_lib>math</forced_lib>\n<forced_lib>mmap</forced_lib>\n<forced_lib>msvcrt</forced_lib>\n<forced_lib>nt</forced_lib>\n<forced_lib>operator</forced_lib>\n<forced_lib>os</forced_lib>\n<forced_lib>os.path</forced_lib>\n<forced_lib>pytest</forced_lib>\n<forced_lib>re</forced_lib>\n<forced_lib>select</forced_lib>\n<forced_lib>signal</forced_lib>\n<forced_lib>socket</forced_lib>\n<forced_lib>sys</forced_lib>\n<forced_lib>thread</forced_lib>\n<forced_lib>time</forced_lib>\n<forced_lib>unicodedata</forced_lib>\n<forced_lib>winsound</forced_lib>\n<forced_lib>wpf</forced_lib>\n<forced_lib>xxsubtype</forced_lib>\n<forced_lib>zipimport</forced_lib>\n<forced_lib>zlib</forced_lib>\n</xml>&&&&&

-/instance/org.eclipse.jdt.ui/cleanup.add_generated_serial_version_id=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert

-/instance/org.eclipse.wst.validation/suspend=false

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles.version=12

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_method=1

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert

-/instance/org.eclipse.compare/org.eclipse.compare.IgnoreWhitespace=true

-/instance/org.eclipse.egit.ui/resourcehistory_graph_split=500,500

-@org.eclipse.mylyn.monitor.ui=3.8.3.v20130107-0100

-/instance/org.eclipse.ui.ide/PROBLEMS_FILTERS_MIGRATE=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.always_use_parentheses_in_expressions=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentSuffixes=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_default_serial_version_id=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true

-/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.BreakpointView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<VariablesViewMemento org.eclipse.debug.ui.SASH_DETAILS_PART\="315" org.eclipse.debug.ui.SASH_VIEW_PART\="684">\r\n<PRESENTATION_CONTEXT_PROPERTIES IMemento.internal.id\="org.eclipse.debug.ui.BreakpointView">\r\n<BOOLEAN BOOLEAN\="true" IMemento.internal.id\="org.eclipse.debug.ui.check"/>\r\n</PRESENTATION_CONTEXT_PROPERTIES>\r\n</VariablesViewMemento>

-@org.eclipse.ui.workbench=3.104.0.v20130204-164612

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.importorder=java;javax;org;com;org.apache.cloudstack;com.cloud;

-/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_member_accesses_with_declaring_class=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_field_access=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.organize_imports=true

-/instance/org.eclipse.jdt.ui/cleanup.qualify_static_method_accesses_with_declaring_class=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert

-@org.eclipse.egit.ui=2.3.1.201302201838-r

-/instance/org.eclipse.ui.editors/quickdiff.nowarn.before.switch=always

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.line_length=120

-/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1

-/instance/org.eclipse.jdt.debug.ui/org.eclipse.debug.ui.VariableView.org.eclipse.jdt.debug.ui.show_null_entries=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldSuffixes=

-/instance/org.eclipse.jdt.ui/content_assist_number_of_computers=21

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localSuffixes=

-/instance/org.eclipse.jdt.junit.core/org.eclipse.jdt.junit.enable_assertions=true

-@org.eclipse.core.resources=3.8.1.v20121114-124432

-/instance/org.eclipse.jdt.ui/spelling_ignore_urls=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16

-/instance/org.eclipse.jdt.ui/content_assist_autoactivation_delay=50

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert

-@org.eclipse.search=3.8.0.v20120523-1540

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert

-/instance/org.eclipse.ui.intro/org.eclipse.epp.package.java.product_INTRO_THEME=org.eclipse.ui.intro.universal.circles

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JSR305_ANNOTATIONS=C\:/bin/Eclipse/current/plugins/edu.umd.cs.findbugs.plugin.eclipse_2.0.2.20121210/lib/jsr305.jar

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JRE_LIB=C\:/bin/Java/jre7/lib/rt.jar

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.lineSplit=180

-/instance/org.eclipse.jdt.ui/content_assist_disabled_computers=org.eclipse.recommenders.completion.rcp.templates.category\u0000org.eclipse.jdt.ui.javaAllProposalCategory\u0000org.eclipse.jdt.ui.javaNoTypeProposalCategory\u0000org.eclipse.recommenders.completion.rcp.chain.category\u0000org.eclipse.jdt.ui.javaTypeProposalCategory\u0000org.eclipse.jdt.ui.textProposalCategory\u0000org.eclipse.recommenders.subwords.rcp.category\u0000

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16

-@org.eclipse.ui.ide=3.8.2.v20121106-165923

-@org.eclipse.core.runtime=3.8.0.v20120912-155025

-/instance/org.eclipse.jdt.ui/editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces_ignore_empty=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_blocks=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_compact_if=16

-/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.PREF_HCR_WITH_COMPILATION_ERRORS=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_html=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert

-/instance/org.eclipse.m2e.core/eclipse.m2.WorkspacelifecycleMappingsLocation=D\:/src/workspaces/master/.metadata/.plugins/org.eclipse.m2e.core/lifecycle-mapping-metadata.xml

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line

-/instance/org.eclipse.jdt.ui/cleanup.make_variable_declarations_final=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true

-/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access=true

+/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_parentheses_in_expressions=false
+/instance/org.eclipse.team.ui/org.eclipse.team.ui.first_time=false
+/instance/org.eclipse.jdt.ui/cleanup.add_default_serial_version_id=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+/instance/org.eclipse.ui.workbench/resourcetypes=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<editors version\="3.1">\r\n<info extension\="pyw" name\="*">\r\n<editor id\="org.python.pydev.editor.PythonEditor"/>\r\n<defaultEditor id\="org.python.pydev.editor.PythonEditor"/>\r\n</info>\r\n<info extension\="py" name\="*">\r\n<editor id\="org.python.pydev.editor.PythonEditor"/>\r\n<defaultEditor id\="org.python.pydev.editor.PythonEditor"/>\r\n</info>\r\n<info extension\="java" name\="*">\r\n<editor id\="org.eclipse.wb.core.guiEditor"/>\r\n</info>\r\n<info extension\="pyx" name\="*">\r\n<editor id\="org.python.pydev.editor.PythonEditor"/>\r\n<defaultEditor id\="org.python.pydev.editor.PythonEditor"/>\r\n</info>\r\n<info extension\="class without source" name\="*">\r\n<editor id\="org.eclipse.jdt.ui.ClassFileEditorNoSource"/>\r\n</info>\r\n<info extension\="html" name\="*">\r\n<editor id\="org.eclipse.ui.browser.editorSupport"/>\r\n</info>\r\n<info extension\="htm" name\="*">\r\n<editor id\="org.eclipse.ui.browser.editorSupport"/>\r\n</info>\r\n<info extension\="sql" name\="*">\r\n<editor id\="org.eclipse.ui.DefaultTextEditor"/>\r\n<defaultEditor id\="org.eclipse.ui.DefaultTextEditor"/>\r\n</info>\r\n<info extension\="jardesc" name\="*">\r\n<editor id\="org.eclipse.jdt.ui.JARDescEditor"/>\r\n<defaultEditor id\="org.eclipse.jdt.ui.JARDescEditor"/>\r\n</info>\r\n<info extension\="jpage" name\="*">\r\n<editor id\="org.eclipse.jdt.debug.ui.SnippetEditor"/>\r\n</info>\r\n<info extension\="shtml" name\="*">\r\n<editor id\="org.eclipse.ui.browser.editorSupport"/>\r\n</info>\r\n</editors>
+/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+/instance/org.eclipse.wst.sse.ui/useQuickDiffPrefPage=true
+/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_method_access=false
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_private_constructors=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation=2
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+/instance/org.eclipse.wst.validation/confirmDialog=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debugdefault_watchpoint_suspend_policy=0
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_nls_tags=false
+@org.eclipse.wst.sse.core=1.1.702.v201301241617
+/instance/org.eclipse.mylyn.context.core/mylyn.attention.migrated=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+/instance/org.eclipse.jdt.ui/useAnnotationsPrefPage=true
+/instance/org.eclipse.jdt.ui/formatter_profile=_Alex
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_override_annotations=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+/instance/org.eclipse.debug.ui/preferredDetailPanes=DefaultDetailPane\:DefaultDetailPane|org.eclipse.jdt.debug.ui.DETAIL_PANE_LINE_BREAKPOINT\:org.eclipse.jdt.debug.ui.DETAIL_PANE_LINE_BREAKPOINT|org.eclipse.jdt.debug.ui.DETAIL_PANE_EXCEPTION_BREAKPOINT\:org.eclipse.jdt.debug.ui.DETAIL_PANE_EXCEPTION_BREAKPOINT|
+/instance/org.eclipse.wst.validation/override=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+@org.eclipse.jdt.core=3.8.3.v20130121-145325
+/instance/org.eclipse.jdt.ui/cleanup.make_local_variable_final=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.sort_members=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_imports=true
+/instance/org.eclipse.jdt.ui/cleanup.add_serial_version_id=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_nls_tags=true
+/instance/org.eclipse.debug.ui/preferredTargets=default\:default|
+/instance/org.eclipse.jdt.debug.ui/org.eclipse.jdt.debug.ui.javaDebug.alertHCRFailed=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_types=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JRE_SRC=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+/instance/org.eclipse.jdt.ui/cleanup_profile=_Alex
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.format_source_code_changes_only=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+/instance/org.eclipse.ui.intro/org.eclipse.epp.package.java.product_fontStyle=relative
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+@org.eclipse.e4.ui.css.swt.theme=0.9.4.v20130123-162658
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.default_breakpoint_suspend_policy=2
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.editor.tab.width=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.make_variable_declarations_final=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+/instance/org.eclipse.core.resources/version=1
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_deprecated_annotations=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.javadoclocations.migrated=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_imports=true
+/instance/org.eclipse.jdt.ui/content_assist_favorite_static_members=
+/instance/org.eclipse.jdt.ui/sp_cleanup.make_type_abstract_if_missing_method=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_private_constructors=true
+/instance/org.eclipse.core.runtime/line.separator=\n
+/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/fontPropagated=true
+/instance/org.eclipse.wst.validation/saveAuto=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_wrapped_lines=false
+/instance/org.eclipse.ui.workbench/ENABLED_DECORATORS=de.tobject.findbugs.decorators.WorkingSetBugCountDecorator\:true,de.tobject.findbugs.decorators.ProjectBugCountDecorator\:true,de.tobject.findbugs.decorators.FolderBugCountDecorator\:true,de.tobject.findbugs.decorators.FileBugCountDecorator\:true,org.eclipse.m2e.core.mavenVersionDecorator\:false,org.eclipse.egit.ui.internal.decorators.GitLightweightDecorator\:true,org.eclipse.jdt.ui.override.decorator\:true,org.eclipse.jdt.ui.interface.decorator\:false,org.eclipse.jdt.ui.buildpath.decorator\:true,org.eclipse.jubula.client.teststyle.tsGuiNodeDecorator\:true,org.eclipse.jubula.client.teststyle.tsTestresultDecorator\:true,org.eclipse.jubula.client.core.model.TestResultNode\:true,org.eclipse.jubula.client.ui.rcp.decorators.resultDurationDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.resultParameterDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.completenessCheckDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.MissingReferenceDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.excelDecorator\:true,org.eclipse.jubula.client.ui.rcp.decorators.activeElementDecorator\:true,org.eclipse.m2e.core.maven2decorator\:true,org.eclipse.mylyn.context.ui.decorator.interest\:true,org.eclipse.mylyn.tasks.ui.decorators.task\:true,org.eclipse.mylyn.team.ui.changeset.decorator\:true,org.eclipse.team.cvs.ui.decorator\:true,org.eclipse.ui.LinkedResourceDecorator\:true,org.eclipse.ui.VirtualResourceDecorator\:true,org.eclipse.ui.ContentTypeDecorator\:true,org.eclipse.ui.ResourceFilterDecorator\:false,org.python.pydev.navigator.decorator.problemsLabelDecorator\:true,
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+@org.eclipse.ui=3.104.0.v20121024-145224
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ondemandthreshold=99
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.ignorelowercasenames=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+/instance/org.eclipse.debug.core/org.eclipse.debug.core.PREF_BREAKPOINT_MANAGER_ENABLED_STATE=true
+/instance/org.eclipse.mylyn.java.ui/org.eclipse.mylyn.java.ui.run.count.3_1_0=1
+/instance/org.eclipse.ui.ide/IMPORT_FILES_AND_FOLDERS_MODE=prompt
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+/instance/org.eclipse.jdt.ui/spelling_ignore_digits=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JUNIT_HOME=C\:/bin/Eclipse/current/plugins/org.junit_3.8.2.v3_8_2_v20100427-1100/
+/instance/org.eclipse.jdt.junit/org.eclipse.jdt.junit.show_in_all_views=false
+/instance/org.eclipse.jdt.ui/sp_cleanup.make_private_fields_final=true
+@org.eclipse.jdt.junit=3.7.100.v20120523-1543
+/instance/org.eclipse.jdt.ui/proposalOrderMigrated=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+/instance/org.eclipse.wst.validation/vf.version=3
+/instance/org.eclipse.jdt.ui/spelling_ignore_single_letters=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+/instance/org.eclipse.m2e.core/eclipse.m2.userSettingsFile=c\:\\bin\\Maven\\current\\conf\\settings.xml
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+/instance/org.eclipse.ui.ide/IMPORT_FILES_AND_FOLDERS_VIRTUAL_FOLDER_MODE=prompt
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<profiles version\="2">\r\n<profile kind\="CleanUpProfile" name\="Alex" version\="2">\r\n<setting id\="cleanup.remove_unused_private_fields" value\="true"/>\r\n<setting id\="cleanup.always_use_parentheses_in_expressions" value\="false"/>\r\n<setting id\="cleanup.never_use_blocks" value\="false"/>\r\n<setting id\="cleanup.add_missing_deprecated_annotations" value\="true"/>\r\n<setting id\="cleanup.remove_unused_private_methods" value\="true"/>\r\n<setting id\="cleanup.convert_to_enhanced_for_loop" value\="false"/>\r\n<setting id\="cleanup.remove_unnecessary_nls_tags" value\="true"/>\r\n<setting id\="cleanup.sort_members" value\="false"/>\r\n<setting id\="cleanup.remove_unused_local_variables" value\="false"/>\r\n<setting id\="cleanup.remove_unused_private_members" value\="false"/>\r\n<setting id\="cleanup.never_use_parentheses_in_expressions" value\="true"/>\r\n<setting id\="cleanup.remove_unnecessary_casts" value\="true"/>\r\n<setting id\="cleanup.make_parameters_final" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_field_access" value\="true"/>\r\n<setting id\="cleanup.use_blocks" value\="false"/>\r\n<setting id\="cleanup.remove_private_constructors" value\="true"/>\r\n<setting id\="cleanup.always_use_this_for_non_static_method_access" value\="false"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces_all" value\="false"/>\r\n<setting id\="cleanup.always_use_this_for_non_static_field_access" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_field_access_only_if_necessary" value\="true"/>\r\n<setting id\="cleanup.add_default_serial_version_id" value\="true"/>\r\n<setting id\="cleanup.make_type_abstract_if_missing_method" value\="false"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.make_variable_declarations_final" value\="false"/>\r\n<setting id\="cleanup.add_missing_nls_tags" value\="false"/>\r\n<setting id\="cleanup.format_source_code" value\="false"/>\r\n<setting id\="cleanup.add_missing_override_annotations" value\="true"/>\r\n<setting id\="cleanup.qualify_static_method_accesses_with_declaring_class" value\="false"/>\r\n<setting id\="cleanup.remove_unused_private_types" value\="true"/>\r\n<setting id\="cleanup.make_local_variable_final" value\="true"/>\r\n<setting id\="cleanup.add_missing_methods" value\="false"/>\r\n<setting id\="cleanup.add_missing_override_annotations_interface_methods" value\="true"/>\r\n<setting id\="cleanup.correct_indentation" value\="true"/>\r\n<setting id\="cleanup.remove_unused_imports" value\="true"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces_ignore_empty" value\="true"/>\r\n<setting id\="cleanup.make_private_fields_final" value\="true"/>\r\n<setting id\="cleanup.add_generated_serial_version_id" value\="false"/>\r\n<setting id\="cleanup.organize_imports" value\="true"/>\r\n<setting id\="cleanup.sort_members_all" value\="false"/>\r\n<setting id\="cleanup.remove_trailing_whitespaces" value\="true"/>\r\n<setting id\="cleanup.use_blocks_only_for_return_and_throw" value\="false"/>\r\n<setting id\="cleanup.use_parentheses_in_expressions" value\="false"/>\r\n<setting id\="cleanup.add_missing_annotations" value\="true"/>\r\n<setting id\="cleanup.qualify_static_field_accesses_with_declaring_class" value\="false"/>\r\n<setting id\="cleanup.use_this_for_non_static_method_access_only_if_necessary" value\="true"/>\r\n<setting id\="cleanup.use_this_for_non_static_method_access" value\="true"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_through_instances_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.add_serial_version_id" value\="false"/>\r\n<setting id\="cleanup.always_use_blocks" value\="true"/>\r\n<setting id\="cleanup.qualify_static_member_accesses_with_declaring_class" value\="true"/>\r\n<setting id\="cleanup.format_source_code_changes_only" value\="false"/>\r\n</profile>\r\n</profiles>\r\n
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+@org.python.pydev=2.7.5.2013052819
+/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_this_for_non_static_field_access=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+/instance/org.eclipse.ui.intro.universal/org.eclipse.epp.package.java.product_INTRO_DATA=<?xml version\="1.0" encoding\="utf-8" ?>\r\n<extensions>\r\n   <page id\="webresources">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="GDWelcome-webresourcesExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n      </group>\r\n   </page>\r\n   <page id\="tutorials">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n         <extension id\="org.eclipse.team" importance\="low"/>\r\n         <extension id\="GDWelcome-tutorialsExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="org.eclipse.mylyn" importance\="low"/>\r\n         <extension id\="org.eclipse.egit" importance\="low"/>\r\n         <extension id\="org.eclipse.mat.tutorials" importance\="low"/>\r\n      </group>\r\n   </page>\r\n   <page id\="migrate">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="GDWelcome-migrateExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n      </group>\r\n   </page>\r\n   <page id\="whatsnew">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench.news" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench.migration" importance\="low"/>\r\n         <extension id\="GDWelcome-whatsnewExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="org.eclipse.mylyn" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench" importance\="low"/>\r\n         <extension id\="org.eclipse.egit" importance\="low"/>\r\n      </group>\r\n   </page>\r\n   <page id\="samples">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="GDWelcome-samplesExtension" importance\="low"/>\r\n      </group>\r\n   </page>\r\n   <page id\="firststeps">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="GDWelcome-firststepsExtension" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n      </group>\r\n   </page>\r\n   <page id\="overview">\r\n      <group path\="page-content/bottom-left" default\="true">\r\n         <extension id\="org.eclipse.epp.mpc.ui" importance\="low"/>\r\n         <extension id\="org.eclipse.mylyn" importance\="low"/>\r\n         <extension id\="org.eclipse.egit" importance\="low"/>\r\n         <extension id\="org.eclipse.m2e" importance\="low"/>\r\n      </group>\r\n      <group path\="page-content/bottom-right" default\="true">\r\n         <extension id\="org.eclipse.jdt" importance\="low"/>\r\n         <extension id\="org.eclipse.ui.workbench" importance\="low"/>\r\n         <extension id\="GDWelcome-overviewExtension" importance\="low"/>\r\n         <extension id\="org.eclipse.mat.overview" importance\="low"/>\r\n      </group>\r\n   </page>\r\n</extensions>\r\n
+/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.DebugVieworg.eclipse.debug.ui.DebugView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<DebugViewMemento org.eclipse.debug.ui.BREADCRUMB_DROPDOWN_AUTO_EXPAND\="false"/>
+/instance/org.eclipse.jdt.ui/spelling_ignore_non_letters=true
+/instance/org.python.pydev/PYDEV_FUNDING_SHOWN=true
+/instance/org.eclipse.jdt.ui/hoverModifierMasks=org.eclipse.jdt.ui.BestMatchHover;0;org.eclipse.jdt.internal.debug.ui.JavaDebugHover;0;org.eclipse.jdt.ui.ProblemHover;0;org.eclipse.jdt.ui.NLSStringHover;327680;org.eclipse.jdt.ui.JavadocHover;393216;org.eclipse.jdt.ui.AnnotationHover;0;org.eclipse.jdt.ui.JavaSourceHover;131072;
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_override_annotations_interface_methods=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.suspend_for_breakpoints_during_evaluation=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces_all=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+/instance/org.eclipse.jdt.ui/cleanup.always_use_blocks=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.code_templates_migrated=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.size=4
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+/instance/org.eclipse.jdt.ui/useQuickDiffPrefPage=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+@org.eclipse.egit.core=2.3.1.201302201838-r
+/configuration/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.exception.name=e
+/instance/org.eclipse.wst.validation/USER_BUILD_PREFERENCE=enabledBuildValidatorList
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_methods=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.never_use_blocks=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_serial_version_id=false
+/instance/org.eclipse.jdt.ui/cleanup_settings_version=2
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+/instance/org.eclipse.ui.intro.universal/org.eclipse.epp.package.java.product_INTRO_ROOT_PAGES=overview,tutorials,samples,whatsnew
+/instance/org.eclipse.e4.ui.css.swt.theme/themeid=org.eclipse.e4.ui.css.theme.e4_classic
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+/instance/org.eclipse.jdt.ui/hoverModifiers=org.eclipse.jdt.ui.BestMatchHover;0;org.eclipse.jdt.internal.debug.ui.JavaDebugHover;\!0;org.eclipse.jdt.ui.ProblemHover;\!0;org.eclipse.jdt.ui.NLSStringHover;Ctrl+Alt;org.eclipse.jdt.ui.JavadocHover;Ctrl+Shift;org.eclipse.jdt.ui.AnnotationHover;\!0;org.eclipse.jdt.ui.JavaSourceHover;Shift;
+/instance/org.eclipse.jdt.ui/cleanup.make_type_abstract_if_missing_method=false
+@org.eclipse.wst.validation=1.2.402.v201212031633
+/instance/org.eclipse.wst.validation/USER_PREFERENCE=saveAutomaticallyfalseprojectsCanOverridetruedisableAllValidationfalseversion1.2.402.v201212031633
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indentation.size=4
+/instance/org.eclipse.jdt.ui/cleanup.sort_members_all=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+\!/=
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_with_declaring_class=true
+/instance/org.eclipse.jdt.ui/cleanup.organize_imports=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+/instance/org.eclipse.jdt.ui/spelling_ignore_mixed=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_lines_in_comments=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+/instance/org.eclipse.jdt.ui/spelling_ignore_upper=true
+/instance/org.eclipse.jdt.ui/content_assist_proposals_foreground=0,0,0
+@org.eclipse.ui.editors=3.8.0.v20120523-1540
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_annotations=true
+/instance/org.eclipse.jdt.ui/sourceHoverBackgroundColor=255,255,225
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_nls_tags=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+@org.eclipse.mylyn.java.ui=3.8.3.v20130107-0100
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_ignore_empty=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+/instance/org.eclipse.ui.ide/platformState=1364576568717
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+@org.eclipse.team.cvs.ui=3.3.500.v20120522-1148
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_method_access=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_this_for_non_static_method_access=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+/instance/org.eclipse.jdt.ui/cleanup.make_private_fields_final=true
+/instance/org.eclipse.ui.editors/lineNumberRuler=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+/instance/org.eclipse.jdt.ui/sp_cleanup.format_source_code_changes_only=true
+/configuration/org.eclipse.ui.ide/RECENT_WORKSPACES_PROTOCOL=3
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JRE_SRCROOT=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_line_comments=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.visibilityCheck=enabled
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+/instance/org.eclipse.debug.ui/org.eclipse.debug.ui.PREF_LAUNCH_PERSPECTIVES=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<launchPerspectives/>\r\n
+/instance/org.eclipse.m2e.editor.xml/org.eclipse.m2e.editor.xml.templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
+/instance/org.eclipse.epp.mpc.ui/CatalogDescriptor=http\://marketplace.eclipse.org
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_local_variables=false
+/instance/org.eclipse.jdt.ui/cleanup.convert_to_enhanced_for_loop=false
+@org.eclipse.team.core=3.6.100.v20120524-0627
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_members=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+/instance/org.eclipse.egit.ui/resourcehistory_rev_split=700,300
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.gettersetter.use.is=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.format_source_code=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+@org.eclipse.ui.intro.universal=3.2.600.v20120912-155524
+/instance/org.eclipse.jdt.ui/spelling_locale_initialized=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.convert_to_enhanced_for_loop=false
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations=true
+/instance/org.eclipse.jdt.launching/org.eclipse.jdt.launching.PREF_VM_XML=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<vmSettings defaultVM\="57,org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType13,1364578133326" defaultVMConnector\="">\r\n<vmType id\="org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType">\r\n<vm id\="1364578133326" name\="jre7" path\="C\:\\bin\\Java\\jre7"/>\r\n</vmType>\r\n</vmSettings>\r\n
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+/configuration/org.eclipse.ui.ide/SHOW_WORKSPACE_SELECTION_DIALOG=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.ExpressionView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<VariablesViewMemento org.eclipse.debug.ui.SASH_DETAILS_PART\="315" org.eclipse.debug.ui.SASH_VIEW_PART\="684">\r\n<PRESENTATION_CONTEXT_PROPERTIES IMemento.internal.id\="org.eclipse.debug.ui.ExpressionView"/>\r\n</VariablesViewMemento>
+/instance/org.eclipse.jdt.ui/cleanup.use_parentheses_in_expressions=false
+/instance/org.python.pydev.debug/INITIAL_INTERPRETER_CMDS=import sys; print('%s %s' % (sys.executable or sys.platform, sys.version))\r\n
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_deprecated_annotations=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.templates_migrated=true
+/instance/org.eclipse.egit.ui/merge_mode=0
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+/instance/org.eclipse.wst.sse.ui/useAnnotationsPrefPage=true
+@org.eclipse.m2e.discovery=1.3.1.20130219-1424
+@org.eclipse.wst.sse.ui=1.3.102.v201301162301
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+/instance/org.eclipse.jdt.launching/org.eclipse.jdt.launching.PREF_CONNECT_TIMEOUT=20000
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+/instance/org.eclipse.ui.workbench/org.eclipse.ui.commands=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<org.eclipse.ui.commands/>
+/instance/org.eclipse.wst.xml.core/indentationChar=space
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_methods=true
+/instance/org.eclipse.wst.sse.ui/content_assist_number_of_computers=2
+/instance/org.eclipse.team.cvs.ui/pref_first_startup=false
+/instance/org.eclipse.egit.ui/Blame_IgnoreWhitespace=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_field_access=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.timeoutForParameterNameFromAttachedJavadoc=50
+/instance/org.eclipse.jdt.ui/formatter_settings_version=12
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+/instance/org.eclipse.jdt.ui/spelling_ignore_sentence=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+/instance/org.eclipse.jdt.ui/sp_cleanup.never_use_parentheses_in_expressions=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+/instance/org.eclipse.jdt.ui/cleanup.add_missing_override_annotations_interface_methods=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+/instance/org.eclipse.jdt.ui/spelling_ignore_java_strings=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+/instance/org.eclipse.jdt.ui/tabWidthPropagated=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<profiles version\="12">\r\n<profile kind\="CodeFormatterProfile" name\="Alex" version\="12">\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.disabling_tag" value\="@formatter\:off"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_field" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.use_on_off_tags" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_package" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation" value\="2"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_package" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.source" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_line_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.join_wrapped_lines" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.lineSplit" value\="180"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indentation.size" value\="4"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.enabling_tag" value\="@formatter\:on"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_assignment" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value\="error"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.char" value\="space"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_method" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_switch" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value\="error"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.compact_else_if" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.tabulation.size" value\="4"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_empty_lines" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.compliance" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value\="2"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value\="enabled"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.line_length" value\="120"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.join_lines_in_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_html" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_source_code" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value\="16"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value\="1.7"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value\="80"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_header" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.format_block_comments" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value\="0"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value\="end_of_line"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value\="1"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value\="insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value\="true"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value\="do not insert"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value\="false"/>\r\n<setting id\="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value\="false"/>\r\n</profile>\r\n</profiles>\r\n
+/instance/org.eclipse.wst.xml.core/lineWidth=80
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+/instance/org.eclipse.jdt.ui/cleanup.make_parameters_final=false
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_blocks=false
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+/instance/org.python.pydev/IRONPYTHON_INTERPRETER_PATH=<xml>\n<name>C\:\\Program Files (x86)\\IronPython 2.7\\ipy64.exe</name>\n<version>2.7</version>\n<executable>C\:\\Program Files (x86)\\IronPython 2.7\\ipy64.exe</executable>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7\\Lib</lib>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7\\DLLs</lib>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7</lib>\n<lib>C\:\\Program Files (x86)\\IronPython 2.7\\lib\\site-packages</lib>\n<forced_lib>IEHost.Execute</forced_lib>\n<forced_lib>Microsoft</forced_lib>\n<forced_lib>Microsoft.Aspnet.Snapin</forced_lib>\n<forced_lib>Microsoft.Build.BuildEngine</forced_lib>\n<forced_lib>Microsoft.Build.Conversion</forced_lib>\n<forced_lib>Microsoft.Build.Framework</forced_lib>\n<forced_lib>Microsoft.Build.Tasks</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Deployment.Bootstrapper</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Deployment.ManifestUtilities</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Hosting</forced_lib>\n<forced_lib>Microsoft.Build.Tasks.Windows</forced_lib>\n<forced_lib>Microsoft.Build.Utilities</forced_lib>\n<forced_lib>Microsoft.CLRAdmin</forced_lib>\n<forced_lib>Microsoft.CSharp</forced_lib>\n<forced_lib>Microsoft.Data.Entity.Build.Tasks</forced_lib>\n<forced_lib>Microsoft.IE</forced_lib>\n<forced_lib>Microsoft.Ink</forced_lib>\n<forced_lib>Microsoft.Ink.TextInput</forced_lib>\n<forced_lib>Microsoft.JScript</forced_lib>\n<forced_lib>Microsoft.JScript.Vsa</forced_lib>\n<forced_lib>Microsoft.ManagementConsole</forced_lib>\n<forced_lib>Microsoft.ManagementConsole.Advanced</forced_lib>\n<forced_lib>Microsoft.ManagementConsole.Internal</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail.ExchangeWebService</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail.ExchangeWebService.Exchange2007</forced_lib>\n<forced_lib>Microsoft.ServiceModel.Channels.Mail.WindowsMobile</forced_lib>\n<forced_lib>Microsoft.SqlServer.Server</forced_lib>\n<forced_lib>Microsoft.StylusInput</forced_lib>\n<forced_lib>Microsoft.StylusInput.PluginData</forced_lib>\n<forced_lib>Microsoft.VisualBasic</forced_lib>\n<forced_lib>Microsoft.VisualBasic.ApplicationServices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Compatibility.VB6</forced_lib>\n<forced_lib>Microsoft.VisualBasic.CompilerServices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Devices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.FileIO</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Logging</forced_lib>\n<forced_lib>Microsoft.VisualBasic.MyServices</forced_lib>\n<forced_lib>Microsoft.VisualBasic.MyServices.Internal</forced_lib>\n<forced_lib>Microsoft.VisualBasic.Vsa</forced_lib>\n<forced_lib>Microsoft.VisualC</forced_lib>\n<forced_lib>Microsoft.VisualC.StlClr</forced_lib>\n<forced_lib>Microsoft.VisualC.StlClr.Generic</forced_lib>\n<forced_lib>Microsoft.Vsa</forced_lib>\n<forced_lib>Microsoft.Vsa.Vb.CodeDOM</forced_lib>\n<forced_lib>Microsoft.Win32</forced_lib>\n<forced_lib>Microsoft.Win32.SafeHandles</forced_lib>\n<forced_lib>Microsoft.Windows.Themes</forced_lib>\n<forced_lib>Microsoft.WindowsCE.Forms</forced_lib>\n<forced_lib>Microsoft.WindowsMobile.DirectX</forced_lib>\n<forced_lib>Microsoft.WindowsMobile.DirectX.Direct3D</forced_lib>\n<forced_lib>Microsoft_VsaVb</forced_lib>\n<forced_lib>RegCode</forced_lib>\n<forced_lib>System</forced_lib>\n<forced_lib>System.AddIn</forced_lib>\n<forced_lib>System.AddIn.Contract</forced_lib>\n<forced_lib>System.AddIn.Contract.Automation</forced_lib>\n<forced_lib>System.AddIn.Contract.Collections</forced_lib>\n<forced_lib>System.AddIn.Hosting</forced_lib>\n<forced_lib>System.AddIn.Pipeline</forced_lib>\n<forced_lib>System.CodeDom</forced_lib>\n<forced_lib>System.CodeDom.Compiler</forced_lib>\n<forced_lib>System.Collections</forced_lib>\n<forced_lib>System.Collections.Generic</forced_lib>\n<forced_lib>System.Collections.ObjectModel</forced_lib>\n<forced_lib>System.Collections.Specialized</forced_lib>\n<forced_lib>System.ComponentModel</forced_lib>\n<forced_lib>System.ComponentModel.DataAnnotations</forced_lib>\n<forced_lib>System.ComponentModel.Design</forced_lib>\n<forced_lib>System.ComponentModel.Design.Data</forced_lib>\n<forced_lib>System.ComponentModel.Design.Serialization</forced_lib>\n<forced_lib>System.Configuration</forced_lib>\n<forced_lib>System.Configuration.Assemblies</forced_lib>\n<forced_lib>System.Configuration.Install</forced_lib>\n<forced_lib>System.Configuration.Internal</forced_lib>\n<forced_lib>System.Configuration.Provider</forced_lib>\n<forced_lib>System.Data</forced_lib>\n<forced_lib>System.Data.Common</forced_lib>\n<forced_lib>System.Data.Common.CommandTrees</forced_lib>\n<forced_lib>System.Data.Design</forced_lib>\n<forced_lib>System.Data.Entity.Design</forced_lib>\n<forced_lib>System.Data.Entity.Design.AspNet</forced_lib>\n<forced_lib>System.Data.EntityClient</forced_lib>\n<forced_lib>System.Data.Linq</forced_lib>\n<forced_lib>System.Data.Linq.Mapping</forced_lib>\n<forced_lib>System.Data.Linq.SqlClient</forced_lib>\n<forced_lib>System.Data.Linq.SqlClient.Implementation</forced_lib>\n<forced_lib>System.Data.Mapping</forced_lib>\n<forced_lib>System.Data.Metadata.Edm</forced_lib>\n<forced_lib>System.Data.Objects</forced_lib>\n<forced_lib>System.Data.Objects.DataClasses</forced_lib>\n<forced_lib>System.Data.Odbc</forced_lib>\n<forced_lib>System.Data.OleDb</forced_lib>\n<forced_lib>System.Data.OracleClient</forced_lib>\n<forced_lib>System.Data.Services</forced_lib>\n<forced_lib>System.Data.Services.Client</forced_lib>\n<forced_lib>System.Data.Services.Common</forced_lib>\n<forced_lib>System.Data.Services.Design</forced_lib>\n<forced_lib>System.Data.Services.Internal</forced_lib>\n<forced_lib>System.Data.Sql</forced_lib>\n<forced_lib>System.Data.SqlClient</forced_lib>\n<forced_lib>System.Data.SqlTypes</forced_lib>\n<forced_lib>System.Deployment.Application</forced_lib>\n<forced_lib>System.Deployment.Internal</forced_lib>\n<forced_lib>System.Diagnostics</forced_lib>\n<forced_lib>System.Diagnostics.CodeAnalysis</forced_lib>\n<forced_lib>System.Diagnostics.Design</forced_lib>\n<forced_lib>System.Diagnostics.Eventing</forced_lib>\n<forced_lib>System.Diagnostics.Eventing.Reader</forced_lib>\n<forced_lib>System.Diagnostics.PerformanceData</forced_lib>\n<forced_lib>System.Diagnostics.SymbolStore</forced_lib>\n<forced_lib>System.DirectoryServices</forced_lib>\n<forced_lib>System.DirectoryServices.AccountManagement</forced_lib>\n<forced_lib>System.DirectoryServices.ActiveDirectory</forced_lib>\n<forced_lib>System.DirectoryServices.Protocols</forced_lib>\n<forced_lib>System.Drawing</forced_lib>\n<forced_lib>System.Drawing.Design</forced_lib>\n<forced_lib>System.Drawing.Drawing2D</forced_lib>\n<forced_lib>System.Drawing.Imaging</forced_lib>\n<forced_lib>System.Drawing.Printing</forced_lib>\n<forced_lib>System.Drawing.Text</forced_lib>\n<forced_lib>System.EnterpriseServices</forced_lib>\n<forced_lib>System.EnterpriseServices.CompensatingResourceManager</forced_lib>\n<forced_lib>System.EnterpriseServices.Internal</forced_lib>\n<forced_lib>System.Globalization</forced_lib>\n<forced_lib>System.IO</forced_lib>\n<forced_lib>System.IO.Compression</forced_lib>\n<forced_lib>System.IO.IsolatedStorage</forced_lib>\n<forced_lib>System.IO.Log</forced_lib>\n<forced_lib>System.IO.Packaging</forced_lib>\n<forced_lib>System.IO.Pipes</forced_lib>\n<forced_lib>System.IO.Ports</forced_lib>\n<forced_lib>System.IdentityModel.Claims</forced_lib>\n<forced_lib>System.IdentityModel.Policy</forced_lib>\n<forced_lib>System.IdentityModel.Selectors</forced_lib>\n<forced_lib>System.IdentityModel.Tokens</forced_lib>\n<forced_lib>System.Linq</forced_lib>\n<forced_lib>System.Linq.Expressions</forced_lib>\n<forced_lib>System.Management</forced_lib>\n<forced_lib>System.Management.Instrumentation</forced_lib>\n<forced_lib>System.Media</forced_lib>\n<forced_lib>System.Messaging</forced_lib>\n<forced_lib>System.Messaging.Design</forced_lib>\n<forced_lib>System.Net</forced_lib>\n<forced_lib>System.Net.Cache</forced_lib>\n<forced_lib>System.Net.Configuration</forced_lib>\n<forced_lib>System.Net.Mail</forced_lib>\n<forced_lib>System.Net.Mime</forced_lib>\n<forced_lib>System.Net.NetworkInformation</forced_lib>\n<forced_lib>System.Net.PeerToPeer</forced_lib>\n<forced_lib>System.Net.PeerToPeer.Collaboration</forced_lib>\n<forced_lib>System.Net.Security</forced_lib>\n<forced_lib>System.Net.Sockets</forced_lib>\n<forced_lib>System.Printing</forced_lib>\n<forced_lib>System.Printing.IndexedProperties</forced_lib>\n<forced_lib>System.Printing.Interop</forced_lib>\n<forced_lib>System.Reflection</forced_lib>\n<forced_lib>System.Reflection.Emit</forced_lib>\n<forced_lib>System.Resources</forced_lib>\n<forced_lib>System.Resources.Tools</forced_lib>\n<forced_lib>System.Runtime</forced_lib>\n<forced_lib>System.Runtime.CompilerServices</forced_lib>\n<forced_lib>System.Runtime.ConstrainedExecution</forced_lib>\n<forced_lib>System.Runtime.Hosting</forced_lib>\n<forced_lib>System.Runtime.InteropServices</forced_lib>\n<forced_lib>System.Runtime.InteropServices.ComTypes</forced_lib>\n<forced_lib>System.Runtime.InteropServices.CustomMarshalers</forced_lib>\n<forced_lib>System.Runtime.InteropServices.Expando</forced_lib>\n<forced_lib>System.Runtime.Remoting</forced_lib>\n<forced_lib>System.Runtime.Remoting.Activation</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels.Http</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels.Ipc</forced_lib>\n<forced_lib>System.Runtime.Remoting.Channels.Tcp</forced_lib>\n<forced_lib>System.Runtime.Remoting.Contexts</forced_lib>\n<forced_lib>System.Runtime.Remoting.Lifetime</forced_lib>\n<forced_lib>System.Runtime.Remoting.Messaging</forced_lib>\n<forced_lib>System.Runtime.Remoting.Metadata</forced_lib>\n<forced_lib>System.Runtime.Remoting.MetadataServices</forced_lib>\n<forced_lib>System.Runtime.Remoting.Proxies</forced_lib>\n<forced_lib>System.Runtime.Remoting.Services</forced_lib>\n<forced_lib>System.Runtime.Serialization</forced_lib>\n<forced_lib>System.Runtime.Serialization.Configuration</forced_lib>\n<forced_lib>System.Runtime.Serialization.Formatters</forced_lib>\n<forced_lib>System.Runtime.Serialization.Formatters.Binary</forced_lib>\n<forced_lib>System.Runtime.Serialization.Formatters.Soap</forced_lib>\n<forced_lib>System.Runtime.Serialization.Json</forced_lib>\n<forced_lib>System.Runtime.Versioning</forced_lib>\n<forced_lib>System.Security</forced_lib>\n<forced_lib>System.Security.AccessControl</forced_lib>\n<forced_lib>System.Security.Authentication</forced_lib>\n<forced_lib>System.Security.Authentication.ExtendedProtection</forced_lib>\n<forced_lib>System.Security.Authentication.ExtendedProtection.Configuration</forced_lib>\n<forced_lib>System.Security.Cryptography</forced_lib>\n<forced_lib>System.Security.Cryptography.Pkcs</forced_lib>\n<forced_lib>System.Security.Cryptography.X509Certificates</forced_lib>\n<forced_lib>System.Security.Cryptography.Xml</forced_lib>\n<forced_lib>System.Security.Permissions</forced_lib>\n<forced_lib>System.Security.Policy</forced_lib>\n<forced_lib>System.Security.Principal</forced_lib>\n<forced_lib>System.Security.RightsManagement</forced_lib>\n<forced_lib>System.ServiceModel</forced_lib>\n<forced_lib>System.ServiceModel.Activation</forced_lib>\n<forced_lib>System.ServiceModel.Activation.Configuration</forced_lib>\n<forced_lib>System.ServiceModel.Channels</forced_lib>\n<forced_lib>System.ServiceModel.ComIntegration</forced_lib>\n<forced_lib>System.ServiceModel.Configuration</forced_lib>\n<forced_lib>System.ServiceModel.Description</forced_lib>\n<forced_lib>System.ServiceModel.Diagnostics</forced_lib>\n<forced_lib>System.ServiceModel.Dispatcher</forced_lib>\n<forced_lib>System.ServiceModel.Install.Configuration</forced_lib>\n<forced_lib>System.ServiceModel.Internal</forced_lib>\n<forced_lib>System.ServiceModel.MsmqIntegration</forced_lib>\n<forced_lib>System.ServiceModel.PeerResolvers</forced_lib>\n<forced_lib>System.ServiceModel.Persistence</forced_lib>\n<forced_lib>System.ServiceModel.Security</forced_lib>\n<forced_lib>System.ServiceModel.Security.Tokens</forced_lib>\n<forced_lib>System.ServiceModel.Syndication</forced_lib>\n<forced_lib>System.ServiceModel.Web</forced_lib>\n<forced_lib>System.ServiceProcess</forced_lib>\n<forced_lib>System.ServiceProcess.Design</forced_lib>\n<forced_lib>System.Speech.AudioFormat</forced_lib>\n<forced_lib>System.Speech.Recognition</forced_lib>\n<forced_lib>System.Speech.Recognition.SrgsGrammar</forced_lib>\n<forced_lib>System.Speech.Synthesis</forced_lib>\n<forced_lib>System.Speech.Synthesis.TtsEngine</forced_lib>\n<forced_lib>System.Text</forced_lib>\n<forced_lib>System.Text.RegularExpressions</forced_lib>\n<forced_lib>System.Threading</forced_lib>\n<forced_lib>System.Timers</forced_lib>\n<forced_lib>System.Transactions</forced_lib>\n<forced_lib>System.Transactions.Configuration</forced_lib>\n<forced_lib>System.Web</forced_lib>\n<forced_lib>System.Web.ApplicationServices</forced_lib>\n<forced_lib>System.Web.Caching</forced_lib>\n<forced_lib>System.Web.ClientServices</forced_lib>\n<forced_lib>System.Web.ClientServices.Providers</forced_lib>\n<forced_lib>System.Web.Compilation</forced_lib>\n<forced_lib>System.Web.Configuration</forced_lib>\n<forced_lib>System.Web.Configuration.Internal</forced_lib>\n<forced_lib>System.Web.DynamicData</forced_lib>\n<forced_lib>System.Web.DynamicData.Design</forced_lib>\n<forced_lib>System.Web.DynamicData.ModelProviders</forced_lib>\n<forced_lib>System.Web.Handlers</forced_lib>\n<forced_lib>System.Web.Hosting</forced_lib>\n<forced_lib>System.Web.Mail</forced_lib>\n<forced_lib>System.Web.Management</forced_lib>\n<forced_lib>System.Web.Mobile</forced_lib>\n<forced_lib>System.Web.Profile</forced_lib>\n<forced_lib>System.Web.Query.Dynamic</forced_lib>\n<forced_lib>System.Web.RegularExpressions</forced_lib>\n<forced_lib>System.Web.Routing</forced_lib>\n<forced_lib>System.Web.Script.Serialization</forced_lib>\n<forced_lib>System.Web.Script.Services</forced_lib>\n<forced_lib>System.Web.Security</forced_lib>\n<forced_lib>System.Web.Services</forced_lib>\n<forced_lib>System.Web.Services.Configuration</forced_lib>\n<forced_lib>System.Web.Services.Description</forced_lib>\n<forced_lib>System.Web.Services.Discovery</forced_lib>\n<forced_lib>System.Web.Services.Protocols</forced_lib>\n<forced_lib>System.Web.SessionState</forced_lib>\n<forced_lib>System.Web.UI</forced_lib>\n<forced_lib>System.Web.UI.Adapters</forced_lib>\n<forced_lib>System.Web.UI.Design</forced_lib>\n<forced_lib>System.Web.UI.Design.MobileControls</forced_lib>\n<forced_lib>System.Web.UI.Design.MobileControls.Converters</forced_lib>\n<forced_lib>System.Web.UI.Design.WebControls</forced_lib>\n<forced_lib>System.Web.UI.Design.WebControls.WebParts</forced_lib>\n<forced_lib>System.Web.UI.MobileControls</forced_lib>\n<forced_lib>System.Web.UI.MobileControls.Adapters</forced_lib>\n<forced_lib>System.Web.UI.MobileControls.Adapters.XhtmlAdapters</forced_lib>\n<forced_lib>System.Web.UI.WebControls</forced_lib>\n<forced_lib>System.Web.UI.WebControls.Adapters</forced_lib>\n<forced_lib>System.Web.UI.WebControls.WebParts</forced_lib>\n<forced_lib>System.Web.Util</forced_lib>\n<forced_lib>System.Windows</forced_lib>\n<forced_lib>System.Windows.Annotations</forced_lib>\n<forced_lib>System.Windows.Annotations.Storage</forced_lib>\n<forced_lib>System.Windows.Automation</forced_lib>\n<forced_lib>System.Windows.Automation.Peers</forced_lib>\n<forced_lib>System.Windows.Automation.Provider</forced_lib>\n<forced_lib>System.Windows.Automation.Text</forced_lib>\n<forced_lib>System.Windows.Controls</forced_lib>\n<forced_lib>System.Windows.Controls.Primitives</forced_lib>\n<forced_lib>System.Windows.Converters</forced_lib>\n<forced_lib>System.Windows.Data</forced_lib>\n<forced_lib>System.Windows.Documents</forced_lib>\n<forced_lib>System.Windows.Documents.Serialization</forced_lib>\n<forced_lib>System.Windows.Forms</forced_lib>\n<forced_lib>System.Windows.Forms.ComponentModel.Com2Interop</forced_lib>\n<forced_lib>System.Windows.Forms.Design</forced_lib>\n<forced_lib>System.Windows.Forms.Design.Behavior</forced_lib>\n<forced_lib>System.Windows.Forms.Integration</forced_lib>\n<forced_lib>System.Windows.Forms.Layout</forced_lib>\n<forced_lib>System.Windows.Forms.PropertyGridInternal</forced_lib>\n<forced_lib>System.Windows.Forms.VisualStyles</forced_lib>\n<forced_lib>System.Windows.Ink</forced_lib>\n<forced_lib>System.Windows.Ink.AnalysisCore</forced_lib>\n<forced_lib>System.Windows.Input</forced_lib>\n<forced_lib>System.Windows.Input.StylusPlugIns</forced_lib>\n<forced_lib>System.Windows.Interop</forced_lib>\n<forced_lib>System.Windows.Markup</forced_lib>\n<forced_lib>System.Windows.Markup.Localizer</forced_lib>\n<forced_lib>System.Windows.Markup.Primitives</forced_lib>\n<forced_lib>System.Windows.Media</forced_lib>\n<forced_lib>System.Windows.Media.Animation</forced_lib>\n<forced_lib>System.Windows.Media.Converters</forced_lib>\n<forced_lib>System.Windows.Media.Effects</forced_lib>\n<forced_lib>System.Windows.Media.Imaging</forced_lib>\n<forced_lib>System.Windows.Media.Media3D</forced_lib>\n<forced_lib>System.Windows.Media.Media3D.Converters</forced_lib>\n<forced_lib>System.Windows.Media.TextFormatting</forced_lib>\n<forced_lib>System.Windows.Navigation</forced_lib>\n<forced_lib>System.Windows.Resources</forced_lib>\n<forced_lib>System.Windows.Shapes</forced_lib>\n<forced_lib>System.Windows.Threading</forced_lib>\n<forced_lib>System.Windows.Xps</forced_lib>\n<forced_lib>System.Windows.Xps.Packaging</forced_lib>\n<forced_lib>System.Windows.Xps.Serialization</forced_lib>\n<forced_lib>System.Workflow.Activities</forced_lib>\n<forced_lib>System.Workflow.Activities.Configuration</forced_lib>\n<forced_lib>System.Workflow.Activities.Rules</forced_lib>\n<forced_lib>System.Workflow.Activities.Rules.Design</forced_lib>\n<forced_lib>System.Workflow.ComponentModel</forced_lib>\n<forced_lib>System.Workflow.ComponentModel.Compiler</forced_lib>\n<forced_lib>System.Workflow.ComponentModel.Design</forced_lib>\n<forced_lib>System.Workflow.ComponentModel.Serialization</forced_lib>\n<forced_lib>System.Workflow.Runtime</forced_lib>\n<forced_lib>System.Workflow.Runtime.Configuration</forced_lib>\n<forced_lib>System.Workflow.Runtime.DebugEngine</forced_lib>\n<forced_lib>System.Workflow.Runtime.Hosting</forced_lib>\n<forced_lib>System.Workflow.Runtime.Tracking</forced_lib>\n<forced_lib>System.Xml</forced_lib>\n<forced_lib>System.Xml.Linq</forced_lib>\n<forced_lib>System.Xml.Schema</forced_lib>\n<forced_lib>System.Xml.Serialization</forced_lib>\n<forced_lib>System.Xml.Serialization.Advanced</forced_lib>\n<forced_lib>System.Xml.Serialization.Configuration</forced_lib>\n<forced_lib>System.Xml.XPath</forced_lib>\n<forced_lib>System.Xml.Xsl</forced_lib>\n<forced_lib>System.Xml.Xsl.Runtime</forced_lib>\n<forced_lib>UIAutomationClientsideProviders</forced_lib>\n<forced_lib>__builtin__</forced_lib>\n<forced_lib>_ast</forced_lib>\n<forced_lib>_bisect</forced_lib>\n<forced_lib>_codecs</forced_lib>\n<forced_lib>_collections</forced_lib>\n<forced_lib>_csv</forced_lib>\n<forced_lib>_ctypes</forced_lib>\n<forced_lib>_ctypes_test</forced_lib>\n<forced_lib>_functools</forced_lib>\n<forced_lib>_heapq</forced_lib>\n<forced_lib>_io</forced_lib>\n<forced_lib>_locale</forced_lib>\n<forced_lib>_md5</forced_lib>\n<forced_lib>_random</forced_lib>\n<forced_lib>_sha</forced_lib>\n<forced_lib>_sha256</forced_lib>\n<forced_lib>_sha512</forced_lib>\n<forced_lib>_sqlite3</forced_lib>\n<forced_lib>_sre</forced_lib>\n<forced_lib>_ssl</forced_lib>\n<forced_lib>_struct</forced_lib>\n<forced_lib>_subprocess</forced_lib>\n<forced_lib>_warnings</forced_lib>\n<forced_lib>_weakref</forced_lib>\n<forced_lib>_winreg</forced_lib>\n<forced_lib>array</forced_lib>\n<forced_lib>binascii</forced_lib>\n<forced_lib>bz2</forced_lib>\n<forced_lib>cPickle</forced_lib>\n<forced_lib>cStringIO</forced_lib>\n<forced_lib>clr</forced_lib>\n<forced_lib>cmath</forced_lib>\n<forced_lib>copy_reg</forced_lib>\n<forced_lib>datetime</forced_lib>\n<forced_lib>email</forced_lib>\n<forced_lib>errno</forced_lib>\n<forced_lib>exceptions</forced_lib>\n<forced_lib>future_builtins</forced_lib>\n<forced_lib>gc</forced_lib>\n<forced_lib>hashlib</forced_lib>\n<forced_lib>imp</forced_lib>\n<forced_lib>itertools</forced_lib>\n<forced_lib>marshal</forced_lib>\n<forced_lib>math</forced_lib>\n<forced_lib>mmap</forced_lib>\n<forced_lib>msvcrt</forced_lib>\n<forced_lib>nt</forced_lib>\n<forced_lib>operator</forced_lib>\n<forced_lib>os</forced_lib>\n<forced_lib>os.path</forced_lib>\n<forced_lib>pytest</forced_lib>\n<forced_lib>re</forced_lib>\n<forced_lib>select</forced_lib>\n<forced_lib>signal</forced_lib>\n<forced_lib>socket</forced_lib>\n<forced_lib>sys</forced_lib>\n<forced_lib>thread</forced_lib>\n<forced_lib>time</forced_lib>\n<forced_lib>unicodedata</forced_lib>\n<forced_lib>winsound</forced_lib>\n<forced_lib>wpf</forced_lib>\n<forced_lib>xxsubtype</forced_lib>\n<forced_lib>zipimport</forced_lib>\n<forced_lib>zlib</forced_lib>\n</xml>&&&&&
+/instance/org.eclipse.jdt.ui/cleanup.add_generated_serial_version_id=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+/instance/org.eclipse.wst.validation/suspend=false
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles.version=12
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+/instance/org.eclipse.compare/org.eclipse.compare.IgnoreWhitespace=true
+/instance/org.eclipse.egit.ui/resourcehistory_graph_split=500,500
+@org.eclipse.mylyn.monitor.ui=3.8.3.v20130107-0100
+/instance/org.eclipse.ui.ide/PROBLEMS_FILTERS_MIGRATE=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.always_use_parentheses_in_expressions=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_default_serial_version_id=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.BreakpointView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<VariablesViewMemento org.eclipse.debug.ui.SASH_DETAILS_PART\="315" org.eclipse.debug.ui.SASH_VIEW_PART\="684">\r\n<PRESENTATION_CONTEXT_PROPERTIES IMemento.internal.id\="org.eclipse.debug.ui.BreakpointView">\r\n<BOOLEAN BOOLEAN\="true" IMemento.internal.id\="org.eclipse.debug.ui.check"/>\r\n</PRESENTATION_CONTEXT_PROPERTIES>\r\n</VariablesViewMemento>
+@org.eclipse.ui.workbench=3.104.0.v20130204-164612
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.importorder=java;javax;org;com;org.apache.cloudstack;com.cloud;
+/instance/org.eclipse.jdt.ui/sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.always_use_this_for_non_static_field_access=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.organize_imports=true
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_method_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+@org.eclipse.egit.ui=2.3.1.201302201838-r
+/instance/org.eclipse.ui.editors/quickdiff.nowarn.before.switch=always
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.line_length=120
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+/instance/org.eclipse.jdt.debug.ui/org.eclipse.debug.ui.VariableView.org.eclipse.jdt.debug.ui.show_null_entries=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+/instance/org.eclipse.jdt.ui/content_assist_number_of_computers=21
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localSuffixes=
+/instance/org.eclipse.jdt.junit.core/org.eclipse.jdt.junit.enable_assertions=true
+@org.eclipse.core.resources=3.8.1.v20121114-124432
+/instance/org.eclipse.jdt.ui/spelling_ignore_urls=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+/instance/org.eclipse.jdt.ui/content_assist_autoactivation_delay=50
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+@org.eclipse.search=3.8.0.v20120523-1540
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+/instance/org.eclipse.ui.intro/org.eclipse.epp.package.java.product_INTRO_THEME=org.eclipse.ui.intro.universal.circles
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JSR305_ANNOTATIONS=C\:/bin/Eclipse/current/plugins/edu.umd.cs.findbugs.plugin.eclipse_2.0.2.20121210/lib/jsr305.jar
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.JRE_LIB=C\:/bin/Java/jre7/lib/rt.jar
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.lineSplit=180
+/instance/org.eclipse.jdt.ui/content_assist_disabled_computers=org.eclipse.recommenders.completion.rcp.templates.category\u0000org.eclipse.jdt.ui.javaAllProposalCategory\u0000org.eclipse.jdt.ui.javaNoTypeProposalCategory\u0000org.eclipse.recommenders.completion.rcp.chain.category\u0000org.eclipse.jdt.ui.javaTypeProposalCategory\u0000org.eclipse.jdt.ui.textProposalCategory\u0000org.eclipse.recommenders.subwords.rcp.category\u0000
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+@org.eclipse.ui.ide=3.8.2.v20121106-165923
+@org.eclipse.core.runtime=3.8.0.v20120912-155025
+/instance/org.eclipse.jdt.ui/editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces_ignore_empty=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.always_use_blocks=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.PREF_HCR_WITH_COMPILATION_ERRORS=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_html=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+/instance/org.eclipse.m2e.core/eclipse.m2.WorkspacelifecycleMappingsLocation=D\:/src/workspaces/master/.metadata/.plugins/org.eclipse.m2e.core/lifecycle-mapping-metadata.xml
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+/instance/org.eclipse.jdt.ui/cleanup.make_variable_declarations_final=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_field_access=true
 /instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
 /instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
 /instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
@@ -432,185 +432,185 @@
 /instance/org.eclipse.jdt.debug.ui/org.eclipse.debug.ui.ExpressionView.org.eclipse.jdt.debug.ui.show_null_entries=true
 /instance/org.eclipse.jdt.ui/sp_cleanup.correct_indentation=false
 /instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_source_code=true
-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0

-/instance/org.eclipse.jdt.ui/cleanup.never_use_parentheses_in_expressions=true

-@org.eclipse.debug.core=3.7.100.v20120521-2012

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert

-file_export_version=3.0

-@org.eclipse.jdt.debug.ui=3.6.100.v20120530-1425

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0

-/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.PREF_REQUEST_TIMEOUT=3000

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.format_source_code=false

-/configuration/org.eclipse.ui.ide/RECENT_WORKSPACES=d\:\\src\\workspaces\\vmsync\nd\:\\src\\workspaces\\master\nd\:\\src\\workspaces\\4.x

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unnecessary_casts=true

-@org.eclipse.jdt.junit.core=3.7.100.v20120523-1257

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert

-/instance/org.eclipse.m2e.discovery/org.eclipse.m2e.discovery.pref.projects=

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.overrideannotation=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.sort_members_all=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentPrefixes=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true

-/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.VariableView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<VariablesViewMemento org.eclipse.debug.ui.SASH_DETAILS_PART\="315" org.eclipse.debug.ui.SASH_VIEW_PART\="684">\r\n<PRESENTATION_CONTEXT_PROPERTIES IMemento.internal.id\="org.eclipse.debug.ui.VariableView"/>\r\n</VariablesViewMemento>

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.internal.ui.navigator.layout=2

-/instance/org.eclipse.jdt.ui/sp_cleanup.make_parameters_final=false

-/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_casts=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert

-@org.eclipse.m2e.core=1.3.1.20130219-1424

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_empty_lines=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2

-/instance/org.eclipse.ui/showIntro=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert

-/instance/org.eclipse.jdt.ui/cleanup.qualify_static_field_accesses_with_declaring_class=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.staticondemandthreshold=99

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldPrefixes=

-/instance/org.eclipse.mylyn.monitor.ui/org.eclipse.mylyn.monitor.activity.tracking.enabled.checked=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localPrefixes=

-/instance/org.eclipse.team.core/ignore_files=target\ntrue\n

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles.version=2

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true

-/instance/org.eclipse.ui.browser/internalWebBrowserHistory=file\:///D\:/src/acs/vmsync/tools/apidoc/target/xmldoc/html/root_admin/updateHypervisorCapabilities.html|*|file\:/D\:/src/acs/vmsync/tools/apidoc/target/xmldoc/html/root_admin/updateHypervisorCapabilities.html|*|

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_imports=1

-/instance/org.eclipse.jdt.ui/spelling_user_dictionary_encoding=

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_annotations=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line

-/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true

-/instance/org.eclipse.jdt.ui/cleanup.never_use_blocks=false

-/instance/org.eclipse.wst.validation/USER_MANUAL_PREFERENCE=enabledManualValidatorList

-@org.eclipse.mylyn.context.core=3.8.3.v20130107-0100

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert

-@org.eclipse.jdt.launching=3.6.101.v20130111-183046

-@org.eclipse.jdt.debug=3.7.101.v20120913-153601

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.compliance=1.6

-@org.eclipse.m2e.editor.xml=1.3.1.20130219-1424

-/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_fields=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_blocks_only_for_return_and_throw=false

-/instance/org.eclipse.jdt.ui/cleanup.remove_unused_local_variables=false

-/instance/org.eclipse.debug.ui/org.eclipse.debug.ui.user_view_bindings=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<viewBindings>\r\n<view id\="org.eclipse.debug.ui.ExpressionView">\r\n<perspective id\="org.eclipse.debug.ui.DebugPerspective" userAction\="opened"/>\r\n</view>\r\n</viewBindings>\r\n

-/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_members=false

-@org.python.pydev.debug=2.7.5.2013052819

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_generated_serial_version_id=false

-/configuration/org.eclipse.ui.ide/MAX_RECENT_WORKSPACES=5

-/instance/org.eclipse.jdt.ui/spelling_ignore_ampersand_in_properties=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_on_off_tags=true

-@org.eclipse.ui.intro=3.4.200.v20120521-2344

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.source=1.6

-/instance/org.eclipse.wst.xml.core/indentationSize=2

-/instance/org.eclipse.jdt.ui/cleanup.use_blocks_only_for_return_and_throw=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_fields=true

-/instance/org.eclipse.jdt.ui/sp_cleanup.make_local_variable_final=false

-/instance/org.eclipse.m2e.core/eclipse.m2.defaultPomEditorPage=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert

-/instance/org.eclipse.jdt.ui/content_assist_autoactivation_triggers_java=.(

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.use_blocks=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.FINDBUGS_ANNOTATIONS=C\:/bin/Eclipse/current/plugins/edu.umd.cs.findbugs.plugin.eclipse_2.0.2.20121210/lib/annotations.jar

-/instance/org.eclipse.egit.core/GitRepositoriesView.GitDirectories=D\:\\src\\acs\\master\\.git;D\:\\src\\acs\\vmsync\\.git;

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true

-/instance/org.eclipse.ui.workbench/editors=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<editors>\r\n<descriptor class\="org.python.pydev.editor.PyEdit" id\="org.python.pydev.editor.PythonEditor" image\="icons/python_file.gif" internal\="true" label\="Python Editor" openMode\="1" open_in_place\="false" plugin\="org.python.pydev"/>\r\n<descriptor class\="org.eclipse.wb.internal.core.editor.multi.DesignerEditor" id\="org.eclipse.wb.core.guiEditor" image\="icons/gui_editor.gif" internal\="true" label\="WindowBuilder Editor" openMode\="1" open_in_place\="false" plugin\="org.eclipse.wb.core.ui"/>\r\n<descriptor class\="org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor" id\="org.eclipse.jdt.ui.ClassFileEditorNoSource" image\="$nl$/icons/full/obj16/classf_obj.gif" internal\="true" label\="Class File Viewer" openMode\="1" open_in_place\="false" plugin\="org.eclipse.jdt.ui"/>\r\n<descriptor id\="org.eclipse.ui.browser.editorSupport" image\="$nl$/icons/obj16/internal_browser.gif" internal\="false" label\="Web Browser" launcher\="org.eclipse.ui.internal.browser.BrowserLauncher" openMode\="4" open_in_place\="false" plugin\="org.eclipse.ui.browser"/>\r\n<descriptor class\="org.eclipse.ui.editors.text.TextEditor" id\="org.eclipse.ui.DefaultTextEditor" image\="$nl$/icons/full/obj16/file_obj.gif" internal\="true" label\="Text Editor" openMode\="1" open_in_place\="false" plugin\="org.eclipse.ui.editors"/>\r\n<descriptor id\="org.eclipse.jdt.ui.JARDescEditor" image\="$nl$/icons/full/obj16/jar_desc_obj.gif" internal\="false" label\="JAR Export Wizard" launcher\="org.eclipse.jdt.internal.ui.jarpackager.OpenJarExportWizardEditorLauncher" openMode\="4" open_in_place\="false" plugin\="org.eclipse.jdt.ui"/>\r\n<descriptor class\="org.eclipse.jdt.internal.debug.ui.snippeteditor.JavaSnippetEditor" id\="org.eclipse.jdt.debug.ui.SnippetEditor" image\="$nl$/icons/full/obj16/jsbook_obj.gif" internal\="true" label\="Scrapbook" openMode\="1" open_in_place\="false" plugin\="org.eclipse.jdt.debug.ui"/>\r\n</editors>

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.compact_else_if=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0

-/instance/org.eclipse.jdt.ui/content_assist_proposals_background=255,255,255

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_header=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.char=space

-/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">// Licensed to the Apache Software Foundation (ASF) under one\r\n// or more contributor license agreements.  See the NOTICE file\r\n// distributed with this work for additional information\r\n// regarding copyright ownership.  The ASF licenses this file\r\n// to you under the Apache License, Version 2.0 (the\r\n// "License"); you may not use this file except in compliance\r\n// with the License.  You may obtain a copy of the License at\r\n//\r\n//   http\://www.apache.org/licenses/LICENSE-2.0\r\n//\r\n// Unless required by applicable law or agreed to in writing,\r\n// software distributed under the License is distributed on an\r\n// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n// KIND, either express or implied.  See the License for the\r\n// specific language governing permissions and limitations\r\n// under the License.</template></templates>

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_block_comments=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert

-/instance/org.eclipse.debug.core/prefWatchExpressions=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<watchExpressions>\r\n<expression enabled\="true" text\="job.getId()"/>\r\n<expression enabled\="true" text\="entry.getValue()"/>\r\n</watchExpressions>\r\n

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert

-/instance/org.eclipse.wst.validation/DELEGATES_PREFERENCE=delegateValidatorList

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert

-@org.eclipse.team.ui=3.6.201.v20130125-135424

-/instance/org.eclipse.ui.editors/overviewRuler_migration=migrated_3.1

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert

-@org.eclipse.wst.xml.core=1.1.702.v201301101836

-/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_methods=true

-@org.eclipse.wst.xml.ui=1.1.302.v201301172222

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line

-/instance/org.eclipse.ui.ide/tipsAndTricks=true

-/instance/org.eclipse.search/org.eclipse.search.defaultPerspective=org.eclipse.search.defaultPerspective.none

-/instance/org.eclipse.egit.ui/default_repository_dir=d\:\\src

-/instance/org.eclipse.jdt.ui/sp_cleanup.on_save_use_additional_actions=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert

-/instance/org.eclipse.jdt.ui/cleanup.sort_members=false

-/instance/org.eclipse.jdt.ui/sp_cleanup.use_parentheses_in_expressions=false

-@org.eclipse.debug.ui=3.8.2.v20130130-171415

-@org.eclipse.compare=3.5.301.v20130125-135424

-/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access=true

-/instance/org.eclipse.jdt.ui/org.eclipse.jface.textfont=1|Courier New|10.0|0|WINDOWS|1|0|0|0|0|0|0|0|0|1|0|0|0|0|Courier New;

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert

-/instance/org.eclipse.ui.editors/printMargin=true

-/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_all=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert

-/instance/org.eclipse.wst.validation/stateTS=0

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert

-@org.eclipse.jdt.ui=3.8.2.v20130107-165834

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.M2_REPO=d\:/src/maven

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unnecessary_nls_tags=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on

-/instance/org.eclipse.wst.xml.ui/org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart.lastActivePage=1

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert

-/instance/org.eclipse.mylyn.wikitext.ui/org.eclipse.mylyn.wikitext.ui.customtemplates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1

-@org.eclipse.ui.browser=3.4.2.v20130123-162658

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16

-/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_methods=false

-/instance/org.eclipse.debug.ui/org.eclipse.debug.ui.switch_perspective_on_suspend=always

-/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_types=true

-/instance/org.eclipse.jdt.ui/content_assist_lru_history=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><history maxLHS\="100" maxRHS\="10"><lhs name\="org.springframework.context.ApplicationContextAware"><rhs name\="com.cloud.utils.component.ComponentContext"/></lhs><lhs name\="org.springframework.beans.factory.Aware"><rhs name\="com.cloud.utils.component.ComponentContext"/></lhs><lhs name\="com.cloud.utils.component.ComponentContext"><rhs name\="com.cloud.utils.component.ComponentContext"/></lhs><lhs name\="org.apache.cloudstack.framework.async.AsyncCompletionCallback"><rhs name\="com.cloud.storage.upload.UploadListener$Callback"/></lhs><lhs name\="com.cloud.storage.upload.UploadListener$Callback"><rhs name\="com.cloud.storage.upload.UploadListener$Callback"/></lhs></history>

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert

-/instance/org.eclipse.ui.editors/spacesForTabs=true

-@org.eclipse.mylyn.wikitext.ui=1.7.3.v20130107-0100

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert

-/instance/org.python.pydev/INTERPRETER_CONFIGURATION_0=DONT_ASK

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line

-@org.eclipse.epp.mpc.ui=1.1.1.I20110907-0947

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16

-/instance/org.eclipse.jdt.launching/org.eclipse.jdt.launching.PREF_DEFAULT_ENVIRONMENTS_XML=

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled

-/instance/org.eclipse.jdt.ui/cleanup.correct_indentation=true

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert

-/instance/org.eclipse.ui.ide/quickStart=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert

-/instance/org.eclipse.ui.workbench/PLUGINS_NOT_ACTIVATED_ON_STARTUP=org.eclipse.m2e.discovery;

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert

-/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert

+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+/instance/org.eclipse.jdt.ui/cleanup.never_use_parentheses_in_expressions=true
+@org.eclipse.debug.core=3.7.100.v20120521-2012
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+file_export_version=3.0
+@org.eclipse.jdt.debug.ui=3.6.100.v20120530-1425
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_trailing_whitespaces=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+/instance/org.eclipse.jdt.debug/org.eclipse.jdt.debug.PREF_REQUEST_TIMEOUT=3000
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.format_source_code=false
+/configuration/org.eclipse.ui.ide/RECENT_WORKSPACES=d\:\\src\\workspaces\\vmsync\nd\:\\src\\workspaces\\master\nd\:\\src\\workspaces\\4.x
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unnecessary_casts=true
+@org.eclipse.jdt.junit.core=3.7.100.v20120523-1257
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+/instance/org.eclipse.m2e.discovery/org.eclipse.m2e.discovery.pref.projects=
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.overrideannotation=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.sort_members_all=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+/instance/org.eclipse.debug.ui/pref_state_memento.org.eclipse.debug.ui.VariableView=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<VariablesViewMemento org.eclipse.debug.ui.SASH_DETAILS_PART\="315" org.eclipse.debug.ui.SASH_VIEW_PART\="684">\r\n<PRESENTATION_CONTEXT_PROPERTIES IMemento.internal.id\="org.eclipse.debug.ui.VariableView"/>\r\n</VariablesViewMemento>
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.internal.ui.navigator.layout=2
+/instance/org.eclipse.jdt.ui/sp_cleanup.make_parameters_final=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unnecessary_casts=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+@org.eclipse.m2e.core=1.3.1.20130219-1424
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_empty_lines=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+/instance/org.eclipse.ui/showIntro=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+/instance/org.eclipse.jdt.ui/cleanup.qualify_static_field_accesses_with_declaring_class=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.staticondemandthreshold=99
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldPrefixes=
+/instance/org.eclipse.mylyn.monitor.ui/org.eclipse.mylyn.monitor.activity.tracking.enabled.checked=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localPrefixes=
+/instance/org.eclipse.team.core/ignore_files=target\ntrue\n
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles.version=2
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+/instance/org.eclipse.ui.browser/internalWebBrowserHistory=file\:///D\:/src/acs/vmsync/tools/apidoc/target/xmldoc/html/root_admin/updateHypervisorCapabilities.html|*|file\:/D\:/src/acs/vmsync/tools/apidoc/target/xmldoc/html/root_admin/updateHypervisorCapabilities.html|*|
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+/instance/org.eclipse.jdt.ui/spelling_user_dictionary_encoding=
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_annotations=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true
+/instance/org.eclipse.jdt.ui/cleanup.never_use_blocks=false
+/instance/org.eclipse.wst.validation/USER_MANUAL_PREFERENCE=enabledManualValidatorList
+@org.eclipse.mylyn.context.core=3.8.3.v20130107-0100
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+@org.eclipse.jdt.launching=3.6.101.v20130111-183046
+@org.eclipse.jdt.debug=3.7.101.v20120913-153601
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.compliance=1.6
+@org.eclipse.m2e.editor.xml=1.3.1.20130219-1424
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_fields=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_blocks_only_for_return_and_throw=false
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_local_variables=false
+/instance/org.eclipse.debug.ui/org.eclipse.debug.ui.user_view_bindings=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<viewBindings>\r\n<view id\="org.eclipse.debug.ui.ExpressionView">\r\n<perspective id\="org.eclipse.debug.ui.DebugPerspective" userAction\="opened"/>\r\n</view>\r\n</viewBindings>\r\n
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_members=false
+@org.python.pydev.debug=2.7.5.2013052819
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_generated_serial_version_id=false
+/configuration/org.eclipse.ui.ide/MAX_RECENT_WORKSPACES=5
+/instance/org.eclipse.jdt.ui/spelling_ignore_ampersand_in_properties=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_on_off_tags=true
+@org.eclipse.ui.intro=3.4.200.v20120521-2344
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.source=1.6
+/instance/org.eclipse.wst.xml.core/indentationSize=2
+/instance/org.eclipse.jdt.ui/cleanup.use_blocks_only_for_return_and_throw=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_fields=true
+/instance/org.eclipse.jdt.ui/sp_cleanup.make_local_variable_final=false
+/instance/org.eclipse.m2e.core/eclipse.m2.defaultPomEditorPage=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+/instance/org.eclipse.jdt.ui/content_assist_autoactivation_triggers_java=.(
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.use_blocks=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.FINDBUGS_ANNOTATIONS=C\:/bin/Eclipse/current/plugins/edu.umd.cs.findbugs.plugin.eclipse_2.0.2.20121210/lib/annotations.jar
+/instance/org.eclipse.egit.core/GitRepositoriesView.GitDirectories=D\:\\src\\acs\\master\\.git;D\:\\src\\acs\\vmsync\\.git;
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+/instance/org.eclipse.ui.workbench/editors=<?xml version\="1.0" encoding\="UTF-8"?>\r\n<editors>\r\n<descriptor class\="org.python.pydev.editor.PyEdit" id\="org.python.pydev.editor.PythonEditor" image\="icons/python_file.gif" internal\="true" label\="Python Editor" openMode\="1" open_in_place\="false" plugin\="org.python.pydev"/>\r\n<descriptor class\="org.eclipse.wb.internal.core.editor.multi.DesignerEditor" id\="org.eclipse.wb.core.guiEditor" image\="icons/gui_editor.gif" internal\="true" label\="WindowBuilder Editor" openMode\="1" open_in_place\="false" plugin\="org.eclipse.wb.core.ui"/>\r\n<descriptor class\="org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor" id\="org.eclipse.jdt.ui.ClassFileEditorNoSource" image\="$nl$/icons/full/obj16/classf_obj.gif" internal\="true" label\="Class File Viewer" openMode\="1" open_in_place\="false" plugin\="org.eclipse.jdt.ui"/>\r\n<descriptor id\="org.eclipse.ui.browser.editorSupport" image\="$nl$/icons/obj16/internal_browser.gif" internal\="false" label\="Web Browser" launcher\="org.eclipse.ui.internal.browser.BrowserLauncher" openMode\="4" open_in_place\="false" plugin\="org.eclipse.ui.browser"/>\r\n<descriptor class\="org.eclipse.ui.editors.text.TextEditor" id\="org.eclipse.ui.DefaultTextEditor" image\="$nl$/icons/full/obj16/file_obj.gif" internal\="true" label\="Text Editor" openMode\="1" open_in_place\="false" plugin\="org.eclipse.ui.editors"/>\r\n<descriptor id\="org.eclipse.jdt.ui.JARDescEditor" image\="$nl$/icons/full/obj16/jar_desc_obj.gif" internal\="false" label\="JAR Export Wizard" launcher\="org.eclipse.jdt.internal.ui.jarpackager.OpenJarExportWizardEditorLauncher" openMode\="4" open_in_place\="false" plugin\="org.eclipse.jdt.ui"/>\r\n<descriptor class\="org.eclipse.jdt.internal.debug.ui.snippeteditor.JavaSnippetEditor" id\="org.eclipse.jdt.debug.ui.SnippetEditor" image\="$nl$/icons/full/obj16/jsbook_obj.gif" internal\="true" label\="Scrapbook" openMode\="1" open_in_place\="false" plugin\="org.eclipse.jdt.debug.ui"/>\r\n</editors>
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.compact_else_if=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+/instance/org.eclipse.jdt.ui/content_assist_proposals_background=255,255,255
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_header=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.char=space
+/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">// Licensed to the Apache Software Foundation (ASF) under one\r\n// or more contributor license agreements.  See the NOTICE file\r\n// distributed with this work for additional information\r\n// regarding copyright ownership.  The ASF licenses this file\r\n// to you under the Apache License, Version 2.0 (the\r\n// "License"); you may not use this file except in compliance\r\n// with the License.  You may obtain a copy of the License at\r\n//\r\n//   http\://www.apache.org/licenses/LICENSE-2.0\r\n//\r\n// Unless required by applicable law or agreed to in writing,\r\n// software distributed under the License is distributed on an\r\n// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n// KIND, either express or implied.  See the License for the\r\n// specific language governing permissions and limitations\r\n// under the License.</template></templates>
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_block_comments=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+/instance/org.eclipse.debug.core/prefWatchExpressions=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\r\n<watchExpressions>\r\n<expression enabled\="true" text\="job.getId()"/>\r\n<expression enabled\="true" text\="entry.getValue()"/>\r\n</watchExpressions>\r\n
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+/instance/org.eclipse.wst.validation/DELEGATES_PREFERENCE=delegateValidatorList
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+@org.eclipse.team.ui=3.6.201.v20130125-135424
+/instance/org.eclipse.ui.editors/overviewRuler_migration=migrated_3.1
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+@org.eclipse.wst.xml.core=1.1.702.v201301101836
+/instance/org.eclipse.jdt.ui/cleanup.remove_unused_private_methods=true
+@org.eclipse.wst.xml.ui=1.1.302.v201301172222
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+/instance/org.eclipse.ui.ide/tipsAndTricks=true
+/instance/org.eclipse.search/org.eclipse.search.defaultPerspective=org.eclipse.search.defaultPerspective.none
+/instance/org.eclipse.egit.ui/default_repository_dir=d\:\\src
+/instance/org.eclipse.jdt.ui/sp_cleanup.on_save_use_additional_actions=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+/instance/org.eclipse.jdt.ui/cleanup.sort_members=false
+/instance/org.eclipse.jdt.ui/sp_cleanup.use_parentheses_in_expressions=false
+@org.eclipse.debug.ui=3.8.2.v20130130-171415
+@org.eclipse.compare=3.5.301.v20130125-135424
+/instance/org.eclipse.jdt.ui/cleanup.use_this_for_non_static_method_access=true
+/instance/org.eclipse.jdt.ui/org.eclipse.jface.textfont=1|Courier New|10.0|0|WINDOWS|1|0|0|0|0|0|0|0|0|1|0|0|0|0|Courier New;
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+/instance/org.eclipse.ui.editors/printMargin=true
+/instance/org.eclipse.jdt.ui/cleanup.remove_trailing_whitespaces_all=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+/instance/org.eclipse.wst.validation/stateTS=0
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+@org.eclipse.jdt.ui=3.8.2.v20130107-165834
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.classpathVariable.M2_REPO=d\:/src/maven
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unnecessary_nls_tags=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+/instance/org.eclipse.wst.xml.ui/org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart.lastActivePage=1
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+/instance/org.eclipse.mylyn.wikitext.ui/org.eclipse.mylyn.wikitext.ui.customtemplates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+@org.eclipse.ui.browser=3.4.2.v20130123-162658
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+/instance/org.eclipse.jdt.ui/sp_cleanup.add_missing_methods=false
+/instance/org.eclipse.debug.ui/org.eclipse.debug.ui.switch_perspective_on_suspend=always
+/instance/org.eclipse.jdt.ui/sp_cleanup.remove_unused_private_types=true
+/instance/org.eclipse.jdt.ui/content_assist_lru_history=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><history maxLHS\="100" maxRHS\="10"><lhs name\="org.springframework.context.ApplicationContextAware"><rhs name\="com.cloud.utils.component.ComponentContext"/></lhs><lhs name\="org.springframework.beans.factory.Aware"><rhs name\="com.cloud.utils.component.ComponentContext"/></lhs><lhs name\="com.cloud.utils.component.ComponentContext"><rhs name\="com.cloud.utils.component.ComponentContext"/></lhs><lhs name\="org.apache.cloudstack.framework.async.AsyncCompletionCallback"><rhs name\="com.cloud.storage.upload.UploadListener$Callback"/></lhs><lhs name\="com.cloud.storage.upload.UploadListener$Callback"><rhs name\="com.cloud.storage.upload.UploadListener$Callback"/></lhs></history>
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+/instance/org.eclipse.ui.editors/spacesForTabs=true
+@org.eclipse.mylyn.wikitext.ui=1.7.3.v20130107-0100
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+/instance/org.python.pydev/INTERPRETER_CONFIGURATION_0=DONT_ASK
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+@org.eclipse.epp.mpc.ui=1.1.1.I20110907-0947
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+/instance/org.eclipse.jdt.launching/org.eclipse.jdt.launching.PREF_DEFAULT_ENVIRONMENTS_XML=
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+/instance/org.eclipse.jdt.ui/cleanup.correct_indentation=true
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+/instance/org.eclipse.ui.ide/quickStart=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+/instance/org.eclipse.ui.workbench/PLUGINS_NOT_ACTIVATED_ON_STARTUP=org.eclipse.m2e.discovery;
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
diff --git a/tools/eclipse/set-eclipse-profile.sh b/tools/eclipse/set-eclipse-profile.sh
index c0cc9a9..70dc8d4 100644
--- a/tools/eclipse/set-eclipse-profile.sh
+++ b/tools/eclipse/set-eclipse-profile.sh
@@ -23,4 +23,4 @@
 	echo Replacing $file; 
     sed -i -e s/activeProfiles=/activeProfiles=eclipse/g $file;
   fi; 
-done
\ No newline at end of file
+done
diff --git a/tools/git/prepare-commit-msg b/tools/git/prepare-commit-msg
index f957c18..dd801b0 100755
--- a/tools/git/prepare-commit-msg
+++ b/tools/git/prepare-commit-msg
@@ -101,4 +101,3 @@
   run_generic_commit $1
   ;;
 esac
-
diff --git a/tools/marvin/marvin/cloudstackTestCase.py b/tools/marvin/marvin/cloudstackTestCase.py
index d178b6e..1164cd9 100644
--- a/tools/marvin/marvin/cloudstackTestCase.py
+++ b/tools/marvin/marvin/cloudstackTestCase.py
@@ -23,7 +23,8 @@
     Network,
     NetworkACL,
     NetworkOffering,
-    VirtualMachine
+    VirtualMachine,
+    Volume
 )
 
 
@@ -98,12 +99,32 @@
         """
             Delete resources (created during tests)
         """
+        volume_list = []
         for obj in resources:
             if isinstance(obj, VirtualMachine):
                 obj.delete(api_client, expunge=True)
+            elif isinstance(obj, Volume):
+                obj.destroy(api_client, expunge=True)
+                volume_list.append(obj)
             else:
                 obj.delete(api_client)
 
+        cls.wait_for_volumes_cleanup(api_client, volume_list)
+
+    def wait_for_volumes_cleanup(cls, api_client, volume_list=[]):
+        """Wait for volumes to be deleted"""
+        for volume in volume_list:
+            max_retries = 24  # Max wait time will be 5 * 24 = 120 seconds
+            while max_retries > 0:
+                volumes = Volume.list(
+                    api_client,
+                    id=volume.id
+                )
+                if volumes is None or len(volumes) == 0:
+                    break
+                max_retries = max_retries - 1
+                time.sleep(5)
+
     def check_wget_from_vm(self, vm, public_ip, network=None, testnegative=False, isVmAccessible=True):
         import urllib.request, urllib.error
         self.debug(f"Checking if we can wget from a VM={vm.name} http server on public_ip={public_ip.ipaddress.ipaddress}, expecting failure == {testnegative} and vm is acceccible == {isVmAccessible}")
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index 68595a0..9892377 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -108,6 +108,8 @@
             cmd.roleid = services["roleid"]
         if "description" in services:
             cmd.description = services["description"]
+        if "ispublic" in services:
+            cmd.ispublic = services["ispublic"]
 
         return Role(apiclient.createRole(cmd).__dict__)
 
@@ -122,6 +124,8 @@
             cmd.description = services["description"]
         if "forced" in services:
             cmd.type = services["forced"]
+        if "ispublic" in services:
+            cmd.ispublic = services["ispublic"]
 
         return Role(apiclient.importRole(cmd).__dict__)
 
@@ -523,7 +527,7 @@
                customcpuspeed=None, custommemory=None, rootdisksize=None,
                rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={},
                properties=None, nicnetworklist=None, bootmode=None, boottype=None, dynamicscalingenabled=None,
-               userdataid=None, userdatadetails=None, extraconfig=None):
+               userdataid=None, userdatadetails=None, extraconfig=None, size=None):
         """Create the instance"""
 
         cmd = deployVirtualMachine.deployVirtualMachineCmd()
@@ -645,7 +649,9 @@
         if rootdiskcontroller:
             cmd.details[0]["rootDiskController"] = rootdiskcontroller
 
-        if "size" in services:
+        if size:
+            cmd.size = size
+        elif "size" in services:
             cmd.size = services["size"]
 
         if group:
@@ -1850,7 +1856,7 @@
 
         if zoneid:
             cmd.zoneid = zoneid
-        elif "zoneid" in services:
+        elif services and "zoneid" in services:
             cmd.zoneid = services["zoneid"]
 
         if domainid:
@@ -1875,12 +1881,19 @@
         return PublicIPAddress(apiclient.associateIpAddress(cmd).__dict__)
 
     def delete(self, apiclient):
-        """Dissociate Public IP address"""
+        """Dissociate Public IP address using the given ID"""
         cmd = disassociateIpAddress.disassociateIpAddressCmd()
         cmd.id = self.ipaddress.id
         apiclient.disassociateIpAddress(cmd)
         return
 
+    def delete_by_ip(self, apiclient):
+        """Dissociate Public IP address using the given IP address"""
+        cmd = disassociateIpAddress.disassociateIpAddressCmd()
+        cmd.ipaddress = self.ipaddress.ipaddress
+        apiclient.disassociateIpAddress(cmd)
+        return
+
     @classmethod
     def list(cls, apiclient, **kwargs):
         """List all Public IPs matching criteria"""
@@ -3127,6 +3140,35 @@
         [setattr(cmd, k, v) for k, v in list(kwargs.items())]
         return (apiclient.updateCluster(cmd))
 
+    def listDrsPlans(cls, apiclient, **kwargs):
+        """List drs plans for cluster"""
+
+        cmd = listClusterDrsPlan.listClusterDrsPlanCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return apiclient.listClusterDrsPlan(cmd)
+
+    def generateDrsPlan(cls, apiclient, migrations=None):
+        """Generate a drs plan for cluster"""
+
+        cmd = generateClusterDrsPlan.generateClusterDrsPlanCmd()
+        cmd.id = cls.id
+        cmd.migrations = migrations
+        return apiclient.generateClusterDrsPlan(cmd)
+
+    def executeDrsPlan(cls, apiclient, migrateto=None):
+        """Execute drs plan on cluster"""
+
+        cmd = executeClusterDrsPlan.executeClusterDrsPlanCmd()
+        cmd.id = cls.id
+        if migrateto:
+            cmd.migrateto = []
+            for vm, host in list(migrateto.items()):
+                cmd.migrateto.append({
+                    'vm': vm,
+                    'host': host
+                })
+        return apiclient.executeClusterDrsPlan(cmd)
+
 
 class Host:
     """Manage Host life cycle"""
@@ -3471,6 +3513,13 @@
             timeout -= 60
         return returnValue
 
+    @classmethod
+    def listObjects(cls, apiclient, path="/"):
+        cmd = listStoragePoolObjects.listStoragePoolObjectsCmd()
+        cmd.id = cls.id
+        cmd.path = path
+        return apiclient.listStoragePoolObjects(cmd)
+
 
 class Network:
     """Manage Network pools"""
@@ -3483,7 +3532,8 @@
                networkofferingid=None, projectid=None,
                subdomainaccess=None, zoneid=None,
                gateway=None, netmask=None, vpcid=None, aclid=None, vlan=None,
-               externalid=None, bypassvlanoverlapcheck=None, associatednetworkid=None, publicmtu=None, privatemtu=None):
+               externalid=None, bypassvlanoverlapcheck=None, associatednetworkid=None, publicmtu=None, privatemtu=None,
+               sourcenatipaddress=None):
         """Create Network for account"""
         cmd = createNetwork.createNetworkCmd()
         cmd.name = services["name"]
@@ -3565,6 +3615,8 @@
             cmd.publicmtu = publicmtu
         if privatemtu:
             cmd.privatemtu = privatemtu
+        if sourcenatipaddress:
+            cmd.sourcenatipaddress = sourcenatipaddress
         return Network(apiclient.createNetwork(cmd).__dict__)
 
     def delete(self, apiclient):
@@ -4178,6 +4230,20 @@
             cmd.listall = True
         return (apiclient.listImageStores(cmd))
 
+    def listObjects(self, apiclient, path="/"):
+        cmd = listImageStoreObjects.listImageStoreObjectsCmd()
+        cmd.id = self.id
+        cmd.path = path
+        return apiclient.listImageStoreObjects(cmd)
+
+    def migrateResources(self, apiclient, destStoreId, templateIdList=[], snapshotIdList=[]):
+        cmd = migrateResourceToAnotherSecondaryStorage.migrateResourceToAnotherSecondaryStorageCmd()
+        cmd.srcpool = self.id
+        cmd.destpool = destStoreId
+        cmd.templates = templateIdList
+        cmd.snapshots = snapshotIdList
+        return apiclient.migrateResourceToAnotherSecondaryStorage(cmd)
+
 
 class PhysicalNetwork:
     """Manage physical network storage"""
@@ -5048,7 +5114,7 @@
     @classmethod
     def create(cls, apiclient, services, vpcofferingid,
                zoneid, networkDomain=None, account=None,
-               domainid=None, **kwargs):
+               domainid=None, start=True, **kwargs):
         """Creates the virtual private connection (VPC)"""
 
         cmd = createVPC.createVPCCmd()
@@ -5056,6 +5122,7 @@
         cmd.displaytext = "-".join([services["displaytext"], random_gen()])
         cmd.vpcofferingid = vpcofferingid
         cmd.zoneid = zoneid
+        cmd.start = start
         if "cidr" in services:
             cmd.cidr = services["cidr"]
         if account:
@@ -6574,3 +6641,573 @@
         cmd.policyuuid = policyuuid
         cmd.ruleuuid = ruleuuid
         return apiclient.removeTungstenFabricPolicyRule(cmd)
+
+class GuestOSCategory:
+    """Manage Guest OS Categories"""
+
+    def __init__(self, items, services):
+        self.__dict__.update(items)
+
+    @classmethod
+    def list(cls, apiclient, id=None, name=None, **kwargs):
+        """List all Guest OS categories"""
+        cmd = listOsCategories.listOsCategoriesCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+            cmd.listall = True
+        if id is not None:
+            cmd.id = id
+        if name is not None:
+            cmd.name = name
+
+        return (apiclient.listOsCategories(cmd))
+
+class GuestOS:
+    """Manage Guest OS"""
+
+    def __init__(self, items, services):
+        self.__dict__.update(items)
+
+    @classmethod
+    def add(cls, apiclient, osdisplayname=None,
+                  oscategoryid=None, name=None, details=None):
+        """Add Guest OS"""
+        cmd = addGuestOs.addGuestOsCmd()
+        cmd.osdisplayname = osdisplayname
+        cmd.oscategoryid = oscategoryid
+        if name is not None:
+            cmd.name = name
+        if details is not None:
+            cmd.details = details
+
+        return (apiclient.addGuestOs(cmd))
+
+    @classmethod
+    def remove(cls, apiclient, id):
+        """Remove Guest OS"""
+        cmd = removeGuestOs.removeGuestOsCmd()
+        cmd.id = id
+
+        return apiclient.removeGuestOs(cmd)
+
+    @classmethod
+    def update(cls, apiclient, id, osdisplayname=None, details=None):
+        """Update Guest OS"""
+        cmd = updateGuestOs.updateGuestOsCmd()
+        cmd.id = id
+        cmd.osdisplayname = osdisplayname
+        if details is not None:
+            cmd.details = details
+
+        return apiclient.updateGuestOs(cmd)
+
+    @classmethod
+    def list(cls, apiclient, id=None, oscategoryid=None, description=None, **kwargs):
+        """List all Guest OS"""
+        cmd = listOsTypes.listOsTypesCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+            cmd.listall = True
+        if id is not None:
+            cmd.id = id
+        if oscategoryid is not None:
+            cmd.oscategoryid = oscategoryid
+        if description is not None:
+            cmd.description = description
+
+        return (apiclient.listOsTypes(cmd))
+
+class GuestOsMapping:
+    """Manage Guest OS Mappings"""
+
+    def __init__(self, items, services):
+        self.__dict__.update(items)
+
+    @classmethod
+    def add(cls, apiclient, ostypeid=None,
+                  hypervisor=None, hypervisorversion=None,
+                  osnameforhypervisor=None, osmappingcheckenabled=None, forced=None):
+        """Add Guest OS mapping"""
+        cmd = addGuestOsMapping.addGuestOsMappingCmd()
+        cmd.ostypeid = ostypeid
+        cmd.hypervisor = hypervisor
+        cmd.hypervisorversion = hypervisorversion
+        cmd.osnameforhypervisor = osnameforhypervisor
+        if osmappingcheckenabled is not None:
+            cmd.osmappingcheckenabled = osmappingcheckenabled
+        if forced is not None:
+            cmd.forced = forced
+
+        return (apiclient.addGuestOsMapping(cmd))
+
+    @classmethod
+    def remove(cls, apiclient, id):
+        """Remove Guest OS mapping"""
+        cmd = removeGuestOsMapping.removeGuestOsMappingCmd()
+        cmd.id = id
+
+        return apiclient.removeGuestOsMapping(cmd)
+
+    @classmethod
+    def update(cls, apiclient, id, osnameforhypervisor=None, osmappingcheckenabled=None):
+        """Update Guest OS mapping"""
+        cmd = updateGuestOsMapping.updateGuestOsMappingCmd()
+        cmd.id = id
+        cmd.osnameforhypervisor = osnameforhypervisor
+        if osmappingcheckenabled is not None:
+            cmd.osmappingcheckenabled = osmappingcheckenabled
+
+        return apiclient.updateGuestOsMapping(cmd)
+
+    @classmethod
+    def list(cls, apiclient, id=None, ostypeid=None, osdisplayname=None,
+             osnameforhypervisor=None, hypervisor=None, hypervisorversion=None, **kwargs):
+        """List all Guest OS mappings"""
+        cmd = listGuestOsMapping.listGuestOsMappingCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+            cmd.listall = True
+        if id is not None:
+            cmd.id = id
+        if ostypeid is not None:
+            cmd.ostypeid = ostypeid
+        if osdisplayname is not None:
+            cmd.osdisplayname = osdisplayname
+        if osnameforhypervisor is not None:
+            cmd.osnameforhypervisor = osnameforhypervisor
+        if hypervisor is not None:
+            cmd.hypervisor = hypervisor
+        if hypervisorversion is not None:
+            cmd.hypervisorversion = hypervisorversion
+
+        return (apiclient.listGuestOsMapping(cmd))
+
+class VMSchedule:
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def create(cls, apiclient, virtualmachineid, action, schedule, timezone, startdate, enabled=False, description=None, enddate=None):
+        cmd = createVMSchedule.createVMScheduleCmd()
+        cmd.virtualmachineid = virtualmachineid
+        cmd.description = description
+        cmd.action = action
+        cmd.schedule = schedule
+        cmd.timezone = timezone
+        cmd.startdate = startdate
+        cmd.enddate = enddate
+        cmd.enabled = enabled
+        return VMSchedule(apiclient.createVMSchedule(cmd).__dict__)
+
+    @classmethod
+    def list(cls, apiclient, virtualmachineid, id=None, enabled=None, action=None):
+        cmd = listVMSchedule.listVMScheduleCmd()
+        cmd.virtualmachineid = virtualmachineid
+        cmd.id = id
+        cmd.enabled = enabled
+        cmd.action = action
+        return apiclient.listVMSchedule(cmd)
+
+    def update(self, apiclient, **kwargs):
+        cmd = updateVMSchedule.updateVMScheduleCmd()
+        cmd.id = self.id
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return apiclient.updateVMSchedule(cmd)
+
+    def delete(self, apiclient):
+        cmd = deleteVMSchedule.deleteVMScheduleCmd()
+        cmd.id = self.id
+        cmd.virtualmachineid = self.virtualmachineid
+        return (apiclient.deleteVMSchedule(cmd))
+
+class VnfTemplate:
+    """Manage VNF template life cycle"""
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def register(cls, apiclient, services, zoneid=None,
+                 account=None, domainid=None, hypervisor=None,
+                 projectid=None, details=None, randomize_name=True,
+                 vnfnics=None, vnfdetails=None):
+        """Create VNF template from URL"""
+
+        # Create template from Virtual machine and Volume ID
+        cmd = registerVnfTemplate.registerVnfTemplateCmd()
+        cmd.displaytext = services["displaytext"]
+        if randomize_name:
+            cmd.name = "-".join([services["name"], random_gen()])
+        else:
+            cmd.name = services["name"]
+        cmd.format = services["format"]
+        if hypervisor:
+            cmd.hypervisor = hypervisor
+        elif "hypervisor" in services:
+            cmd.hypervisor = services["hypervisor"]
+
+        if "ostypeid" in services:
+            cmd.ostypeid = services["ostypeid"]
+        elif "ostype" in services:
+            # Find OSTypeId from Os type
+            sub_cmd = listOsTypes.listOsTypesCmd()
+            sub_cmd.description = services["ostype"]
+            ostypes = apiclient.listOsTypes(sub_cmd)
+
+            if not isinstance(ostypes, list):
+                raise Exception(
+                    "Unable to find Ostype id with desc: %s" %
+                    services["ostype"])
+            cmd.ostypeid = ostypes[0].id
+        else:
+            raise Exception(
+                "Unable to find Ostype is required for registering template")
+
+        cmd.url = services["url"]
+
+        if zoneid:
+            cmd.zoneid = zoneid
+        else:
+            cmd.zoneid = services["zoneid"]
+
+        cmd.isfeatured = services[
+            "isfeatured"] if "isfeatured" in services else False
+        cmd.ispublic = services[
+            "ispublic"] if "ispublic" in services else False
+        cmd.isextractable = services[
+            "isextractable"] if "isextractable" in services else False
+        cmd.isdynamicallyscalable = services["isdynamicallyscalable"] if "isdynamicallyscalable" in services else False
+        cmd.passwordenabled = services[
+            "passwordenabled"] if "passwordenabled" in services else False
+        cmd.deployasis = services["deployasis"] if "deployasis" in services else False
+
+        if account:
+            cmd.account = account
+
+        if domainid:
+            cmd.domainid = domainid
+
+        if projectid:
+            cmd.projectid = projectid
+        elif "projectid" in services:
+            cmd.projectid = services["projectid"]
+
+        if details:
+            cmd.details = details
+
+        if "directdownload" in services:
+            cmd.directdownload = services["directdownload"]
+
+        if vnfnics:
+            cmd.vnfnics = vnfnics
+
+        if vnfdetails:
+            cmd.vnfdetails = vnfdetails
+
+        # Register Template
+        template = apiclient.registerVnfTemplate(cmd)
+
+        if isinstance(template, list):
+            return VnfTemplate(template[0].__dict__)
+
+    def delete(self, apiclient, zoneid=None):
+        """Delete VNF Template"""
+
+        cmd = deleteVnfTemplate.deleteVnfTemplateCmd()
+        cmd.id = self.id
+        if zoneid:
+            cmd.zoneid = zoneid
+        apiclient.deleteVnfTemplate(cmd)
+
+    def update(self, apiclient, **kwargs):
+        """Updates the template details"""
+
+        cmd = updateVnfTemplate.updateVnfTemplateCmd()
+        cmd.id = self.id
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return (apiclient.updateVnfTemplate(cmd))
+
+    @classmethod
+    def list(cls, apiclient, **kwargs):
+        """List all templates matching criteria"""
+
+        cmd = listVnfTemplates.listVnfTemplatesCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+            cmd.listall = True
+        return (apiclient.listVnfTemplates(cmd))
+
+class VnfAppliance:
+    """Manage VNF Appliance life cycle"""
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def create(cls, apiclient, services, templateid=None, accountid=None,
+               domainid=None, zoneid=None, networkids=None,
+               serviceofferingid=None, securitygroupids=None,
+               projectid=None, startvm=None, diskofferingid=None,
+               affinitygroupnames=None, affinitygroupids=None, group=None,
+               hostid=None, clusterid=None, keypair=None, ipaddress=None, mode='default',
+               method='GET', hypervisor=None, customcpunumber=None,
+               customcpuspeed=None, custommemory=None, rootdisksize=None,
+               rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={},
+               properties=None, nicnetworklist=None, bootmode=None, boottype=None, dynamicscalingenabled=None,
+               userdataid=None, userdatadetails=None, extraconfig=None,
+               vnfconfiguremanagement=None, vnfcidrlist=None):
+        """Create the VNF appliance"""
+
+        cmd = deployVnfAppliance.deployVnfApplianceCmd()
+
+        if serviceofferingid:
+            cmd.serviceofferingid = serviceofferingid
+        elif "serviceoffering" in services:
+            cmd.serviceofferingid = services["serviceoffering"]
+
+        if zoneid:
+            cmd.zoneid = zoneid
+        elif "zoneid" in services:
+            cmd.zoneid = services["zoneid"]
+
+        if hypervisor:
+            cmd.hypervisor = hypervisor
+
+        if "displayname" in services:
+            cmd.displayname = services["displayname"]
+
+        if "name" in services:
+            cmd.name = services["name"]
+
+        if accountid:
+            cmd.account = accountid
+        elif "account" in services:
+            cmd.account = services["account"]
+
+        if domainid:
+            cmd.domainid = domainid
+        elif "domainid" in services:
+            cmd.domainid = services["domainid"]
+
+        if networkids:
+            cmd.networkids = networkids
+            allow_egress = False
+        elif "networkids" in services:
+            cmd.networkids = services["networkids"]
+            allow_egress = False
+        else:
+            # When no networkids are passed, network
+            # is created using the "defaultOfferingWithSourceNAT"
+            # which has an egress policy of DENY. But guests in tests
+            # need access to test network connectivity
+            allow_egress = True
+
+        if templateid:
+            cmd.templateid = templateid
+        elif "template" in services:
+            cmd.templateid = services["template"]
+
+        if diskofferingid:
+            cmd.diskofferingid = diskofferingid
+        elif "diskoffering" in services:
+            cmd.diskofferingid = services["diskoffering"]
+
+        if keypair:
+            cmd.keypair = keypair
+        elif "keypair" in services:
+            cmd.keypair = services["keypair"]
+
+        if ipaddress:
+            cmd.ipaddress = ipaddress
+        elif "ipaddress" in services:
+            cmd.ipaddress = services["ipaddress"]
+
+        if securitygroupids:
+            cmd.securitygroupids = [str(sg_id) for sg_id in securitygroupids]
+
+        if "affinitygroupnames" in services:
+            cmd.affinitygroupnames = services["affinitygroupnames"]
+        elif affinitygroupnames:
+            cmd.affinitygroupnames = affinitygroupnames
+
+        if affinitygroupids:
+            cmd.affinitygroupids = affinitygroupids
+
+        if projectid:
+            cmd.projectid = projectid
+
+        if startvm is not None:
+            cmd.startvm = startvm
+
+        if hostid:
+            cmd.hostid = hostid
+
+        if clusterid:
+            cmd.clusterid = clusterid
+
+        if "userdata" in services:
+            cmd.userdata = base64.urlsafe_b64encode(services["userdata"].encode()).decode()
+
+        if userdataid is not None:
+            cmd.userdataid = userdataid
+
+        if userdatadetails is not None:
+            cmd.userdatadetails = userdatadetails
+
+        if "dhcpoptionsnetworklist" in services:
+            cmd.dhcpoptionsnetworklist = services["dhcpoptionsnetworklist"]
+
+        if dynamicscalingenabled is not None:
+            cmd.dynamicscalingenabled = dynamicscalingenabled
+
+        cmd.details = [{}]
+
+        if customcpunumber:
+            cmd.details[0]["cpuNumber"] = customcpunumber
+
+        if customcpuspeed:
+            cmd.details[0]["cpuSpeed"] = customcpuspeed
+
+        if custommemory:
+            cmd.details[0]["memory"] = custommemory
+
+        if not rootdisksize is None and rootdisksize >= 0:
+            cmd.details[0]["rootdisksize"] = rootdisksize
+
+        if rootdiskcontroller:
+            cmd.details[0]["rootDiskController"] = rootdiskcontroller
+
+        if "size" in services:
+            cmd.size = services["size"]
+
+        if group:
+            cmd.group = group
+
+        cmd.datadisktemplatetodiskofferinglist = []
+        for datadisktemplate, diskoffering in list(datadisktemplate_diskoffering_list.items()):
+            cmd.datadisktemplatetodiskofferinglist.append({
+                'datadisktemplateid': datadisktemplate,
+                'diskofferingid': diskoffering
+            })
+
+        # program default access to ssh
+        if mode.lower() == 'basic':
+            cls.ssh_access_group(apiclient, cmd)
+
+        if macaddress:
+            cmd.macaddress = macaddress
+        elif macaddress in services:
+            cmd.macaddress = services["macaddress"]
+
+        if properties:
+            cmd.properties = properties
+
+        if nicnetworklist:
+            cmd.nicnetworklist = nicnetworklist
+
+        if bootmode:
+            cmd.bootmode = bootmode
+
+        if boottype:
+            cmd.boottype = boottype
+
+        if extraconfig:
+            cmd.extraconfig = extraconfig
+
+        if vnfconfiguremanagement:
+            cmd.vnfconfiguremanagement = vnfconfiguremanagement
+
+        if vnfcidrlist:
+            cmd.vnfcidrlist = vnfcidrlist
+
+        vnf_app = apiclient.deployVnfAppliance(cmd, method=method)
+
+        return VnfAppliance(vnf_app.__dict__)
+
+    def delete(self, apiclient, expunge=True, **kwargs):
+        """Destroy an VNF appliance"""
+        cmd = destroyVirtualMachine.destroyVirtualMachineCmd()
+        cmd.id = self.id
+        cmd.expunge = expunge
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        apiclient.destroyVirtualMachine(cmd)
+
+class ObjectStoragePool:
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    """Manage Object Stores"""
+    @classmethod
+    def create(cls, apiclient, name, url, provider, services=None):
+        """Add Object Store"""
+        cmd = addObjectStoragePool.addObjectStoragePoolCmd()
+        cmd.name = name
+        cmd.url = url
+        cmd.provider = provider
+        if services:
+            if "details" in services:
+                cmd.details = services["details"]
+
+        return ObjectStoragePool(apiclient.addObjectStoragePool(cmd).__dict__)
+
+    def delete(self, apiclient):
+        """Delete Object Store"""
+        cmd = deleteObjectStoragePool.deleteObjectStoragePoolCmd()
+        cmd.id = self.id
+        apiclient.deleteObjectStoragePool(cmd)
+
+    @classmethod
+    def list(cls, apiclient, **kwargs):
+        cmd = listObjectStoragePools.listObjectStoragePoolsCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+            cmd.listall = True
+        return (apiclient.listObjectStoragePools(cmd))
+
+    def update(self, apiclient, **kwargs):
+        """Update the Object Store"""
+
+        cmd = updateObjectStoragePool.updateObjectStoragePoolCmd()
+        cmd.id = self.id
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return apiclient.updateObjectStoragePool(cmd)
+
+class Bucket:
+    """Manage Bucket Life cycle"""
+
+    def __init__(self, items):
+        self.__dict__.update(items)
+
+    @classmethod
+    def create(cls, apiclient, name, objectstorageid, **kwargs):
+        """Create Bucket"""
+        cmd = createBucket.createBucketCmd()
+        cmd.name = name
+        cmd.objectstorageid = objectstorageid
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+
+        return Bucket(apiclient.createBucket(cmd).__dict__)
+
+    def delete(self, apiclient):
+        """Delete Bucket"""
+        cmd = deleteBucket.deleteBucketCmd()
+        cmd.id = self.id
+        apiclient.deleteBucket(cmd)
+
+    @classmethod
+    def list(cls, apiclient, **kwargs):
+        cmd = listBuckets.listBucketsCmd()
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+            cmd.listall = True
+        return (apiclient.listBuckets(cmd))
+
+    def update(self, apiclient, **kwargs):
+        """Update Bucket"""
+
+        cmd = updateBucket.updateBucketCmd()
+        cmd.id = self.id
+        [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+        return apiclient.updateBucket(cmd)
diff --git a/tools/marvin/marvin/lib/common.py b/tools/marvin/marvin/lib/common.py
index f1f09bb..cc77365 100644
--- a/tools/marvin/marvin/lib/common.py
+++ b/tools/marvin/marvin/lib/common.py
@@ -58,6 +58,7 @@
                                   listNetworkOfferings,
                                   listResourceLimits,
                                   listVPCOfferings,
+                                  listManagementServers,
                                   migrateSystemVm)
 from marvin.sshClient import SshClient
 from marvin.codes import (PASS, FAILED, ISOLATED_NETWORK, VPC_NETWORK,
@@ -1056,6 +1057,14 @@
         cmd.listall=True
     return(apiclient.listVPCOfferings(cmd))
 
+def list_mgmt_servers(apiclient, **kwargs):
+    """ Lists Management Servers """
+
+    cmd = listManagementServers.listManagementServersCmd()
+    [setattr(cmd, k, v) for k, v in list(kwargs.items())]
+    if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
+        cmd.listall=True
+    return(apiclient.listManagementServers(cmd))
 
 def update_resource_count(apiclient, domainid, accountid=None,
                           projectid=None, rtype=None):
diff --git a/tools/marvin/marvin/lib/utils.py b/tools/marvin/marvin/lib/utils.py
index bc4d15c..d3cbd42 100644
--- a/tools/marvin/marvin/lib/utils.py
+++ b/tools/marvin/marvin/lib/utils.py
@@ -288,6 +288,18 @@
     assert hosts_list_validation_result[0] == PASS, "host list validation failed"
     return hosts_list_validation_result[1].hypervisor
 
+def get_hypervisor_version(apiclient):
+
+    """Return the hypervisor type of the hosts in setup"""
+
+    cmd = listHosts.listHostsCmd()
+    cmd.type = 'Routing'
+    cmd.listall = True
+    hosts = apiclient.listHosts(cmd)
+    hosts_list_validation_result = validateList(hosts)
+    assert hosts_list_validation_result[0] == PASS, "host list validation failed"
+    return hosts_list_validation_result[1].hypervisorversion
+
 def is_snapshot_on_nfs(apiclient, dbconn, config, zoneid, snapshotid):
     """
     Checks whether a snapshot with id (not UUID) `snapshotid` is present on the nfs storage
diff --git a/tools/marvin/marvin/misc/build/CI.md b/tools/marvin/marvin/misc/build/CI.md
index 3400e7e..d986850 100644
--- a/tools/marvin/marvin/misc/build/CI.md
+++ b/tools/marvin/marvin/misc/build/CI.md
@@ -90,7 +90,7 @@
 
 2. NFS storage - the nfs server is a single server serving as both primary and secondary storage. This is likely a limitation when compared to true production deployments but serves in good stead for a test setup. Where it becomes a limitation is in testing different storage backends. Object stores, local storage, clustered local storage etc are not addressed by this setup.
 
-3. Hypervisor hosts - There currently are 4 hosts in this environment. These are arranged at the moment in three pods so as to be capable of being deployed in a two zone environment. One zone with two pods and and a second zone with a single pod. This covers tests that depend on 
+3. Hypervisor hosts - There currently are 4 hosts in this environment. These are arranged at the moment in three pods so as to be capable of being deployed in a two zone environment. One zone with two pods and a second zone with a single pod. This covers tests that depend on 
 a. single zone/pod/cluster
 b. multiple cluster
 c. inter-zone tests
diff --git a/tools/marvin/marvin/misc/build/vm-start.sh b/tools/marvin/marvin/misc/build/vm-start.sh
index e23f434..86d7cc5b 100755
--- a/tools/marvin/marvin/misc/build/vm-start.sh
+++ b/tools/marvin/marvin/misc/build/vm-start.sh
@@ -53,4 +53,3 @@
 #Minimum mem requirements for RHEL/Ubuntu
 $(xe vm-memory-limits-set  static-min=1GiB static-max=1GiB dynamic-min=1GiB dynamic-max=1GiB uuid=$vmuuid)
 $(xe vm-start uuid=$vmuuid)
-
diff --git a/tools/marvin/pom.xml b/tools/marvin/pom.xml
index 760165f..e41fd5e 100644
--- a/tools/marvin/pom.xml
+++ b/tools/marvin/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloud-tools</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py
index c2e8476..515670f 100644
--- a/tools/marvin/setup.py
+++ b/tools/marvin/setup.py
@@ -27,7 +27,7 @@
         raise RuntimeError("python setuptools is required to build Marvin")
 
 
-VERSION = "4.18.3.0-SNAPSHOT"
+VERSION = "4.19.1.0"
 
 setup(name="Marvin",
       version=VERSION,
diff --git a/tools/ngui/static/bootstrap/js/bootstrap.min.js b/tools/ngui/static/bootstrap/js/bootstrap.min.js
index 319a85d..8f130f0 100644
--- a/tools/ngui/static/bootstrap/js/bootstrap.min.js
+++ b/tools/ngui/static/bootstrap/js/bootstrap.min.js
@@ -4,4 +4,4 @@
 * Copyright 2012 Twitter, Inc.
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 */
-!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.focus().trigger("shown")}):b.$element.focus().trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?a.proxy(this.$element[0].focus,this.$element[0]):a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!b)return;e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b):b()):b&&b()}};var c=a.fn.modal;a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f).one("hide",function(){c.focus()})})}(window.jQuery),!function(a){function d(){a(".dropdown-backdrop").remove(),a(b).each(function(){e(a(this)).removeClass("open")})}function e(b){var c=b.attr("data-target"),d;c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,"")),d=c&&a(c);if(!d||!d.length)d=b.parent();return d}var b="[data-toggle=dropdown]",c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),f,g;if(c.is(".disabled, :disabled"))return;return f=e(c),g=f.hasClass("open"),d(),g||("ontouchstart"in document.documentElement&&a('<div class="dropdown-backdrop"/>').insertBefore(a(this)).on("click",d),f.toggleClass("open")),c.focus(),!1},keydown:function(c){var d,f,g,h,i,j;if(!/(38|40|27)/.test(c.keyCode))return;d=a(this),c.preventDefault(),c.stopPropagation();if(d.is(".disabled, :disabled"))return;h=e(d),i=h.hasClass("open");if(!i||i&&c.keyCode==27)return c.which==27&&h.find(b).focus(),d.click();f=a("[role=menu] li:not(.divider):visible a",h);if(!f.length)return;j=f.index(f.filter(":focus")),c.keyCode==38&&j>0&&j--,c.keyCode==40&&j<f.length-1&&j++,~j||(j=0),f.eq(j).focus()}};var f=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=f,this},a(document).on("click.dropdown.data-api",d).on("click.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle).on("keydown.dropdown.data-api",b+", [role=menu]",c.prototype.keydown)}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll-spy.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var c=a(this),d=c.data("target")||c.attr("href"),e=/^#\w/.test(d)&&a(d);return e&&e.length&&[[e.position().top+(!a.isWindow(b.$scrollElement.get(0))&&b.$scrollElement.scrollTop()),d]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu").length&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active:last a")[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),!function(a){var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f,g,h,i;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,g=this.options.trigger.split(" ");for(i=g.length;i--;)h=g[i],h=="click"?this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this)):h!="manual"&&(e=h=="hover"?"mouseenter":"focus",f=h=="hover"?"mouseleave":"blur",this.$element.on(e+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f+"."+this.type,this.options.selector,a.proxy(this.leave,this)));this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,this.$element.data(),b),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a.fn[this.type].defaults,d={},e;this._options&&a.each(this._options,function(a,b){c[a]!=b&&(d[a]=b)},this),e=a(b.currentTarget)[this.type](d).data(this.type);if(!e.options.delay||!e.options.delay.show)return e.show();clearTimeout(this.timeout),e.hoverState="in",this.timeout=setTimeout(function(){e.hoverState=="in"&&e.show()},e.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var b,c,d,e,f,g,h=a.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(h);if(h.isDefaultPrevented())return;b=this.tip(),this.setContent(),this.options.animation&&b.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,b[0],this.$element[0]):this.options.placement,b.detach().css({top:0,left:0,display:"block"}),this.options.container?b.appendTo(this.options.container):b.insertAfter(this.$element),c=this.getPosition(),d=b[0].offsetWidth,e=b[0].offsetHeight;switch(f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}this.applyPlacement(g,f),this.$element.trigger("shown")}},applyPlacement:function(a,b){var c=this.tip(),d=c[0].offsetWidth,e=c[0].offsetHeight,f,g,h,i;c.offset(a).addClass(b).addClass("in"),f=c[0].offsetWidth,g=c[0].offsetHeight,b=="top"&&g!=e&&(a.top=a.top+e-g,i=!0),b=="bottom"||b=="top"?(h=0,a.left<0&&(h=a.left*-2,a.left=0,c.offset(a),f=c[0].offsetWidth,g=c[0].offsetHeight),this.replaceArrow(h-d+f,f,"left")):this.replaceArrow(g-e,g,"top"),i&&c.offset(a)},replaceArrow:function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function e(){var b=setTimeout(function(){c.off(a.support.transition.end).detach()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.detach()})}var b=this,c=this.tip(),d=a.Event("hide");this.$element.trigger(d);if(d.isDefaultPrevented())return;return c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?e():c.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var b=this.$element[0];return a.extend({},typeof b.getBoundingClientRect=="function"?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(b){var c=b?a(b.currentTarget)[this.type](this._options).data(this.type):this;c.tip().hasClass("in")?c.hide():c.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(window.jQuery),!function(a){var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=(typeof c.content=="function"?c.content.call(b[0]):c.content)||b.attr("data-content"),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),!function(a){var b=function(b,c){this.options=a.extend({},a.fn.affix.defaults,c),this.$window=a(window).on("scroll.affix.data-api",a.proxy(this.checkPosition,this)).on("click.affix.data-api",a.proxy(function(){setTimeout(a.proxy(this.checkPosition,this),1)},this)),this.$element=a(b),this.checkPosition()};b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var b=a(document).height(),c=this.$window.scrollTop(),d=this.$element.offset(),e=this.options.offset,f=e.bottom,g=e.top,h="affix affix-top affix-bottom",i;typeof e!="object"&&(f=g=e),typeof g=="function"&&(g=e.top()),typeof f=="function"&&(f=e.bottom()),i=this.unpin!=null&&c+this.unpin<=d.top?!1:f!=null&&d.top+this.$element.height()>=b-f?"bottom":g!=null&&c<=g?"top":!1;if(this.affixed===i)return;this.affixed=i,this.unpin=i=="bottom"?d.top-c:null,this.$element.removeClass(h).addClass("affix"+(i?"-"+i:""))};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("affix"),f=typeof c=="object"&&c;e||d.data("affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.defaults={offset:0},a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery),!function(a){var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.alert.data-api",b,c.prototype.close)}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning||this.$element.hasClass("in"))return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),a.support.transition&&this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning||!this.$element.hasClass("in"))return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=a.extend({},a.fn.collapse.defaults,d.data(),typeof c=="object"&&c);e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();c[a(e).hasClass("in")?"addClass":"removeClass"]("collapsed"),a(e).collapse(f)})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(b){var c=this.getActiveIndex(),d=this;if(b>this.$items.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){d.to(b)}):c==b?this.pause().cycle():this.slide(b>c?"next":"prev",a(this.$items[b]))},pause:function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h](),j=a.Event("slide",{relatedTarget:e[0],direction:g});if(e.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")}));if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),c.data()),g;e.carousel(f),(g=c.attr("data-slide-to"))&&e.data("carousel").pause().to(g).cycle(),b.preventDefault()})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=a(this.options.menu),this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:b.top+b.height,left:b.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(c=a.isFunction(this.source)?this.source(this.query,a.proxy(this.process,this)):this.source,c?this.process(c):this)},process:function(b){var c=this;return b=a.grep(b,function(a){return c.matcher(a)}),b=this.sorter(b),b.length?this.render(b.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("focus",a.proxy(this.focus,this)).on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",a.proxy(this.keydown,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this)).on("mouseleave","li",a.proxy(this.mouseleave,this))},eventSupported:function(a){var b=a in this.$element;return b||(this.$element.setAttribute(a,"return;"),b=typeof this.$element[a]=="function"),b},move:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},keydown:function(b){this.suppressKeyPressRepeat=~a.inArray(b.keyCode,[40,38,9,13,27]),this.move(b)},keypress:function(a){if(this.suppressKeyPressRepeat)return;this.move(a)},keyup:function(a){switch(a.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},focus:function(a){this.focused=!0},blur:function(a){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(a){a.stopPropagation(),a.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(b){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")},mouseleave:function(a){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var c=a.fn.typeahead;a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},a.fn.typeahead.Constructor=b,a.fn.typeahead.noConflict=function(){return a.fn.typeahead=c,this},a(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;c.typeahead(c.data())})}(window.jQuery)
\ No newline at end of file
+!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.focus().trigger("shown")}):b.$element.focus().trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?a.proxy(this.$element[0].focus,this.$element[0]):a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!b)return;e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b):b()):b&&b()}};var c=a.fn.modal;a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f).one("hide",function(){c.focus()})})}(window.jQuery),!function(a){function d(){a(".dropdown-backdrop").remove(),a(b).each(function(){e(a(this)).removeClass("open")})}function e(b){var c=b.attr("data-target"),d;c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,"")),d=c&&a(c);if(!d||!d.length)d=b.parent();return d}var b="[data-toggle=dropdown]",c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),f,g;if(c.is(".disabled, :disabled"))return;return f=e(c),g=f.hasClass("open"),d(),g||("ontouchstart"in document.documentElement&&a('<div class="dropdown-backdrop"/>').insertBefore(a(this)).on("click",d),f.toggleClass("open")),c.focus(),!1},keydown:function(c){var d,f,g,h,i,j;if(!/(38|40|27)/.test(c.keyCode))return;d=a(this),c.preventDefault(),c.stopPropagation();if(d.is(".disabled, :disabled"))return;h=e(d),i=h.hasClass("open");if(!i||i&&c.keyCode==27)return c.which==27&&h.find(b).focus(),d.click();f=a("[role=menu] li:not(.divider):visible a",h);if(!f.length)return;j=f.index(f.filter(":focus")),c.keyCode==38&&j>0&&j--,c.keyCode==40&&j<f.length-1&&j++,~j||(j=0),f.eq(j).focus()}};var f=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=f,this},a(document).on("click.dropdown.data-api",d).on("click.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle).on("keydown.dropdown.data-api",b+", [role=menu]",c.prototype.keydown)}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll-spy.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var c=a(this),d=c.data("target")||c.attr("href"),e=/^#\w/.test(d)&&a(d);return e&&e.length&&[[e.position().top+(!a.isWindow(b.$scrollElement.get(0))&&b.$scrollElement.scrollTop()),d]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu").length&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active:last a")[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),!function(a){var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f,g,h,i;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,g=this.options.trigger.split(" ");for(i=g.length;i--;)h=g[i],h=="click"?this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this)):h!="manual"&&(e=h=="hover"?"mouseenter":"focus",f=h=="hover"?"mouseleave":"blur",this.$element.on(e+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f+"."+this.type,this.options.selector,a.proxy(this.leave,this)));this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,this.$element.data(),b),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a.fn[this.type].defaults,d={},e;this._options&&a.each(this._options,function(a,b){c[a]!=b&&(d[a]=b)},this),e=a(b.currentTarget)[this.type](d).data(this.type);if(!e.options.delay||!e.options.delay.show)return e.show();clearTimeout(this.timeout),e.hoverState="in",this.timeout=setTimeout(function(){e.hoverState=="in"&&e.show()},e.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var b,c,d,e,f,g,h=a.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(h);if(h.isDefaultPrevented())return;b=this.tip(),this.setContent(),this.options.animation&&b.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,b[0],this.$element[0]):this.options.placement,b.detach().css({top:0,left:0,display:"block"}),this.options.container?b.appendTo(this.options.container):b.insertAfter(this.$element),c=this.getPosition(),d=b[0].offsetWidth,e=b[0].offsetHeight;switch(f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}this.applyPlacement(g,f),this.$element.trigger("shown")}},applyPlacement:function(a,b){var c=this.tip(),d=c[0].offsetWidth,e=c[0].offsetHeight,f,g,h,i;c.offset(a).addClass(b).addClass("in"),f=c[0].offsetWidth,g=c[0].offsetHeight,b=="top"&&g!=e&&(a.top=a.top+e-g,i=!0),b=="bottom"||b=="top"?(h=0,a.left<0&&(h=a.left*-2,a.left=0,c.offset(a),f=c[0].offsetWidth,g=c[0].offsetHeight),this.replaceArrow(h-d+f,f,"left")):this.replaceArrow(g-e,g,"top"),i&&c.offset(a)},replaceArrow:function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function e(){var b=setTimeout(function(){c.off(a.support.transition.end).detach()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.detach()})}var b=this,c=this.tip(),d=a.Event("hide");this.$element.trigger(d);if(d.isDefaultPrevented())return;return c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?e():c.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var b=this.$element[0];return a.extend({},typeof b.getBoundingClientRect=="function"?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(b){var c=b?a(b.currentTarget)[this.type](this._options).data(this.type):this;c.tip().hasClass("in")?c.hide():c.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(window.jQuery),!function(a){var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=(typeof c.content=="function"?c.content.call(b[0]):c.content)||b.attr("data-content"),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),!function(a){var b=function(b,c){this.options=a.extend({},a.fn.affix.defaults,c),this.$window=a(window).on("scroll.affix.data-api",a.proxy(this.checkPosition,this)).on("click.affix.data-api",a.proxy(function(){setTimeout(a.proxy(this.checkPosition,this),1)},this)),this.$element=a(b),this.checkPosition()};b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var b=a(document).height(),c=this.$window.scrollTop(),d=this.$element.offset(),e=this.options.offset,f=e.bottom,g=e.top,h="affix affix-top affix-bottom",i;typeof e!="object"&&(f=g=e),typeof g=="function"&&(g=e.top()),typeof f=="function"&&(f=e.bottom()),i=this.unpin!=null&&c+this.unpin<=d.top?!1:f!=null&&d.top+this.$element.height()>=b-f?"bottom":g!=null&&c<=g?"top":!1;if(this.affixed===i)return;this.affixed=i,this.unpin=i=="bottom"?d.top-c:null,this.$element.removeClass(h).addClass("affix"+(i?"-"+i:""))};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("affix"),f=typeof c=="object"&&c;e||d.data("affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.defaults={offset:0},a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery),!function(a){var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.alert.data-api",b,c.prototype.close)}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning||this.$element.hasClass("in"))return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),a.support.transition&&this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning||!this.$element.hasClass("in"))return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=a.extend({},a.fn.collapse.defaults,d.data(),typeof c=="object"&&c);e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();c[a(e).hasClass("in")?"addClass":"removeClass"]("collapsed"),a(e).collapse(f)})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(b){var c=this.getActiveIndex(),d=this;if(b>this.$items.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){d.to(b)}):c==b?this.pause().cycle():this.slide(b>c?"next":"prev",a(this.$items[b]))},pause:function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h](),j=a.Event("slide",{relatedTarget:e[0],direction:g});if(e.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")}));if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),c.data()),g;e.carousel(f),(g=c.attr("data-slide-to"))&&e.data("carousel").pause().to(g).cycle(),b.preventDefault()})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=a(this.options.menu),this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:b.top+b.height,left:b.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(c=a.isFunction(this.source)?this.source(this.query,a.proxy(this.process,this)):this.source,c?this.process(c):this)},process:function(b){var c=this;return b=a.grep(b,function(a){return c.matcher(a)}),b=this.sorter(b),b.length?this.render(b.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("focus",a.proxy(this.focus,this)).on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",a.proxy(this.keydown,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this)).on("mouseleave","li",a.proxy(this.mouseleave,this))},eventSupported:function(a){var b=a in this.$element;return b||(this.$element.setAttribute(a,"return;"),b=typeof this.$element[a]=="function"),b},move:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},keydown:function(b){this.suppressKeyPressRepeat=~a.inArray(b.keyCode,[40,38,9,13,27]),this.move(b)},keypress:function(a){if(this.suppressKeyPressRepeat)return;this.move(a)},keyup:function(a){switch(a.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},focus:function(a){this.focused=!0},blur:function(a){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(a){a.stopPropagation(),a.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(b){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")},mouseleave:function(a){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var c=a.fn.typeahead;a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},a.fn.typeahead.Constructor=b,a.fn.typeahead.noConflict=function(){return a.fn.typeahead=c,this},a(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;c.typeahead(c.data())})}(window.jQuery)
diff --git a/tools/ngui/static/js/app/dashboard/dashboard.js b/tools/ngui/static/js/app/dashboard/dashboard.js
index 5cd17fb..b248758 100644
--- a/tools/ngui/static/js/app/dashboard/dashboard.js
+++ b/tools/ngui/static/js/app/dashboard/dashboard.js
@@ -14,4 +14,3 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
diff --git a/tools/ngui/static/js/app/infrastructure/infrastructure.js b/tools/ngui/static/js/app/infrastructure/infrastructure.js
index 5cd17fb..b248758 100644
--- a/tools/ngui/static/js/app/infrastructure/infrastructure.js
+++ b/tools/ngui/static/js/app/infrastructure/infrastructure.js
@@ -14,4 +14,3 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
diff --git a/tools/ngui/static/js/lib/angular.js b/tools/ngui/static/js/lib/angular.js
index 4a17e64..e960afe 100644
--- a/tools/ngui/static/js/lib/angular.js
+++ b/tools/ngui/static/js/lib/angular.js
@@ -14844,4 +14844,4 @@
   });
 
 })(window, document);
-angular.element(document).find('head').append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');
\ No newline at end of file
+angular.element(document).find('head').append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');
diff --git a/tools/pom.xml b/tools/pom.xml
index 8e1b651..e154784 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -25,7 +25,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <build>
diff --git a/tools/transifex/.tx/config b/tools/transifex/.tx/config
index 45bd841..9e3983e 100644
--- a/tools/transifex/.tx/config
+++ b/tools/transifex/.tx/config
@@ -19,4 +19,3 @@
 trans.pt_BR = work-dir/pt_BR.json
 trans.ru_RU = work-dir/ru_RU.json
 trans.zh_CN = work-dir/zh_CN.json
-
diff --git a/tools/transifex/sync-transifex-ui.sh b/tools/transifex/sync-transifex-ui.sh
index 0e3c494..13d6ed6 100755
--- a/tools/transifex/sync-transifex-ui.sh
+++ b/tools/transifex/sync-transifex-ui.sh
@@ -159,4 +159,3 @@
                 exit 1
                 ;;
 esac
-
diff --git a/tools/utils/database_comparision_during_upgrade/README b/tools/utils/database_comparision_during_upgrade/README
index 2c4251d..45c55ea 100644
--- a/tools/utils/database_comparision_during_upgrade/README
+++ b/tools/utils/database_comparision_during_upgrade/README
@@ -42,4 +42,3 @@
 	•	Database user password
 
 8.	Result will be shown in the form of files . 
-
diff --git a/tools/utils/database_comparision_during_upgrade/before_upgrade_data_collection.sh b/tools/utils/database_comparision_during_upgrade/before_upgrade_data_collection.sh
index eb912f4..a168233 100644
--- a/tools/utils/database_comparision_during_upgrade/before_upgrade_data_collection.sh
+++ b/tools/utils/database_comparision_during_upgrade/before_upgrade_data_collection.sh
@@ -23,5 +23,3 @@
 mkdir data_before_upgrade
 
 mysql -u $dbuser -p$dbpwd -h $dbhost --skip-column-names  -e "select  name, value  from cloud.configuration" > ./data_before_upgrade/configuration_before_upgrade
-
-
diff --git a/tools/utils/database_comparision_during_upgrade/fresh_install_data_collection.sh b/tools/utils/database_comparision_during_upgrade/fresh_install_data_collection.sh
index df493d3..aa97304 100644
--- a/tools/utils/database_comparision_during_upgrade/fresh_install_data_collection.sh
+++ b/tools/utils/database_comparision_during_upgrade/fresh_install_data_collection.sh
@@ -46,4 +46,3 @@
                 mysql -u $dbuser -p$dbpwd -h $dbhost -e "describe cloud_usage.$tablename" > ./base_data/usage_data/$tablename
         fi
 done
-
diff --git a/tools/utils/database_comparision_during_upgrade/test_config_before_and_after_upgrade.sh b/tools/utils/database_comparision_during_upgrade/test_config_before_and_after_upgrade.sh
index 48278e0..b7a0c30 100644
--- a/tools/utils/database_comparision_during_upgrade/test_config_before_and_after_upgrade.sh
+++ b/tools/utils/database_comparision_during_upgrade/test_config_before_and_after_upgrade.sh
@@ -110,4 +110,3 @@
 
 rm -rf $path2 *.sort category description scope component temp temp1 $a
 rm -rf mismatch_config_between_before_and_after_upgrade  config_difference_before_and_after_upgrade.sort t
-
diff --git a/tools/utils/database_comparision_during_upgrade/test_config_between_fresh_and_upgraded_setup.sh b/tools/utils/database_comparision_during_upgrade/test_config_between_fresh_and_upgraded_setup.sh
index 8848358..d7fe251 100644
--- a/tools/utils/database_comparision_during_upgrade/test_config_between_fresh_and_upgraded_setup.sh
+++ b/tools/utils/database_comparision_during_upgrade/test_config_between_fresh_and_upgraded_setup.sh
@@ -154,16 +154,3 @@
 rm -rf $path2 *.sort category description scope component temp temp1 $a
 rm -rf mismatch_config_between_before_and_after_upgrade  config_difference_before_and_after_upgrade.sort t
 #rm -rf $path2
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tools/utils/database_comparision_during_upgrade/usage_schema_comparison.sh b/tools/utils/database_comparision_during_upgrade/usage_schema_comparison.sh
index bff6457..7538d36 100644
--- a/tools/utils/database_comparision_during_upgrade/usage_schema_comparison.sh
+++ b/tools/utils/database_comparision_during_upgrade/usage_schema_comparison.sh
@@ -73,4 +73,3 @@
 
 
 rm -rf $path1
-
diff --git a/tools/whisker/LICENSE b/tools/whisker/LICENSE
index af091a4..2dab15b 100644
--- a/tools/whisker/LICENSE
+++ b/tools/whisker/LICENSE
@@ -4738,4 +4738,3 @@
         from The Apache Software Foundation  http://www.apache.org/ 
             EasySSLProtocolSocketFactory.java 
             EasyX509TrustManager.java 
-
diff --git a/ui/.babelrc b/ui/.babelrc
index 7804579..d86eab1 100644
--- a/ui/.babelrc
+++ b/ui/.babelrc
@@ -7,4 +7,4 @@
       "plugins": ["require-context-hook"]
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/.gitattributes b/ui/.gitattributes
index e507319..2c6c39a 100644
--- a/ui/.gitattributes
+++ b/ui/.gitattributes
@@ -1 +1 @@
-public/* linguist-vendored
\ No newline at end of file
+public/* linguist-vendored
diff --git a/ui/README.md b/ui/README.md
index 99b04a3..252aea1 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -8,13 +8,13 @@
 
 Install node: (Debian/Ubuntu)
 
-    curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
+    curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
     sudo apt-get install -y nodejs
     # Or use distro provided: sudo apt-get install npm nodejs
 
 Install node: (CentOS/Fedora/RHEL)
 
-    curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
+    curl -sL https://rpm.nodesource.com/setup_16.x | sudo bash -
     sudo yum install nodejs
 
 Install node: (Mac OS)
diff --git a/ui/docs/screenshot-dashboard.png b/ui/docs/screenshot-dashboard.png
index 556cd7d..875bc2c 100644
--- a/ui/docs/screenshot-dashboard.png
+++ b/ui/docs/screenshot-dashboard.png
Binary files differ
diff --git a/ui/jest.config.js b/ui/jest.config.js
index b49888a..eb5f3f4 100644
--- a/ui/jest.config.js
+++ b/ui/jest.config.js
@@ -42,7 +42,7 @@
     '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
   ],
   transformIgnorePatterns: [
-    '<rootDir>/node_modules/(?!ant-design-vue|vue|@babel/runtime|lodash-es|@ant-design)'
+    '<rootDir>/node_modules/(?!ant-design-vue|vue|@babel/runtime|lodash-es|@ant-design|@vue-js-cron)'
   ],
   collectCoverage: true,
   collectCoverageFrom: [
@@ -50,5 +50,5 @@
     '!**/node_modules/**',
     '!<rootDir>/src/locales/*.{js, json}'
   ],
-  coverageReporters: ['html', 'text-summary']
+  coverageReporters: ['html', 'text-summary', 'lcov']
 }
diff --git a/ui/package-lock.json b/ui/package-lock.json
index da49155..f3087d1 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "cloudstack-ui",
-  "version": "4.18.2",
+  "version": "4.19.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -25,12 +25,13 @@
       }
     },
     "@ampproject/remapping": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
-      "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+      "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
       "dev": true,
       "requires": {
-        "@jridgewell/trace-mapping": "^0.3.0"
+        "@jridgewell/gen-mapping": "^0.1.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
       }
     },
     "@ant-design/colors": {
@@ -41,10 +42,23 @@
         "@ctrl/tinycolor": "^3.4.0"
       }
     },
+    "@ant-design/icons": {
+      "version": "4.8.3",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
+      "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
+      "requires": {
+        "@ant-design/colors": "^6.0.0",
+        "@ant-design/icons-svg": "^4.3.0",
+        "@babel/runtime": "^7.11.2",
+        "classnames": "^2.2.6",
+        "lodash": "^4.17.15",
+        "rc-util": "^5.9.4"
+      }
+    },
     "@ant-design/icons-svg": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz",
-      "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw=="
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+      "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="
     },
     "@ant-design/icons-vue": {
       "version": "6.1.0",
@@ -55,6 +69,18 @@
         "@ant-design/icons-svg": "^4.2.1"
       }
     },
+    "@ant-design/react-slick": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.0.2.tgz",
+      "integrity": "sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==",
+      "requires": {
+        "@babel/runtime": "^7.10.4",
+        "classnames": "^2.2.5",
+        "json2mq": "^0.2.0",
+        "resize-observer-polyfill": "^1.5.1",
+        "throttle-debounce": "^5.0.0"
+      }
+    },
     "@apollo/protobufjs": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz",
@@ -85,10 +111,11 @@
       }
     },
     "@apollographql/apollo-tools": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.2.tgz",
-      "integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==",
-      "dev": true
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz",
+      "integrity": "sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw==",
+      "dev": true,
+      "requires": {}
     },
     "@apollographql/graphql-playground-html": {
       "version": "1.6.27",
@@ -115,40 +142,40 @@
       }
     },
     "@babel/code-frame": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
-      "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+      "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
       "dev": true,
       "requires": {
-        "@babel/highlight": "^7.16.7"
+        "@babel/highlight": "^7.18.6"
       }
     },
     "@babel/compat-data": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
-      "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz",
+      "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==",
       "dev": true
     },
     "@babel/core": {
-      "version": "7.17.8",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz",
-      "integrity": "sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz",
+      "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==",
       "dev": true,
       "requires": {
         "@ampproject/remapping": "^2.1.0",
-        "@babel/code-frame": "^7.16.7",
-        "@babel/generator": "^7.17.7",
-        "@babel/helper-compilation-targets": "^7.17.7",
-        "@babel/helper-module-transforms": "^7.17.7",
-        "@babel/helpers": "^7.17.8",
-        "@babel/parser": "^7.17.8",
-        "@babel/template": "^7.16.7",
-        "@babel/traverse": "^7.17.3",
-        "@babel/types": "^7.17.0",
+        "@babel/code-frame": "^7.18.6",
+        "@babel/generator": "^7.18.6",
+        "@babel/helper-compilation-targets": "^7.18.6",
+        "@babel/helper-module-transforms": "^7.18.6",
+        "@babel/helpers": "^7.18.6",
+        "@babel/parser": "^7.18.6",
+        "@babel/template": "^7.18.6",
+        "@babel/traverse": "^7.18.6",
+        "@babel/types": "^7.18.6",
         "convert-source-map": "^1.7.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
-        "json5": "^2.1.2",
+        "json5": "^2.2.1",
         "semver": "^6.3.0"
       },
       "dependencies": {
@@ -161,52 +188,57 @@
       }
     },
     "@babel/generator": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
-      "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
+      "version": "7.18.7",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz",
+      "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.17.0",
-        "jsesc": "^2.5.1",
-        "source-map": "^0.5.0"
+        "@babel/types": "^7.18.7",
+        "@jridgewell/gen-mapping": "^0.3.2",
+        "jsesc": "^2.5.1"
       },
       "dependencies": {
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
-          "dev": true
+        "@jridgewell/gen-mapping": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+          "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+          "dev": true,
+          "requires": {
+            "@jridgewell/set-array": "^1.0.1",
+            "@jridgewell/sourcemap-codec": "^1.4.10",
+            "@jridgewell/trace-mapping": "^0.3.9"
+          }
         }
       }
     },
     "@babel/helper-annotate-as-pure": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
-      "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
+      "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-builder-binary-assignment-operator-visitor": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz",
-      "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.6.tgz",
+      "integrity": "sha512-KT10c1oWEpmrIRYnthbzHgoOf6B+Xd6a5yhdbNtdhtG7aO1or5HViuf1TQR36xY/QprXA5nvxO6nAjhJ4y38jw==",
       "dev": true,
       "requires": {
-        "@babel/helper-explode-assignable-expression": "^7.16.7",
-        "@babel/types": "^7.16.7"
+        "@babel/helper-explode-assignable-expression": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-compilation-targets": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
-      "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz",
+      "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==",
       "dev": true,
       "requires": {
-        "@babel/compat-data": "^7.17.7",
-        "@babel/helper-validator-option": "^7.16.7",
-        "browserslist": "^4.17.5",
+        "@babel/compat-data": "^7.18.6",
+        "@babel/helper-validator-option": "^7.18.6",
+        "browserslist": "^4.20.2",
         "semver": "^6.3.0"
       },
       "dependencies": {
@@ -219,28 +251,28 @@
       }
     },
     "@babel/helper-create-class-features-plugin": {
-      "version": "7.17.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz",
-      "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz",
+      "integrity": "sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==",
       "dev": true,
       "requires": {
-        "@babel/helper-annotate-as-pure": "^7.16.7",
-        "@babel/helper-environment-visitor": "^7.16.7",
-        "@babel/helper-function-name": "^7.16.7",
-        "@babel/helper-member-expression-to-functions": "^7.16.7",
-        "@babel/helper-optimise-call-expression": "^7.16.7",
-        "@babel/helper-replace-supers": "^7.16.7",
-        "@babel/helper-split-export-declaration": "^7.16.7"
+        "@babel/helper-annotate-as-pure": "^7.18.6",
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-function-name": "^7.18.6",
+        "@babel/helper-member-expression-to-functions": "^7.18.6",
+        "@babel/helper-optimise-call-expression": "^7.18.6",
+        "@babel/helper-replace-supers": "^7.18.6",
+        "@babel/helper-split-export-declaration": "^7.18.6"
       }
     },
     "@babel/helper-create-regexp-features-plugin": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz",
-      "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz",
+      "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==",
       "dev": true,
       "requires": {
-        "@babel/helper-annotate-as-pure": "^7.16.7",
-        "regexpu-core": "^5.0.1"
+        "@babel/helper-annotate-as-pure": "^7.18.6",
+        "regexpu-core": "^5.1.0"
       }
     },
     "@babel/helper-define-polyfill-provider": {
@@ -260,12 +292,12 @@
       },
       "dependencies": {
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -279,194 +311,182 @@
       }
     },
     "@babel/helper-environment-visitor": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
-      "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.16.7"
-      }
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz",
+      "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==",
+      "dev": true
     },
     "@babel/helper-explode-assignable-expression": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz",
-      "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz",
+      "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-function-name": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
-      "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz",
+      "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==",
       "dev": true,
       "requires": {
-        "@babel/helper-get-function-arity": "^7.16.7",
-        "@babel/template": "^7.16.7",
-        "@babel/types": "^7.16.7"
-      }
-    },
-    "@babel/helper-get-function-arity": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
-      "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/template": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-hoist-variables": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
-      "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+      "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-member-expression-to-functions": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz",
-      "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz",
+      "integrity": "sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.17.0"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-module-imports": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
-      "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+      "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-module-transforms": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
-      "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz",
+      "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==",
       "dev": true,
       "requires": {
-        "@babel/helper-environment-visitor": "^7.16.7",
-        "@babel/helper-module-imports": "^7.16.7",
-        "@babel/helper-simple-access": "^7.17.7",
-        "@babel/helper-split-export-declaration": "^7.16.7",
-        "@babel/helper-validator-identifier": "^7.16.7",
-        "@babel/template": "^7.16.7",
-        "@babel/traverse": "^7.17.3",
-        "@babel/types": "^7.17.0"
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-module-imports": "^7.18.6",
+        "@babel/helper-simple-access": "^7.18.6",
+        "@babel/helper-split-export-declaration": "^7.18.6",
+        "@babel/helper-validator-identifier": "^7.18.6",
+        "@babel/template": "^7.18.6",
+        "@babel/traverse": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-optimise-call-expression": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz",
-      "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz",
+      "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-plugin-utils": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz",
-      "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz",
+      "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==",
       "dev": true
     },
     "@babel/helper-remap-async-to-generator": {
-      "version": "7.16.8",
-      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz",
-      "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz",
+      "integrity": "sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-annotate-as-pure": "^7.16.7",
-        "@babel/helper-wrap-function": "^7.16.8",
-        "@babel/types": "^7.16.8"
+        "@babel/helper-annotate-as-pure": "^7.18.6",
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-wrap-function": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-replace-supers": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz",
-      "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz",
+      "integrity": "sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g==",
       "dev": true,
       "requires": {
-        "@babel/helper-environment-visitor": "^7.16.7",
-        "@babel/helper-member-expression-to-functions": "^7.16.7",
-        "@babel/helper-optimise-call-expression": "^7.16.7",
-        "@babel/traverse": "^7.16.7",
-        "@babel/types": "^7.16.7"
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-member-expression-to-functions": "^7.18.6",
+        "@babel/helper-optimise-call-expression": "^7.18.6",
+        "@babel/traverse": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-simple-access": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
-      "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz",
+      "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.17.0"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-skip-transparent-expression-wrappers": {
-      "version": "7.16.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz",
-      "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.6.tgz",
+      "integrity": "sha512-4KoLhwGS9vGethZpAhYnMejWkX64wsnHPDwvOsKWU6Fg4+AlK2Jz3TyjQLMEPvz+1zemi/WBdkYxCD0bAfIkiw==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.0"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-split-export-declaration": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
-      "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+      "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.16.7"
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helper-validator-identifier": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
-      "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
+      "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
       "dev": true
     },
     "@babel/helper-validator-option": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz",
-      "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
+      "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
       "dev": true
     },
     "@babel/helper-wrap-function": {
-      "version": "7.16.8",
-      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz",
-      "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz",
+      "integrity": "sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw==",
       "dev": true,
       "requires": {
-        "@babel/helper-function-name": "^7.16.7",
-        "@babel/template": "^7.16.7",
-        "@babel/traverse": "^7.16.8",
-        "@babel/types": "^7.16.8"
+        "@babel/helper-function-name": "^7.18.6",
+        "@babel/template": "^7.18.6",
+        "@babel/traverse": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/helpers": {
-      "version": "7.17.8",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz",
-      "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz",
+      "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==",
       "dev": true,
       "requires": {
-        "@babel/template": "^7.16.7",
-        "@babel/traverse": "^7.17.3",
-        "@babel/types": "^7.17.0"
+        "@babel/template": "^7.18.6",
+        "@babel/traverse": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/highlight": {
-      "version": "7.16.10",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
-      "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+      "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
       "dev": true,
       "requires": {
-        "@babel/helper-validator-identifier": "^7.16.7",
+        "@babel/helper-validator-identifier": "^7.18.6",
         "chalk": "^2.0.0",
         "js-tokens": "^4.0.0"
       },
@@ -503,13 +523,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -524,199 +544,200 @@
       }
     },
     "@babel/parser": {
-      "version": "7.17.8",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz",
-      "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ=="
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz",
+      "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw=="
     },
     "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz",
-      "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz",
+      "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz",
-      "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.6.tgz",
+      "integrity": "sha512-Udgu8ZRgrBrttVz6A0EVL0SJ1z+RLbIeqsu632SA1hf0awEppD6TvdznoH+orIF8wtFFAV/Enmw9Y+9oV8TQcw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0",
-        "@babel/plugin-proposal-optional-chaining": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6",
+        "@babel/plugin-proposal-optional-chaining": "^7.18.6"
       }
     },
     "@babel/plugin-proposal-async-generator-functions": {
-      "version": "7.16.8",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz",
-      "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz",
+      "integrity": "sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-remap-async-to-generator": "^7.16.8",
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-remap-async-to-generator": "^7.18.6",
         "@babel/plugin-syntax-async-generators": "^7.8.4"
       }
     },
     "@babel/plugin-proposal-class-properties": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz",
-      "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
+      "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-create-class-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-proposal-class-static-block": {
-      "version": "7.17.6",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz",
-      "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz",
+      "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.17.6",
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-create-class-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-class-static-block": "^7.14.5"
       }
     },
     "@babel/plugin-proposal-decorators": {
-      "version": "7.17.8",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.8.tgz",
-      "integrity": "sha512-U69odN4Umyyx1xO1rTII0IDkAEC+RNlcKXtqOblfpzqy1C+aOplb76BQNq0+XdpVkOaPlpEDwd++joY8FNFJKA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.6.tgz",
+      "integrity": "sha512-gAdhsjaYmiZVxx5vTMiRfj31nB7LhwBJFMSLzeDxc7X4tKLixup0+k9ughn0RcpBrv9E3PBaXJW7jF5TCihAOg==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.17.6",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-replace-supers": "^7.16.7",
-        "@babel/plugin-syntax-decorators": "^7.17.0",
-        "charcodes": "^0.2.0"
+        "@babel/helper-create-class-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-replace-supers": "^7.18.6",
+        "@babel/helper-split-export-declaration": "^7.18.6",
+        "@babel/plugin-syntax-decorators": "^7.18.6"
       }
     },
     "@babel/plugin-proposal-dynamic-import": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz",
-      "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz",
+      "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-dynamic-import": "^7.8.3"
       }
     },
     "@babel/plugin-proposal-export-namespace-from": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz",
-      "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.6.tgz",
+      "integrity": "sha512-zr/QcUlUo7GPo6+X1wC98NJADqmy5QTFWWhqeQWiki4XHafJtLl/YMGkmRB2szDD2IYJCCdBTd4ElwhId9T7Xw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
       }
     },
     "@babel/plugin-proposal-json-strings": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz",
-      "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz",
+      "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-json-strings": "^7.8.3"
       }
     },
     "@babel/plugin-proposal-logical-assignment-operators": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz",
-      "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.6.tgz",
+      "integrity": "sha512-zMo66azZth/0tVd7gmkxOkOjs2rpHyhpcFo565PUP37hSp6hSd9uUKIfTDFMz58BwqgQKhJ9YxtM5XddjXVn+Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
       }
     },
     "@babel/plugin-proposal-nullish-coalescing-operator": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz",
-      "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
+      "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
       }
     },
     "@babel/plugin-proposal-numeric-separator": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz",
-      "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
+      "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-numeric-separator": "^7.10.4"
       }
     },
     "@babel/plugin-proposal-object-rest-spread": {
-      "version": "7.17.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz",
-      "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.6.tgz",
+      "integrity": "sha512-9yuM6wr4rIsKa1wlUAbZEazkCrgw2sMPEXCr4Rnwetu7cEW1NydkCWytLuYletbf8vFxdJxFhwEZqMpOx2eZyw==",
       "dev": true,
       "requires": {
-        "@babel/compat-data": "^7.17.0",
-        "@babel/helper-compilation-targets": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/compat-data": "^7.18.6",
+        "@babel/helper-compilation-targets": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
-        "@babel/plugin-transform-parameters": "^7.16.7"
+        "@babel/plugin-transform-parameters": "^7.18.6"
       }
     },
     "@babel/plugin-proposal-optional-catch-binding": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz",
-      "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz",
+      "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
       }
     },
     "@babel/plugin-proposal-optional-chaining": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz",
-      "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.6.tgz",
+      "integrity": "sha512-PatI6elL5eMzoypFAiYDpYQyMtXTn+iMhuxxQt5mAXD4fEmKorpSI3PHd+i3JXBJN3xyA6MvJv7at23HffFHwA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6",
         "@babel/plugin-syntax-optional-chaining": "^7.8.3"
       }
     },
     "@babel/plugin-proposal-private-methods": {
-      "version": "7.16.11",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz",
-      "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
+      "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.16.10",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-create-class-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-proposal-private-property-in-object": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz",
-      "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz",
+      "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==",
       "dev": true,
       "requires": {
-        "@babel/helper-annotate-as-pure": "^7.16.7",
-        "@babel/helper-create-class-features-plugin": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-annotate-as-pure": "^7.18.6",
+        "@babel/helper-create-class-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
       }
     },
     "@babel/plugin-proposal-unicode-property-regex": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz",
-      "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
+      "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-regexp-features-plugin": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-syntax-async-generators": {
@@ -756,12 +777,12 @@
       }
     },
     "@babel/plugin-syntax-decorators": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.0.tgz",
-      "integrity": "sha512-qWe85yCXsvDEluNP0OyeQjH63DlhAR3W7K9BxxU1MvbDb48tgBG+Ao6IJJ6smPDrrVzSQZrbF6donpkFBMcs3A==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz",
+      "integrity": "sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-syntax-dynamic-import": {
@@ -783,12 +804,21 @@
       }
     },
     "@babel/plugin-syntax-flow": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz",
-      "integrity": "sha512-UDo3YGQO0jH6ytzVwgSLv9i/CzMcUjbKenL67dTrAZPPv6GFAtDhe6jqnvmoKzC/7htNTohhos+onPtDMqJwaQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz",
+      "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
+      }
+    },
+    "@babel/plugin-syntax-import-assertions": {
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz",
+      "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-syntax-import-meta": {
@@ -810,12 +840,12 @@
       }
     },
     "@babel/plugin-syntax-jsx": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz",
-      "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
+      "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-syntax-logical-assignment-operators": {
@@ -891,284 +921,286 @@
       }
     },
     "@babel/plugin-syntax-typescript": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz",
-      "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz",
+      "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-arrow-functions": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz",
-      "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz",
+      "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-async-to-generator": {
-      "version": "7.16.8",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz",
-      "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz",
+      "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-imports": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-remap-async-to-generator": "^7.16.8"
+        "@babel/helper-module-imports": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-remap-async-to-generator": "^7.18.6"
       }
     },
     "@babel/plugin-transform-block-scoped-functions": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz",
-      "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz",
+      "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-block-scoping": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz",
-      "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.6.tgz",
+      "integrity": "sha512-pRqwb91C42vs1ahSAWJkxOxU1RHWDn16XAa6ggQ72wjLlWyYeAcLvTtE0aM8ph3KNydy9CQF2nLYcjq1WysgxQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-classes": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz",
-      "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.6.tgz",
+      "integrity": "sha512-XTg8XW/mKpzAF3actL554Jl/dOYoJtv3l8fxaEczpgz84IeeVf+T1u2CSvPHuZbt0w3JkIx4rdn/MRQI7mo0HQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-annotate-as-pure": "^7.16.7",
-        "@babel/helper-environment-visitor": "^7.16.7",
-        "@babel/helper-function-name": "^7.16.7",
-        "@babel/helper-optimise-call-expression": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-replace-supers": "^7.16.7",
-        "@babel/helper-split-export-declaration": "^7.16.7",
+        "@babel/helper-annotate-as-pure": "^7.18.6",
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-function-name": "^7.18.6",
+        "@babel/helper-optimise-call-expression": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-replace-supers": "^7.18.6",
+        "@babel/helper-split-export-declaration": "^7.18.6",
         "globals": "^11.1.0"
       }
     },
     "@babel/plugin-transform-computed-properties": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz",
-      "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.6.tgz",
+      "integrity": "sha512-9repI4BhNrR0KenoR9vm3/cIc1tSBIo+u1WVjKCAynahj25O8zfbiE6JtAtHPGQSs4yZ+bA8mRasRP+qc+2R5A==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-destructuring": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz",
-      "integrity": "sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.6.tgz",
+      "integrity": "sha512-tgy3u6lRp17ilY8r1kP4i2+HDUwxlVqq3RTc943eAWSzGgpU1qhiKpqZ5CMyHReIYPHdo3Kg8v8edKtDqSVEyQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-dotall-regex": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz",
-      "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz",
+      "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-regexp-features-plugin": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-duplicate-keys": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz",
-      "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.6.tgz",
+      "integrity": "sha512-NJU26U/208+sxYszf82nmGYqVF9QN8py2HFTblPT9hbawi8+1C5a9JubODLTGFuT0qlkqVinmkwOD13s0sZktg==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-exponentiation-operator": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz",
-      "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz",
+      "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==",
       "dev": true,
       "requires": {
-        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-flow-strip-types": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.16.7.tgz",
-      "integrity": "sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.18.6.tgz",
+      "integrity": "sha512-wE0xtA7csz+hw4fKPwxmu5jnzAsXPIO57XnRwzXP3T19jWh1BODnPGoG9xKYwvAwusP7iUktHayRFbMPGtODaQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/plugin-syntax-flow": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/plugin-syntax-flow": "^7.18.6"
       }
     },
     "@babel/plugin-transform-for-of": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz",
-      "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.6.tgz",
+      "integrity": "sha512-WAjoMf4wIiSsy88KmG7tgj2nFdEK7E46tArVtcgED7Bkj6Fg/tG5SbvNIOKxbFS2VFgNh6+iaPswBeQZm4ox8w==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-function-name": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz",
-      "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.6.tgz",
+      "integrity": "sha512-kJha/Gbs5RjzIu0CxZwf5e3aTTSlhZnHMT8zPWnJMjNpLOUgqevg+PN5oMH68nMCXnfiMo4Bhgxqj59KHTlAnA==",
       "dev": true,
       "requires": {
-        "@babel/helper-compilation-targets": "^7.16.7",
-        "@babel/helper-function-name": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-compilation-targets": "^7.18.6",
+        "@babel/helper-function-name": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-literals": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz",
-      "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.6.tgz",
+      "integrity": "sha512-x3HEw0cJZVDoENXOp20HlypIHfl0zMIhMVZEBVTfmqbObIpsMxMbmU5nOEO8R7LYT+z5RORKPlTI5Hj4OsO9/Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-member-expression-literals": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz",
-      "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz",
+      "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-modules-amd": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz",
-      "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz",
+      "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-transforms": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
+        "@babel/helper-module-transforms": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
         "babel-plugin-dynamic-import-node": "^2.3.3"
       }
     },
     "@babel/plugin-transform-modules-commonjs": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz",
-      "integrity": "sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz",
+      "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-transforms": "^7.17.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-simple-access": "^7.17.7",
+        "@babel/helper-module-transforms": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-simple-access": "^7.18.6",
         "babel-plugin-dynamic-import-node": "^2.3.3"
       }
     },
     "@babel/plugin-transform-modules-systemjs": {
-      "version": "7.17.8",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz",
-      "integrity": "sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.6.tgz",
+      "integrity": "sha512-UbPYpXxLjTw6w6yXX2BYNxF3p6QY225wcTkfQCy3OMnSlS/C3xGtwUjEzGkldb/sy6PWLiCQ3NbYfjWUTI3t4g==",
       "dev": true,
       "requires": {
-        "@babel/helper-hoist-variables": "^7.16.7",
-        "@babel/helper-module-transforms": "^7.17.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-validator-identifier": "^7.16.7",
+        "@babel/helper-hoist-variables": "^7.18.6",
+        "@babel/helper-module-transforms": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-validator-identifier": "^7.18.6",
         "babel-plugin-dynamic-import-node": "^2.3.3"
       }
     },
     "@babel/plugin-transform-modules-umd": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz",
-      "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz",
+      "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-transforms": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-module-transforms": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-named-capturing-groups-regex": {
-      "version": "7.16.8",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz",
-      "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz",
+      "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-regexp-features-plugin": "^7.16.7"
+        "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-new-target": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz",
-      "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz",
+      "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-object-super": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz",
-      "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz",
+      "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-replace-supers": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-replace-supers": "^7.18.6"
       }
     },
     "@babel/plugin-transform-parameters": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz",
-      "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.6.tgz",
+      "integrity": "sha512-FjdqgMv37yVl/gwvzkcB+wfjRI8HQmc5EgOG9iGNvUY1ok+TjsoaMP7IqCDZBhkFcM5f3OPVMs6Dmp03C5k4/A==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-property-literals": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz",
-      "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz",
+      "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-regenerator": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz",
-      "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz",
+      "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==",
       "dev": true,
       "requires": {
-        "regenerator-transform": "^0.14.2"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "regenerator-transform": "^0.15.0"
       }
     },
     "@babel/plugin-transform-reserved-words": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz",
-      "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz",
+      "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-runtime": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz",
-      "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.6.tgz",
+      "integrity": "sha512-8uRHk9ZmRSnWqUgyae249EJZ94b0yAGLBIqzZzl+0iEdbno55Pmlt/32JZsHwXD9k/uZj18Aqqk35wBX4CBTXA==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-imports": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "babel-plugin-polyfill-corejs2": "^0.3.0",
-        "babel-plugin-polyfill-corejs3": "^0.5.0",
-        "babel-plugin-polyfill-regenerator": "^0.3.0",
+        "@babel/helper-module-imports": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "babel-plugin-polyfill-corejs2": "^0.3.1",
+        "babel-plugin-polyfill-corejs3": "^0.5.2",
+        "babel-plugin-polyfill-regenerator": "^0.3.1",
         "semver": "^6.3.0"
       },
       "dependencies": {
@@ -1181,113 +1213,114 @@
       }
     },
     "@babel/plugin-transform-shorthand-properties": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz",
-      "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz",
+      "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-spread": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz",
-      "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.6.tgz",
+      "integrity": "sha512-ayT53rT/ENF8WWexIRg9AiV9h0aIteyWn5ptfZTZQrjk/+f3WdrJGCY4c9wcgl2+MKkKPhzbYp97FTsquZpDCw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.18.6"
       }
     },
     "@babel/plugin-transform-sticky-regex": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz",
-      "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz",
+      "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-template-literals": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz",
-      "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.6.tgz",
+      "integrity": "sha512-UuqlRrQmT2SWRvahW46cGSany0uTlcj8NYOS5sRGYi8FxPYPoLd5DDmMd32ZXEj2Jq+06uGVQKHxa/hJx2EzKw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-typeof-symbol": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz",
-      "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.6.tgz",
+      "integrity": "sha512-7m71iS/QhsPk85xSjFPovHPcH3H9qeyzsujhTc+vcdnsXavoWYJ74zx0lP5RhpC5+iDnVLO+PPMHzC11qels1g==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-typescript": {
-      "version": "7.16.8",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz",
-      "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.6.tgz",
+      "integrity": "sha512-ijHNhzIrLj5lQCnI6aaNVRtGVuUZhOXFLRVFs7lLrkXTHip4FKty5oAuQdk4tywG0/WjXmjTfQCWmuzrvFer1w==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/plugin-syntax-typescript": "^7.16.7"
+        "@babel/helper-create-class-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/plugin-syntax-typescript": "^7.18.6"
       }
     },
     "@babel/plugin-transform-unicode-escapes": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz",
-      "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz",
+      "integrity": "sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/plugin-transform-unicode-regex": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz",
-      "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz",
+      "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==",
       "dev": true,
       "requires": {
-        "@babel/helper-create-regexp-features-plugin": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7"
+        "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
     "@babel/preset-env": {
-      "version": "7.16.11",
-      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz",
-      "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.6.tgz",
+      "integrity": "sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==",
       "dev": true,
       "requires": {
-        "@babel/compat-data": "^7.16.8",
-        "@babel/helper-compilation-targets": "^7.16.7",
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-validator-option": "^7.16.7",
-        "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7",
-        "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7",
-        "@babel/plugin-proposal-async-generator-functions": "^7.16.8",
-        "@babel/plugin-proposal-class-properties": "^7.16.7",
-        "@babel/plugin-proposal-class-static-block": "^7.16.7",
-        "@babel/plugin-proposal-dynamic-import": "^7.16.7",
-        "@babel/plugin-proposal-export-namespace-from": "^7.16.7",
-        "@babel/plugin-proposal-json-strings": "^7.16.7",
-        "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7",
-        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
-        "@babel/plugin-proposal-numeric-separator": "^7.16.7",
-        "@babel/plugin-proposal-object-rest-spread": "^7.16.7",
-        "@babel/plugin-proposal-optional-catch-binding": "^7.16.7",
-        "@babel/plugin-proposal-optional-chaining": "^7.16.7",
-        "@babel/plugin-proposal-private-methods": "^7.16.11",
-        "@babel/plugin-proposal-private-property-in-object": "^7.16.7",
-        "@babel/plugin-proposal-unicode-property-regex": "^7.16.7",
+        "@babel/compat-data": "^7.18.6",
+        "@babel/helper-compilation-targets": "^7.18.6",
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-validator-option": "^7.18.6",
+        "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",
+        "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.6",
+        "@babel/plugin-proposal-async-generator-functions": "^7.18.6",
+        "@babel/plugin-proposal-class-properties": "^7.18.6",
+        "@babel/plugin-proposal-class-static-block": "^7.18.6",
+        "@babel/plugin-proposal-dynamic-import": "^7.18.6",
+        "@babel/plugin-proposal-export-namespace-from": "^7.18.6",
+        "@babel/plugin-proposal-json-strings": "^7.18.6",
+        "@babel/plugin-proposal-logical-assignment-operators": "^7.18.6",
+        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
+        "@babel/plugin-proposal-numeric-separator": "^7.18.6",
+        "@babel/plugin-proposal-object-rest-spread": "^7.18.6",
+        "@babel/plugin-proposal-optional-catch-binding": "^7.18.6",
+        "@babel/plugin-proposal-optional-chaining": "^7.18.6",
+        "@babel/plugin-proposal-private-methods": "^7.18.6",
+        "@babel/plugin-proposal-private-property-in-object": "^7.18.6",
+        "@babel/plugin-proposal-unicode-property-regex": "^7.18.6",
         "@babel/plugin-syntax-async-generators": "^7.8.4",
         "@babel/plugin-syntax-class-properties": "^7.12.13",
         "@babel/plugin-syntax-class-static-block": "^7.14.5",
         "@babel/plugin-syntax-dynamic-import": "^7.8.3",
         "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+        "@babel/plugin-syntax-import-assertions": "^7.18.6",
         "@babel/plugin-syntax-json-strings": "^7.8.3",
         "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
         "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
@@ -1297,44 +1330,44 @@
         "@babel/plugin-syntax-optional-chaining": "^7.8.3",
         "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
         "@babel/plugin-syntax-top-level-await": "^7.14.5",
-        "@babel/plugin-transform-arrow-functions": "^7.16.7",
-        "@babel/plugin-transform-async-to-generator": "^7.16.8",
-        "@babel/plugin-transform-block-scoped-functions": "^7.16.7",
-        "@babel/plugin-transform-block-scoping": "^7.16.7",
-        "@babel/plugin-transform-classes": "^7.16.7",
-        "@babel/plugin-transform-computed-properties": "^7.16.7",
-        "@babel/plugin-transform-destructuring": "^7.16.7",
-        "@babel/plugin-transform-dotall-regex": "^7.16.7",
-        "@babel/plugin-transform-duplicate-keys": "^7.16.7",
-        "@babel/plugin-transform-exponentiation-operator": "^7.16.7",
-        "@babel/plugin-transform-for-of": "^7.16.7",
-        "@babel/plugin-transform-function-name": "^7.16.7",
-        "@babel/plugin-transform-literals": "^7.16.7",
-        "@babel/plugin-transform-member-expression-literals": "^7.16.7",
-        "@babel/plugin-transform-modules-amd": "^7.16.7",
-        "@babel/plugin-transform-modules-commonjs": "^7.16.8",
-        "@babel/plugin-transform-modules-systemjs": "^7.16.7",
-        "@babel/plugin-transform-modules-umd": "^7.16.7",
-        "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8",
-        "@babel/plugin-transform-new-target": "^7.16.7",
-        "@babel/plugin-transform-object-super": "^7.16.7",
-        "@babel/plugin-transform-parameters": "^7.16.7",
-        "@babel/plugin-transform-property-literals": "^7.16.7",
-        "@babel/plugin-transform-regenerator": "^7.16.7",
-        "@babel/plugin-transform-reserved-words": "^7.16.7",
-        "@babel/plugin-transform-shorthand-properties": "^7.16.7",
-        "@babel/plugin-transform-spread": "^7.16.7",
-        "@babel/plugin-transform-sticky-regex": "^7.16.7",
-        "@babel/plugin-transform-template-literals": "^7.16.7",
-        "@babel/plugin-transform-typeof-symbol": "^7.16.7",
-        "@babel/plugin-transform-unicode-escapes": "^7.16.7",
-        "@babel/plugin-transform-unicode-regex": "^7.16.7",
+        "@babel/plugin-transform-arrow-functions": "^7.18.6",
+        "@babel/plugin-transform-async-to-generator": "^7.18.6",
+        "@babel/plugin-transform-block-scoped-functions": "^7.18.6",
+        "@babel/plugin-transform-block-scoping": "^7.18.6",
+        "@babel/plugin-transform-classes": "^7.18.6",
+        "@babel/plugin-transform-computed-properties": "^7.18.6",
+        "@babel/plugin-transform-destructuring": "^7.18.6",
+        "@babel/plugin-transform-dotall-regex": "^7.18.6",
+        "@babel/plugin-transform-duplicate-keys": "^7.18.6",
+        "@babel/plugin-transform-exponentiation-operator": "^7.18.6",
+        "@babel/plugin-transform-for-of": "^7.18.6",
+        "@babel/plugin-transform-function-name": "^7.18.6",
+        "@babel/plugin-transform-literals": "^7.18.6",
+        "@babel/plugin-transform-member-expression-literals": "^7.18.6",
+        "@babel/plugin-transform-modules-amd": "^7.18.6",
+        "@babel/plugin-transform-modules-commonjs": "^7.18.6",
+        "@babel/plugin-transform-modules-systemjs": "^7.18.6",
+        "@babel/plugin-transform-modules-umd": "^7.18.6",
+        "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6",
+        "@babel/plugin-transform-new-target": "^7.18.6",
+        "@babel/plugin-transform-object-super": "^7.18.6",
+        "@babel/plugin-transform-parameters": "^7.18.6",
+        "@babel/plugin-transform-property-literals": "^7.18.6",
+        "@babel/plugin-transform-regenerator": "^7.18.6",
+        "@babel/plugin-transform-reserved-words": "^7.18.6",
+        "@babel/plugin-transform-shorthand-properties": "^7.18.6",
+        "@babel/plugin-transform-spread": "^7.18.6",
+        "@babel/plugin-transform-sticky-regex": "^7.18.6",
+        "@babel/plugin-transform-template-literals": "^7.18.6",
+        "@babel/plugin-transform-typeof-symbol": "^7.18.6",
+        "@babel/plugin-transform-unicode-escapes": "^7.18.6",
+        "@babel/plugin-transform-unicode-regex": "^7.18.6",
         "@babel/preset-modules": "^0.1.5",
-        "@babel/types": "^7.16.8",
-        "babel-plugin-polyfill-corejs2": "^0.3.0",
-        "babel-plugin-polyfill-corejs3": "^0.5.0",
-        "babel-plugin-polyfill-regenerator": "^0.3.0",
-        "core-js-compat": "^3.20.2",
+        "@babel/types": "^7.18.6",
+        "babel-plugin-polyfill-corejs2": "^0.3.1",
+        "babel-plugin-polyfill-corejs3": "^0.5.2",
+        "babel-plugin-polyfill-regenerator": "^0.3.1",
+        "core-js-compat": "^3.22.1",
         "semver": "^6.3.0"
       },
       "dependencies": {
@@ -1347,14 +1380,14 @@
       }
     },
     "@babel/preset-flow": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.16.7.tgz",
-      "integrity": "sha512-6ceP7IyZdUYQ3wUVqyRSQXztd1YmFHWI4Xv11MIqAlE4WqxBSd/FZ61V9k+TS5Gd4mkHOtQtPp9ymRpxH4y1Ug==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz",
+      "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-validator-option": "^7.16.7",
-        "@babel/plugin-transform-flow-strip-types": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-validator-option": "^7.18.6",
+        "@babel/plugin-transform-flow-strip-types": "^7.18.6"
       }
     },
     "@babel/preset-modules": {
@@ -1371,20 +1404,20 @@
       }
     },
     "@babel/preset-typescript": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz",
-      "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz",
+      "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.16.7",
-        "@babel/helper-validator-option": "^7.16.7",
-        "@babel/plugin-transform-typescript": "^7.16.7"
+        "@babel/helper-plugin-utils": "^7.18.6",
+        "@babel/helper-validator-option": "^7.18.6",
+        "@babel/plugin-transform-typescript": "^7.18.6"
       }
     },
     "@babel/register": {
-      "version": "7.17.7",
-      "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.17.7.tgz",
-      "integrity": "sha512-fg56SwvXRifootQEDQAu1mKdjh5uthPzdO0N6t358FktfL4XjAVXuH58ULoiW8mesxiOgNIrxiImqEwv0+hRRA==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.6.tgz",
+      "integrity": "sha512-tkYtONzaO8rQubZzpBnvZPFcHgh8D9F55IjOsYton4X2IBoyRn2ZSWQqySTZnUn2guZbxbQiAB27hJEbvXamhQ==",
       "dev": true,
       "requires": {
         "clone-deep": "^4.0.1",
@@ -1419,104 +1452,91 @@
       }
     },
     "@babel/runtime": {
-      "version": "7.17.8",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz",
-      "integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==",
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
+      "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
       "requires": {
-        "regenerator-runtime": "^0.13.4"
+        "regenerator-runtime": "^0.14.0"
       }
     },
     "@babel/template": {
-      "version": "7.16.7",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
-      "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz",
+      "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.16.7",
-        "@babel/parser": "^7.16.7",
-        "@babel/types": "^7.16.7"
+        "@babel/code-frame": "^7.18.6",
+        "@babel/parser": "^7.18.6",
+        "@babel/types": "^7.18.6"
       }
     },
     "@babel/traverse": {
-      "version": "7.17.3",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
-      "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
+      "version": "7.18.6",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz",
+      "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.16.7",
-        "@babel/generator": "^7.17.3",
-        "@babel/helper-environment-visitor": "^7.16.7",
-        "@babel/helper-function-name": "^7.16.7",
-        "@babel/helper-hoist-variables": "^7.16.7",
-        "@babel/helper-split-export-declaration": "^7.16.7",
-        "@babel/parser": "^7.17.3",
-        "@babel/types": "^7.17.0",
+        "@babel/code-frame": "^7.18.6",
+        "@babel/generator": "^7.18.6",
+        "@babel/helper-environment-visitor": "^7.18.6",
+        "@babel/helper-function-name": "^7.18.6",
+        "@babel/helper-hoist-variables": "^7.18.6",
+        "@babel/helper-split-export-declaration": "^7.18.6",
+        "@babel/parser": "^7.18.6",
+        "@babel/types": "^7.18.6",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
       }
     },
     "@babel/types": {
-      "version": "7.17.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
-      "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
+      "version": "7.18.7",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz",
+      "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-validator-identifier": "^7.16.7",
+        "@babel/helper-validator-identifier": "^7.18.6",
         "to-fast-properties": "^2.0.0"
       }
     },
     "@ctrl/tinycolor": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
-      "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA=="
     },
     "@fortawesome/fontawesome-common-types": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz",
-      "integrity": "sha512-CA3MAZBTxVsF6SkfkHXDerkhcQs0QPofy43eFdbWJJkZiq3SfiaH1msOkac59rQaqto5EqWnASboY1dBuKen5w=="
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
+      "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw=="
     },
     "@fortawesome/fontawesome-svg-core": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz",
-      "integrity": "sha512-UIL6crBWhjTNQcONt96ExjUnKt1D68foe3xjEensLDclqQ6YagwCRYVQdrp/hW0ALRp/5Fv/VKw+MqTUWYYvPg==",
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz",
+      "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==",
       "requires": {
-        "@fortawesome/fontawesome-common-types": "^0.3.0"
+        "@fortawesome/fontawesome-common-types": "6.5.2"
       }
     },
     "@fortawesome/free-brands-svg-icons": {
-      "version": "5.15.4",
-      "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
-      "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz",
+      "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==",
       "requires": {
-        "@fortawesome/fontawesome-common-types": "^0.2.36"
-      },
-      "dependencies": {
-        "@fortawesome/fontawesome-common-types": {
-          "version": "0.2.36",
-          "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
-          "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
-        }
+        "@fortawesome/fontawesome-common-types": "6.5.2"
       }
     },
     "@fortawesome/free-solid-svg-icons": {
-      "version": "5.15.4",
-      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
-      "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
+      "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
       "requires": {
-        "@fortawesome/fontawesome-common-types": "^0.2.36"
-      },
-      "dependencies": {
-        "@fortawesome/fontawesome-common-types": {
-          "version": "0.2.36",
-          "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
-          "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
-        }
+        "@fortawesome/fontawesome-common-types": "6.5.2"
       }
     },
     "@fortawesome/vue-fontawesome": {
-      "version": "3.0.0-5",
-      "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-5.tgz",
-      "integrity": "sha512-aNmBT4bOecrFsZTog1l6AJDQHPP3ocXV+WQ3Ogy8WZCqstB/ahfhH4CPu5i4N9Hw0MBKXqE+LX+NbUxcj8cVTw=="
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.6.tgz",
+      "integrity": "sha512-akrL7lTroyNpPkoHtvK2UpsMzJr6jXdHaQ0YdcwqDsB8jdwlpNHZYijpOUd9KJsARr+VB3WXY4EyObepqJ4ytQ==",
+      "requires": {}
     },
     "@gar/promisify": {
       "version": "1.1.3",
@@ -1573,67 +1593,6 @@
         "postcss": "^7.0.0"
       }
     },
-    "@intlify/core-base": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.9.tgz",
-      "integrity": "sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==",
-      "requires": {
-        "@intlify/devtools-if": "9.1.9",
-        "@intlify/message-compiler": "9.1.9",
-        "@intlify/message-resolver": "9.1.9",
-        "@intlify/runtime": "9.1.9",
-        "@intlify/shared": "9.1.9",
-        "@intlify/vue-devtools": "9.1.9"
-      }
-    },
-    "@intlify/devtools-if": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.9.tgz",
-      "integrity": "sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==",
-      "requires": {
-        "@intlify/shared": "9.1.9"
-      }
-    },
-    "@intlify/message-compiler": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.9.tgz",
-      "integrity": "sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==",
-      "requires": {
-        "@intlify/message-resolver": "9.1.9",
-        "@intlify/shared": "9.1.9",
-        "source-map": "0.6.1"
-      }
-    },
-    "@intlify/message-resolver": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.9.tgz",
-      "integrity": "sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA=="
-    },
-    "@intlify/runtime": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.9.tgz",
-      "integrity": "sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==",
-      "requires": {
-        "@intlify/message-compiler": "9.1.9",
-        "@intlify/message-resolver": "9.1.9",
-        "@intlify/shared": "9.1.9"
-      }
-    },
-    "@intlify/shared": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.9.tgz",
-      "integrity": "sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw=="
-    },
-    "@intlify/vue-devtools": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.9.tgz",
-      "integrity": "sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==",
-      "requires": {
-        "@intlify/message-resolver": "9.1.9",
-        "@intlify/runtime": "9.1.9",
-        "@intlify/shared": "9.1.9"
-      }
-    },
     "@istanbuljs/load-nyc-config": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1704,13 +1663,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "slash": {
@@ -1810,13 +1769,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "rimraf": {
@@ -1938,13 +1897,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "node-notifier": {
@@ -2082,13 +2041,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "slash": {
@@ -2136,22 +2095,38 @@
       "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==",
       "dev": true
     },
+    "@jridgewell/gen-mapping": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+      "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+      "dev": true,
+      "requires": {
+        "@jridgewell/set-array": "^1.0.0",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
     "@jridgewell/resolve-uri": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
-      "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz",
+      "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==",
+      "dev": true
+    },
+    "@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
       "dev": true
     },
     "@jridgewell/sourcemap-codec": {
-      "version": "1.4.11",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
-      "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
+      "version": "1.4.14",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
       "dev": true
     },
     "@jridgewell/trace-mapping": {
-      "version": "0.3.4",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
-      "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
+      "version": "0.3.14",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
+      "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
       "dev": true,
       "requires": {
         "@jridgewell/resolve-uri": "^3.0.3",
@@ -2280,7 +2255,7 @@
     "@protobufjs/aspromise": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
-      "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=",
+      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
       "dev": true
     },
     "@protobufjs/base64": {
@@ -2298,13 +2273,13 @@
     "@protobufjs/eventemitter": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
-      "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=",
+      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
       "dev": true
     },
     "@protobufjs/fetch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
-      "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+      "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
       "dev": true,
       "requires": {
         "@protobufjs/aspromise": "^1.1.1",
@@ -2314,33 +2289,43 @@
     "@protobufjs/float": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
-      "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=",
+      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
       "dev": true
     },
     "@protobufjs/inquire": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
-      "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=",
+      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
       "dev": true
     },
     "@protobufjs/path": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
-      "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=",
+      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
       "dev": true
     },
     "@protobufjs/pool": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
-      "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=",
+      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
       "dev": true
     },
     "@protobufjs/utf8": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
-      "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=",
+      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
       "dev": true
     },
+    "@rc-component/portal": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
+      "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
+      "requires": {
+        "@babel/runtime": "^7.18.0",
+        "classnames": "^2.3.2",
+        "rc-util": "^5.24.4"
+      }
+    },
     "@simonwep/pickr": {
       "version": "1.8.2",
       "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz",
@@ -2472,9 +2457,9 @@
       }
     },
     "@types/babel__traverse": {
-      "version": "7.14.2",
-      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz",
-      "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==",
+      "version": "7.17.1",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz",
+      "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==",
       "dev": true,
       "requires": {
         "@babel/types": "^7.3.0"
@@ -2510,9 +2495,9 @@
       }
     },
     "@types/content-disposition": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.4.tgz",
-      "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==",
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.5.tgz",
+      "integrity": "sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==",
       "dev": true
     },
     "@types/cookies": {
@@ -2552,9 +2537,9 @@
       }
     },
     "@types/express-serve-static-core": {
-      "version": "4.17.28",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz",
-      "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==",
+      "version": "4.17.29",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz",
+      "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==",
       "dev": true,
       "requires": {
         "@types/node": "*",
@@ -2603,9 +2588,9 @@
       "dev": true
     },
     "@types/http-proxy": {
-      "version": "1.17.8",
-      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
-      "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==",
+      "version": "1.17.9",
+      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz",
+      "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==",
       "dev": true,
       "requires": {
         "@types/node": "*"
@@ -2686,15 +2671,15 @@
       }
     },
     "@types/json-schema": {
-      "version": "7.0.10",
-      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz",
-      "integrity": "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==",
+      "version": "7.0.11",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
       "dev": true
     },
     "@types/json5": {
       "version": "0.0.29",
       "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
-      "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
+      "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
       "dev": true
     },
     "@types/keygrip": {
@@ -2729,9 +2714,9 @@
       }
     },
     "@types/long": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
-      "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
       "dev": true
     },
     "@types/mime": {
@@ -2753,9 +2738,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "17.0.23",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
-      "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==",
+      "version": "18.0.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
+      "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
       "dev": true
     },
     "@types/normalize-package-data": {
@@ -2807,7 +2792,7 @@
     "@types/strip-bom": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
-      "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=",
+      "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
       "dev": true
     },
     "@types/strip-json-comments": {
@@ -2832,9 +2817,9 @@
       }
     },
     "@types/uglify-js": {
-      "version": "3.13.1",
-      "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz",
-      "integrity": "sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==",
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.16.0.tgz",
+      "integrity": "sha512-0yeUr92L3r0GLRnBOvtYK1v2SjqMIqQDHMl7GLb+l2L8+6LSFWEEWEIgVsPdMn5ImLM8qzWT8xFPtQYpp8co0g==",
       "dev": true,
       "requires": {
         "source-map": "^0.6.1"
@@ -2884,9 +2869,9 @@
       },
       "dependencies": {
         "source-map": {
-          "version": "0.7.3",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+          "version": "0.7.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+          "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
           "dev": true
         }
       }
@@ -2915,6 +2900,23 @@
       "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
       "dev": true
     },
+    "@vue-js-cron/ant": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@vue-js-cron/ant/-/ant-1.1.3.tgz",
+      "integrity": "sha512-E7PbZX/Fwb4w4GYYllnhDx8ywTce3OQ3WS20BPS8EbPlmShmkdvtU9dyNC+C+bGyzXfUwPIdLbtQ1vKzLrMm0A==",
+      "requires": {
+        "@vue-js-cron/core": "3.7.1",
+        "ant-design-vue": "^3.2.12"
+      }
+    },
+    "@vue-js-cron/core": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/@vue-js-cron/core/-/core-3.7.1.tgz",
+      "integrity": "sha512-aWCkbfCbwpEJwmptY0tRFlxH4ZaVtnmR2Q3f0L8SzSC58cn45VYMe2/eSK53221cBmk/w+18Hq563u+W7Jp9Eg==",
+      "requires": {
+        "mustache": "^4.2.0"
+      }
+    },
     "@vue/babel-helper-vue-jsx-merge-props": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz",
@@ -2969,15 +2971,15 @@
         "html-tags": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz",
-          "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=",
+          "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==",
           "dev": true
         }
       }
     },
     "@vue/babel-preset-app": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.17.tgz",
-      "integrity": "sha512-iFv9J3F5VKUPcbx+TqW5qhGmAVyXQxPRpKpPOuTLFIVTzg+iwJnrqVbL4kJU5ECGDxPESW2oCVgxv9bTlDPu7w==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.19.tgz",
+      "integrity": "sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.11.0",
@@ -3075,7 +3077,7 @@
         "html-tags": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz",
-          "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=",
+          "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==",
           "dev": true
         }
       }
@@ -3092,17 +3094,17 @@
       }
     },
     "@vue/cli": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli/-/cli-4.5.17.tgz",
-      "integrity": "sha512-73nK2o/o7Wk9myPySdjxpMzABLynGmnQbJ5wz3CJ9SFrss8Y2LwLAnzlO77mr3uA2nFPdTJbkbUcZlvBWCVSrA==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli/-/cli-4.5.19.tgz",
+      "integrity": "sha512-y+rPxbhwMNf9ZL3K1XuRDHaggnX4fVVDsm3oFzbZXKxK674Hi+cM+dc17TfKWNH8LbXiKH6R5KEWFoqIM/AbOA==",
       "dev": true,
       "requires": {
         "@types/ejs": "^2.7.0",
         "@types/inquirer": "^6.5.0",
-        "@vue/cli-shared-utils": "^4.5.17",
-        "@vue/cli-ui": "^4.5.17",
-        "@vue/cli-ui-addon-webpack": "^4.5.17",
-        "@vue/cli-ui-addon-widgets": "^4.5.17",
+        "@vue/cli-shared-utils": "^4.5.19",
+        "@vue/cli-ui": "^4.5.19",
+        "@vue/cli-ui-addon-webpack": "^4.5.19",
+        "@vue/cli-ui-addon-widgets": "^4.5.19",
         "boxen": "^4.1.0",
         "cmd-shim": "^3.0.3",
         "commander": "^2.20.0",
@@ -3156,12 +3158,12 @@
           }
         },
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -3190,20 +3192,20 @@
       }
     },
     "@vue/cli-overlay": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-4.5.17.tgz",
-      "integrity": "sha512-QKKp66VbMg+X8Qh0wgXSwgxLfxY7EIkZkV6bZ6nFqBx8xtaJQVDbTL+4zcUPPA6nygbIcQ6gvTinNEqIqX6FUQ==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-4.5.19.tgz",
+      "integrity": "sha512-GdxvNSmOw7NHIazCO8gTK+xZbaOmScTtxj6eHVeMbYpDYVPJ+th3VMLWNpw/b6uOjwzzcyKlA5dRQ1DAb+gF/g==",
       "dev": true
     },
     "@vue/cli-plugin-babel": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.17.tgz",
-      "integrity": "sha512-6kZuc3PdoUvGAnndUq6+GqjIXn3bqdTR8lOcAb1BH2b4N7IKGlmzcipALGS23HLVMAvDgNuUS7vf0unin9j2cg==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.19.tgz",
+      "integrity": "sha512-8ebXzaMW9KNTMAN6+DzkhFsjty1ieqT7hIW5Lbk4v30Qhfjkms7lBWyXPGkoq+wAikXFa1Gnam2xmWOBqDDvWg==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.11.0",
-        "@vue/babel-preset-app": "^4.5.17",
-        "@vue/cli-shared-utils": "^4.5.17",
+        "@vue/babel-preset-app": "^4.5.19",
+        "@vue/cli-shared-utils": "^4.5.19",
         "babel-loader": "^8.1.0",
         "cache-loader": "^4.1.0",
         "thread-loader": "^2.1.3",
@@ -3211,12 +3213,12 @@
       }
     },
     "@vue/cli-plugin-eslint": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.17.tgz",
-      "integrity": "sha512-bVNDP+SuWcuJrBMc+JLaKvlxx25XKIlZBa+zzFnxhHZlwPZ7CeBD3e2wnsygJyPoKgDZcZwDgmEz1BZzMEjsNw==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.19.tgz",
+      "integrity": "sha512-53sa4Pu9j5KajesFlj494CcO8vVo3e3nnZ1CCKjGGnrF90id1rUeepcFfz5XjwfEtbJZp2x/NoX/EZE6zCzSFQ==",
       "dev": true,
       "requires": {
-        "@vue/cli-shared-utils": "^4.5.17",
+        "@vue/cli-shared-utils": "^4.5.19",
         "eslint-loader": "^2.2.1",
         "globby": "^9.2.0",
         "inquirer": "^7.1.0",
@@ -3225,24 +3227,24 @@
       }
     },
     "@vue/cli-plugin-router": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-router/-/cli-plugin-router-4.5.17.tgz",
-      "integrity": "sha512-9r9CSwqv2+39XHQPDZJ0uaTtTP7oe0Gx17m7kBhHG3FA7R7AOSk2aVzhHZmDRhzlOxjx9kQSvrOSMfUG0kV4dQ==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-router/-/cli-plugin-router-4.5.19.tgz",
+      "integrity": "sha512-3icGzH1IbVYmMMsOwYa0lal/gtvZLebFXdE5hcQJo2mnTwngXGMTyYAzL56EgHBPjbMmRpyj6Iw9k4aVInVX6A==",
       "dev": true,
       "requires": {
-        "@vue/cli-shared-utils": "^4.5.17"
+        "@vue/cli-shared-utils": "^4.5.19"
       }
     },
     "@vue/cli-plugin-unit-jest": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-4.5.17.tgz",
-      "integrity": "sha512-Ta8hx68Y252umik1yD50dyyrwhzj4KzTEpngY9YKB6bY6pga9GK2olqyrb3+2X8o6tnSQkTFz+UcobfRQJDs5A==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-unit-jest/-/cli-plugin-unit-jest-4.5.19.tgz",
+      "integrity": "sha512-yX61mpeU7DnjOv+Lxtjmr3pzESqBLIXeTK4MJpa/UdzrhnylHP4r6mCYETNLEYtxp8WZUXPjZFIzrKn5poZPJg==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.11.0",
         "@babel/plugin-transform-modules-commonjs": "^7.9.6",
         "@types/jest": "^24.0.19",
-        "@vue/cli-shared-utils": "^4.5.17",
+        "@vue/cli-shared-utils": "^4.5.19",
         "babel-core": "^7.0.0-bridge.0",
         "babel-jest": "^24.9.0",
         "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
@@ -3303,13 +3305,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "slash": {
@@ -3321,7 +3323,7 @@
         "source-map": {
           "version": "0.5.7",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
           "dev": true
         },
         "supports-color": {
@@ -3355,15 +3357,16 @@
       }
     },
     "@vue/cli-plugin-vuex": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.17.tgz",
-      "integrity": "sha512-ck/ju2T2dmPKLWK/5QctNJs9SCb+eSZbbmr8neFkMc7GlbXw6qLWw5v3Vpd4KevdQA8QuQOA1pjUmzpCiU/mYQ==",
-      "dev": true
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.19.tgz",
+      "integrity": "sha512-DUmfdkG3pCdkP7Iznd87RfE9Qm42mgp2hcrNcYQYSru1W1gX2dG/JcW8bxmeGSa06lsxi9LEIc/QD1yPajSCZw==",
+      "dev": true,
+      "requires": {}
     },
     "@vue/cli-service": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-4.5.17.tgz",
-      "integrity": "sha512-MqfkRYIcIUACe3nYlzNrYstJTWRXHlIqh6JCkbWbdnXWN+IfaVdlG8zw5Q0DVcSdGvkevUW7zB4UhtZB4uyAcA==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-4.5.19.tgz",
+      "integrity": "sha512-+Wpvj8fMTCt9ZPOLu5YaLkFCQmB4MrZ26aRmhhKiCQ/4PMoL6mLezfqdt6c+m2htM+1WV5RunRo+0WHl2DfwZA==",
       "dev": true,
       "requires": {
         "@intervolga/optimize-cssnano-plugin": "^1.0.5",
@@ -3372,10 +3375,10 @@
         "@types/minimist": "^1.2.0",
         "@types/webpack": "^4.0.0",
         "@types/webpack-dev-server": "^3.11.0",
-        "@vue/cli-overlay": "^4.5.17",
-        "@vue/cli-plugin-router": "^4.5.17",
-        "@vue/cli-plugin-vuex": "^4.5.17",
-        "@vue/cli-shared-utils": "^4.5.17",
+        "@vue/cli-overlay": "^4.5.19",
+        "@vue/cli-plugin-router": "^4.5.19",
+        "@vue/cli-plugin-vuex": "^4.5.19",
+        "@vue/cli-shared-utils": "^4.5.19",
         "@vue/component-compiler-utils": "^3.1.2",
         "@vue/preload-webpack-plugin": "^1.1.0",
         "@vue/web-component-wrapper": "^1.2.0",
@@ -3515,7 +3518,7 @@
             "hash-sum": {
               "version": "1.0.2",
               "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
-              "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
+              "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
               "dev": true
             }
           }
@@ -3533,16 +3536,16 @@
           },
           "dependencies": {
             "json5": {
-              "version": "2.2.1",
-              "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
-              "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+              "version": "2.2.3",
+              "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+              "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
               "dev": true,
               "optional": true
             },
             "loader-utils": {
-              "version": "2.0.2",
-              "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
-              "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+              "version": "2.0.4",
+              "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+              "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
               "dev": true,
               "optional": true,
               "requires": {
@@ -3567,9 +3570,9 @@
       }
     },
     "@vue/cli-shared-utils": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.17.tgz",
-      "integrity": "sha512-VoFNdxvTW4vZu3ne+j1Mf7mU99J2SAoRVn9XPrsouTUUJablglM8DASk7Ixhsh6ymyL/W9EADQFR6Pgj8Ujjuw==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.19.tgz",
+      "integrity": "sha512-JYpdsrC/d9elerKxbEUtmSSU6QRM60rirVubOewECHkBHj+tLNznWq/EhCjswywtePyLaMUK25eTqnTSZlEE+g==",
       "dev": true,
       "requires": {
         "@achrinza/node-ipc": "9.2.2",
@@ -3624,13 +3627,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "lru-cache": {
@@ -3675,14 +3678,14 @@
       }
     },
     "@vue/cli-ui": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-ui/-/cli-ui-4.5.17.tgz",
-      "integrity": "sha512-x5o+RUNkPLO6wDNY5/Sec9ZuZExzARU8KkP2SwM2SWLirkjKgLVvyL4/snAIWwRLxsxXVuyZ8YJdqnWidixzCg==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-ui/-/cli-ui-4.5.19.tgz",
+      "integrity": "sha512-wiZ63+uqB1b2+AH9rUkubvQEzDCwEBLJZ4xaTkRlI+eSPUj1nZM2SzvwbWo2aL0ObFAQwWaXwqjh2VW3zQEvJw==",
       "dev": true,
       "requires": {
         "@achrinza/node-ipc": "9.2.2",
         "@akryum/winattr": "^3.0.0",
-        "@vue/cli-shared-utils": "^4.5.17",
+        "@vue/cli-shared-utils": "^4.5.19",
         "apollo-server-express": "^2.13.1",
         "clone": "^2.1.1",
         "deepmerge": "^4.2.2",
@@ -3713,7 +3716,7 @@
         "clone": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
-          "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+          "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
           "dev": true
         },
         "lru-cache": {
@@ -3734,48 +3737,48 @@
       }
     },
     "@vue/cli-ui-addon-webpack": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-webpack/-/cli-ui-addon-webpack-4.5.17.tgz",
-      "integrity": "sha512-AZMnDzToM2uxW/73mfjJweea3atC5sxOC3Nel5UEFnQK0hWN0/8NW6nPAiKDc+kJpZWFb7Y6ReP2hwYizUJK2w==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-webpack/-/cli-ui-addon-webpack-4.5.19.tgz",
+      "integrity": "sha512-ESkNIVbEMGwe42OcGbfF4fQD/JRHiN4notSGkQ5fnjVnEkNlH4UJotoNgea4lmnnCIMjMEN4wz3f+ADXKjzOlA==",
       "dev": true
     },
     "@vue/cli-ui-addon-widgets": {
-      "version": "4.5.17",
-      "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-widgets/-/cli-ui-addon-widgets-4.5.17.tgz",
-      "integrity": "sha512-K49weNsBggUL54Etdqml0hR3PpNzQSXUxC0G52qGNuZwPPxpZfZSQIH8GQ4jBTS8ySXmQYDT99DyxKSedE7McQ==",
+      "version": "4.5.19",
+      "resolved": "https://registry.npmjs.org/@vue/cli-ui-addon-widgets/-/cli-ui-addon-widgets-4.5.19.tgz",
+      "integrity": "sha512-MuujsH7WHSe4UJVok2EbmiTW/Ig3dlUMYqr1cvRY+G5YDpzychPetiQsHzoJsBc5yfEdRRzmJ04KAuzfAzIA1g==",
       "dev": true
     },
     "@vue/compiler-core": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.31.tgz",
-      "integrity": "sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
+      "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
       "requires": {
         "@babel/parser": "^7.16.4",
-        "@vue/shared": "3.2.31",
+        "@vue/shared": "3.2.37",
         "estree-walker": "^2.0.2",
         "source-map": "^0.6.1"
       }
     },
     "@vue/compiler-dom": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz",
-      "integrity": "sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
+      "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
       "requires": {
-        "@vue/compiler-core": "3.2.31",
-        "@vue/shared": "3.2.31"
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
     "@vue/compiler-sfc": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz",
-      "integrity": "sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
+      "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
       "requires": {
         "@babel/parser": "^7.16.4",
-        "@vue/compiler-core": "3.2.31",
-        "@vue/compiler-dom": "3.2.31",
-        "@vue/compiler-ssr": "3.2.31",
-        "@vue/reactivity-transform": "3.2.31",
-        "@vue/shared": "3.2.31",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/reactivity-transform": "3.2.37",
+        "@vue/shared": "3.2.37",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.25.7",
         "postcss": "^8.1.10",
@@ -3788,11 +3791,11 @@
           "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
         },
         "postcss": {
-          "version": "8.4.12",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz",
-          "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==",
+          "version": "8.4.14",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
+          "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
           "requires": {
-            "nanoid": "^3.3.1",
+            "nanoid": "^3.3.4",
             "picocolors": "^1.0.0",
             "source-map-js": "^1.0.2"
           }
@@ -3800,12 +3803,12 @@
       }
     },
     "@vue/compiler-ssr": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz",
-      "integrity": "sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
+      "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
       "requires": {
-        "@vue/compiler-dom": "3.2.31",
-        "@vue/shared": "3.2.31"
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
     "@vue/component-compiler-utils": {
@@ -3828,7 +3831,7 @@
         "hash-sum": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
-          "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
+          "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
           "dev": true
         },
         "lru-cache": {
@@ -3844,15 +3847,15 @@
         "yallist": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
           "dev": true
         }
       }
     },
     "@vue/devtools-api": {
-      "version": "6.1.3",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz",
-      "integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg=="
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.0.tgz",
+      "integrity": "sha512-pF1G4wky+hkifDiZSWn8xfuLOJI1ZXtuambpBEYaf7Xaf6zC/pM29rvAGpd3qaGXnr4BAXU1Pxz/VfvBGwexGA=="
     },
     "@vue/eslint-config-standard": {
       "version": "5.1.2",
@@ -3869,66 +3872,68 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
       "integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "@vue/reactivity": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz",
-      "integrity": "sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
+      "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
       "requires": {
-        "@vue/shared": "3.2.31"
+        "@vue/shared": "3.2.37"
       }
     },
     "@vue/reactivity-transform": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz",
-      "integrity": "sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
+      "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
       "requires": {
         "@babel/parser": "^7.16.4",
-        "@vue/compiler-core": "3.2.31",
-        "@vue/shared": "3.2.31",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.25.7"
       }
     },
     "@vue/runtime-core": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.31.tgz",
-      "integrity": "sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
+      "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
       "requires": {
-        "@vue/reactivity": "3.2.31",
-        "@vue/shared": "3.2.31"
+        "@vue/reactivity": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
     "@vue/runtime-dom": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz",
-      "integrity": "sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
+      "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
       "requires": {
-        "@vue/runtime-core": "3.2.31",
-        "@vue/shared": "3.2.31",
+        "@vue/runtime-core": "3.2.37",
+        "@vue/shared": "3.2.37",
         "csstype": "^2.6.8"
       }
     },
     "@vue/server-renderer": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.31.tgz",
-      "integrity": "sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
+      "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
       "requires": {
-        "@vue/compiler-ssr": "3.2.31",
-        "@vue/shared": "3.2.31"
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
     "@vue/shared": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz",
-      "integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ=="
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
+      "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
     },
     "@vue/test-utils": {
-      "version": "2.0.0-rc.17",
-      "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0-rc.17.tgz",
-      "integrity": "sha512-7LHZKsFRV/HqDoMVY+cJamFzgHgsrmQFalROHC5FMWrzPzd+utG5e11krj1tVsnxYufGA2ABShX4nlcHXED+zQ==",
-      "dev": true
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0.tgz",
+      "integrity": "sha512-zL5kygNq7hONrO1CzaUGprEAklAX+pH8J1MPMCU3Rd2xtSYkZ+PmKU3oEDRg8VAGdL5lNJHzDgrud5amFPtirw==",
+      "dev": true,
+      "requires": {}
     },
     "@vue/web-component-wrapper": {
       "version": "1.3.0",
@@ -3940,7 +3945,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
       "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/helper-module-context": "1.9.0",
         "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
@@ -3950,26 +3954,22 @@
     "@webassemblyjs/floating-point-hex-parser": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz",
-      "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==",
-      "dev": true
+      "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA=="
     },
     "@webassemblyjs/helper-api-error": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz",
-      "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==",
-      "dev": true
+      "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw=="
     },
     "@webassemblyjs/helper-buffer": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz",
-      "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==",
-      "dev": true
+      "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA=="
     },
     "@webassemblyjs/helper-code-frame": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz",
       "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/wast-printer": "1.9.0"
       }
@@ -3977,14 +3977,12 @@
     "@webassemblyjs/helper-fsm": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz",
-      "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==",
-      "dev": true
+      "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw=="
     },
     "@webassemblyjs/helper-module-context": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz",
       "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0"
       }
@@ -3992,14 +3990,12 @@
     "@webassemblyjs/helper-wasm-bytecode": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz",
-      "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==",
-      "dev": true
+      "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw=="
     },
     "@webassemblyjs/helper-wasm-section": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz",
       "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/helper-buffer": "1.9.0",
@@ -4011,7 +4007,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz",
       "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==",
-      "dev": true,
       "requires": {
         "@xtuc/ieee754": "^1.2.0"
       }
@@ -4020,7 +4015,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz",
       "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==",
-      "dev": true,
       "requires": {
         "@xtuc/long": "4.2.2"
       }
@@ -4028,14 +4022,12 @@
     "@webassemblyjs/utf8": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz",
-      "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==",
-      "dev": true
+      "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w=="
     },
     "@webassemblyjs/wasm-edit": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz",
       "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/helper-buffer": "1.9.0",
@@ -4051,7 +4043,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz",
       "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
@@ -4064,7 +4055,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz",
       "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/helper-buffer": "1.9.0",
@@ -4076,7 +4066,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz",
       "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/helper-api-error": "1.9.0",
@@ -4090,7 +4079,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz",
       "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/floating-point-hex-parser": "1.9.0",
@@ -4104,7 +4092,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz",
       "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/wast-parser": "1.9.0",
@@ -4123,19 +4110,23 @@
     "@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
-      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
-      "dev": true
+      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
     },
     "@xtuc/long": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
-      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
-      "dev": true
+      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
+    },
+    "@zxing/text-encoding": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
+      "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
+      "optional": true
     },
     "abab": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
-      "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==",
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+      "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
       "dev": true
     },
     "abbrev": {
@@ -4156,8 +4147,7 @@
     "acorn": {
       "version": "6.4.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
-      "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
-      "dev": true
+      "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ=="
     },
     "acorn-globals": {
       "version": "4.3.4",
@@ -4173,7 +4163,8 @@
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
       "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "acorn-walk": {
       "version": "6.2.0",
@@ -4182,9 +4173,9 @@
       "dev": true
     },
     "address": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz",
-      "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz",
+      "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==",
       "dev": true
     },
     "agent-base": {
@@ -4229,24 +4220,18 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
       "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
-      "dev": true
+      "requires": {}
     },
     "ajv-keywords": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
       "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
-      "dev": true
+      "requires": {}
     },
     "alphanum-sort": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz",
-      "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
-      "dev": true
-    },
-    "amdefine": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
-      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+      "integrity": "sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==",
       "dev": true
     },
     "ansi-align": {
@@ -4319,7 +4304,7 @@
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+      "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
     },
     "ansi-styles": {
       "version": "4.3.0",
@@ -4330,21 +4315,22 @@
       }
     },
     "ant-design-vue": {
-      "version": "2.2.8",
-      "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.2.8.tgz",
-      "integrity": "sha512-3graq9/gCfJQs6hznrHV6sa9oDmk/D1H3Oo0vLdVpPS/I61fZPk8NEyNKCHpNA6fT2cx6xx9U3QS63uuyikg/Q==",
+      "version": "3.2.20",
+      "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-3.2.20.tgz",
+      "integrity": "sha512-YWpMfGaGoRastIXEYfCoJiaRiDHk4chqtYhlKQM5GqPt6NfvrM1Vg2e60yHtjxlZjed91wCMm0rAmyUr7Hwzdg==",
       "requires": {
-        "@ant-design/icons-vue": "^6.0.0",
+        "@ant-design/colors": "^6.0.0",
+        "@ant-design/icons-vue": "^6.1.0",
         "@babel/runtime": "^7.10.5",
+        "@ctrl/tinycolor": "^3.4.0",
         "@simonwep/pickr": "~1.8.0",
         "array-tree-filter": "^2.1.0",
-        "async-validator": "^3.3.0",
+        "async-validator": "^4.0.0",
+        "dayjs": "^1.10.5",
         "dom-align": "^1.12.1",
         "dom-scroll-into-view": "^2.0.0",
         "lodash": "^4.17.21",
         "lodash-es": "^4.17.15",
-        "moment": "^2.27.0",
-        "omit.js": "^2.0.0",
         "resize-observer-polyfill": "^1.5.1",
         "scroll-into-view-if-needed": "^2.2.25",
         "shallow-equal": "^1.0.0",
@@ -4352,32 +4338,71 @@
         "warning": "^4.0.0"
       }
     },
-    "antd-theme-generator": {
-      "version": "1.2.10",
-      "resolved": "https://registry.npmjs.org/antd-theme-generator/-/antd-theme-generator-1.2.10.tgz",
-      "integrity": "sha512-Eh/5bJP7SejZCTOg1Rq4WB9PkfGXEKo3XWRCFj6Cf1Zsp76Jc9eiXUOijQ2YMtkhLMN2RJOnaNR+OOSpfAonDw==",
+    "antd": {
+      "version": "4.24.16",
+      "resolved": "https://registry.npmjs.org/antd/-/antd-4.24.16.tgz",
+      "integrity": "sha512-zZrK4UYxHtU6tGOOf0uG/kBRx1kTvypfuSB3GqE/SBQxFhZ/TZ+yj7Z1qwI8vGfMtUUJdLeuoCAqGDa1zPsXnQ==",
       "requires": {
-        "glob": "^7.1.3",
-        "hash.js": "^1.1.5",
-        "less": "^3.9.0",
-        "less-bundle-promise": "^1.0.7",
-        "less-plugin-npm-import": "^2.1.0",
-        "postcss": "^6.0.21",
+        "@ant-design/colors": "^6.0.0",
+        "@ant-design/icons": "^4.8.2",
+        "@ant-design/react-slick": "~1.0.2",
+        "@babel/runtime": "^7.18.3",
+        "@ctrl/tinycolor": "^3.6.1",
+        "classnames": "^2.2.6",
+        "copy-to-clipboard": "^3.2.0",
+        "lodash": "^4.17.21",
+        "moment": "^2.29.2",
+        "rc-cascader": "~3.7.3",
+        "rc-checkbox": "~3.0.1",
+        "rc-collapse": "~3.4.2",
+        "rc-dialog": "~9.0.2",
+        "rc-drawer": "~6.3.0",
+        "rc-dropdown": "~4.0.1",
+        "rc-field-form": "~1.38.2",
+        "rc-image": "~5.13.0",
+        "rc-input": "~0.1.4",
+        "rc-input-number": "~7.3.11",
+        "rc-mentions": "~1.13.1",
+        "rc-menu": "~9.8.4",
+        "rc-motion": "^2.9.0",
+        "rc-notification": "~4.6.1",
+        "rc-pagination": "~3.2.0",
+        "rc-picker": "~2.7.6",
+        "rc-progress": "~3.4.2",
+        "rc-rate": "~2.9.3",
+        "rc-resize-observer": "^1.3.1",
+        "rc-segmented": "~2.3.0",
+        "rc-select": "~14.1.18",
+        "rc-slider": "~10.0.1",
+        "rc-steps": "~5.0.0",
+        "rc-switch": "~3.2.2",
+        "rc-table": "~7.26.0",
+        "rc-tabs": "~12.5.10",
+        "rc-textarea": "~0.4.7",
+        "rc-tooltip": "~5.2.2",
+        "rc-tree": "~5.7.12",
+        "rc-tree-select": "~5.5.5",
+        "rc-trigger": "^5.3.4",
+        "rc-upload": "~4.3.6",
+        "rc-util": "^5.37.0",
+        "scroll-into-view-if-needed": "^2.2.25"
+      }
+    },
+    "antd-theme-generator": {
+      "version": "1.2.11",
+      "resolved": "https://registry.npmjs.org/antd-theme-generator/-/antd-theme-generator-1.2.11.tgz",
+      "integrity": "sha512-7A3lXyLb7eD7MXK7aSgZZ4DxQEdhZwyKhzIm70orUZPQJ8N8TWhZphyOWSGCe8yUqGQhi8PcpM2pLmTriZyKBw==",
+      "requires": {
+        "glob": "*",
+        "hash.js": "*",
+        "less": "*",
+        "less-bundle-promise": "^1.0.11",
+        "less-plugin-npm-import": "*",
+        "postcss": "*",
         "postcss-less": "^3.1.4",
-        "strip-css-comments": "^4.1.0"
+        "strip-css-comments": "*"
       },
       "dependencies": {
-        "ajv": {
-          "version": "6.6.2",
-          "bundled": true,
-          "optional": true,
-          "requires": {
-            "fast-deep-equal": "^2.0.1",
-            "fast-json-stable-stringify": "^2.0.0",
-            "json-schema-traverse": "^0.4.1",
-            "uri-js": "^4.2.2"
-          }
-        },
         "ansi-styles": {
           "version": "3.2.1",
           "bundled": true,
@@ -4527,11 +4552,6 @@
           "bundled": true,
           "optional": true
         },
-        "fast-deep-equal": {
-          "version": "2.0.1",
-          "bundled": true,
-          "optional": true
-        },
         "fast-json-stable-stringify": {
           "version": "2.0.0",
           "bundled": true,
@@ -4593,6 +4613,24 @@
           "requires": {
             "ajv": "^6.5.5",
             "har-schema": "^2.0.0"
+          },
+          "dependencies": {
+            "ajv": {
+              "version": "6.6.2",
+              "bundled": true,
+              "optional": true,
+              "requires": {
+                "fast-deep-equal": "^2.0.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+              }
+            },
+            "fast-deep-equal": {
+              "version": "2.0.1",
+              "bundled": true,
+              "optional": true
+            }
           }
         },
         "has-flag": {
@@ -4708,6 +4746,10 @@
               "requires": {
                 "asap": "~2.0.3"
               }
+            },
+            "resolve": {
+              "version": "1.1.7",
+              "bundled": true
             }
           }
         },
@@ -4781,12 +4823,6 @@
             "chalk": "^2.4.1",
             "source-map": "^0.6.1",
             "supports-color": "^5.4.0"
-          },
-          "dependencies": {
-            "source-map": {
-              "version": "0.6.1",
-              "bundled": true
-            }
           }
         },
         "promise": {
@@ -4844,10 +4880,6 @@
             "uuid": "^3.3.2"
           }
         },
-        "resolve": {
-          "version": "1.1.7",
-          "bundled": true
-        },
         "safe-buffer": {
           "version": "5.1.2",
           "bundled": true,
@@ -4860,8 +4892,7 @@
         },
         "source-map": {
           "version": "0.6.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "sshpk": {
           "version": "1.15.2",
@@ -4963,7 +4994,7 @@
     "any-promise": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
-      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
+      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
       "dev": true
     },
     "anymatch": {
@@ -4997,9 +5028,9 @@
       }
     },
     "apollo-graphql": {
-      "version": "0.9.5",
-      "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.5.tgz",
-      "integrity": "sha512-RGt5k2JeBqrmnwRM0VOgWFiGKlGJMfmiif/4JvdaEqhMJ+xqe/9cfDYzXfn33ke2eWixsAbjEbRfy8XbaN9nTw==",
+      "version": "0.9.7",
+      "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.7.tgz",
+      "integrity": "sha512-bezL9ItUWUGHTm1bI/XzIgiiZbhXpsC7uxk4UxFPmcVJwJsDc3ayZ99oXxAaK+3Rbg/IoqrHckA6CwmkCsbaSA==",
       "dev": true,
       "requires": {
         "core-js-pure": "^3.10.2",
@@ -5038,9 +5069,9 @@
       }
     },
     "apollo-server-core": {
-      "version": "2.25.3",
-      "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.3.tgz",
-      "integrity": "sha512-Midow3uZoJ9TjFNeCNSiWElTVZlvmB7G7tG6PPoxIR9Px90/v16Q6EzunDIO0rTJHRC3+yCwZkwtf8w2AcP0sA==",
+      "version": "2.25.4",
+      "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.4.tgz",
+      "integrity": "sha512-1u3BnFKbCt6F9SPM7ZoWmtHK6ubme56H8hV5Mjv3KbfSairU76SU79IhO05BEJE57S6N+ddb1rm3Uk93X6YeGw==",
       "dev": true,
       "requires": {
         "@apollographql/apollo-tools": "^0.5.0",
@@ -5092,12 +5123,13 @@
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz",
       "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "apollo-server-express": {
-      "version": "2.25.3",
-      "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.3.tgz",
-      "integrity": "sha512-tTFYn0oKH2qqLwVj7Ez2+MiKleXACODiGh5IxsB7VuYCPMAi9Yl8iUSlwTjQUvgCWfReZjnf0vFL2k5YhDlrtQ==",
+      "version": "2.25.4",
+      "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.4.tgz",
+      "integrity": "sha512-1Yd9DscLlCP5BhfAkNxg+aGcaTKnL36FyezdL7Iqc+KelON5PAyX8qpAChKL8Z3L2YHJzIk/Haf4dFJLKUjx9w==",
       "dev": true,
       "requires": {
         "@apollographql/graphql-playground-html": "1.6.27",
@@ -5107,7 +5139,7 @@
         "@types/express": "^4.17.12",
         "@types/express-serve-static-core": "^4.17.21",
         "accepts": "^1.3.5",
-        "apollo-server-core": "^2.25.3",
+        "apollo-server-core": "^2.25.4",
         "apollo-server-types": "^0.9.0",
         "body-parser": "^1.18.3",
         "cors": "^2.8.5",
@@ -5175,7 +5207,7 @@
     "archive-type": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz",
-      "integrity": "sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA=",
+      "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==",
       "dev": true,
       "requires": {
         "file-type": "^4.2.0"
@@ -5184,7 +5216,7 @@
         "file-type": {
           "version": "4.4.0",
           "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
-          "integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU=",
+          "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==",
           "dev": true
         }
       }
@@ -5209,54 +5241,45 @@
     "arr-diff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
-      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
-      "dev": true
+      "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA=="
     },
     "arr-flatten": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
-      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
-      "dev": true
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
     },
     "arr-union": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
-      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
-      "dev": true
+      "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q=="
     },
     "array-equal": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
-      "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
+      "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==",
       "dev": true
     },
     "array-find": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz",
-      "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=",
-      "dev": true
-    },
-    "array-find-index": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
-      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+      "integrity": "sha512-kO/vVCacW9mnpn3WPWbTVlEnOabK2L7LWi2HViURtCM46y1zb6I8UMjx4LgbiqadTgHnLInUronwn3ampNTJtQ==",
       "dev": true
     },
     "array-flatten": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
       "dev": true
     },
     "array-includes": {
-      "version": "3.1.4",
-      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz",
-      "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==",
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz",
+      "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "es-abstract": "^1.19.1",
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.19.5",
         "get-intrinsic": "^1.1.1",
         "is-string": "^1.0.7"
       }
@@ -5269,7 +5292,7 @@
     "array-union": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
-      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
       "dev": true,
       "requires": {
         "array-uniq": "^1.0.1"
@@ -5278,24 +5301,37 @@
     "array-uniq": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
-      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
       "dev": true
     },
     "array-unique": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
-      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
-      "dev": true
+      "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ=="
     },
     "array.prototype.flat": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz",
-      "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
+      "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3",
-        "es-abstract": "^1.19.0"
+        "es-abstract": "^1.19.2",
+        "es-shim-unscopables": "^1.0.0"
+      }
+    },
+    "array.prototype.reduce": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz",
+      "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.19.2",
+        "es-array-method-boxes-properly": "^1.0.0",
+        "is-string": "^1.0.7"
       }
     },
     "arrify": {
@@ -5316,7 +5352,6 @@
       "version": "5.4.1",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
       "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
-      "dev": true,
       "requires": {
         "bn.js": "^4.0.0",
         "inherits": "^2.0.1",
@@ -5327,8 +5362,7 @@
         "bn.js": {
           "version": "4.12.0",
           "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
-          "dev": true
+          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         }
       }
     },
@@ -5336,7 +5370,6 @@
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
       "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
-      "dev": true,
       "requires": {
         "object-assign": "^4.1.1",
         "util": "0.10.3"
@@ -5345,14 +5378,12 @@
         "inherits": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
-          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
-          "dev": true
+          "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA=="
         },
         "util": {
           "version": "0.10.3",
           "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
-          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
-          "dev": true,
+          "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==",
           "requires": {
             "inherits": "2.0.1"
           }
@@ -5362,13 +5393,12 @@
     "assert-plus": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+      "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
     },
     "assign-symbols": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
-      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
-      "dev": true
+      "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw=="
     },
     "ast-types": {
       "version": "0.13.3",
@@ -5383,9 +5413,9 @@
       "dev": true
     },
     "async": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
-      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+      "version": "2.6.4",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
       "dev": true,
       "requires": {
         "lodash": "^4.17.14"
@@ -5397,12 +5427,6 @@
       "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
       "dev": true
     },
-    "async-foreach": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
-      "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
-      "dev": true
-    },
     "async-limiter": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
@@ -5427,20 +5451,19 @@
       }
     },
     "async-validator": {
-      "version": "3.5.2",
-      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz",
-      "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ=="
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
     },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "atob": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
-      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
-      "dev": true
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
     },
     "autoprefixer": {
       "version": "9.8.8",
@@ -5457,10 +5480,15 @@
         "postcss-value-parser": "^4.1.0"
       }
     },
+    "available-typed-arrays": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+      "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
+    },
     "aws-sign2": {
       "version": "0.7.0",
       "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
-      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+      "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
     },
     "aws4": {
       "version": "1.11.0",
@@ -5478,7 +5506,7 @@
     "babel-code-frame": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
-      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+      "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==",
       "dev": true,
       "requires": {
         "chalk": "^1.1.3",
@@ -5489,13 +5517,13 @@
         "ansi-styles": {
           "version": "2.2.1",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
           "dev": true
         },
         "chalk": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
           "dev": true,
           "requires": {
             "ansi-styles": "^2.2.1",
@@ -5508,13 +5536,13 @@
         "js-tokens": {
           "version": "3.0.2",
           "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
-          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==",
           "dev": true
         },
         "supports-color": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
           "dev": true
         }
       }
@@ -5523,7 +5551,8 @@
       "version": "7.0.0-bridge.0",
       "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
       "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "babel-eslint": {
       "version": "10.1.0",
@@ -5540,12 +5569,12 @@
       },
       "dependencies": {
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -5688,9 +5717,9 @@
           "dev": true
         },
         "istanbul-lib-instrument": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz",
-          "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz",
+          "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==",
           "dev": true,
           "requires": {
             "@babel/core": "^7.12.3",
@@ -5764,13 +5793,13 @@
           }
         },
         "micromatch": {
-          "version": "4.0.4",
-          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
-          "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+          "version": "4.0.5",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+          "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
           "dev": true,
           "requires": {
-            "braces": "^3.0.1",
-            "picomatch": "^2.2.3"
+            "braces": "^3.0.2",
+            "picomatch": "^2.3.1"
           }
         },
         "semver": {
@@ -5802,9 +5831,9 @@
       }
     },
     "babel-loader": {
-      "version": "8.2.4",
-      "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.4.tgz",
-      "integrity": "sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==",
+      "version": "8.2.5",
+      "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
+      "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==",
       "dev": true,
       "requires": {
         "find-cache-dir": "^3.3.1",
@@ -5838,7 +5867,7 @@
     "babel-messages": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
-      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+      "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==",
       "dev": true,
       "requires": {
         "babel-runtime": "^6.22.0"
@@ -5854,13 +5883,12 @@
       }
     },
     "babel-plugin-import": {
-      "version": "1.13.3",
-      "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.13.3.tgz",
-      "integrity": "sha512-1qCWdljJOrDRH/ybaCZuDgySii4yYrtQ8OJQwrcDqdt0y67N30ng3X3nABg6j7gR7qUJgcMa9OMhc4AGViDwWw==",
+      "version": "1.13.5",
+      "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.13.5.tgz",
+      "integrity": "sha512-IkqnoV+ov1hdJVofly9pXRJmeDm9EtROfrc5i6eII0Hix2xMs5FEm8FG3ExMvazbnZBbgHIt6qdO8And6lCloQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-imports": "^7.0.0",
-        "@babel/runtime": "^7.0.0"
+        "@babel/helper-module-imports": "^7.0.0"
       }
     },
     "babel-plugin-istanbul": {
@@ -5906,7 +5934,7 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         }
       }
@@ -5978,7 +6006,7 @@
     "babel-plugin-transform-strict-mode": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
-      "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+      "integrity": "sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw==",
       "dev": true,
       "requires": {
         "babel-runtime": "^6.22.0",
@@ -6018,7 +6046,7 @@
     "babel-runtime": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
-      "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
       "dev": true,
       "requires": {
         "core-js": "^2.4.0",
@@ -6042,7 +6070,7 @@
     "babel-template": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
-      "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+      "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==",
       "dev": true,
       "requires": {
         "babel-runtime": "^6.26.0",
@@ -6055,7 +6083,7 @@
     "babel-traverse": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
-      "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+      "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==",
       "dev": true,
       "requires": {
         "babel-code-frame": "^6.26.0",
@@ -6087,7 +6115,7 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         }
       }
@@ -6095,7 +6123,7 @@
     "babel-types": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
-      "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+      "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==",
       "dev": true,
       "requires": {
         "babel-runtime": "^6.26.0",
@@ -6107,7 +6135,7 @@
         "to-fast-properties": {
           "version": "1.0.3",
           "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
-          "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+          "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==",
           "dev": true
         }
       }
@@ -6121,7 +6149,7 @@
     "backo2": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
-      "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+      "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==",
       "dev": true
     },
     "balanced-match": {
@@ -6133,7 +6161,6 @@
       "version": "0.11.2",
       "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
       "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
-      "dev": true,
       "requires": {
         "cache-base": "^1.0.1",
         "class-utils": "^0.3.5",
@@ -6147,8 +6174,7 @@
         "define-property": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
-          "dev": true,
+          "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
           "requires": {
             "is-descriptor": "^1.0.0"
           }
@@ -6157,7 +6183,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
           "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -6166,7 +6191,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
           "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -6175,7 +6199,6 @@
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
           "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
             "is-data-descriptor": "^1.0.0",
@@ -6187,19 +6210,18 @@
     "base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
-      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-      "dev": true
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
     },
     "batch": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
-      "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+      "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
       "dev": true
     },
     "bcrypt-pbkdf": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
-      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
       "requires": {
         "tweetnacl": "^0.14.3"
       }
@@ -6246,43 +6268,54 @@
         "safe-buffer": "^5.1.1"
       }
     },
-    "block-stream": {
-      "version": "0.0.9",
-      "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
-      "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
-      "dev": true,
+    "block-stream2": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz",
+      "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==",
       "requires": {
-        "inherits": "~2.0.0"
+        "readable-stream": "^3.4.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
-      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
-      "dev": true
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
     },
     "bn.js": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
-      "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==",
-      "dev": true
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
+      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
     },
     "body-parser": {
-      "version": "1.19.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
-      "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
+      "version": "1.20.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+      "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
       "dev": true,
       "requires": {
         "bytes": "3.1.2",
         "content-type": "~1.0.4",
         "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "http-errors": "1.8.1",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
-        "on-finished": "~2.3.0",
-        "qs": "6.9.7",
-        "raw-body": "2.4.3",
-        "type-is": "~1.6.18"
+        "on-finished": "2.4.1",
+        "qs": "6.10.3",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
       },
       "dependencies": {
         "debug": {
@@ -6294,6 +6327,25 @@
             "ms": "2.0.0"
           }
         },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+          "dev": true
+        },
+        "http-errors": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+          "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+          "dev": true,
+          "requires": {
+            "depd": "2.0.0",
+            "inherits": "2.0.4",
+            "setprototypeof": "1.2.0",
+            "statuses": "2.0.1",
+            "toidentifier": "1.0.1"
+          }
+        },
         "iconv-lite": {
           "version": "0.4.24",
           "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -6306,13 +6358,22 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         },
         "qs": {
-          "version": "6.9.7",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
-          "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
+          "version": "6.10.3",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+          "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+          "dev": true,
+          "requires": {
+            "side-channel": "^1.0.4"
+          }
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
           "dev": true
         }
       }
@@ -6320,7 +6381,7 @@
     "bonjour": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
-      "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+      "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==",
       "dev": true,
       "requires": {
         "array-flatten": "^2.1.0",
@@ -6342,7 +6403,7 @@
     "boolbase": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
-      "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
+      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
       "dev": true
     },
     "boxen": {
@@ -6412,7 +6473,6 @@
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
       "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
-      "dev": true,
       "requires": {
         "arr-flatten": "^1.1.0",
         "array-unique": "^0.3.2",
@@ -6429,8 +6489,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "requires": {
             "is-extendable": "^0.1.0"
           }
@@ -6440,8 +6499,12 @@
     "brorand": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
-      "dev": true
+      "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
+    },
+    "browser-or-node": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
+      "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg=="
     },
     "browser-process-hrtime": {
       "version": "1.0.0",
@@ -6461,7 +6524,7 @@
         "resolve": {
           "version": "1.1.7",
           "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
-          "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+          "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==",
           "dev": true
         }
       }
@@ -6470,7 +6533,6 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
       "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
-      "dev": true,
       "requires": {
         "buffer-xor": "^1.0.3",
         "cipher-base": "^1.0.0",
@@ -6484,7 +6546,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
       "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
-      "dev": true,
       "requires": {
         "browserify-aes": "^1.0.4",
         "browserify-des": "^1.0.0",
@@ -6495,7 +6556,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
       "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
-      "dev": true,
       "requires": {
         "cipher-base": "^1.0.1",
         "des.js": "^1.0.0",
@@ -6507,7 +6567,6 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
       "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
-      "dev": true,
       "requires": {
         "bn.js": "^5.0.0",
         "randombytes": "^2.0.1"
@@ -6517,7 +6576,6 @@
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
       "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
-      "dev": true,
       "requires": {
         "bn.js": "^5.1.1",
         "browserify-rsa": "^4.0.1",
@@ -6534,7 +6592,6 @@
           "version": "3.6.0",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
           "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-          "dev": true,
           "requires": {
             "inherits": "^2.0.3",
             "string_decoder": "^1.1.1",
@@ -6544,8 +6601,7 @@
         "safe-buffer": {
           "version": "5.2.1",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-          "dev": true
+          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
         }
       }
     },
@@ -6553,30 +6609,20 @@
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
       "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
-      "dev": true,
       "requires": {
         "pako": "~1.0.5"
       }
     },
     "browserslist": {
-      "version": "4.20.2",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
-      "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
+      "version": "4.21.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz",
+      "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==",
       "dev": true,
       "requires": {
-        "caniuse-lite": "^1.0.30001317",
-        "electron-to-chromium": "^1.4.84",
-        "escalade": "^3.1.1",
-        "node-releases": "^2.0.2",
-        "picocolors": "^1.0.0"
-      },
-      "dependencies": {
-        "picocolors": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-          "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
-          "dev": true
-        }
+        "caniuse-lite": "^1.0.30001359",
+        "electron-to-chromium": "^1.4.172",
+        "node-releases": "^2.0.5",
+        "update-browserslist-db": "^1.0.4"
       }
     },
     "bs-logger": {
@@ -6626,20 +6672,18 @@
     "buffer-crc32": {
       "version": "0.2.13",
       "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-      "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
-      "dev": true
+      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="
     },
     "buffer-fill": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
-      "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+      "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
       "dev": true
     },
     "buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
-      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
-      "dev": true
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
     "buffer-indexof": {
       "version": "1.1.1",
@@ -6656,19 +6700,17 @@
     "buffer-xor": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
-      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
-      "dev": true
+      "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
     },
     "builtin-status-codes": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
-      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
-      "dev": true
+      "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="
     },
     "builtins": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
-      "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
+      "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ=="
     },
     "busboy": {
       "version": "0.3.1",
@@ -6714,7 +6756,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
       "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
-      "dev": true,
       "requires": {
         "collection-visit": "^1.0.0",
         "component-emitter": "^1.2.1",
@@ -6825,7 +6866,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "get-intrinsic": "^1.0.2"
@@ -6834,13 +6874,13 @@
     "call-me-maybe": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
-      "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
+      "integrity": "sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==",
       "dev": true
     },
     "caller-callsite": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
-      "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+      "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==",
       "dev": true,
       "requires": {
         "callsites": "^2.0.0"
@@ -6849,7 +6889,7 @@
         "callsites": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
-          "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+          "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==",
           "dev": true
         }
       }
@@ -6857,7 +6897,7 @@
     "caller-path": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
-      "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+      "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==",
       "dev": true,
       "requires": {
         "caller-callsite": "^2.0.0"
@@ -6872,7 +6912,7 @@
     "camel-case": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
-      "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+      "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
       "dev": true,
       "requires": {
         "no-case": "^2.2.0",
@@ -6884,24 +6924,6 @@
       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
       "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
     },
-    "camelcase-keys": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
-      "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
-      "dev": true,
-      "requires": {
-        "camelcase": "^2.0.0",
-        "map-obj": "^1.0.0"
-      },
-      "dependencies": {
-        "camelcase": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
-          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
-          "dev": true
-        }
-      }
-    },
     "caniuse-api": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -6915,9 +6937,9 @@
       }
     },
     "caniuse-lite": {
-      "version": "1.0.30001320",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz",
-      "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==",
+      "version": "1.0.30001361",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz",
+      "integrity": "sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==",
       "dev": true
     },
     "capture-exit": {
@@ -6938,7 +6960,7 @@
     "caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
-      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+      "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
     },
     "caw": {
       "version": "2.0.1",
@@ -6961,12 +6983,6 @@
         "supports-color": "^7.1.0"
       }
     },
-    "charcodes": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/charcodes/-/charcodes-0.2.0.tgz",
-      "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==",
-      "dev": true
-    },
     "chardet": {
       "version": "0.7.0",
       "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@@ -6976,7 +6992,7 @@
     "charenc": {
       "version": "0.0.2",
       "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
-      "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+      "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
     },
     "chart.js": {
       "version": "3.9.1",
@@ -6986,7 +7002,8 @@
     "chartjs-adapter-moment": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.0.tgz",
-      "integrity": "sha512-PqlerEvQcc5hZLQ/NQWgBxgVQ4TRdvkW3c/t+SUEQSj78ia3hgLkf2VZ2yGJtltNbEEFyYGm+cA6XXevodYvWA=="
+      "integrity": "sha512-PqlerEvQcc5hZLQ/NQWgBxgVQ4TRdvkW3c/t+SUEQSj78ia3hgLkf2VZ2yGJtltNbEEFyYGm+cA6XXevodYvWA==",
+      "requires": {}
     },
     "check-types": {
       "version": "8.0.3",
@@ -7062,8 +7079,7 @@
     "chrome-trace-event": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
-      "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
-      "dev": true
+      "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
     },
     "ci-info": {
       "version": "2.0.0",
@@ -7073,13 +7089,12 @@
     "cint": {
       "version": "8.2.1",
       "resolved": "https://registry.npmjs.org/cint/-/cint-8.2.1.tgz",
-      "integrity": "sha1-cDhrG0jidz0NYxZqVa/5TvRFahI="
+      "integrity": "sha512-gyWqJHXgDFPNx7PEyFJotutav+al92TTC3dWlMFyTETlOyKBQMZb7Cetqmj3GlrnSILHwSJRwf4mIGzc7C5lXw=="
     },
     "cipher-base": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
       "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -7089,7 +7104,6 @@
       "version": "0.3.6",
       "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
       "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
-      "dev": true,
       "requires": {
         "arr-union": "^3.1.0",
         "define-property": "^0.2.5",
@@ -7100,14 +7114,18 @@
         "define-property": {
           "version": "0.2.5",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
+          "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
           "requires": {
             "is-descriptor": "^0.1.0"
           }
         }
       }
     },
+    "classnames": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+      "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+    },
     "clean-css": {
       "version": "4.2.4",
       "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
@@ -7130,7 +7148,7 @@
     "cli-cursor": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
-      "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+      "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
       "dev": true,
       "requires": {
         "restore-cursor": "^2.0.0"
@@ -7179,9 +7197,9 @@
       "dev": true
     },
     "clipboard": {
-      "version": "2.0.10",
-      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
-      "integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
       "requires": {
         "good-listener": "^1.2.2",
         "select": "^1.1.2",
@@ -7258,7 +7276,7 @@
     "clone": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
-      "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
       "dev": true
     },
     "clone-deep": {
@@ -7286,7 +7304,7 @@
     "clone-response": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
-      "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
+      "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==",
       "requires": {
         "mimic-response": "^1.0.0"
       }
@@ -7315,7 +7333,7 @@
     "co": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
-      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+      "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
       "dev": true
     },
     "coa": {
@@ -7361,13 +7379,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -7384,13 +7402,12 @@
     "code-point-at": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+      "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="
     },
     "collection-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
-      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
-      "dev": true,
+      "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==",
       "requires": {
         "map-visit": "^1.0.0",
         "object-visit": "^1.0.0"
@@ -7418,7 +7435,7 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         }
       }
@@ -7437,9 +7454,9 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
     },
     "color-string": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz",
-      "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==",
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
       "dev": true,
       "requires": {
         "color-name": "^1.0.0",
@@ -7449,7 +7466,7 @@
     "colors": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
-      "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs="
+      "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw=="
     },
     "combined-stream": {
       "version": "1.0.8",
@@ -7467,14 +7484,12 @@
     "commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
-      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
-      "dev": true
+      "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
     },
     "component-emitter": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
-      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
-      "dev": true
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
     },
     "compressible": {
       "version": "2.0.18",
@@ -7503,7 +7518,7 @@
         "bytes": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
-          "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+          "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
           "dev": true
         },
         "debug": {
@@ -7518,7 +7533,7 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         }
       }
@@ -7531,13 +7546,12 @@
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
     "concat-stream": {
       "version": "1.6.2",
       "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
       "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
-      "dev": true,
       "requires": {
         "buffer-from": "^1.0.0",
         "inherits": "^2.0.3",
@@ -7548,7 +7562,7 @@
     "condense-newlines": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz",
-      "integrity": "sha1-PemFVTE5R10yUCyDsC9gaE0kxV8=",
+      "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==",
       "dev": true,
       "requires": {
         "extend-shallow": "^2.0.1",
@@ -7559,7 +7573,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "dev": true,
           "requires": {
             "is-extendable": "^0.1.0"
@@ -7568,7 +7582,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "dev": true,
           "requires": {
             "is-buffer": "^1.1.5"
@@ -7608,13 +7622,12 @@
     "console-browserify": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
-      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
-      "dev": true
+      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA=="
     },
     "console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+      "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
     },
     "consolidate": {
       "version": "0.15.1",
@@ -7628,8 +7641,7 @@
     "constants-browserify": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
-      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
-      "dev": true
+      "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ=="
     },
     "content-disposition": {
       "version": "0.5.4",
@@ -7664,15 +7676,15 @@
       }
     },
     "cookie": {
-      "version": "0.4.2",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
-      "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
       "dev": true
     },
     "cookie-signature": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
-      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
       "dev": true
     },
     "copy-anything": {
@@ -7688,7 +7700,6 @@
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
       "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
-      "dev": true,
       "requires": {
         "aproba": "^1.1.1",
         "fs-write-stream-atomic": "^1.0.8",
@@ -7699,19 +7710,17 @@
       },
       "dependencies": {
         "mkdirp": {
-          "version": "0.5.5",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-          "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
-          "dev": true,
+          "version": "0.5.6",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+          "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
           "requires": {
-            "minimist": "^1.2.5"
+            "minimist": "^1.2.6"
           }
         },
         "rimraf": {
           "version": "2.7.1",
           "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
           "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-          "dev": true,
           "requires": {
             "glob": "^7.1.3"
           }
@@ -7721,8 +7730,15 @@
     "copy-descriptor": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
-      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
-      "dev": true
+      "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw=="
+    },
+    "copy-to-clipboard": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
+      "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
+      "requires": {
+        "toggle-selection": "^1.0.6"
+      }
     },
     "copy-webpack-plugin": {
       "version": "5.1.2",
@@ -7776,7 +7792,7 @@
         "globby": {
           "version": "7.1.1",
           "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
-          "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
+          "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==",
           "dev": true,
           "requires": {
             "array-union": "^1.0.1",
@@ -7854,7 +7870,7 @@
         "slash": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
-          "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+          "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==",
           "dev": true
         },
         "ssri": {
@@ -7881,17 +7897,17 @@
       }
     },
     "core-js": {
-      "version": "3.21.1",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz",
-      "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig=="
+      "version": "3.23.3",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.23.3.tgz",
+      "integrity": "sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q=="
     },
     "core-js-compat": {
-      "version": "3.21.1",
-      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz",
-      "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==",
+      "version": "3.23.3",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.3.tgz",
+      "integrity": "sha512-WSzUs2h2vvmKsacLHNTdpyOC9k43AEhcGoFlVgCY4L7aw98oSBKtPL6vD0/TqZjRWRQYdDSLkzZIni4Crbbiqw==",
       "dev": true,
       "requires": {
-        "browserslist": "^4.19.1",
+        "browserslist": "^4.21.0",
         "semver": "7.0.0"
       },
       "dependencies": {
@@ -7904,9 +7920,9 @@
       }
     },
     "core-js-pure": {
-      "version": "3.21.1",
-      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz",
-      "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==",
+      "version": "3.23.3",
+      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.3.tgz",
+      "integrity": "sha512-XpoouuqIj4P+GWtdyV8ZO3/u4KftkeDVMfvp+308eGMhCrA3lVDSmAxO0c6GGOcmgVlaKDrgWVMo49h2ab/TDA==",
       "dev": true
     },
     "core-util-is": {
@@ -7939,7 +7955,7 @@
         "parse-json": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
-          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
           "dev": true,
           "requires": {
             "error-ex": "^1.3.1",
@@ -7952,7 +7968,6 @@
       "version": "4.0.4",
       "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
       "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
-      "dev": true,
       "requires": {
         "bn.js": "^4.1.0",
         "elliptic": "^6.5.3"
@@ -7961,8 +7976,7 @@
         "bn.js": {
           "version": "4.12.0",
           "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
-          "dev": true
+          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         }
       }
     },
@@ -7970,7 +7984,6 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
       "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
-      "dev": true,
       "requires": {
         "cipher-base": "^1.0.1",
         "inherits": "^2.0.1",
@@ -7983,7 +7996,6 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
       "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
-      "dev": true,
       "requires": {
         "cipher-base": "^1.0.3",
         "create-hash": "^1.1.0",
@@ -7993,6 +8005,11 @@
         "sha.js": "^2.4.8"
       }
     },
+    "cronstrue": {
+      "version": "2.26.0",
+      "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.26.0.tgz",
+      "integrity": "sha512-M1VdV3hpBAsd1Zzvqcvf63wgDpcwCuS4WiNEVFpJ0s33MGO2sVDTfswYq0EPypCmESrCzmgL8h68DTzJuSDbVA=="
+    },
     "cross-spawn": {
       "version": "6.0.5",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -8026,13 +8043,12 @@
     "crypt": {
       "version": "0.0.2",
       "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
-      "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+      "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
     },
     "crypto-browserify": {
       "version": "3.12.0",
       "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
       "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
-      "dev": true,
       "requires": {
         "browserify-cipher": "^1.0.0",
         "browserify-sign": "^4.0.0",
@@ -8067,7 +8083,7 @@
     "css-color-names": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
-      "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
+      "integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==",
       "dev": true
     },
     "css-declaration-sorter": {
@@ -8172,7 +8188,7 @@
     "cssfilter": {
       "version": "0.0.10",
       "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
-      "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=",
+      "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
       "dev": true
     },
     "cssnano": {
@@ -8228,13 +8244,13 @@
     "cssnano-util-get-arguments": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz",
-      "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=",
+      "integrity": "sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw==",
       "dev": true
     },
     "cssnano-util-get-match": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz",
-      "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=",
+      "integrity": "sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw==",
       "dev": true
     },
     "cssnano-util-raw-cache": {
@@ -8314,25 +8330,15 @@
         "ndjson": "^1.4.0"
       }
     },
-    "currently-unhandled": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
-      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
-      "dev": true,
-      "requires": {
-        "array-find-index": "^1.0.1"
-      }
-    },
     "cyclist": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
-      "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
-      "dev": true
+      "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
     },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
       "requires": {
         "assert-plus": "^1.0.0"
       }
@@ -8351,7 +8357,7 @@
         "tr46": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
-          "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+          "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
           "dev": true,
           "requires": {
             "punycode": "^2.1.0"
@@ -8376,10 +8382,23 @@
         }
       }
     },
+    "date-fns": {
+      "version": "2.30.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+      "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+      "requires": {
+        "@babel/runtime": "^7.21.0"
+      }
+    },
+    "dayjs": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
+      "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
+    },
     "deasync": {
-      "version": "0.1.24",
-      "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.24.tgz",
-      "integrity": "sha512-i98vg42xNfRZCymummMAN0rIcQ1gZFinSe3btvPIvy6JFTaeHcumeKybRo2HTv86nasfmT0nEgAn2ggLZhOCVA==",
+      "version": "0.1.26",
+      "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.26.tgz",
+      "integrity": "sha512-YKw0BmJSWxkjtQsbgn6Q9CHSWB7DKMen8vKrgyC006zy0UZ6nWyGidB0IzZgqkVRkOglAeUaFtiRTeLyel72bg==",
       "dev": true,
       "requires": {
         "bindings": "^1.5.0",
@@ -8397,14 +8416,13 @@
     "decamelize": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
       "dev": true
     },
     "decode-uri-component": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
-      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
-      "dev": true
+      "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
     },
     "decompress": {
       "version": "4.2.1",
@@ -8434,7 +8452,7 @@
             "pify": {
               "version": "3.0.0",
               "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-              "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+              "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
               "dev": true
             }
           }
@@ -8442,7 +8460,7 @@
         "pify": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
           "dev": true
         }
       }
@@ -8450,7 +8468,7 @@
     "decompress-response": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
-      "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+      "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==",
       "requires": {
         "mimic-response": "^1.0.0"
       }
@@ -8469,7 +8487,7 @@
         "file-type": {
           "version": "5.2.0",
           "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
-          "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
+          "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==",
           "dev": true
         }
       }
@@ -8509,7 +8527,7 @@
         "file-type": {
           "version": "5.2.0",
           "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
-          "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
+          "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==",
           "dev": true
         }
       }
@@ -8517,7 +8535,7 @@
     "decompress-unzip": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz",
-      "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=",
+      "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==",
       "dev": true,
       "requires": {
         "file-type": "^3.8.0",
@@ -8529,13 +8547,13 @@
         "file-type": {
           "version": "3.9.0",
           "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
-          "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
+          "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==",
           "dev": true
         },
         "get-stream": {
           "version": "2.3.1",
           "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
-          "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
+          "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==",
           "dev": true,
           "requires": {
             "object-assign": "^4.0.1",
@@ -8545,7 +8563,7 @@
         "pify": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
           "dev": true
         }
       }
@@ -8690,7 +8708,7 @@
     "defaults": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
-      "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+      "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==",
       "dev": true,
       "requires": {
         "clone": "^1.0.2"
@@ -8702,19 +8720,19 @@
       "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ=="
     },
     "define-properties": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
-      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+      "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
       "dev": true,
       "requires": {
-        "object-keys": "^1.0.12"
+        "has-property-descriptors": "^1.0.0",
+        "object-keys": "^1.1.1"
       }
     },
     "define-property": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
       "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
-      "dev": true,
       "requires": {
         "is-descriptor": "^1.0.2",
         "isobject": "^3.0.1"
@@ -8724,7 +8742,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
           "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -8733,7 +8750,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
           "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -8742,7 +8758,6 @@
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
           "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
             "is-data-descriptor": "^1.0.0",
@@ -8769,7 +8784,7 @@
         "globby": {
           "version": "6.1.0",
           "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
-          "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+          "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==",
           "dev": true,
           "requires": {
             "array-union": "^1.0.1",
@@ -8782,7 +8797,7 @@
             "pify": {
               "version": "2.3.0",
               "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-              "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+              "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
               "dev": true
             }
           }
@@ -8813,7 +8828,7 @@
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
     },
     "delegate": {
       "version": "3.2.0",
@@ -8823,39 +8838,38 @@
     "delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+      "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
     },
     "depd": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
-      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="
     },
     "deprecated-decorator": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz",
-      "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=",
+      "integrity": "sha512-MHidOOnCHGlZDKsI21+mbIIhf4Fff+hhCTB7gtVg4uoIqjcrTZc5v6M+GS2zVI0sV7PqK415rb8XaOSQsQkHOw==",
       "dev": true
     },
     "des.js": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
       "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.1",
         "minimalistic-assert": "^1.0.0"
       }
     },
     "destroy": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
-      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
       "dev": true
     },
     "detect-newline": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
-      "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
+      "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==",
       "dev": true
     },
     "detect-node": {
@@ -8883,7 +8897,6 @@
       "version": "5.0.3",
       "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
       "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
-      "dev": true,
       "requires": {
         "bn.js": "^4.1.0",
         "miller-rabin": "^4.0.0",
@@ -8893,8 +8906,7 @@
         "bn.js": {
           "version": "4.12.0",
           "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
-          "dev": true
+          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         }
       }
     },
@@ -8910,7 +8922,7 @@
     "dns-equal": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
-      "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
+      "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==",
       "dev": true
     },
     "dns-packet": {
@@ -8926,7 +8938,7 @@
     "dns-txt": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
-      "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+      "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==",
       "dev": true,
       "requires": {
         "buffer-indexof": "^1.0.0"
@@ -8942,9 +8954,9 @@
       }
     },
     "dom-align": {
-      "version": "1.12.2",
-      "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz",
-      "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg=="
+      "version": "1.12.3",
+      "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.3.tgz",
+      "integrity": "sha512-Gj9hZN3a07cbR6zviMUBOMPdWxYhbMI+x+WS0NAIu2zFZmbK8ys9R79g+iG9qLnlCwpFoaB+fKy8Pdv470GsPA=="
     },
     "dom-converter": {
       "version": "0.2.0",
@@ -8971,9 +8983,9 @@
       },
       "dependencies": {
         "domelementtype": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
-          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+          "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
           "dev": true
         }
       }
@@ -8981,8 +8993,7 @@
     "domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
-      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
-      "dev": true
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA=="
     },
     "domelementtype": {
       "version": "1.3.1",
@@ -9017,9 +9028,9 @@
       },
       "dependencies": {
         "domelementtype": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
-          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+          "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
           "dev": true
         }
       }
@@ -9083,7 +9094,7 @@
         "cacheable-request": {
           "version": "2.1.4",
           "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz",
-          "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=",
+          "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==",
           "dev": true,
           "requires": {
             "clone-response": "1.0.2",
@@ -9098,7 +9109,7 @@
             "lowercase-keys": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
-              "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=",
+              "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==",
               "dev": true
             }
           }
@@ -9106,7 +9117,7 @@
         "get-stream": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+          "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==",
           "dev": true
         },
         "got": {
@@ -9143,7 +9154,7 @@
         "into-stream": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
-          "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=",
+          "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==",
           "dev": true,
           "requires": {
             "from2": "^2.1.1",
@@ -9188,7 +9199,7 @@
         "sort-keys": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
-          "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+          "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==",
           "dev": true,
           "requires": {
             "is-plain-obj": "^1.0.0"
@@ -9216,13 +9227,12 @@
     "duplexer3": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
-      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
+      "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA=="
     },
     "duplexify": {
       "version": "3.7.1",
       "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
       "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
-      "dev": true,
       "requires": {
         "end-of-stream": "^1.0.0",
         "inherits": "^2.0.1",
@@ -9239,7 +9249,7 @@
     "ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
-      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
       "requires": {
         "jsbn": "~0.1.0",
         "safer-buffer": "^2.1.0"
@@ -9282,7 +9292,7 @@
         "yallist": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
           "dev": true
         }
       }
@@ -9290,7 +9300,7 @@
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
       "dev": true
     },
     "ejs": {
@@ -9300,16 +9310,15 @@
       "dev": true
     },
     "electron-to-chromium": {
-      "version": "1.4.92",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.92.tgz",
-      "integrity": "sha512-YAVbvQIcDE/IJ/vzDMjD484/hsRbFPW2qXJPaYTfOhtligmfYEYOep+5QojpaEU9kq6bMvNeC2aG7arYvTHYsA==",
+      "version": "1.4.176",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.176.tgz",
+      "integrity": "sha512-92JdgyRlcNDwuy75MjuFSb3clt6DGJ2IXSpg0MCjKd3JV9eSmuUAIyWiGAp/EtT0z2D4rqbYqThQLV90maH3Zw==",
       "dev": true
     },
     "elliptic": {
       "version": "6.5.4",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
       "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
-      "dev": true,
       "requires": {
         "bn.js": "^4.11.9",
         "brorand": "^1.1.0",
@@ -9323,8 +9332,7 @@
         "bn.js": {
           "version": "4.12.0",
           "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
-          "dev": true
+          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         }
       }
     },
@@ -9341,7 +9349,7 @@
     "encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
       "dev": true
     },
     "encoding": {
@@ -9365,7 +9373,6 @@
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz",
       "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==",
-      "dev": true,
       "requires": {
         "graceful-fs": "^4.1.2",
         "memory-fs": "^0.5.0",
@@ -9376,7 +9383,6 @@
           "version": "0.5.0",
           "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
           "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
-          "dev": true,
           "requires": {
             "errno": "^0.1.3",
             "readable-stream": "^2.0.1"
@@ -9387,7 +9393,7 @@
     "enquire.js": {
       "version": "2.1.6",
       "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz",
-      "integrity": "sha1-PoeAybi4NQhMP2DhZtvDwqPImBQ="
+      "integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw=="
     },
     "entities": {
       "version": "2.2.0",
@@ -9415,7 +9421,6 @@
       "version": "0.1.8",
       "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
       "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
-      "dev": true,
       "requires": {
         "prr": "~1.0.1"
       }
@@ -9430,40 +9435,58 @@
       }
     },
     "error-stack-parser": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz",
-      "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==",
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
+      "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
       "dev": true,
       "requires": {
-        "stackframe": "^1.1.1"
+        "stackframe": "^1.3.4"
       }
     },
     "es-abstract": {
-      "version": "1.19.1",
-      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
-      "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
+      "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "es-to-primitive": "^1.2.1",
         "function-bind": "^1.1.1",
+        "function.prototype.name": "^1.1.5",
         "get-intrinsic": "^1.1.1",
         "get-symbol-description": "^1.0.0",
         "has": "^1.0.3",
-        "has-symbols": "^1.0.2",
+        "has-property-descriptors": "^1.0.0",
+        "has-symbols": "^1.0.3",
         "internal-slot": "^1.0.3",
         "is-callable": "^1.2.4",
-        "is-negative-zero": "^2.0.1",
+        "is-negative-zero": "^2.0.2",
         "is-regex": "^1.1.4",
-        "is-shared-array-buffer": "^1.0.1",
+        "is-shared-array-buffer": "^1.0.2",
         "is-string": "^1.0.7",
-        "is-weakref": "^1.0.1",
-        "object-inspect": "^1.11.0",
+        "is-weakref": "^1.0.2",
+        "object-inspect": "^1.12.0",
         "object-keys": "^1.1.1",
         "object.assign": "^4.1.2",
-        "string.prototype.trimend": "^1.0.4",
-        "string.prototype.trimstart": "^1.0.4",
-        "unbox-primitive": "^1.0.1"
+        "regexp.prototype.flags": "^1.4.3",
+        "string.prototype.trimend": "^1.0.5",
+        "string.prototype.trimstart": "^1.0.5",
+        "unbox-primitive": "^1.0.2"
+      }
+    },
+    "es-array-method-boxes-properly": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
+      "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
+      "dev": true
+    },
+    "es-shim-unscopables": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+      "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
       }
     },
     "es-to-primitive": {
@@ -9477,6 +9500,11 @@
         "is-symbol": "^1.0.2"
       }
     },
+    "es6-error": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
+    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -9491,13 +9519,13 @@
     "escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
       "dev": true
     },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
     },
     "escodegen": {
       "version": "1.14.3",
@@ -9595,7 +9623,7 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "eslint-scope": {
@@ -9629,7 +9657,7 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "import-fresh": {
@@ -9693,7 +9721,8 @@
       "version": "14.1.1",
       "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz",
       "integrity": "sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "eslint-import-resolver-node": {
       "version": "0.3.6",
@@ -9715,12 +9744,12 @@
           }
         },
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -9757,7 +9786,7 @@
         "enhanced-resolve": {
           "version": "0.9.1",
           "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
-          "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=",
+          "integrity": "sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw==",
           "dev": true,
           "requires": {
             "graceful-fs": "^4.1.2",
@@ -9768,22 +9797,22 @@
         "memory-fs": {
           "version": "0.2.0",
           "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz",
-          "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=",
+          "integrity": "sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng==",
           "dev": true
         },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         },
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -9797,7 +9826,7 @@
         "tapable": {
           "version": "0.1.10",
           "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz",
-          "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=",
+          "integrity": "sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==",
           "dev": true
         }
       }
@@ -9868,7 +9897,7 @@
         "find-up": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
-          "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+          "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
           "dev": true,
           "requires": {
             "locate-path": "^2.0.0"
@@ -9877,7 +9906,7 @@
         "locate-path": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
-          "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+          "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
           "dev": true,
           "requires": {
             "p-locate": "^2.0.0",
@@ -9896,7 +9925,7 @@
         "p-locate": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
-          "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+          "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
           "dev": true,
           "requires": {
             "p-limit": "^1.1.0"
@@ -9905,13 +9934,13 @@
         "p-try": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
-          "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+          "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
           "dev": true
         },
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         }
       }
@@ -9944,9 +9973,9 @@
       }
     },
     "eslint-plugin-import": {
-      "version": "2.25.4",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz",
-      "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==",
+      "version": "2.26.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
+      "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
       "dev": true,
       "requires": {
         "array-includes": "^3.1.4",
@@ -9954,14 +9983,14 @@
         "debug": "^2.6.9",
         "doctrine": "^2.1.0",
         "eslint-import-resolver-node": "^0.3.6",
-        "eslint-module-utils": "^2.7.2",
+        "eslint-module-utils": "^2.7.3",
         "has": "^1.0.3",
-        "is-core-module": "^2.8.0",
+        "is-core-module": "^2.8.1",
         "is-glob": "^4.0.3",
-        "minimatch": "^3.0.4",
+        "minimatch": "^3.1.2",
         "object.values": "^1.1.5",
-        "resolve": "^1.20.0",
-        "tsconfig-paths": "^3.12.0"
+        "resolve": "^1.22.0",
+        "tsconfig-paths": "^3.14.1"
       },
       "dependencies": {
         "debug": {
@@ -9985,16 +10014,16 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         },
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -10031,12 +10060,12 @@
           "dev": true
         },
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -10059,7 +10088,8 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz",
       "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "eslint-plugin-vue": {
       "version": "7.20.0",
@@ -10094,7 +10124,6 @@
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
       "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
-      "dev": true,
       "requires": {
         "esrecurse": "^4.1.0",
         "estraverse": "^4.1.1"
@@ -10160,7 +10189,6 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
       "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
-      "dev": true,
       "requires": {
         "estraverse": "^5.2.0"
       },
@@ -10168,16 +10196,14 @@
         "estraverse": {
           "version": "5.3.0",
           "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-          "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-          "dev": true
+          "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
         }
       }
     },
     "estraverse": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-      "dev": true
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
     },
     "estree-walker": {
       "version": "2.0.2",
@@ -10193,7 +10219,7 @@
     "etag": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
       "dev": true
     },
     "event-pubsub": {
@@ -10211,23 +10237,18 @@
     "events": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
-      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
-      "dev": true
+      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
     },
     "eventsource": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
-      "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
-      "dev": true,
-      "requires": {
-        "original": "^1.0.0"
-      }
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
+      "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
+      "dev": true
     },
     "evp_bytestokey": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
       "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
-      "dev": true,
       "requires": {
         "md5.js": "^1.3.4",
         "safe-buffer": "^5.1.1"
@@ -10260,14 +10281,13 @@
     "exit": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
-      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+      "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
       "dev": true
     },
     "expand-brackets": {
       "version": "2.1.4",
       "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
-      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
-      "dev": true,
+      "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==",
       "requires": {
         "debug": "^2.3.3",
         "define-property": "^0.2.5",
@@ -10282,7 +10302,6 @@
           "version": "2.6.9",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "dev": true,
           "requires": {
             "ms": "2.0.0"
           }
@@ -10290,8 +10309,7 @@
         "define-property": {
           "version": "0.2.5",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
+          "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
           "requires": {
             "is-descriptor": "^0.1.0"
           }
@@ -10299,8 +10317,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "requires": {
             "is-extendable": "^0.1.0"
           }
@@ -10308,15 +10325,14 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
-          "dev": true
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         }
       }
     },
     "expand-tilde": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
-      "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+      "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==",
       "dev": true,
       "requires": {
         "homedir-polyfill": "^1.0.1"
@@ -10357,44 +10373,45 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         }
       }
     },
     "express": {
-      "version": "4.17.3",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
-      "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+      "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
       "dev": true,
       "requires": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.19.2",
+        "body-parser": "1.20.0",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
-        "cookie": "0.4.2",
+        "cookie": "0.5.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
-        "depd": "~1.1.2",
+        "depd": "2.0.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "~1.1.2",
+        "finalhandler": "1.2.0",
         "fresh": "0.5.2",
+        "http-errors": "2.0.0",
         "merge-descriptors": "1.0.1",
         "methods": "~1.1.2",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
         "path-to-regexp": "0.1.7",
         "proxy-addr": "~2.0.7",
-        "qs": "6.9.7",
+        "qs": "6.10.3",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.17.2",
-        "serve-static": "1.14.2",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
         "setprototypeof": "1.2.0",
-        "statuses": "~1.5.0",
+        "statuses": "2.0.1",
         "type-is": "~1.6.18",
         "utils-merge": "1.0.1",
         "vary": "~1.1.2"
@@ -10409,30 +10426,58 @@
             "ms": "2.0.0"
           }
         },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+          "dev": true
+        },
+        "http-errors": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+          "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+          "dev": true,
+          "requires": {
+            "depd": "2.0.0",
+            "inherits": "2.0.4",
+            "setprototypeof": "1.2.0",
+            "statuses": "2.0.1",
+            "toidentifier": "1.0.1"
+          }
+        },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         },
         "qs": {
-          "version": "6.9.7",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
-          "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
-          "dev": true
+          "version": "6.10.3",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+          "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+          "dev": true,
+          "requires": {
+            "side-channel": "^1.0.4"
+          }
         },
         "safe-buffer": {
           "version": "5.2.1",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
           "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
           "dev": true
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+          "dev": true
         }
       }
     },
     "express-history-api-fallback": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/express-history-api-fallback/-/express-history-api-fallback-2.2.1.tgz",
-      "integrity": "sha1-OirSf3vryQ/FM9EQ18bYMJe80Fc=",
+      "integrity": "sha512-swxwm3aP8vrOOvlzOdZvHlSZtJGwHKaY94J6AkrAgCTmcbko3IRwbkhLv2wKV1WeZhjxX58aLMpP3atDBnKuZg==",
       "dev": true
     },
     "ext-list": {
@@ -10462,8 +10507,7 @@
     "extend-shallow": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
-      "dev": true,
+      "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
       "requires": {
         "assign-symbols": "^1.0.0",
         "is-extendable": "^1.0.1"
@@ -10473,7 +10517,6 @@
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
           "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-          "dev": true,
           "requires": {
             "is-plain-object": "^2.0.4"
           }
@@ -10482,7 +10525,6 @@
           "version": "2.0.4",
           "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
           "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
-          "dev": true,
           "requires": {
             "isobject": "^3.0.1"
           }
@@ -10515,7 +10557,6 @@
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
       "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
-      "dev": true,
       "requires": {
         "array-unique": "^0.3.2",
         "define-property": "^1.0.0",
@@ -10530,8 +10571,7 @@
         "define-property": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
-          "dev": true,
+          "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
           "requires": {
             "is-descriptor": "^1.0.0"
           }
@@ -10539,8 +10579,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "requires": {
             "is-extendable": "^0.1.0"
           }
@@ -10549,7 +10588,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
           "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -10558,7 +10596,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
           "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -10567,7 +10604,6 @@
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
           "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
             "is-data-descriptor": "^1.0.0",
@@ -10579,7 +10615,7 @@
     "extract-from-css": {
       "version": "0.4.4",
       "resolved": "https://registry.npmjs.org/extract-from-css/-/extract-from-css-0.4.4.tgz",
-      "integrity": "sha1-HqffLnx8brmSL6COitrqSG9vj5I=",
+      "integrity": "sha512-41qWGBdtKp9U7sgBxAQ7vonYqSXzgW/SiAYzq4tdWSVhAShvpVCH1nyvPQgjse6EdgbW7Y7ERdT3674/lKr65A==",
       "dev": true,
       "requires": {
         "css": "^2.1.0"
@@ -10588,7 +10624,7 @@
     "extsprintf": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
-      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+      "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
     },
     "fast-deep-equal": {
       "version": "3.1.3",
@@ -10617,9 +10653,17 @@
     "fast-levenshtein": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
       "dev": true
     },
+    "fast-xml-parser": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.0.tgz",
+      "integrity": "sha512-5Wln/SBrtlN37aboiNNFHfSALwLzpUx1vJhDgDVPKKG3JrNe8BWTUoNKqkeKk/HqNbKxC8nEAJaBydq30yHoLA==",
+      "requires": {
+        "strnum": "^1.0.5"
+      }
+    },
     "fastq": {
       "version": "1.13.0",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
@@ -10650,7 +10694,7 @@
     "fd-slicer": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
-      "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
+      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
       "dev": true,
       "requires": {
         "pend": "~1.2.0"
@@ -10726,7 +10770,7 @@
     "filename-reserved-regex": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
-      "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
+      "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
       "dev": true
     },
     "filenamify": {
@@ -10749,8 +10793,7 @@
     "fill-range": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
-      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
-      "dev": true,
+      "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==",
       "requires": {
         "extend-shallow": "^2.0.1",
         "is-number": "^3.0.0",
@@ -10761,26 +10804,30 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "requires": {
             "is-extendable": "^0.1.0"
           }
         }
       }
     },
+    "filter-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
+      "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
+    },
     "finalhandler": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
-      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
       "dev": true,
       "requires": {
         "debug": "2.6.9",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "statuses": "~1.5.0",
+        "statuses": "2.0.1",
         "unpipe": "~1.0.0"
       },
       "dependencies": {
@@ -10796,7 +10843,13 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+          "dev": true
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
           "dev": true
         }
       }
@@ -10814,13 +10867,13 @@
         "json5": {
           "version": "0.5.1",
           "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
-          "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+          "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==",
           "dev": true
         },
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         }
       }
@@ -10829,7 +10882,6 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
       "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
-      "dev": true,
       "requires": {
         "commondir": "^1.0.1",
         "make-dir": "^2.0.0",
@@ -10840,7 +10892,6 @@
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
           "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
           "requires": {
             "pify": "^4.0.1",
             "semver": "^5.6.0"
@@ -10849,14 +10900,12 @@
         "pify": {
           "version": "4.0.1",
           "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
-          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
-          "dev": true
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
         },
         "semver": {
           "version": "5.7.1",
           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
-          "dev": true
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
         }
       }
     },
@@ -10918,31 +10967,29 @@
       "dev": true
     },
     "flow-parser": {
-      "version": "0.174.1",
-      "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.174.1.tgz",
-      "integrity": "sha512-nDMOvlFR+4doLpB3OJpseHZ7uEr3ENptlF6qMas/kzQmNcLzMwfQeFX0gGJ/+em7UdldB/nGsk55tDTOvjbCuw==",
+      "version": "0.181.2",
+      "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.181.2.tgz",
+      "integrity": "sha512-+QzNZEmhYNF9SHrKI8M2lzT07UGkJW6Zoeg7wP+aGkFxh0Mh/wx8eyS/lcwY9bd3B4azS6K50ZjyIjzMWpowGg==",
       "dev": true
     },
     "flush-write-stream": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
       "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.3",
         "readable-stream": "^2.3.6"
       }
     },
     "follow-redirects": {
-      "version": "1.14.9",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
-      "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+      "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
     },
     "for-each": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
       "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
-      "dev": true,
       "requires": {
         "is-callable": "^1.1.3"
       }
@@ -10950,13 +10997,12 @@
     "for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
-      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
-      "dev": true
+      "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ=="
     },
     "forever-agent": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+      "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
     },
     "form-data": {
       "version": "2.3.3",
@@ -10977,8 +11023,7 @@
     "fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
-      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
-      "dev": true,
+      "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==",
       "requires": {
         "map-cache": "^0.2.2"
       }
@@ -10986,14 +11031,13 @@
     "fresh": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
       "dev": true
     },
     "from2": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
-      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
-      "dev": true,
+      "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
       "requires": {
         "inherits": "^2.0.1",
         "readable-stream": "^2.0.0"
@@ -11014,7 +11058,7 @@
     "fs-exists-sync": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
-      "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=",
+      "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==",
       "dev": true
     },
     "fs-extra": {
@@ -11039,8 +11083,7 @@
     "fs-write-stream-atomic": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
-      "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
-      "dev": true,
+      "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==",
       "requires": {
         "graceful-fs": "^4.1.2",
         "iferr": "^0.1.5",
@@ -11051,7 +11094,7 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
     },
     "fsevents": {
       "version": "2.3.2",
@@ -11060,38 +11103,6 @@
       "dev": true,
       "optional": true
     },
-    "fstream": {
-      "version": "1.0.12",
-      "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
-      "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.2",
-        "inherits": "~2.0.0",
-        "mkdirp": ">=0.5 0",
-        "rimraf": "2"
-      },
-      "dependencies": {
-        "mkdirp": {
-          "version": "0.5.6",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
-          "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
-          "dev": true,
-          "requires": {
-            "minimist": "^1.2.6"
-          }
-        },
-        "rimraf": {
-          "version": "2.7.1",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
-          "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-          "dev": true,
-          "requires": {
-            "glob": "^7.1.3"
-          }
-        }
-      }
-    },
     "fswin": {
       "version": "2.17.1227",
       "resolved": "https://registry.npmjs.org/fswin/-/fswin-2.17.1227.tgz",
@@ -11101,19 +11112,36 @@
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "function.prototype.name": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+      "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.19.0",
+        "functions-have-names": "^1.2.2"
+      }
     },
     "functional-red-black-tree": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
-      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+      "dev": true
+    },
+    "functions-have-names": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
       "dev": true
     },
     "gauge": {
       "version": "2.7.4",
       "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
-      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
       "requires": {
         "aproba": "^1.0.3",
         "console-control-strings": "^1.0.0",
@@ -11125,25 +11153,16 @@
         "wide-align": "^1.1.0"
       }
     },
-    "gaze": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
-      "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
-      "dev": true,
-      "requires": {
-        "globule": "^1.0.0"
-      }
-    },
     "generate-function": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-1.1.0.tgz",
-      "integrity": "sha1-VMIbCAGSsW2Yd3ecW7gWZudyNl8=",
+      "integrity": "sha512-Wv4qgYgt2m9QH7K+jklCX/o4gn1ijnS4nT+nxPYBbhdqZLDLtvNh2o26KP/nxN42Tk6AnrGftCLzjiMuckZeQw==",
       "dev": true
     },
     "generate-object-property": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
-      "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+      "integrity": "sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ==",
       "dev": true,
       "requires": {
         "is-property": "^1.0.0"
@@ -11162,14 +11181,13 @@
       "dev": true
     },
     "get-intrinsic": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
-      "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
-      "dev": true,
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
+      "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
       "requires": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
-        "has-symbols": "^1.0.1"
+        "has-symbols": "^1.0.3"
       }
     },
     "get-package-type": {
@@ -11213,13 +11231,12 @@
     "get-value": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
-      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
-      "dev": true
+      "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="
     },
     "getpass": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
-      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
       "requires": {
         "assert-plus": "^1.0.0"
       }
@@ -11227,13 +11244,13 @@
     "git-clone": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/git-clone/-/git-clone-0.1.0.tgz",
-      "integrity": "sha1-DXYWN3gJOu9/HDAjjyqe8/B6Lrk=",
+      "integrity": "sha512-zs9rlfa7HyaJAKG9o+V7C6qfMzyc+tb1IIXdUFcOBcR1U7siKy/uPdauLlrH1mc0vOgUwIv4BF+QxPiiTYz3Rw==",
       "dev": true
     },
     "git-config-path": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz",
-      "integrity": "sha1-bTP37WPbDQ4RgTFQO6s6ykfVRmQ=",
+      "integrity": "sha512-KcJ2dlrrP5DbBnYIZ2nlikALfRhKzNSX0stvv3ImJ+fvC4hXKoV+U+74SV0upg+jlQZbrtQzc0bu6/Zh+7aQbg==",
       "dev": true,
       "requires": {
         "extend-shallow": "^2.0.1",
@@ -11244,7 +11261,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "dev": true,
           "requires": {
             "is-extendable": "^0.1.0"
@@ -11253,14 +11270,14 @@
       }
     },
     "glob": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
-      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
         "inherits": "2",
-        "minimatch": "^3.0.4",
+        "minimatch": "^3.1.1",
         "once": "^1.3.0",
         "path-is-absolute": "^1.0.0"
       }
@@ -11268,7 +11285,7 @@
     "glob-parent": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
-      "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+      "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
       "dev": true,
       "requires": {
         "is-glob": "^3.1.0",
@@ -11278,7 +11295,7 @@
         "is-glob": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
-          "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+          "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==",
           "dev": true,
           "requires": {
             "is-extglob": "^2.1.0"
@@ -11289,7 +11306,7 @@
     "glob-to-regexp": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
-      "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
+      "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==",
       "dev": true
     },
     "global-dirs": {
@@ -11343,50 +11360,35 @@
         }
       }
     },
-    "globule": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz",
-      "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==",
-      "dev": true,
-      "requires": {
-        "glob": "~7.1.1",
-        "lodash": "~4.17.10",
-        "minimatch": "~3.0.2"
-      },
-      "dependencies": {
-        "glob": {
-          "version": "7.1.7",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
-          "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
-          "dev": true,
-          "requires": {
-            "fs.realpath": "^1.0.0",
-            "inflight": "^1.0.4",
-            "inherits": "2",
-            "minimatch": "^3.0.4",
-            "once": "^1.3.0",
-            "path-is-absolute": "^1.0.0"
-          }
-        },
-        "minimatch": {
-          "version": "3.0.8",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
-          "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
-          "dev": true,
-          "requires": {
-            "brace-expansion": "^1.1.7"
-          }
-        }
-      }
-    },
     "good-listener": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
-      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
       "requires": {
         "delegate": "^3.1.2"
       }
     },
+    "gopd": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "requires": {
+        "get-intrinsic": "^1.1.3"
+      },
+      "dependencies": {
+        "get-intrinsic": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+          "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
+          "requires": {
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-proto": "^1.0.1",
+            "has-symbols": "^1.0.3"
+          }
+        }
+      }
+    },
     "got": {
       "version": "9.6.0",
       "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -11406,9 +11408,9 @@
       }
     },
     "graceful-fs": {
-      "version": "4.2.9",
-      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
-      "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="
+      "version": "4.2.10",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
     },
     "graphql": {
       "version": "14.7.0",
@@ -11449,9 +11451,9 @@
       },
       "dependencies": {
         "tslib": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
-          "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+          "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
           "dev": true
         }
       }
@@ -11473,12 +11475,13 @@
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz",
       "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "growly": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
-      "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
+      "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==",
       "dev": true
     },
     "gzip-size": {
@@ -11508,7 +11511,7 @@
     "har-schema": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
-      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+      "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
     },
     "har-validator": {
       "version": "5.1.5",
@@ -11523,7 +11526,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1"
       }
@@ -11531,15 +11533,15 @@
     "has-ansi": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
       "requires": {
         "ansi-regex": "^2.0.0"
       }
     },
     "has-bigints": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
-      "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+      "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
       "dev": true
     },
     "has-flag": {
@@ -11547,6 +11549,20 @@
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
     },
+    "has-property-descriptors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+      "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+      "dev": true,
+      "requires": {
+        "get-intrinsic": "^1.1.1"
+      }
+    },
+    "has-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="
+    },
     "has-symbol-support-x": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz",
@@ -11556,8 +11572,7 @@
     "has-symbols": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-      "dev": true
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
     },
     "has-to-string-tag-x": {
       "version": "1.4.1",
@@ -11572,7 +11587,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
       "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-      "dev": true,
       "requires": {
         "has-symbols": "^1.0.2"
       }
@@ -11580,13 +11594,12 @@
     "has-unicode": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+      "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
     },
     "has-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
-      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
-      "dev": true,
+      "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==",
       "requires": {
         "get-value": "^2.0.6",
         "has-values": "^1.0.0",
@@ -11596,8 +11609,7 @@
     "has-values": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
-      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
-      "dev": true,
+      "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==",
       "requires": {
         "is-number": "^3.0.0",
         "kind-of": "^4.0.0"
@@ -11606,8 +11618,7 @@
         "kind-of": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
-          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
-          "dev": true,
+          "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -11623,7 +11634,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
       "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.4",
         "readable-stream": "^3.6.0",
@@ -11634,7 +11644,6 @@
           "version": "3.6.0",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
           "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-          "dev": true,
           "requires": {
             "inherits": "^2.0.3",
             "string_decoder": "^1.1.1",
@@ -11644,8 +11653,7 @@
         "safe-buffer": {
           "version": "5.2.1",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-          "dev": true
+          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
         }
       }
     },
@@ -11658,7 +11666,6 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
       "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.3",
         "minimalistic-assert": "^1.0.1"
@@ -11685,8 +11692,7 @@
     "hmac-drbg": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
-      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
-      "dev": true,
+      "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
       "requires": {
         "hash.js": "^1.0.3",
         "minimalistic-assert": "^1.0.0",
@@ -11719,7 +11725,7 @@
     "hpack.js": {
       "version": "2.1.6",
       "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
-      "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+      "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
       "dev": true,
       "requires": {
         "inherits": "^2.0.1",
@@ -11731,13 +11737,13 @@
     "hsl-regex": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
-      "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=",
+      "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==",
       "dev": true
     },
     "hsla-regex": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
-      "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=",
+      "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==",
       "dev": true
     },
     "html-encoding-sniffer": {
@@ -11785,15 +11791,15 @@
       }
     },
     "html-tags": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
-      "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
+      "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==",
       "dev": true
     },
     "html-webpack-plugin": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
-      "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
+      "integrity": "sha512-Br4ifmjQojUP4EmHnRBoUIYcZ9J7M4bTMcm7u6xoIAIuq2Nte4TzXX0533owvkQKQD1WeMTTTyD4Ni4QKxS0Bg==",
       "dev": true,
       "requires": {
         "html-minifier": "^3.2.3",
@@ -11814,19 +11820,19 @@
         "emojis-list": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
-          "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+          "integrity": "sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng==",
           "dev": true
         },
         "json5": {
           "version": "0.5.1",
           "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
-          "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+          "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==",
           "dev": true
         },
         "loader-utils": {
           "version": "0.2.17",
           "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
-          "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+          "integrity": "sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug==",
           "dev": true,
           "requires": {
             "big.js": "^3.1.3",
@@ -11860,9 +11866,9 @@
       },
       "dependencies": {
         "dom-serializer": {
-          "version": "1.3.2",
-          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
-          "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+          "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
           "dev": true,
           "requires": {
             "domelementtype": "^2.0.1",
@@ -11871,9 +11877,9 @@
           }
         },
         "domelementtype": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
-          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+          "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
           "dev": true
         },
         "domutils": {
@@ -11897,7 +11903,7 @@
     "http-deceiver": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
-      "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
+      "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
       "dev": true
     },
     "http-errors": {
@@ -11914,9 +11920,9 @@
       }
     },
     "http-parser-js": {
-      "version": "0.5.6",
-      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz",
-      "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==",
+      "version": "0.5.8",
+      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
+      "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
       "dev": true
     },
     "http-proxy": {
@@ -11992,13 +11998,13 @@
           "dev": true
         },
         "micromatch": {
-          "version": "4.0.4",
-          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
-          "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+          "version": "4.0.5",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+          "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
           "dev": true,
           "requires": {
-            "braces": "^3.0.1",
-            "picomatch": "^2.2.3"
+            "braces": "^3.0.2",
+            "picomatch": "^2.3.1"
           }
         },
         "to-regex-range": {
@@ -12015,7 +12021,7 @@
     "http-signature": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
       "requires": {
         "assert-plus": "^1.0.0",
         "jsprim": "^1.2.2",
@@ -12025,13 +12031,12 @@
     "https-browserify": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
-      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
-      "dev": true
+      "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="
     },
     "https-proxy-agent": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
-      "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
       "requires": {
         "agent-base": "6",
         "debug": "4"
@@ -12046,7 +12051,7 @@
     "humanize-ms": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
-      "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+      "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
       "requires": {
         "ms": "^2.0.0"
       }
@@ -12072,14 +12077,12 @@
     "ieee754": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
-      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-      "dev": true
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
     },
     "iferr": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
-      "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
-      "dev": true
+      "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA=="
     },
     "ignore": {
       "version": "4.0.6",
@@ -12098,20 +12101,20 @@
     "image-size": {
       "version": "0.5.5",
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
-      "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
+      "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
       "dev": true,
       "optional": true
     },
     "immutable": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
-      "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
+      "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
       "dev": true
     },
     "import-cwd": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
-      "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=",
+      "integrity": "sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg==",
       "dev": true,
       "requires": {
         "import-from": "^2.1.0"
@@ -12120,7 +12123,7 @@
     "import-fresh": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
-      "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+      "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==",
       "dev": true,
       "requires": {
         "caller-path": "^2.0.0",
@@ -12130,7 +12133,7 @@
     "import-from": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
-      "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=",
+      "integrity": "sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w==",
       "dev": true,
       "requires": {
         "resolve-from": "^3.0.0"
@@ -12139,7 +12142,7 @@
     "import-global": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/import-global/-/import-global-0.1.0.tgz",
-      "integrity": "sha1-l7OP1EQRTuwWgkqTX42ldbV6oc4=",
+      "integrity": "sha512-8+hPJLML+m1ym9NSeZXTXFkY5+ml0fYFAzO5yhZiaFQvk9kO0NkE7vd7e7kCVjkTmAxsDPbrWwLQACMwGTDgIg==",
       "dev": true,
       "requires": {
         "global-dirs": "^0.1.0"
@@ -12148,7 +12151,7 @@
         "global-dirs": {
           "version": "0.1.1",
           "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
-          "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
+          "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==",
           "dev": true,
           "requires": {
             "ini": "^1.3.4"
@@ -12159,7 +12162,7 @@
     "import-lazy": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
-      "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
+      "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A=="
     },
     "import-local": {
       "version": "2.0.0",
@@ -12174,13 +12177,7 @@
     "imurmurhash": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
-    },
-    "in-publish": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
-      "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
-      "dev": true
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
     },
     "indent-string": {
       "version": "4.0.0",
@@ -12190,7 +12187,7 @@
     "indexes-of": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
-      "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
+      "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==",
       "dev": true
     },
     "infer-owner": {
@@ -12201,7 +12198,7 @@
     "inflight": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -12348,7 +12345,7 @@
     "into-stream": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-2.0.1.tgz",
-      "integrity": "sha1-25sANpRFPq4JHYpchMwRUHt4HTE=",
+      "integrity": "sha512-7EHX+bSqaYHfWnrREpeGUKO6ox5tW6NgziFx7cZqxBZ3RNRir9cnPCDvJNjrROLP6guznhxMkyus0sK2qQzhrQ==",
       "dev": true,
       "requires": {
         "from2": "^2.1.1"
@@ -12364,14 +12361,14 @@
       }
     },
     "ip": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
-      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
+      "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
     },
     "ip-regex": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
-      "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
+      "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==",
       "dev": true
     },
     "ipaddr.js": {
@@ -12383,14 +12380,13 @@
     "is-absolute-url": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
-      "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=",
+      "integrity": "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg==",
       "dev": true
     },
     "is-accessor-descriptor": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
-      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
-      "dev": true,
+      "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==",
       "requires": {
         "kind-of": "^3.0.2"
       },
@@ -12398,8 +12394,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-          "dev": true,
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -12410,7 +12405,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
       "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -12419,7 +12413,7 @@
     "is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
       "dev": true
     },
     "is-bigint": {
@@ -12458,8 +12452,7 @@
     "is-callable": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
-      "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==",
-      "dev": true
+      "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
     },
     "is-ci": {
       "version": "2.0.0",
@@ -12472,7 +12465,7 @@
     "is-color-stop": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz",
-      "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=",
+      "integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==",
       "dev": true,
       "requires": {
         "css-color-names": "^0.0.4",
@@ -12484,9 +12477,9 @@
       }
     },
     "is-core-module": {
-      "version": "2.8.1",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
-      "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+      "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
       "dev": true,
       "requires": {
         "has": "^1.0.3"
@@ -12495,8 +12488,7 @@
     "is-data-descriptor": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
-      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
-      "dev": true,
+      "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==",
       "requires": {
         "kind-of": "^3.0.2"
       },
@@ -12504,8 +12496,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-          "dev": true,
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -12525,7 +12516,6 @@
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
       "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
-      "dev": true,
       "requires": {
         "is-accessor-descriptor": "^0.1.6",
         "is-data-descriptor": "^0.1.4",
@@ -12535,15 +12525,14 @@
         "kind-of": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
-          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
-          "dev": true
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
         }
       }
     },
     "is-directory": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
-      "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+      "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==",
       "dev": true
     },
     "is-docker": {
@@ -12555,25 +12544,18 @@
     "is-extendable": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
-      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
-      "dev": true
+      "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="
     },
     "is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
-      "dev": true
-    },
-    "is-finite": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
-      "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
       "dev": true
     },
     "is-fullwidth-code-point": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+      "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
       "requires": {
         "number-is-nan": "^1.0.0"
       }
@@ -12584,6 +12566,14 @@
       "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
       "dev": true
     },
+    "is-generator-function": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+      "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+      "requires": {
+        "has-tostringtag": "^1.0.0"
+      }
+    },
     "is-glob": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -12605,12 +12595,12 @@
     "is-lambda": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
-      "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU="
+      "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="
     },
     "is-natural-number": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
-      "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=",
+      "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==",
       "dev": true
     },
     "is-negative-zero": {
@@ -12627,8 +12617,7 @@
     "is-number": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
-      "dev": true,
+      "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
       "requires": {
         "kind-of": "^3.0.2"
       },
@@ -12636,8 +12625,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-          "dev": true,
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -12645,9 +12633,9 @@
       }
     },
     "is-number-object": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
-      "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==",
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+      "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
       "dev": true,
       "requires": {
         "has-tostringtag": "^1.0.0"
@@ -12698,7 +12686,7 @@
     "is-plain-obj": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
-      "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+      "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
       "dev": true
     },
     "is-plain-object": {
@@ -12715,7 +12703,7 @@
     "is-property": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
-      "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+      "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
       "dev": true
     },
     "is-regex": {
@@ -12741,15 +12729,18 @@
       "dev": true
     },
     "is-shared-array-buffer": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
-      "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==",
-      "dev": true
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+      "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2"
+      }
     },
     "is-stream": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
       "dev": true
     },
     "is-string": {
@@ -12770,16 +12761,18 @@
         "has-symbols": "^1.0.2"
       }
     },
+    "is-typed-array": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
+      "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
+      "requires": {
+        "which-typed-array": "^1.1.11"
+      }
+    },
     "is-typedarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
-    },
-    "is-utf8": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
-      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
-      "dev": true
+      "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
     },
     "is-weakref": {
       "version": "1.0.2",
@@ -12799,20 +12792,18 @@
     "is-whitespace": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz",
-      "integrity": "sha1-Fjnssb4DauxppUy7QBz77XEUq38=",
+      "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==",
       "dev": true
     },
     "is-windows": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
-      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
-      "dev": true
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
     },
     "is-wsl": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
-      "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
-      "dev": true
+      "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw=="
     },
     "is-yarn-global": {
       "version": "0.3.0",
@@ -12822,29 +12813,28 @@
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
     },
     "isbinaryfile": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz",
-      "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==",
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+      "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
       "dev": true
     },
     "isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
     },
     "isobject": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
-      "dev": true
+      "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
     },
     "isstream": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+      "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
     },
     "istanbul-lib-coverage": {
       "version": "2.0.5",
@@ -12889,7 +12879,7 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "make-dir": {
@@ -12999,7 +12989,7 @@
     "javascript-stringify": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-1.6.0.tgz",
-      "integrity": "sha1-FC0RHzpuPa6PSpr9d9RYVbWpzOM=",
+      "integrity": "sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ==",
       "dev": true
     },
     "jest": {
@@ -13061,7 +13051,7 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "emoji-regex": {
@@ -13082,13 +13072,13 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "is-fullwidth-code-point": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
           "dev": true
         },
         "jest-cli": {
@@ -13134,7 +13124,7 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         },
         "string-width": {
@@ -13296,13 +13286,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "slash": {
@@ -13366,13 +13356,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -13440,13 +13430,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -13571,7 +13561,7 @@
         "tr46": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
-          "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+          "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
           "dev": true,
           "requires": {
             "punycode": "^2.1.0"
@@ -13659,7 +13649,7 @@
         "normalize-path": {
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-          "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+          "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
           "dev": true,
           "requires": {
             "remove-trailing-separator": "^1.0.1"
@@ -13723,13 +13713,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -13797,13 +13787,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -13865,13 +13855,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "slash": {
@@ -13904,7 +13894,8 @@
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
       "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "jest-regex-util": {
       "version": "24.9.0",
@@ -13957,13 +13948,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -14047,13 +14038,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -14147,7 +14138,7 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "emoji-regex": {
@@ -14168,13 +14159,13 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "is-fullwidth-code-point": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
           "dev": true
         },
         "locate-path": {
@@ -14199,7 +14190,7 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         },
         "slash": {
@@ -14293,7 +14284,7 @@
     "jest-serializer-vue": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/jest-serializer-vue/-/jest-serializer-vue-2.0.2.tgz",
-      "integrity": "sha1-sjjvKGNX7GtIBCG9RxRQUJh9WbM=",
+      "integrity": "sha512-nK/YIFo6qe3i9Ge+hr3h4PpRehuPPGZFt8LDBdTHYldMb7ZWlkanZS8Ls7D8h6qmQP2lBQVDLP0DKn5bJ9QApQ==",
       "dev": true,
       "requires": {
         "pretty": "2.0.0"
@@ -14352,13 +14343,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "mkdirp": {
@@ -14445,13 +14436,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "mkdirp": {
@@ -14526,13 +14517,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -14599,13 +14590,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "string-length": {
@@ -14691,13 +14682,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -14724,7 +14715,7 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -14741,21 +14732,15 @@
     "jju": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
-      "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo="
-    },
-    "js-base64": {
-      "version": "2.6.4",
-      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
-      "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
-      "dev": true
+      "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="
     },
     "js-beautify": {
-      "version": "1.14.0",
-      "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.0.tgz",
-      "integrity": "sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==",
+      "version": "1.14.4",
+      "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.4.tgz",
+      "integrity": "sha512-+b4A9c3glceZEmxyIbxDOYB0ZJdReLvyU1077RqKsO4dZx9FUHjTOJn8VHwpg33QoucIykOiYbh7MfqBOghnrA==",
       "dev": true,
       "requires": {
-        "config-chain": "^1.1.12",
+        "config-chain": "^1.1.13",
         "editorconfig": "^0.15.3",
         "glob": "^7.1.3",
         "nopt": "^5.0.0"
@@ -14789,7 +14774,7 @@
     "jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+      "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
     },
     "jscodeshift": {
       "version": "0.11.0",
@@ -14846,9 +14831,9 @@
           }
         },
         "tslib": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
-          "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+          "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
           "dev": true
         },
         "write-file-atomic": {
@@ -14907,7 +14892,7 @@
         "tr46": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
-          "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+          "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
           "dev": true,
           "requires": {
             "punycode": "^2.1.0"
@@ -14950,13 +14935,12 @@
     "json-buffer": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
-      "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg="
+      "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ=="
     },
     "json-parse-better-errors": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
-      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
-      "dev": true
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
     },
     "json-parse-even-better-errors": {
       "version": "2.3.1",
@@ -14966,7 +14950,7 @@
     "json-parse-helpfulerror": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz",
-      "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=",
+      "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==",
       "requires": {
         "jju": "^1.1.0"
       }
@@ -14984,13 +14968,26 @@
     "json-stable-stringify-without-jsonify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "json-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-stream/-/json-stream-1.0.0.tgz",
+      "integrity": "sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg=="
+    },
     "json-stringify-safe": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+    },
+    "json2mq": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+      "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+      "requires": {
+        "string-convert": "^0.2.0"
+      }
     },
     "json5": {
       "version": "2.2.1",
@@ -15000,7 +14997,7 @@
     "jsonfile": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
-      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.1.6"
@@ -15009,7 +15006,7 @@
     "jsonparse": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
-      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
+      "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="
     },
     "jsprim": {
       "version": "1.4.2",
@@ -15039,8 +15036,7 @@
     "kind-of": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
-      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
-      "dev": true
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
     },
     "kleur": {
       "version": "3.0.3",
@@ -15056,9 +15052,9 @@
       }
     },
     "launch-editor": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.3.0.tgz",
-      "integrity": "sha512-3QrsCXejlWYHjBPFXTyGNhPj4rrQdB+5+r5r3wArpLH201aR+nWUgw/zKKkTmilCfY/sv6u8qo98pNvtg8LUTA==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.4.0.tgz",
+      "integrity": "sha512-mZ0BHeSn/ohL+Ib+b+JnxC59vcNz6v5IR9d0CuM8f0x8ni8oK3IIG6G0vMkpxc0gFsmvINkztGOHiWTaX4BmAg==",
       "dev": true,
       "requires": {
         "picocolors": "^1.0.0",
@@ -15074,12 +15070,12 @@
       }
     },
     "launch-editor-middleware": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.3.0.tgz",
-      "integrity": "sha512-GJR64trLdFFwCoL9DMn/d1SZX0OzTDPixu4mcfWTShQ4tIqCHCGvlg9fOEYQXyBlrSMQwylsJfUWncheShfV2w==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.4.0.tgz",
+      "integrity": "sha512-/M7AX/6xktZY60KE7j71XLrj9U6H5TBoP+mJzhYB3fcdAq8rcazit/K0qWiu1jvytUPXP4lJRd1VJFwvdMQ/uw==",
       "dev": true,
       "requires": {
-        "launch-editor": "^2.3.0"
+        "launch-editor": "^2.4.0"
       }
     },
     "left-pad": {
@@ -15133,9 +15129,9 @@
       }
     },
     "less-bundle-promise": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/less-bundle-promise/-/less-bundle-promise-1.0.7.tgz",
-      "integrity": "sha512-B4mN+YtkOxAPUHyorhup+ETVNZ9E1PO65sPhgPvDDHDVtR1oYRd87EbYVYOsU0Oev0MW/6MSouS5QYlhe7XrzA=="
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/less-bundle-promise/-/less-bundle-promise-1.0.11.tgz",
+      "integrity": "sha512-LozmEciljdXe0CwEH6uWTlpQDlOVM8d3kkj14P+Jeze/AUhaPZs02x6INJh4TYSeO5xw4RxkpzXTELZSkLKC6Q=="
     },
     "less-loader": {
       "version": "5.0.0",
@@ -15151,7 +15147,7 @@
         "clone": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
-          "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+          "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
           "dev": true
         },
         "json5": {
@@ -15191,7 +15187,7 @@
     "levn": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
       "dev": true,
       "requires": {
         "prelude-ls": "~1.1.2",
@@ -15236,7 +15232,7 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="
         }
       }
     },
@@ -15249,7 +15245,7 @@
     "load-json-file": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
-      "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+      "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.1.2",
@@ -15261,7 +15257,7 @@
         "parse-json": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
-          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
           "dev": true,
           "requires": {
             "error-ex": "^1.3.1",
@@ -15283,7 +15279,7 @@
         "find-cache-dir": {
           "version": "0.1.1",
           "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
-          "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
+          "integrity": "sha512-Z9XSBoNE7xQiV6MSgPuCfyMokH2K7JdpRkOYE1+mu3d4BFJtx3GW+f6Bo4q8IX6rlf5MYbLBKW0pjl2cWdkm2A==",
           "dev": true,
           "requires": {
             "commondir": "^1.0.1",
@@ -15294,7 +15290,7 @@
         "find-up": {
           "version": "1.1.2",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
-          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==",
           "dev": true,
           "requires": {
             "path-exists": "^2.0.0",
@@ -15313,7 +15309,7 @@
         "path-exists": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
-          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==",
           "dev": true,
           "requires": {
             "pinkie-promise": "^2.0.0"
@@ -15322,7 +15318,7 @@
         "pkg-dir": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
-          "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+          "integrity": "sha512-c6pv3OE78mcZ92ckebVDqg0aWSoKhOTbwCV6qbCWMk546mAL9pZln0+QsN/yQ7fkucd4+yJPLrCBXNt8Ruk+Eg==",
           "dev": true,
           "requires": {
             "find-up": "^1.0.0"
@@ -15333,8 +15329,7 @@
     "loader-runner": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
-      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
-      "dev": true
+      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw=="
     },
     "loader-utils": {
       "version": "2.0.2",
@@ -15367,13 +15362,13 @@
     "lodash.clonedeep": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
-      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
       "dev": true
     },
     "lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
-      "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
       "dev": true
     },
     "lodash.defaultsdeep": {
@@ -15385,19 +15380,19 @@
     "lodash.kebabcase": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
-      "integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=",
+      "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==",
       "dev": true
     },
     "lodash.mapvalues": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz",
-      "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=",
+      "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==",
       "dev": true
     },
     "lodash.memoize": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
-      "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
+      "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
       "dev": true
     },
     "lodash.merge": {
@@ -15409,19 +15404,19 @@
     "lodash.sortby": {
       "version": "4.7.0",
       "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
-      "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
+      "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
       "dev": true
     },
     "lodash.transform": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
-      "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=",
+      "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==",
       "dev": true
     },
     "lodash.uniq": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
-      "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+      "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
       "dev": true
     },
     "log-symbols": {
@@ -15465,13 +15460,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "supports-color": {
@@ -15505,16 +15500,6 @@
         "js-tokens": "^3.0.0 || ^4.0.0"
       }
     },
-    "loud-rejection": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
-      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
-      "dev": true,
-      "requires": {
-        "currently-unhandled": "^0.4.1",
-        "signal-exit": "^3.0.0"
-      }
-    },
     "lowdb": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz",
@@ -15531,7 +15516,7 @@
     "lower-case": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
-      "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=",
+      "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==",
       "dev": true
     },
     "lowercase-keys": {
@@ -15611,20 +15596,12 @@
     "map-cache": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
-      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
-      "dev": true
-    },
-    "map-obj": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
-      "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
-      "dev": true
+      "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg=="
     },
     "map-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
-      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
-      "dev": true,
+      "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==",
       "requires": {
         "object-visit": "^1.0.0"
       }
@@ -15643,7 +15620,6 @@
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
       "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
-      "dev": true,
       "requires": {
         "hash-base": "^3.0.0",
         "inherits": "^2.0.1",
@@ -15659,127 +15635,18 @@
     "media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
       "dev": true
     },
     "memory-fs": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
-      "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
-      "dev": true,
+      "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==",
       "requires": {
         "errno": "^0.1.3",
         "readable-stream": "^2.0.1"
       }
     },
-    "meow": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
-      "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
-      "dev": true,
-      "requires": {
-        "camelcase-keys": "^2.0.0",
-        "decamelize": "^1.1.2",
-        "loud-rejection": "^1.0.0",
-        "map-obj": "^1.0.1",
-        "minimist": "^1.1.3",
-        "normalize-package-data": "^2.3.4",
-        "object-assign": "^4.0.1",
-        "read-pkg-up": "^1.0.1",
-        "redent": "^1.0.0",
-        "trim-newlines": "^1.0.0"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "1.1.2",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
-          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
-          "dev": true,
-          "requires": {
-            "path-exists": "^2.0.0",
-            "pinkie-promise": "^2.0.0"
-          }
-        },
-        "load-json-file": {
-          "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
-          "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
-          "dev": true,
-          "requires": {
-            "graceful-fs": "^4.1.2",
-            "parse-json": "^2.2.0",
-            "pify": "^2.0.0",
-            "pinkie-promise": "^2.0.0",
-            "strip-bom": "^2.0.0"
-          }
-        },
-        "parse-json": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-          "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
-          "dev": true,
-          "requires": {
-            "error-ex": "^1.2.0"
-          }
-        },
-        "path-exists": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
-          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
-          "dev": true,
-          "requires": {
-            "pinkie-promise": "^2.0.0"
-          }
-        },
-        "path-type": {
-          "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
-          "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
-          "dev": true,
-          "requires": {
-            "graceful-fs": "^4.1.2",
-            "pify": "^2.0.0",
-            "pinkie-promise": "^2.0.0"
-          }
-        },
-        "pify": {
-          "version": "2.3.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
-          "dev": true
-        },
-        "read-pkg": {
-          "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-          "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
-          "dev": true,
-          "requires": {
-            "load-json-file": "^1.0.0",
-            "normalize-package-data": "^2.3.2",
-            "path-type": "^1.0.0"
-          }
-        },
-        "read-pkg-up": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-          "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
-          "dev": true,
-          "requires": {
-            "find-up": "^1.0.0",
-            "read-pkg": "^1.0.0"
-          }
-        },
-        "strip-bom": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
-          "dev": true,
-          "requires": {
-            "is-utf8": "^0.2.0"
-          }
-        }
-      }
-    },
     "merge": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz",
@@ -15789,7 +15656,7 @@
     "merge-descriptors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
       "dev": true
     },
     "merge-source-map": {
@@ -15816,14 +15683,13 @@
     "methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
-      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
       "dev": true
     },
     "micromatch": {
       "version": "3.1.10",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
       "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
-      "dev": true,
       "requires": {
         "arr-diff": "^4.0.0",
         "array-unique": "^0.3.2",
@@ -15844,7 +15710,6 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
       "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
-      "dev": true,
       "requires": {
         "bn.js": "^4.0.0",
         "brorand": "^1.0.1"
@@ -15853,8 +15718,7 @@
         "bn.js": {
           "version": "4.12.0",
           "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
-          "dev": true
+          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         }
       }
     },
@@ -15923,7 +15787,7 @@
         "normalize-url": {
           "version": "1.9.1",
           "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
-          "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
+          "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==",
           "dev": true,
           "requires": {
             "object-assign": "^4.0.1",
@@ -15935,13 +15799,13 @@
         "prepend-http": {
           "version": "1.0.4",
           "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
-          "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+          "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==",
           "dev": true
         },
         "query-string": {
           "version": "4.3.4",
           "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
-          "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+          "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==",
           "dev": true,
           "requires": {
             "object-assign": "^4.1.0",
@@ -15974,14 +15838,12 @@
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
-      "dev": true
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
     },
     "minimalistic-crypto-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
-      "dev": true
+      "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="
     },
     "minimatch": {
       "version": "3.1.2",
@@ -15996,10 +15858,84 @@
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
       "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
     },
+    "minio": {
+      "version": "7.0.33",
+      "resolved": "https://registry.npmjs.org/minio/-/minio-7.0.33.tgz",
+      "integrity": "sha512-8wXGH98nZiLPe2xZhMV7UJ+48L1UlhgekxgpUhJWMO1h24TvZ0wUjtIt9e7DfNACopXh1spL8iuDQD7Lrq8Upw==",
+      "requires": {
+        "async": "^3.1.0",
+        "block-stream2": "^2.0.0",
+        "browser-or-node": "^1.3.0",
+        "buffer-crc32": "^0.2.13",
+        "crypto-browserify": "^3.12.0",
+        "es6-error": "^4.1.1",
+        "fast-xml-parser": "^4.1.3",
+        "ipaddr.js": "^2.0.1",
+        "json-stream": "^1.0.0",
+        "lodash": "^4.17.21",
+        "mime-types": "^2.1.14",
+        "mkdirp": "^0.5.1",
+        "query-string": "^7.1.1",
+        "through2": "^3.0.1",
+        "web-encoding": "^1.1.5",
+        "xml": "^1.0.0",
+        "xml2js": "^0.4.15"
+      },
+      "dependencies": {
+        "async": {
+          "version": "3.2.4",
+          "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+          "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
+        },
+        "decode-uri-component": {
+          "version": "0.2.2",
+          "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+          "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
+        },
+        "ipaddr.js": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
+          "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ=="
+        },
+        "mkdirp": {
+          "version": "0.5.6",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+          "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+          "requires": {
+            "minimist": "^1.2.6"
+          }
+        },
+        "query-string": {
+          "version": "7.1.3",
+          "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
+          "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
+          "requires": {
+            "decode-uri-component": "^0.2.2",
+            "filter-obj": "^1.1.0",
+            "split-on-first": "^1.0.0",
+            "strict-uri-encode": "^2.0.0"
+          }
+        },
+        "strict-uri-encode": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+          "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
+        },
+        "through2": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
+          "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==",
+          "requires": {
+            "inherits": "^2.0.4",
+            "readable-stream": "2 || 3"
+          }
+        }
+      }
+    },
     "minipass": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz",
-      "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==",
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
+      "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
       "requires": {
         "yallist": "^4.0.0"
       }
@@ -16069,7 +16005,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
       "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
-      "dev": true,
       "requires": {
         "concat-stream": "^1.5.0",
         "duplexify": "^3.4.2",
@@ -16092,7 +16027,6 @@
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
       "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
-      "dev": true,
       "requires": {
         "for-in": "^1.0.2",
         "is-extendable": "^1.0.1"
@@ -16102,7 +16036,6 @@
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
           "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-          "dev": true,
           "requires": {
             "is-plain-object": "^2.0.4"
           }
@@ -16111,7 +16044,6 @@
           "version": "2.0.4",
           "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
           "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
-          "dev": true,
           "requires": {
             "isobject": "^3.0.1"
           }
@@ -16124,15 +16056,29 @@
       "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
     },
     "moment": {
-      "version": "2.29.1",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
-      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
+      "version": "2.29.3",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
+      "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
+    },
+    "moment-timezone": {
+      "version": "0.5.43",
+      "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz",
+      "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==",
+      "requires": {
+        "moment": "^2.29.4"
+      },
+      "dependencies": {
+        "moment": {
+          "version": "2.29.4",
+          "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+          "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+        }
+      }
     },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
-      "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
-      "dev": true,
+      "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==",
       "requires": {
         "aproba": "^1.1.1",
         "copy-concurrently": "^1.0.0",
@@ -16143,19 +16089,17 @@
       },
       "dependencies": {
         "mkdirp": {
-          "version": "0.5.5",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-          "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
-          "dev": true,
+          "version": "0.5.6",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+          "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
           "requires": {
-            "minimist": "^1.2.5"
+            "minimist": "^1.2.6"
           }
         },
         "rimraf": {
           "version": "2.7.1",
           "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
           "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-          "dev": true,
           "requires": {
             "glob": "^7.1.3"
           }
@@ -16180,9 +16124,14 @@
     "multicast-dns-service-types": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
-      "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
+      "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==",
       "dev": true
     },
+    "mustache": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+      "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="
+    },
     "mute-stream": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -16201,21 +16150,21 @@
       }
     },
     "nan": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
-      "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
-      "dev": true
+      "version": "2.16.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz",
+      "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==",
+      "dev": true,
+      "optional": true
     },
     "nanoid": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
-      "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw=="
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
     },
     "nanomatch": {
       "version": "1.2.13",
       "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
       "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
-      "dev": true,
       "requires": {
         "arr-diff": "^4.0.0",
         "array-unique": "^0.3.2",
@@ -16245,13 +16194,13 @@
     "natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
       "dev": true
     },
     "ndjson": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz",
-      "integrity": "sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=",
+      "integrity": "sha512-hUPLuaziboGjNF7wHngkgVc0FOclR8dDk/HfEvTtDr/iUrqBWiRcRSTK3/nLOqKH33th714BrMmTPtObI9gZxQ==",
       "dev": true,
       "requires": {
         "json-stringify-safe": "^5.0.1",
@@ -16263,7 +16212,7 @@
     "neat-csv": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/neat-csv/-/neat-csv-2.1.0.tgz",
-      "integrity": "sha1-BvWDYMTDuVW9Rn3cha5FEaOQekw=",
+      "integrity": "sha512-SRzLDeOV/RKF5Em08QWEEbfceBMTl9f+zkqupMqDbEa19ieeQ7UKz42mQBj6zNQaCC4u7uauG4dF8aUOWTnZ8w==",
       "dev": true,
       "requires": {
         "csv-parser": "^1.6.0",
@@ -16274,7 +16223,7 @@
         "get-stream": {
           "version": "2.3.1",
           "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
-          "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
+          "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==",
           "dev": true,
           "requires": {
             "object-assign": "^4.0.1",
@@ -16291,8 +16240,7 @@
     "neo-async": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
-      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
-      "dev": true
+      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
     },
     "nested-error-stacks": {
       "version": "2.0.1",
@@ -16323,7 +16271,7 @@
     "node-alias": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/node-alias/-/node-alias-1.0.4.tgz",
-      "integrity": "sha1-HxuRa1a56iQcATX5fO1pQPVW8pI=",
+      "integrity": "sha512-9uG48bfkbG9BlKe8QrlxuiPNaKl3wpQn6tJbrojVqgkJuWIO28ifRKrRDrrK+ee72rJ25EaE//PhSIo8E29lLw==",
       "requires": {
         "chalk": "^1.1.1",
         "lodash": "^4.2.0"
@@ -16332,12 +16280,12 @@
         "ansi-styles": {
           "version": "2.2.1",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+          "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="
         },
         "chalk": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
           "requires": {
             "ansi-styles": "^2.2.1",
             "escape-string-regexp": "^1.0.2",
@@ -16349,7 +16297,7 @@
         "supports-color": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+          "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="
         }
       }
     },
@@ -16366,7 +16314,7 @@
         "clone": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
-          "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+          "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
           "dev": true
         }
       }
@@ -16374,7 +16322,7 @@
     "node-dir": {
       "version": "0.1.17",
       "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz",
-      "integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=",
+      "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==",
       "dev": true,
       "requires": {
         "minimatch": "^3.0.2"
@@ -16415,14 +16363,13 @@
     "node-int64": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
-      "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=",
+      "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
       "dev": true
     },
     "node-libs-browser": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
       "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
-      "dev": true,
       "requires": {
         "assert": "^1.1.1",
         "browserify-zlib": "^0.2.0",
@@ -16453,7 +16400,6 @@
           "version": "4.9.2",
           "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
           "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
-          "dev": true,
           "requires": {
             "base64-js": "^1.0.2",
             "ieee754": "^1.1.4",
@@ -16463,8 +16409,7 @@
         "punycode": {
           "version": "1.4.1",
           "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
-          "dev": true
+          "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
         }
       }
     },
@@ -16500,9 +16445,9 @@
       }
     },
     "node-releases": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz",
-      "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==",
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
+      "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==",
       "dev": true
     },
     "nopt": {
@@ -16532,12 +16477,12 @@
           "dev": true
         },
         "resolve": {
-          "version": "1.22.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
-          "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+          "version": "1.22.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+          "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
           "dev": true,
           "requires": {
-            "is-core-module": "^2.8.1",
+            "is-core-module": "^2.9.0",
             "path-parse": "^1.0.7",
             "supports-preserve-symlinks-flag": "^1.0.0"
           }
@@ -16559,7 +16504,7 @@
     "normalize-range": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
-      "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+      "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
       "dev": true
     },
     "normalize-url": {
@@ -16673,7 +16618,7 @@
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
-      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==",
       "dev": true,
       "requires": {
         "path-key": "^2.0.0"
@@ -16693,7 +16638,7 @@
     "nprogress": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
-      "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E="
+      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
     },
     "nth-check": {
       "version": "1.0.2",
@@ -16707,18 +16652,18 @@
     "num2fraction": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
-      "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+      "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==",
       "dev": true
     },
     "number-is-nan": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+      "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="
     },
     "nwsapi": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
-      "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==",
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz",
+      "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==",
       "dev": true
     },
     "oauth-sign": {
@@ -16729,13 +16674,12 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
     },
     "object-copy": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
-      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
-      "dev": true,
+      "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==",
       "requires": {
         "copy-descriptor": "^0.1.0",
         "define-property": "^0.2.5",
@@ -16745,8 +16689,7 @@
         "define-property": {
           "version": "0.2.5",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
+          "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
           "requires": {
             "is-descriptor": "^0.1.0"
           }
@@ -16754,8 +16697,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-          "dev": true,
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -16769,9 +16711,9 @@
       "dev": true
     },
     "object-inspect": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
-      "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
+      "version": "1.12.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+      "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
       "dev": true
     },
     "object-is": {
@@ -16799,8 +16741,7 @@
     "object-visit": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
-      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
-      "dev": true,
+      "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==",
       "requires": {
         "isobject": "^3.0.0"
       }
@@ -16818,21 +16759,21 @@
       }
     },
     "object.getownpropertydescriptors": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz",
-      "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==",
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz",
+      "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==",
       "dev": true,
       "requires": {
+        "array.prototype.reduce": "^1.0.4",
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3",
-        "es-abstract": "^1.19.1"
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.20.1"
       }
     },
     "object.pick": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
-      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
-      "dev": true,
+      "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==",
       "requires": {
         "isobject": "^3.0.1"
       }
@@ -16854,15 +16795,10 @@
       "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
       "dev": true
     },
-    "omit.js": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/omit.js/-/omit.js-2.0.2.tgz",
-      "integrity": "sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg=="
-    },
     "on-finished": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
-      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
       "dev": true,
       "requires": {
         "ee-first": "1.1.1"
@@ -16877,7 +16813,7 @@
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
       "requires": {
         "wrappy": "1"
       }
@@ -16885,7 +16821,7 @@
     "onetime": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
-      "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+      "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
       "dev": true,
       "requires": {
         "mimic-fn": "^1.0.0"
@@ -16981,13 +16917,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "strip-ansi": {
@@ -17010,43 +16946,17 @@
         }
       }
     },
-    "original": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
-      "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
-      "dev": true,
-      "requires": {
-        "url-parse": "^1.4.3"
-      }
-    },
     "os-browserify": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
-      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
-      "dev": true
-    },
-    "os-homedir": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
-      "dev": true
+      "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="
     },
     "os-tmpdir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
       "dev": true
     },
-    "osenv": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
-      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
-      "dev": true,
-      "requires": {
-        "os-homedir": "^1.0.0",
-        "os-tmpdir": "^1.0.0"
-      }
-    },
     "p-cancelable": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
@@ -17055,7 +16965,7 @@
     "p-each-series": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz",
-      "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=",
+      "integrity": "sha512-J/e9xiZZQNrt+958FFzJ+auItsBGq+UrQ7nE89AUP7UOTtjHnkISANXLdayhVzh538UnLMCSlf13lFfRIAKQOA==",
       "dev": true,
       "requires": {
         "p-reduce": "^1.0.0"
@@ -17073,13 +16983,13 @@
     "p-finally": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
       "dev": true
     },
     "p-is-promise": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
-      "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
+      "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==",
       "dev": true
     },
     "p-limit": {
@@ -17109,7 +17019,7 @@
     "p-reduce": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz",
-      "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=",
+      "integrity": "sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==",
       "dev": true
     },
     "p-retry": {
@@ -17182,14 +17092,12 @@
     "pako": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
-      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
-      "dev": true
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
     },
     "parallel-transform": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
       "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
-      "dev": true,
       "requires": {
         "cyclist": "^1.0.1",
         "inherits": "^2.0.3",
@@ -17199,7 +17107,7 @@
     "param-case": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
-      "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+      "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
       "dev": true,
       "requires": {
         "no-case": "^2.2.0"
@@ -17218,7 +17126,6 @@
       "version": "5.1.6",
       "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
       "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
-      "dev": true,
       "requires": {
         "asn1.js": "^5.2.0",
         "browserify-aes": "^1.0.0",
@@ -17253,7 +17160,7 @@
     "parse-passwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
-      "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+      "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
       "dev": true
     },
     "parse5": {
@@ -17288,19 +17195,17 @@
     "pascalcase": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
-      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
-      "dev": true
+      "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw=="
     },
     "path-browserify": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
-      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
-      "dev": true
+      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ=="
     },
     "path-dirname": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
-      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+      "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==",
       "dev": true
     },
     "path-exists": {
@@ -17311,18 +17216,18 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
     },
     "path-is-inside": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
-      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
       "dev": true
     },
     "path-key": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
-      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
       "dev": true
     },
     "path-parse": {
@@ -17333,7 +17238,7 @@
     "path-to-regexp": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
       "dev": true
     },
     "path-type": {
@@ -17349,7 +17254,6 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
       "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
-      "dev": true,
       "requires": {
         "create-hash": "^1.1.2",
         "create-hmac": "^1.1.4",
@@ -17361,13 +17265,13 @@
     "pend": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
-      "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
+      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
       "dev": true
     },
     "performance-now": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+      "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
     },
     "picocolors": {
       "version": "0.2.1",
@@ -17392,7 +17296,7 @@
         "cross-spawn": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==",
           "dev": true,
           "requires": {
             "lru-cache": "^4.0.1",
@@ -17418,7 +17322,7 @@
         "get-stream": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+          "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==",
           "dev": true
         },
         "lru-cache": {
@@ -17443,7 +17347,7 @@
         "yallist": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
           "dev": true
         }
       }
@@ -17451,19 +17355,19 @@
     "pify": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+      "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
       "dev": true
     },
     "pinkie": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==",
       "dev": true
     },
     "pinkie-promise": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==",
       "dev": true,
       "requires": {
         "pinkie": "^2.0.0"
@@ -17479,7 +17383,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
       "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
-      "dev": true,
       "requires": {
         "find-up": "^3.0.0"
       },
@@ -17488,7 +17391,6 @@
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
           "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-          "dev": true,
           "requires": {
             "locate-path": "^3.0.0"
           }
@@ -17497,7 +17399,6 @@
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
           "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
-          "dev": true,
           "requires": {
             "p-locate": "^3.0.0",
             "path-exists": "^3.0.0"
@@ -17507,7 +17408,6 @@
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
           "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
-          "dev": true,
           "requires": {
             "p-limit": "^2.0.0"
           }
@@ -17515,8 +17415,7 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-          "dev": true
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="
         }
       }
     },
@@ -17569,8 +17468,7 @@
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
-      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
-      "dev": true
+      "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg=="
     },
     "postcss": {
       "version": "7.0.39",
@@ -18124,9 +18022,9 @@
       }
     },
     "postcss-selector-parser": {
-      "version": "6.0.9",
-      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz",
-      "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==",
+      "version": "6.0.10",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+      "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
       "dev": true,
       "requires": {
         "cssesc": "^3.0.0",
@@ -18172,25 +18070,25 @@
     "prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
       "dev": true
     },
     "prepend-http": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
-      "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
+      "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="
     },
     "prettier": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
-      "integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
+      "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
       "dev": true,
       "optional": true
     },
     "pretty": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz",
-      "integrity": "sha1-rbx5YLe7/iiaVX3F9zdhmiINBqU=",
+      "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==",
       "dev": true,
       "requires": {
         "condense-newlines": "^0.2.1",
@@ -18201,7 +18099,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "dev": true,
           "requires": {
             "is-extendable": "^0.1.0"
@@ -18258,15 +18156,15 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         }
       }
     },
     "prismjs": {
-      "version": "1.27.0",
-      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
-      "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
+      "version": "1.28.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz",
+      "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==",
       "dev": true
     },
     "private": {
@@ -18278,8 +18176,7 @@
     "process": {
       "version": "0.11.10",
       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
-      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
-      "dev": true
+      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
     },
     "process-exists": {
       "version": "3.1.0",
@@ -18303,7 +18200,7 @@
     "promise-inflight": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
-      "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
+      "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="
     },
     "promise-retry": {
       "version": "2.0.1",
@@ -18326,7 +18223,7 @@
     "proto-list": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
-      "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
+      "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
       "dev": true
     },
     "proxy-addr": {
@@ -18342,8 +18239,7 @@
     "prr": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
-      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
-      "dev": true
+      "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="
     },
     "ps-list": {
       "version": "4.1.0",
@@ -18358,7 +18254,7 @@
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==",
       "dev": true
     },
     "psl": {
@@ -18370,7 +18266,6 @@
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
       "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
-      "dev": true,
       "requires": {
         "bn.js": "^4.1.0",
         "browserify-rsa": "^4.0.0",
@@ -18383,8 +18278,7 @@
         "bn.js": {
           "version": "4.12.0",
           "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
-          "dev": true
+          "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         }
       }
     },
@@ -18401,7 +18295,6 @@
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
       "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
-      "dev": true,
       "requires": {
         "duplexify": "^3.6.0",
         "inherits": "^2.0.3",
@@ -18412,7 +18305,6 @@
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
           "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
-          "dev": true,
           "requires": {
             "end-of-stream": "^1.1.0",
             "once": "^1.3.1"
@@ -18436,7 +18328,7 @@
     "q": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
-      "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
+      "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
       "dev": true
     },
     "qrious": {
@@ -18463,14 +18355,12 @@
     "querystring": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
-      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
-      "dev": true
+      "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="
     },
     "querystring-es3": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
-      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
-      "dev": true
+      "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="
     },
     "querystringify": {
       "version": "2.2.0",
@@ -18488,7 +18378,6 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
       "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
-      "dev": true,
       "requires": {
         "safe-buffer": "^5.1.0"
       }
@@ -18497,7 +18386,6 @@
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
       "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
-      "dev": true,
       "requires": {
         "randombytes": "^2.0.5",
         "safe-buffer": "^5.1.0"
@@ -18510,17 +18398,36 @@
       "dev": true
     },
     "raw-body": {
-      "version": "2.4.3",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
-      "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
       "dev": true,
       "requires": {
         "bytes": "3.1.2",
-        "http-errors": "1.8.1",
+        "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "unpipe": "1.0.0"
       },
       "dependencies": {
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+          "dev": true
+        },
+        "http-errors": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+          "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+          "dev": true,
+          "requires": {
+            "depd": "2.0.0",
+            "inherits": "2.0.4",
+            "setprototypeof": "1.2.0",
+            "statuses": "2.0.1",
+            "toidentifier": "1.0.1"
+          }
+        },
         "iconv-lite": {
           "version": "0.4.24",
           "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -18529,6 +18436,12 @@
           "requires": {
             "safer-buffer": ">= 2.1.2 < 3"
           }
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+          "dev": true
         }
       }
     },
@@ -18543,6 +18456,53 @@
         "strip-json-comments": "~2.0.1"
       }
     },
+    "rc-align": {
+      "version": "4.0.15",
+      "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.15.tgz",
+      "integrity": "sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "dom-align": "^1.7.0",
+        "rc-util": "^5.26.0",
+        "resize-observer-polyfill": "^1.5.1"
+      }
+    },
+    "rc-cascader": {
+      "version": "3.7.3",
+      "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.7.3.tgz",
+      "integrity": "sha512-KBpT+kzhxDW+hxPiNk4zaKa99+Lie2/8nnI11XF+FIOPl4Bj9VlFZi61GrnWzhLGA7VEN+dTxAkNOjkySDa0dA==",
+      "requires": {
+        "@babel/runtime": "^7.12.5",
+        "array-tree-filter": "^2.1.0",
+        "classnames": "^2.3.1",
+        "rc-select": "~14.1.0",
+        "rc-tree": "~5.7.0",
+        "rc-util": "^5.6.1"
+      }
+    },
+    "rc-checkbox": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.0.1.tgz",
+      "integrity": "sha512-k7nxDWxYF+jDI0ZcCvuvj71xONmWRVe5+1MKcERRR9MRyP3tZ69b+yUCSXXh+sik4/Hc9P5wHr2nnUoGS2zBjA==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.3.2",
+        "rc-util": "^5.25.2"
+      }
+    },
+    "rc-collapse": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.4.2.tgz",
+      "integrity": "sha512-jpTwLgJzkhAgp2Wpi3xmbTbbYExg6fkptL67Uu5LCRVEj6wqmy0DHTjjeynsjOLsppHGHu41t1ELntZ0lEvS/Q==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "rc-motion": "^2.3.4",
+        "rc-util": "^5.2.1",
+        "shallowequal": "^1.1.0"
+      }
+    },
     "rc-config-loader": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-3.0.0.tgz",
@@ -18554,6 +18514,393 @@
         "require-from-string": "^2.0.2"
       }
     },
+    "rc-dialog": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.0.2.tgz",
+      "integrity": "sha512-s3U+24xWUuB6Bn2Lk/Qt6rufy+uT+QvWkiFhNBcO9APLxcFFczWamaq7x9h8SCuhfc1nHcW4y8NbMsnAjNnWyg==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "@rc-component/portal": "^1.0.0-8",
+        "classnames": "^2.2.6",
+        "rc-motion": "^2.3.0",
+        "rc-util": "^5.21.0"
+      }
+    },
+    "rc-drawer": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-6.3.0.tgz",
+      "integrity": "sha512-uBZVb3xTAR+dBV53d/bUhTctCw3pwcwJoM7g5aX+7vgwt2zzVzoJ6aqFjYJpBlZ9zp0dVYN8fV+hykFE7c4lig==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "@rc-component/portal": "^1.1.1",
+        "classnames": "^2.2.6",
+        "rc-motion": "^2.6.1",
+        "rc-util": "^5.21.2"
+      }
+    },
+    "rc-dropdown": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.0.1.tgz",
+      "integrity": "sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g==",
+      "requires": {
+        "@babel/runtime": "^7.18.3",
+        "classnames": "^2.2.6",
+        "rc-trigger": "^5.3.1",
+        "rc-util": "^5.17.0"
+      }
+    },
+    "rc-field-form": {
+      "version": "1.38.2",
+      "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.38.2.tgz",
+      "integrity": "sha512-O83Oi1qPyEv31Sg+Jwvsj6pXc8uQI2BtIAkURr5lvEYHVggXJhdU/nynK8wY1gbw0qR48k731sN5ON4egRCROA==",
+      "requires": {
+        "@babel/runtime": "^7.18.0",
+        "async-validator": "^4.1.0",
+        "rc-util": "^5.32.2"
+      }
+    },
+    "rc-image": {
+      "version": "5.13.0",
+      "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.13.0.tgz",
+      "integrity": "sha512-iZTOmw5eWo2+gcrJMMcnd7SsxVHl3w5xlyCgsULUdJhJbnuI8i/AL0tVOsE7aLn9VfOh1qgDT3mC2G75/c7mqg==",
+      "requires": {
+        "@babel/runtime": "^7.11.2",
+        "@rc-component/portal": "^1.0.2",
+        "classnames": "^2.2.6",
+        "rc-dialog": "~9.0.0",
+        "rc-motion": "^2.6.2",
+        "rc-util": "^5.0.6"
+      }
+    },
+    "rc-input": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-0.1.4.tgz",
+      "integrity": "sha512-FqDdNz+fV2dKNgfXzcSLKvC+jEs1709t7nD+WdfjrdSaOcefpgc7BUJYadc3usaING+b7ediMTfKxuJBsEFbXA==",
+      "requires": {
+        "@babel/runtime": "^7.11.1",
+        "classnames": "^2.2.1",
+        "rc-util": "^5.18.1"
+      }
+    },
+    "rc-input-number": {
+      "version": "7.3.11",
+      "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.3.11.tgz",
+      "integrity": "sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.5",
+        "rc-util": "^5.23.0"
+      }
+    },
+    "rc-mentions": {
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.13.1.tgz",
+      "integrity": "sha512-FCkaWw6JQygtOz0+Vxz/M/NWqrWHB9LwqlY2RtcuFqWJNFK9njijOOzTSsBGANliGufVUzx/xuPHmZPBV0+Hgw==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.6",
+        "rc-menu": "~9.8.0",
+        "rc-textarea": "^0.4.0",
+        "rc-trigger": "^5.0.4",
+        "rc-util": "^5.22.5"
+      }
+    },
+    "rc-menu": {
+      "version": "9.8.4",
+      "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.8.4.tgz",
+      "integrity": "sha512-lmw2j8I2fhdIzHmC9ajfImfckt0WDb2KVJJBBRIsxPEw2kGkEfjLMUoB1NgiNT/Q5cC8PdjGOGQjHJIJMwyNMw==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "rc-motion": "^2.4.3",
+        "rc-overflow": "^1.2.8",
+        "rc-trigger": "^5.1.2",
+        "rc-util": "^5.27.0"
+      }
+    },
+    "rc-motion": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.0.tgz",
+      "integrity": "sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ==",
+      "requires": {
+        "@babel/runtime": "^7.11.1",
+        "classnames": "^2.2.1",
+        "rc-util": "^5.21.0"
+      }
+    },
+    "rc-notification": {
+      "version": "4.6.1",
+      "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.6.1.tgz",
+      "integrity": "sha512-NSmFYwrrdY3+un1GvDAJQw62Xi9LNMSsoQyo95tuaYrcad5Bn9gJUL8AREufRxSQAQnr64u3LtP3EUyLYT6bhw==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "rc-motion": "^2.2.0",
+        "rc-util": "^5.20.1"
+      }
+    },
+    "rc-overflow": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.2.tgz",
+      "integrity": "sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==",
+      "requires": {
+        "@babel/runtime": "^7.11.1",
+        "classnames": "^2.2.1",
+        "rc-resize-observer": "^1.0.0",
+        "rc-util": "^5.37.0"
+      }
+    },
+    "rc-pagination": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.2.0.tgz",
+      "integrity": "sha512-5tIXjB670WwwcAJzAqp2J+cOBS9W3cH/WU1EiYwXljuZ4vtZXKlY2Idq8FZrnYBz8KhN3vwPo9CoV/SJS6SL1w==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.1"
+      }
+    },
+    "rc-picker": {
+      "version": "2.7.6",
+      "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.7.6.tgz",
+      "integrity": "sha512-H9if/BUJUZBOhPfWcPeT15JUI3/ntrG9muzERrXDkSoWmDj4yzmBvumozpxYrHwjcKnjyDGAke68d+whWwvhHA==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.1",
+        "date-fns": "2.x",
+        "dayjs": "1.x",
+        "moment": "^2.24.0",
+        "rc-trigger": "^5.0.4",
+        "rc-util": "^5.37.0",
+        "shallowequal": "^1.1.0"
+      }
+    },
+    "rc-progress": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.2.tgz",
+      "integrity": "sha512-iAGhwWU+tsayP+Jkl9T4+6rHeQTG9kDz8JAHZk4XtQOcYN5fj9H34NXNEdRdZx94VUDHMqCb1yOIvi8eJRh67w==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.6",
+        "rc-util": "^5.16.1"
+      }
+    },
+    "rc-rate": {
+      "version": "2.9.3",
+      "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.3.tgz",
+      "integrity": "sha512-2THssUSnRhtqIouQIIXqsZGzRczvp4WsH4WvGuhiwm+LG2fVpDUJliP9O1zeDOZvYfBE/Bup4SgHun/eCkbjgQ==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.5",
+        "rc-util": "^5.0.1"
+      }
+    },
+    "rc-resize-observer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.0.tgz",
+      "integrity": "sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==",
+      "requires": {
+        "@babel/runtime": "^7.20.7",
+        "classnames": "^2.2.1",
+        "rc-util": "^5.38.0",
+        "resize-observer-polyfill": "^1.5.1"
+      }
+    },
+    "rc-segmented": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.3.0.tgz",
+      "integrity": "sha512-I3FtM5Smua/ESXutFfb8gJ8ZPcvFR+qUgeeGFQHBOvRiRKyAk4aBE5nfqrxXx+h8/vn60DQjOt6i4RNtrbOobg==",
+      "requires": {
+        "@babel/runtime": "^7.11.1",
+        "classnames": "^2.2.1",
+        "rc-motion": "^2.4.4",
+        "rc-util": "^5.17.0"
+      }
+    },
+    "rc-select": {
+      "version": "14.1.18",
+      "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.1.18.tgz",
+      "integrity": "sha512-4JgY3oG2Yz68ECMUSCON7mtxuJvCSj+LJpHEg/AONaaVBxIIrmI/ZTuMJkyojall/X50YdBe5oMKqHHPNiPzEg==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "rc-motion": "^2.0.1",
+        "rc-overflow": "^1.0.0",
+        "rc-trigger": "^5.0.4",
+        "rc-util": "^5.16.1",
+        "rc-virtual-list": "^3.2.0"
+      }
+    },
+    "rc-slider": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.0.1.tgz",
+      "integrity": "sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.5",
+        "rc-util": "^5.18.1",
+        "shallowequal": "^1.1.0"
+      }
+    },
+    "rc-steps": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-5.0.0.tgz",
+      "integrity": "sha512-9TgRvnVYirdhbV0C3syJFj9EhCRqoJAsxt4i1rED5o8/ZcSv5TLIYyo4H8MCjLPvbe2R+oBAm/IYBEtC+OS1Rw==",
+      "requires": {
+        "@babel/runtime": "^7.16.7",
+        "classnames": "^2.2.3",
+        "rc-util": "^5.16.1"
+      }
+    },
+    "rc-switch": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz",
+      "integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.1",
+        "rc-util": "^5.0.1"
+      }
+    },
+    "rc-table": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.26.0.tgz",
+      "integrity": "sha512-0cD8e6S+DTGAt5nBZQIPFYEaIukn17sfa5uFL98faHlH/whZzD8ii3dbFL4wmUDEL4BLybhYop+QUfZJ4CPvNQ==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.5",
+        "rc-resize-observer": "^1.1.0",
+        "rc-util": "^5.22.5",
+        "shallowequal": "^1.1.0"
+      }
+    },
+    "rc-tabs": {
+      "version": "12.5.10",
+      "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-12.5.10.tgz",
+      "integrity": "sha512-Ay0l0jtd4eXepFH9vWBvinBjqOpqzcsJTerBGwJy435P2S90Uu38q8U/mvc1sxUEVOXX5ZCFbxcWPnfG3dH+tQ==",
+      "requires": {
+        "@babel/runtime": "^7.11.2",
+        "classnames": "2.x",
+        "rc-dropdown": "~4.0.0",
+        "rc-menu": "~9.8.0",
+        "rc-motion": "^2.6.2",
+        "rc-resize-observer": "^1.0.0",
+        "rc-util": "^5.16.0"
+      }
+    },
+    "rc-textarea": {
+      "version": "0.4.7",
+      "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.4.7.tgz",
+      "integrity": "sha512-IQPd1CDI3mnMlkFyzt2O4gQ2lxUsnBAeJEoZGJnkkXgORNqyM9qovdrCj9NzcRfpHgLdzaEbU3AmobNFGUznwQ==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "^2.2.1",
+        "rc-resize-observer": "^1.0.0",
+        "rc-util": "^5.24.4",
+        "shallowequal": "^1.1.0"
+      }
+    },
+    "rc-tooltip": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.2.2.tgz",
+      "integrity": "sha512-jtQzU/18S6EI3lhSGoDYhPqNpWajMtS5VV/ld1LwyfrDByQpYmw/LW6U7oFXXLukjfDHQ7Ju705A82PRNFWYhg==",
+      "requires": {
+        "@babel/runtime": "^7.11.2",
+        "classnames": "^2.3.1",
+        "rc-trigger": "^5.0.0"
+      }
+    },
+    "rc-tree": {
+      "version": "5.7.12",
+      "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.7.12.tgz",
+      "integrity": "sha512-LXA5nY2hG5koIAlHW5sgXgLpOMz+bFRbnZZ+cCg0tQs4Wv1AmY7EDi1SK7iFXhslYockbqUerQan82jljoaItg==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "rc-motion": "^2.0.1",
+        "rc-util": "^5.16.1",
+        "rc-virtual-list": "^3.5.1"
+      }
+    },
+    "rc-tree-select": {
+      "version": "5.5.5",
+      "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.5.5.tgz",
+      "integrity": "sha512-k2av7jF6tW9bIO4mQhaVdV4kJ1c54oxV3/hHVU+oD251Gb5JN+m1RbJFTMf1o0rAFqkvto33rxMdpafaGKQRJw==",
+      "requires": {
+        "@babel/runtime": "^7.10.1",
+        "classnames": "2.x",
+        "rc-select": "~14.1.0",
+        "rc-tree": "~5.7.0",
+        "rc-util": "^5.16.1"
+      }
+    },
+    "rc-trigger": {
+      "version": "5.3.4",
+      "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.3.4.tgz",
+      "integrity": "sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==",
+      "requires": {
+        "@babel/runtime": "^7.18.3",
+        "classnames": "^2.2.6",
+        "rc-align": "^4.0.0",
+        "rc-motion": "^2.0.0",
+        "rc-util": "^5.19.2"
+      }
+    },
+    "rc-upload": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.6.tgz",
+      "integrity": "sha512-Bt7ESeG5tT3IY82fZcP+s0tQU2xmo1W6P3S8NboUUliquJLQYLkUcsaExi3IlBVr43GQMCjo30RA2o0i70+NjA==",
+      "requires": {
+        "@babel/runtime": "^7.18.3",
+        "classnames": "^2.2.5",
+        "rc-util": "^5.2.0"
+      }
+    },
+    "rc-util": {
+      "version": "5.39.1",
+      "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.39.1.tgz",
+      "integrity": "sha512-OW/ERynNDgNr4y0oiFmtes3rbEamXw7GHGbkbNd9iRr7kgT03T6fT0b9WpJ3mbxKhyOcAHnGcIoh5u/cjrC2OQ==",
+      "requires": {
+        "@babel/runtime": "^7.18.3",
+        "react-is": "^18.2.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "18.2.0",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+        }
+      }
+    },
+    "rc-virtual-list": {
+      "version": "3.11.4",
+      "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.11.4.tgz",
+      "integrity": "sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA==",
+      "requires": {
+        "@babel/runtime": "^7.20.0",
+        "classnames": "^2.2.6",
+        "rc-resize-observer": "^1.0.0",
+        "rc-util": "^5.36.0"
+      }
+    },
+    "react": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+      "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "react-dom": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+      "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.0"
+      }
+    },
     "react-is": {
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -18630,13 +18977,13 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         },
         "read-pkg": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
-          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==",
           "dev": true,
           "requires": {
             "load-json-file": "^4.0.0",
@@ -18690,42 +19037,6 @@
         "source-map": "~0.6.1"
       }
     },
-    "redent": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-      "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
-      "dev": true,
-      "requires": {
-        "indent-string": "^2.1.0",
-        "strip-indent": "^1.0.1"
-      },
-      "dependencies": {
-        "get-stdin": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
-          "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
-          "dev": true
-        },
-        "indent-string": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-          "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
-          "dev": true,
-          "requires": {
-            "repeating": "^2.0.0"
-          }
-        },
-        "strip-indent": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
-          "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
-          "dev": true,
-          "requires": {
-            "get-stdin": "^4.0.1"
-          }
-        }
-      }
-    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -18742,14 +19053,14 @@
       }
     },
     "regenerator-runtime": {
-      "version": "0.13.9",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
-      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+      "version": "0.14.1",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
     },
     "regenerator-transform": {
-      "version": "0.14.5",
-      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz",
-      "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==",
+      "version": "0.15.0",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz",
+      "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==",
       "dev": true,
       "requires": {
         "@babel/runtime": "^7.8.4"
@@ -18759,20 +19070,20 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
       "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
-      "dev": true,
       "requires": {
         "extend-shallow": "^3.0.2",
         "safe-regex": "^1.1.0"
       }
     },
     "regexp.prototype.flags": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz",
-      "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==",
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+      "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3"
+        "define-properties": "^1.1.3",
+        "functions-have-names": "^1.2.2"
       }
     },
     "regexpp": {
@@ -18782,9 +19093,9 @@
       "dev": true
     },
     "regexpu-core": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz",
-      "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==",
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz",
+      "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==",
       "dev": true,
       "requires": {
         "regenerate": "^1.4.2",
@@ -18796,11 +19107,11 @@
       }
     },
     "registry-auth-token": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
-      "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz",
+      "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==",
       "requires": {
-        "rc": "^1.2.8"
+        "rc": "1.2.8"
       }
     },
     "registry-url": {
@@ -18829,7 +19140,7 @@
         "jsesc": {
           "version": "0.5.0",
           "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
-          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
           "dev": true
         }
       }
@@ -18837,13 +19148,13 @@
     "relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
-      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
+      "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
       "dev": true
     },
     "remove-trailing-separator": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
-      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+      "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
       "dev": true
     },
     "renderkid": {
@@ -18860,28 +19171,28 @@
       },
       "dependencies": {
         "css-select": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz",
-          "integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==",
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
+          "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
           "dev": true,
           "requires": {
             "boolbase": "^1.0.0",
-            "css-what": "^5.1.0",
-            "domhandler": "^4.3.0",
+            "css-what": "^6.0.1",
+            "domhandler": "^4.3.1",
             "domutils": "^2.8.0",
             "nth-check": "^2.0.1"
           }
         },
         "css-what": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
-          "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==",
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+          "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
           "dev": true
         },
         "dom-serializer": {
-          "version": "1.3.2",
-          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
-          "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+          "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
           "dev": true,
           "requires": {
             "domelementtype": "^2.0.1",
@@ -18890,9 +19201,9 @@
           }
         },
         "domelementtype": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
-          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+          "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
           "dev": true
         },
         "domutils": {
@@ -18907,9 +19218,9 @@
           }
         },
         "nth-check": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
-          "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==",
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+          "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
           "dev": true,
           "requires": {
             "boolbase": "^1.0.0"
@@ -18920,23 +19231,12 @@
     "repeat-element": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
-      "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
-      "dev": true
+      "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ=="
     },
     "repeat-string": {
       "version": "1.6.1",
       "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
-      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
-      "dev": true
-    },
-    "repeating": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
-      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
-      "dev": true,
-      "requires": {
-        "is-finite": "^1.0.0"
-      }
+      "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w=="
     },
     "request": {
       "version": "2.88.2",
@@ -18988,7 +19288,7 @@
     "require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
       "dev": true
     },
     "require-from-string": {
@@ -19015,7 +19315,7 @@
     "requires-port": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
-      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
       "dev": true
     },
     "resize-observer-polyfill": {
@@ -19034,7 +19334,7 @@
     "resolve-cwd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
-      "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+      "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==",
       "dev": true,
       "requires": {
         "resolve-from": "^3.0.0"
@@ -19043,19 +19343,18 @@
     "resolve-from": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
-      "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+      "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==",
       "dev": true
     },
     "resolve-url": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
-      "dev": true
+      "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg=="
     },
     "responselike": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
-      "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
+      "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==",
       "requires": {
         "lowercase-keys": "^1.0.0"
       }
@@ -19063,7 +19362,7 @@
     "restore-cursor": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
-      "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+      "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
       "dev": true,
       "requires": {
         "onetime": "^2.0.0",
@@ -19073,13 +19372,12 @@
     "ret": {
       "version": "0.1.15",
       "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
-      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
-      "dev": true
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
     },
     "retry": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
-      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
+      "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="
     },
     "reusify": {
       "version": "1.0.4",
@@ -19090,13 +19388,13 @@
     "rgb-regex": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
-      "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=",
+      "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==",
       "dev": true
     },
     "rgba-regex": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz",
-      "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=",
+      "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==",
       "dev": true
     },
     "rimraf": {
@@ -19111,7 +19409,6 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
       "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
-      "dev": true,
       "requires": {
         "hash-base": "^3.0.0",
         "inherits": "^2.0.1"
@@ -19151,8 +19448,7 @@
     "run-queue": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
-      "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
-      "dev": true,
+      "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==",
       "requires": {
         "aproba": "^1.1.1"
       }
@@ -19174,8 +19470,7 @@
     "safe-regex": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
-      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
-      "dev": true,
+      "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==",
       "requires": {
         "ret": "~0.1.10"
       }
@@ -19231,7 +19526,7 @@
         "normalize-path": {
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-          "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+          "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
           "dev": true,
           "requires": {
             "remove-trailing-separator": "^1.0.1"
@@ -19240,9 +19535,9 @@
       }
     },
     "sass": {
-      "version": "1.49.9",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz",
-      "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz",
+      "integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==",
       "dev": true,
       "requires": {
         "chokidar": ">=3.0.0 <4.0.0",
@@ -19250,172 +19545,6 @@
         "source-map-js": ">=0.6.2 <2.0.0"
       }
     },
-    "sass-graph": {
-      "version": "2.2.5",
-      "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz",
-      "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==",
-      "dev": true,
-      "requires": {
-        "glob": "^7.0.0",
-        "lodash": "^4.0.0",
-        "scss-tokenizer": "^0.2.3",
-        "yargs": "^13.3.2"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
-          "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
-          "dev": true
-        },
-        "ansi-styles": {
-          "version": "3.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-          "dev": true,
-          "requires": {
-            "color-convert": "^1.9.0"
-          }
-        },
-        "cliui": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
-          "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
-          "dev": true,
-          "requires": {
-            "string-width": "^3.1.0",
-            "strip-ansi": "^5.2.0",
-            "wrap-ansi": "^5.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "1.9.3",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-          "dev": true,
-          "requires": {
-            "color-name": "1.1.3"
-          }
-        },
-        "color-name": {
-          "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-          "dev": true
-        },
-        "emoji-regex": {
-          "version": "7.0.3",
-          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
-          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
-          "dev": true
-        },
-        "find-up": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
-          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-          "dev": true,
-          "requires": {
-            "locate-path": "^3.0.0"
-          }
-        },
-        "is-fullwidth-code-point": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
-          "dev": true
-        },
-        "locate-path": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
-          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
-          "dev": true,
-          "requires": {
-            "p-locate": "^3.0.0",
-            "path-exists": "^3.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
-          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
-          "dev": true,
-          "requires": {
-            "p-limit": "^2.0.0"
-          }
-        },
-        "path-exists": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-          "dev": true
-        },
-        "string-width": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-          "dev": true,
-          "requires": {
-            "emoji-regex": "^7.0.1",
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^5.1.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "5.2.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
-          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^4.1.0"
-          }
-        },
-        "wrap-ansi": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
-          "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^3.2.0",
-            "string-width": "^3.0.0",
-            "strip-ansi": "^5.0.0"
-          }
-        },
-        "y18n": {
-          "version": "4.0.3",
-          "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
-          "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
-          "dev": true
-        },
-        "yargs": {
-          "version": "13.3.2",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
-          "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
-          "dev": true,
-          "requires": {
-            "cliui": "^5.0.0",
-            "find-up": "^3.0.0",
-            "get-caller-file": "^2.0.1",
-            "require-directory": "^2.1.1",
-            "require-main-filename": "^2.0.0",
-            "set-blocking": "^2.0.0",
-            "string-width": "^3.0.0",
-            "which-module": "^2.0.0",
-            "y18n": "^4.0.0",
-            "yargs-parser": "^13.1.2"
-          }
-        },
-        "yargs-parser": {
-          "version": "13.1.2",
-          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
-          "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
-          "dev": true,
-          "requires": {
-            "camelcase": "^5.0.0",
-            "decamelize": "^1.2.0"
-          }
-        }
-      }
-    },
     "sass-loader": {
       "version": "8.0.2",
       "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz",
@@ -19460,8 +19589,7 @@
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
-      "dev": true
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
     },
     "saxes": {
       "version": "3.1.11",
@@ -19472,6 +19600,15 @@
         "xmlchars": "^2.1.1"
       }
     },
+    "scheduler": {
+      "version": "0.23.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+      "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+      "peer": true,
+      "requires": {
+        "loose-envify": "^1.1.0"
+      }
+    },
     "schema-utils": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
@@ -19491,31 +19628,10 @@
         "compute-scroll-into-view": "^1.0.17"
       }
     },
-    "scss-tokenizer": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
-      "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
-      "dev": true,
-      "requires": {
-        "js-base64": "^2.1.8",
-        "source-map": "^0.4.2"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.4.4",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
-          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
-          "dev": true,
-          "requires": {
-            "amdefine": ">=0.0.4"
-          }
-        }
-      }
-    },
     "sec": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/sec/-/sec-1.0.0.tgz",
-      "integrity": "sha1-Az1go60g7PLgCUDRT5eCNGV3QzU=",
+      "integrity": "sha512-rVnXtdy9keLxyssT6xung9WwtdB+kha3De93w50RNzwOslyLaDGhqoOqrh4InPoOhhqUZ4JOnovGd3BvLZ+f7A==",
       "dev": true
     },
     "seek-bzip": {
@@ -19538,12 +19654,12 @@
     "select": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
     },
     "select-hose": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
-      "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
+      "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==",
       "dev": true
     },
     "selfsigned": {
@@ -19556,9 +19672,9 @@
       }
     },
     "semver": {
-      "version": "7.3.5",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
-      "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
       "requires": {
         "lru-cache": "^6.0.0"
       }
@@ -19584,24 +19700,24 @@
       "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA=="
     },
     "send": {
-      "version": "0.17.2",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
-      "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
       "dev": true,
       "requires": {
         "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "destroy": "~1.0.4",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
         "fresh": "0.5.2",
-        "http-errors": "1.8.1",
+        "http-errors": "2.0.0",
         "mime": "1.6.0",
         "ms": "2.1.3",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "range-parser": "~1.2.1",
-        "statuses": "~1.5.0"
+        "statuses": "2.0.1"
       },
       "dependencies": {
         "debug": {
@@ -19616,16 +19732,41 @@
             "ms": {
               "version": "2.0.0",
               "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+              "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
               "dev": true
             }
           }
         },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+          "dev": true
+        },
+        "http-errors": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+          "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+          "dev": true,
+          "requires": {
+            "depd": "2.0.0",
+            "inherits": "2.0.4",
+            "setprototypeof": "1.2.0",
+            "statuses": "2.0.1",
+            "toidentifier": "1.0.1"
+          }
+        },
         "ms": {
           "version": "2.1.3",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
           "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
           "dev": true
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+          "dev": true
         }
       }
     },
@@ -19633,7 +19774,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
       "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
-      "dev": true,
       "requires": {
         "randombytes": "^2.1.0"
       }
@@ -19641,7 +19781,7 @@
     "serve-index": {
       "version": "1.9.1",
       "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
-      "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+      "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
       "dev": true,
       "requires": {
         "accepts": "~1.3.4",
@@ -19665,7 +19805,7 @@
         "http-errors": {
           "version": "1.6.3",
           "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
-          "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+          "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
           "dev": true,
           "requires": {
             "depd": "~1.1.2",
@@ -19677,13 +19817,13 @@
         "inherits": {
           "version": "2.0.3",
           "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
           "dev": true
         },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
         },
         "setprototypeof": {
@@ -19695,27 +19835,26 @@
       }
     },
     "serve-static": {
-      "version": "1.14.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
-      "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
       "dev": true,
       "requires": {
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.17.2"
+        "send": "0.18.0"
       }
     },
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
     },
     "set-value": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
       "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
-      "dev": true,
       "requires": {
         "extend-shallow": "^2.0.1",
         "is-extendable": "^0.1.1",
@@ -19726,8 +19865,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "requires": {
             "is-extendable": "^0.1.0"
           }
@@ -19736,7 +19874,6 @@
           "version": "2.0.4",
           "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
           "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
-          "dev": true,
           "requires": {
             "isobject": "^3.0.1"
           }
@@ -19746,8 +19883,7 @@
     "setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
-      "dev": true
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
     },
     "setprototypeof": {
       "version": "1.2.0",
@@ -19759,7 +19895,6 @@
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
       "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
-      "dev": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -19779,10 +19914,15 @@
       "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
       "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
     },
+    "shallowequal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+    },
     "shebang-command": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
       "dev": true,
       "requires": {
         "shebang-regex": "^1.0.0"
@@ -19791,7 +19931,7 @@
     "shebang-regex": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
-      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
       "dev": true
     },
     "shell-quote": {
@@ -19837,7 +19977,7 @@
     "sigmund": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
-      "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
+      "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==",
       "dev": true
     },
     "signal-exit": {
@@ -19848,7 +19988,7 @@
     "simple-swizzle": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
-      "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
       "dev": true,
       "requires": {
         "is-arrayish": "^0.3.1"
@@ -19905,13 +20045,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "is-fullwidth-code-point": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
           "dev": true
         }
       }
@@ -19925,7 +20065,6 @@
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
       "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
-      "dev": true,
       "requires": {
         "base": "^0.11.1",
         "debug": "^2.2.0",
@@ -19941,7 +20080,6 @@
           "version": "2.6.9",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "dev": true,
           "requires": {
             "ms": "2.0.0"
           }
@@ -19949,8 +20087,7 @@
         "define-property": {
           "version": "0.2.5",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
+          "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
           "requires": {
             "is-descriptor": "^0.1.0"
           }
@@ -19958,8 +20095,7 @@
         "extend-shallow": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
-          "dev": true,
+          "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
           "requires": {
             "is-extendable": "^0.1.0"
           }
@@ -19967,14 +20103,12 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
-          "dev": true
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
         "source-map": {
           "version": "0.5.7",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
-          "dev": true
+          "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
         }
       }
     },
@@ -19982,7 +20116,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
       "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
-      "dev": true,
       "requires": {
         "define-property": "^1.0.0",
         "isobject": "^3.0.0",
@@ -19992,8 +20125,7 @@
         "define-property": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
-          "dev": true,
+          "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
           "requires": {
             "is-descriptor": "^1.0.0"
           }
@@ -20002,7 +20134,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
           "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -20011,7 +20142,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
           "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
-          "dev": true,
           "requires": {
             "kind-of": "^6.0.0"
           }
@@ -20020,7 +20150,6 @@
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
           "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
-          "dev": true,
           "requires": {
             "is-accessor-descriptor": "^1.0.0",
             "is-data-descriptor": "^1.0.0",
@@ -20033,7 +20162,6 @@
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
       "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
-      "dev": true,
       "requires": {
         "kind-of": "^3.2.0"
       },
@@ -20041,8 +20169,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-          "dev": true,
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -20069,13 +20196,13 @@
       }
     },
     "sockjs-client": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.0.tgz",
-      "integrity": "sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ==",
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz",
+      "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==",
       "dev": true,
       "requires": {
         "debug": "^3.2.7",
-        "eventsource": "^1.1.0",
+        "eventsource": "^2.0.2",
         "faye-websocket": "^0.11.4",
         "inherits": "^2.0.4",
         "url-parse": "^1.5.10"
@@ -20102,19 +20229,19 @@
       }
     },
     "socks-proxy-agent": {
-      "version": "6.1.1",
-      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz",
-      "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==",
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
+      "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
       "requires": {
         "agent-base": "^6.0.2",
-        "debug": "^4.3.1",
-        "socks": "^2.6.1"
+        "debug": "^4.3.3",
+        "socks": "^2.6.2"
       }
     },
     "sort-keys": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
-      "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+      "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==",
       "dev": true,
       "requires": {
         "is-plain-obj": "^1.0.0"
@@ -20123,7 +20250,7 @@
     "sort-keys-length": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz",
-      "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=",
+      "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==",
       "dev": true,
       "requires": {
         "sort-keys": "^1.0.0"
@@ -20153,7 +20280,6 @@
       "version": "0.5.3",
       "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
       "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
-      "dev": true,
       "requires": {
         "atob": "^2.1.2",
         "decode-uri-component": "^0.2.0",
@@ -20166,7 +20292,6 @@
       "version": "0.5.21",
       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
       "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
-      "dev": true,
       "requires": {
         "buffer-from": "^1.0.0",
         "source-map": "^0.6.0"
@@ -20175,8 +20300,7 @@
     "source-map-url": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
-      "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
-      "dev": true
+      "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
     },
     "sourcemap-codec": {
       "version": "1.4.8",
@@ -20186,7 +20310,7 @@
     "spawn-please": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-0.3.0.tgz",
-      "integrity": "sha1-2zOOxM/2Orxp8dDgjO6euL69nRE="
+      "integrity": "sha512-gf9GJwAWhW0gnQp0dGui+nhIVICx1lGM1Ox95HzfaDBOQTauqlvHFLpo4vtAB3E377SA0YMIyRCh1w0S6R5m2w=="
     },
     "spdx-correct": {
       "version": "3.1.1",
@@ -20260,11 +20384,15 @@
         }
       }
     },
+    "split-on-first": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+      "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
+    },
     "split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
       "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
-      "dev": true,
       "requires": {
         "extend-shallow": "^3.0.0"
       }
@@ -20281,7 +20409,7 @@
     "sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
     },
     "sshpk": {
       "version": "1.17.0",
@@ -20331,16 +20459,15 @@
       }
     },
     "stackframe": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz",
-      "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==",
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
+      "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
       "dev": true
     },
     "static-extend": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
-      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
-      "dev": true,
+      "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==",
       "requires": {
         "define-property": "^0.2.5",
         "object-copy": "^0.1.0"
@@ -20349,8 +20476,7 @@
         "define-property": {
           "version": "0.2.5",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
-          "dev": true,
+          "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
           "requires": {
             "is-descriptor": "^0.1.0"
           }
@@ -20360,28 +20486,19 @@
     "statuses": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
-      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
       "dev": true
     },
-    "stdout-stream": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
-      "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
-      "dev": true,
-      "requires": {
-        "readable-stream": "^2.0.1"
-      }
-    },
     "stealthy-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
-      "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
+      "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==",
       "dev": true
     },
     "steno": {
       "version": "0.4.4",
       "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz",
-      "integrity": "sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=",
+      "integrity": "sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w==",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.1.3"
@@ -20391,7 +20508,6 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
       "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
-      "dev": true,
       "requires": {
         "inherits": "~2.0.1",
         "readable-stream": "^2.0.2"
@@ -20401,7 +20517,6 @@
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
       "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
-      "dev": true,
       "requires": {
         "end-of-stream": "^1.1.0",
         "stream-shift": "^1.0.0"
@@ -20411,7 +20526,6 @@
       "version": "2.8.3",
       "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
       "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
-      "dev": true,
       "requires": {
         "builtin-status-codes": "^3.0.0",
         "inherits": "^2.0.1",
@@ -20423,25 +20537,37 @@
     "stream-shift": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
-      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
-      "dev": true
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
     },
     "streamsearch": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
-      "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=",
+      "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==",
       "dev": true
     },
     "strict-uri-encode": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
-      "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
+      "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==",
       "dev": true
     },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "string-convert": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+      "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
+    },
     "string-length": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",
-      "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=",
+      "integrity": "sha512-Qka42GGrS8Mm3SZ+7cH8UXiIWI867/b/Z/feQSpQx/rbfB8UGknGEZVaUQMOUVj+soY6NpWAxily63HI1OckVQ==",
       "dev": true,
       "requires": {
         "astral-regex": "^1.0.0",
@@ -20449,15 +20575,15 @@
       },
       "dependencies": {
         "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+          "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
           "dev": true
         },
         "strip-ansi": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
           "dev": true,
           "requires": {
             "ansi-regex": "^3.0.0"
@@ -20468,7 +20594,7 @@
     "string-width": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+      "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
       "requires": {
         "code-point-at": "^1.0.0",
         "is-fullwidth-code-point": "^1.0.0",
@@ -20476,37 +20602,31 @@
       }
     },
     "string.prototype.trimend": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
-      "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
+      "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3"
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.19.5"
       }
     },
     "string.prototype.trimstart": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
-      "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
+      "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
       "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3"
-      }
-    },
-    "string_decoder": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-      "requires": {
-        "safe-buffer": "~5.1.0"
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.19.5"
       }
     },
     "strip-ansi": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
       "requires": {
         "ansi-regex": "^2.0.0"
       }
@@ -20514,7 +20634,7 @@
     "strip-bom": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
-      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
       "dev": true
     },
     "strip-dirs": {
@@ -20529,7 +20649,7 @@
     "strip-eof": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
-      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==",
       "dev": true
     },
     "strip-final-newline": {
@@ -20541,13 +20661,13 @@
     "strip-indent": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
-      "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
+      "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==",
       "dev": true
     },
     "strip-json-comments": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
     },
     "strip-outer": {
       "version": "1.0.1",
@@ -20558,6 +20678,11 @@
         "escape-string-regexp": "^1.0.2"
       }
     },
+    "strnum": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
+      "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
+    },
     "stylehacks": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz",
@@ -20612,7 +20737,7 @@
     "svg-tags": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
-      "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=",
+      "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
       "dev": true
     },
     "svgo": {
@@ -20668,13 +20793,13 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "mkdirp": {
@@ -20748,7 +20873,7 @@
         "is-fullwidth-code-point": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
           "dev": true
         },
         "string-width": {
@@ -20776,8 +20901,7 @@
     "tapable": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
-      "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
-      "dev": true
+      "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
     },
     "tar": {
       "version": "6.1.11",
@@ -20928,7 +21052,7 @@
         "pify": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
           "dev": true
         }
       }
@@ -20962,7 +21086,6 @@
       "version": "4.8.0",
       "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
       "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
-      "dev": true,
       "requires": {
         "commander": "^2.20.0",
         "source-map": "~0.6.1",
@@ -20972,8 +21095,7 @@
         "commander": {
           "version": "2.20.3",
           "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-          "dev": true
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
         }
       }
     },
@@ -20981,7 +21103,6 @@
       "version": "1.4.5",
       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
       "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
-      "dev": true,
       "requires": {
         "cacache": "^12.0.2",
         "find-cache-dir": "^2.1.0",
@@ -20998,7 +21119,6 @@
           "version": "12.0.4",
           "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
           "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
-          "dev": true,
           "requires": {
             "bluebird": "^3.5.5",
             "chownr": "^1.1.1",
@@ -21020,32 +21140,28 @@
         "chownr": {
           "version": "1.1.4",
           "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
-          "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
-          "dev": true
+          "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
         },
         "lru-cache": {
           "version": "5.1.1",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
           "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
-          "dev": true,
           "requires": {
             "yallist": "^3.0.2"
           }
         },
         "mkdirp": {
-          "version": "0.5.5",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-          "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
-          "dev": true,
+          "version": "0.5.6",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+          "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
           "requires": {
-            "minimist": "^1.2.5"
+            "minimist": "^1.2.6"
           }
         },
         "rimraf": {
           "version": "2.7.1",
           "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
           "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-          "dev": true,
           "requires": {
             "glob": "^7.1.3"
           }
@@ -21054,7 +21170,6 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
           "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
-          "dev": true,
           "requires": {
             "ajv": "^6.1.0",
             "ajv-errors": "^1.0.0",
@@ -21065,7 +21180,6 @@
           "version": "6.0.2",
           "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
           "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
-          "dev": true,
           "requires": {
             "figgy-pudding": "^3.5.1"
           }
@@ -21074,7 +21188,6 @@
           "version": "1.4.3",
           "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
           "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
-          "dev": true,
           "requires": {
             "source-list-map": "^2.0.0",
             "source-map": "~0.6.1"
@@ -21083,14 +21196,12 @@
         "y18n": {
           "version": "4.0.3",
           "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
-          "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
-          "dev": true
+          "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
         },
         "yallist": {
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
-          "dev": true
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
         }
       }
     },
@@ -21109,7 +21220,7 @@
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
       "dev": true
     },
     "thenify": {
@@ -21124,7 +21235,7 @@
     "thenify-all": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
-      "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
+      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
       "dev": true,
       "requires": {
         "thenify": ">= 3.1.0 < 4"
@@ -21166,20 +21277,24 @@
     "throat": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz",
-      "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=",
+      "integrity": "sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA==",
       "dev": true
     },
+    "throttle-debounce": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz",
+      "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg=="
+    },
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
       "dev": true
     },
     "through2": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
       "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
-      "dev": true,
       "requires": {
         "readable-stream": "~2.3.6",
         "xtend": "~4.0.1"
@@ -21194,14 +21309,13 @@
     "timed-out": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
-      "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+      "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==",
       "dev": true
     },
     "timers-browserify": {
       "version": "2.0.12",
       "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
       "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==",
-      "dev": true,
       "requires": {
         "setimmediate": "^1.0.4"
       }
@@ -21209,7 +21323,7 @@
     "timsort": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
-      "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
+      "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==",
       "dev": true
     },
     "tiny-emitter": {
@@ -21235,8 +21349,7 @@
     "to-arraybuffer": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
-      "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
-      "dev": true
+      "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA=="
     },
     "to-buffer": {
       "version": "1.1.1",
@@ -21247,14 +21360,13 @@
     "to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
-      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
       "dev": true
     },
     "to-object-path": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
-      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
-      "dev": true,
+      "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==",
       "requires": {
         "kind-of": "^3.0.2"
       },
@@ -21262,8 +21374,7 @@
         "kind-of": {
           "version": "3.2.2",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-          "dev": true,
+          "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
           "requires": {
             "is-buffer": "^1.1.5"
           }
@@ -21279,7 +21390,6 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
       "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
-      "dev": true,
       "requires": {
         "define-property": "^2.0.2",
         "extend-shallow": "^3.0.2",
@@ -21290,13 +21400,17 @@
     "to-regex-range": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
-      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
-      "dev": true,
+      "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==",
       "requires": {
         "is-number": "^3.0.0",
         "repeat-string": "^1.6.1"
       }
     },
+    "toggle-selection": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+      "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
+    },
     "toidentifier": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -21306,7 +21420,7 @@
     "toposort": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
-      "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
+      "integrity": "sha512-FclLrw8b9bMWf4QlCJuHBEVhSRsqDj6u3nIjAzPeJvgl//1hBlffdlk0MALceL14+koWEdU4ofRAXofbODxQzg==",
       "dev": true
     },
     "tough-cookie": {
@@ -21321,33 +21435,18 @@
     "tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
-      "dev": true
-    },
-    "trim-newlines": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
-      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
       "dev": true
     },
     "trim-repeated": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
-      "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
+      "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
       "dev": true,
       "requires": {
         "escape-string-regexp": "^1.0.2"
       }
     },
-    "true-case-path": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
-      "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
-      "dev": true,
-      "requires": {
-        "glob": "^7.1.2"
-      }
-    },
     "tryer": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
@@ -21384,7 +21483,7 @@
         "camelcase": {
           "version": "4.1.0",
           "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-          "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+          "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==",
           "dev": true
         },
         "mkdirp": {
@@ -21463,13 +21562,12 @@
     "tty-browserify": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
-      "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
-      "dev": true
+      "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw=="
     },
     "tunnel-agent": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
       "requires": {
         "safe-buffer": "^5.0.1"
       }
@@ -21477,12 +21575,12 @@
     "tweetnacl": {
       "version": "0.14.5",
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
-      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+      "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
     },
     "type-check": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
       "dev": true,
       "requires": {
         "prelude-ls": "~1.1.2"
@@ -21506,8 +21604,7 @@
     "typedarray": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
-      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
-      "dev": true
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
     },
     "typedarray-to-buffer": {
       "version": "3.1.5",
@@ -21641,9 +21738,9 @@
           }
         },
         "uglify-js": {
-          "version": "3.15.3",
-          "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz",
-          "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==",
+          "version": "3.16.1",
+          "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.1.tgz",
+          "integrity": "sha512-X5BGTIDH8U6IQ1TIRP62YC36k+ULAa1d59BxlWvPUJ1NkW5L3FwcGfEzuVvGmhJFBu0YJ5Ge25tmRISqCmLiRQ==",
           "dev": true
         },
         "webpack-sources": {
@@ -21671,14 +21768,14 @@
       }
     },
     "unbox-primitive": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
-      "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+      "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
       "dev": true,
       "requires": {
-        "function-bind": "^1.1.1",
-        "has-bigints": "^1.0.1",
-        "has-symbols": "^1.0.2",
+        "call-bind": "^1.0.2",
+        "has-bigints": "^1.0.2",
+        "has-symbols": "^1.0.3",
         "which-boxed-primitive": "^1.0.2"
       }
     },
@@ -21724,7 +21821,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
       "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
-      "dev": true,
       "requires": {
         "arr-union": "^3.1.0",
         "get-value": "^2.0.6",
@@ -21735,13 +21831,13 @@
     "uniq": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
-      "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
+      "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==",
       "dev": true
     },
     "uniqs": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz",
-      "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=",
+      "integrity": "sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ==",
       "dev": true
     },
     "unique-filename": {
@@ -21777,20 +21873,19 @@
     "unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
-      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
       "dev": true
     },
     "unquote": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
-      "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=",
+      "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==",
       "dev": true
     },
     "unset-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
-      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
-      "dev": true,
+      "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==",
       "requires": {
         "has-value": "^0.3.1",
         "isobject": "^3.0.0"
@@ -21799,8 +21894,7 @@
         "has-value": {
           "version": "0.3.1",
           "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
-          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
-          "dev": true,
+          "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
           "requires": {
             "get-value": "^2.0.3",
             "has-values": "^0.1.4",
@@ -21810,8 +21904,7 @@
             "isobject": {
               "version": "2.1.0",
               "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
-              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
-              "dev": true,
+              "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
               "requires": {
                 "isarray": "1.0.0"
               }
@@ -21821,8 +21914,7 @@
         "has-values": {
           "version": "0.1.4",
           "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
-          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
-          "dev": true
+          "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ=="
         }
       }
     },
@@ -21832,6 +21924,24 @@
       "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
       "dev": true
     },
+    "update-browserslist-db": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
+      "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==",
+      "dev": true,
+      "requires": {
+        "escalade": "^3.1.1",
+        "picocolors": "^1.0.0"
+      },
+      "dependencies": {
+        "picocolors": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+          "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+          "dev": true
+        }
+      }
+    },
     "update-notifier": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz",
@@ -21866,7 +21976,7 @@
     "upper-case": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
-      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
+      "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==",
       "dev": true
     },
     "uri-js": {
@@ -21880,14 +21990,12 @@
     "urix": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
-      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
-      "dev": true
+      "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg=="
     },
     "url": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
-      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
-      "dev": true,
+      "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==",
       "requires": {
         "punycode": "1.3.2",
         "querystring": "0.2.0"
@@ -21896,8 +22004,7 @@
         "punycode": {
           "version": "1.3.2",
           "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
-          "dev": true
+          "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="
         }
       }
     },
@@ -21953,7 +22060,7 @@
     "url-parse-lax": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
-      "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
+      "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==",
       "requires": {
         "prepend-http": "^2.0.0"
       }
@@ -21961,20 +22068,18 @@
     "url-to-options": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
-      "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=",
+      "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==",
       "dev": true
     },
     "use": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
-      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
-      "dev": true
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
     },
     "util": {
       "version": "0.11.1",
       "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
       "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
-      "dev": true,
       "requires": {
         "inherits": "2.0.3"
       },
@@ -21982,15 +22087,14 @@
         "inherits": {
           "version": "2.0.3",
           "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true
+          "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
         }
       }
     },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
     "util.promisify": {
       "version": "1.1.1",
@@ -22008,13 +22112,13 @@
     "utila": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
-      "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
+      "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
       "dev": true
     },
     "utils-merge": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
-      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
       "dev": true
     },
     "uuid": {
@@ -22041,7 +22145,7 @@
     "validate-npm-package-name": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
-      "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+      "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==",
       "requires": {
         "builtins": "^1.0.3"
       }
@@ -22049,7 +22153,7 @@
     "vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
-      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
       "dev": true
     },
     "vendors": {
@@ -22061,7 +22165,7 @@
     "verror": {
       "version": "1.10.0",
       "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
-      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
       "requires": {
         "assert-plus": "^1.0.0",
         "core-util-is": "1.0.2",
@@ -22071,32 +22175,32 @@
         "core-util-is": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+          "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
         }
       }
     },
     "vm-browserify": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
-      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
-      "dev": true
+      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
     },
     "vue": {
-      "version": "3.2.31",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.31.tgz",
-      "integrity": "sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz",
+      "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
       "requires": {
-        "@vue/compiler-dom": "3.2.31",
-        "@vue/compiler-sfc": "3.2.31",
-        "@vue/runtime-dom": "3.2.31",
-        "@vue/server-renderer": "3.2.31",
-        "@vue/shared": "3.2.31"
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-sfc": "3.2.37",
+        "@vue/runtime-dom": "3.2.37",
+        "@vue/server-renderer": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
     "vue-chartjs": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-4.1.2.tgz",
-      "integrity": "sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg=="
+      "integrity": "sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg==",
+      "requires": {}
     },
     "vue-clipboard2": {
       "version": "0.3.3",
@@ -22215,13 +22319,13 @@
           "dev": true
         },
         "micromatch": {
-          "version": "4.0.4",
-          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
-          "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+          "version": "4.0.5",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+          "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
           "dev": true,
           "requires": {
-            "braces": "^3.0.1",
-            "picomatch": "^2.2.3"
+            "braces": "^3.0.2",
+            "picomatch": "^2.3.1"
           }
         },
         "path-type": {
@@ -22286,14 +22390,63 @@
       "dev": true
     },
     "vue-i18n": {
-      "version": "9.1.9",
-      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.9.tgz",
-      "integrity": "sha512-JeRdNVxS2OGp1E+pye5XB6+M6BBkHwAv9C80Q7+kzoMdUDGRna06tjC0vCB/jDX9aWrl5swxOMFcyAr7or8XTA==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
       "requires": {
-        "@intlify/core-base": "9.1.9",
-        "@intlify/shared": "9.1.9",
-        "@intlify/vue-devtools": "9.1.9",
-        "@vue/devtools-api": "^6.0.0-beta.7"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
+      },
+      "dependencies": {
+        "@intlify/core-base": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+          "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
+          "requires": {
+            "@intlify/devtools-if": "9.2.2",
+            "@intlify/message-compiler": "9.2.2",
+            "@intlify/shared": "9.2.2",
+            "@intlify/vue-devtools": "9.2.2"
+          }
+        },
+        "@intlify/devtools-if": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+          "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
+          "requires": {
+            "@intlify/shared": "9.2.2"
+          }
+        },
+        "@intlify/message-compiler": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+          "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
+          "requires": {
+            "@intlify/shared": "9.2.2",
+            "source-map": "0.6.1"
+          }
+        },
+        "@intlify/shared": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+          "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
+        },
+        "@intlify/vue-devtools": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+          "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
+          "requires": {
+            "@intlify/core-base": "9.2.2",
+            "@intlify/shared": "9.2.2"
+          }
+        },
+        "@vue/devtools-api": {
+          "version": "6.5.0",
+          "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
+          "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
+        }
       }
     },
     "vue-jest": {
@@ -22342,19 +22495,19 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "source-map": {
           "version": "0.5.6",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
-          "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
+          "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==",
           "dev": true
         },
         "supports-color": {
@@ -22394,13 +22547,21 @@
       }
     },
     "vue-router": {
-      "version": "4.0.14",
-      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.14.tgz",
-      "integrity": "sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw==",
+      "version": "4.0.16",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz",
+      "integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==",
       "requires": {
         "@vue/devtools-api": "^6.0.0"
       }
     },
+    "vue-social-auth": {
+      "version": "1.4.9",
+      "resolved": "https://registry.npmjs.org/vue-social-auth/-/vue-social-auth-1.4.9.tgz",
+      "integrity": "sha512-LRyfLG09xzeqCAtCmy1PX1psJBhqqQt6MEhL5drp5TTdbmCpHGg5PPyWMNItNjBGLcUncxHA8P13vWE5k95hig==",
+      "requires": {
+        "graceful-fs": "^4.1.15"
+      }
+    },
     "vue-style-loader": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@@ -22414,7 +22575,7 @@
         "hash-sum": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
-          "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
+          "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
           "dev": true
         },
         "json5": {
@@ -22483,7 +22644,8 @@
     "vue-web-storage": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/vue-web-storage/-/vue-web-storage-6.1.0.tgz",
-      "integrity": "sha512-Qsa6QkUyGP+Tj0oxLRc6vEATv6axY89LbwfX8eaMM3i7K/Nl9m0NTELtNp0s8Xhg9F0SkeEP4NC3+LQL3ygMQw=="
+      "integrity": "sha512-Qsa6QkUyGP+Tj0oxLRc6vEATv6axY89LbwfX8eaMM3i7K/Nl9m0NTELtNp0s8Xhg9F0SkeEP4NC3+LQL3ygMQw==",
+      "requires": {}
     },
     "vue3-clipboard": {
       "version": "1.0.0",
@@ -22493,6 +22655,12 @@
         "clipboard": "^2.0.6"
       }
     },
+    "vue3-google-login": {
+      "version": "2.0.26",
+      "resolved": "https://registry.npmjs.org/vue3-google-login/-/vue3-google-login-2.0.26.tgz",
+      "integrity": "sha512-BuTSIeSjINNHNPs+BDF4COnjWvff27IfCBDxK6JPRqvm57lF8iK4B3+zcG8ud6BXfZdyuiDlxletbEDgg4/RFA==",
+      "requires": {}
+    },
     "vuedraggable": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
@@ -22557,7 +22725,7 @@
     "watch": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz",
-      "integrity": "sha1-NApxe952Vyb6CqB9ch4BR6VR3ww=",
+      "integrity": "sha512-1u+Z5n9Jc1E2c7qDO8SinPoZuHj7FgbgU1olSFoyaklduDvvtX7GMMtlE6OC9FTXq4KvNAOfj6Zu4vI1e9bAKA==",
       "dev": true,
       "requires": {
         "exec-sh": "^0.2.0",
@@ -22568,7 +22736,6 @@
       "version": "1.7.5",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
       "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
-      "dev": true,
       "requires": {
         "chokidar": "^3.4.1",
         "graceful-fs": "^4.1.2",
@@ -22600,7 +22767,7 @@
             "normalize-path": {
               "version": "2.1.1",
               "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-              "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+              "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
               "dev": true,
               "optional": true,
               "requires": {
@@ -22651,7 +22818,7 @@
         "is-binary-path": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
-          "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+          "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -22684,23 +22851,45 @@
     "wcwidth": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
-      "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+      "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
       "dev": true,
       "requires": {
         "defaults": "^1.0.3"
       }
     },
+    "web-encoding": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz",
+      "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==",
+      "requires": {
+        "@zxing/text-encoding": "0.9.0",
+        "util": "^0.12.3"
+      },
+      "dependencies": {
+        "util": {
+          "version": "0.12.5",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
+          "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "is-arguments": "^1.0.4",
+            "is-generator-function": "^1.0.7",
+            "is-typed-array": "^1.1.3",
+            "which-typed-array": "^1.1.2"
+          }
+        }
+      }
+    },
     "webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
       "dev": true
     },
     "webpack": {
       "version": "4.46.0",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz",
       "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==",
-      "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.9.0",
         "@webassemblyjs/helper-module-context": "1.9.0",
@@ -22731,7 +22920,6 @@
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
           "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
-          "dev": true,
           "requires": {
             "minimist": "^1.2.0"
           }
@@ -22740,7 +22928,6 @@
           "version": "1.4.0",
           "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
           "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
-          "dev": true,
           "requires": {
             "big.js": "^5.2.2",
             "emojis-list": "^3.0.0",
@@ -22748,19 +22935,17 @@
           }
         },
         "mkdirp": {
-          "version": "0.5.5",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-          "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
-          "dev": true,
+          "version": "0.5.6",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+          "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
           "requires": {
-            "minimist": "^1.2.5"
+            "minimist": "^1.2.6"
           }
         },
         "schema-utils": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
           "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
-          "dev": true,
           "requires": {
             "ajv": "^6.1.0",
             "ajv-errors": "^1.0.0",
@@ -22771,7 +22956,6 @@
           "version": "1.4.3",
           "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
           "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
-          "dev": true,
           "requires": {
             "source-list-map": "^2.0.0",
             "source-map": "~0.6.1"
@@ -22844,7 +23028,7 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "commander": {
@@ -22856,7 +23040,7 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "mkdirp": {
@@ -23011,7 +23195,7 @@
             "normalize-path": {
               "version": "2.1.1",
               "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-              "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+              "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
               "dev": true,
               "requires": {
                 "remove-trailing-separator": "^1.0.1"
@@ -23079,7 +23263,7 @@
         "color-name": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
           "dev": true
         },
         "emoji-regex": {
@@ -23111,7 +23295,7 @@
         "has-flag": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
           "dev": true
         },
         "http-proxy-middleware": {
@@ -23135,7 +23319,7 @@
         "is-binary-path": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
-          "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+          "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==",
           "dev": true,
           "requires": {
             "binary-extensions": "^1.0.0"
@@ -23144,7 +23328,7 @@
         "is-fullwidth-code-point": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
           "dev": true
         },
         "locate-path": {
@@ -23169,7 +23353,7 @@
         "path-exists": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
           "dev": true
         },
         "readdirp": {
@@ -23372,7 +23556,7 @@
     "whatwg-url": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
       "dev": true,
       "requires": {
         "tr46": "~0.0.3",
@@ -23403,9 +23587,21 @@
     "which-module": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
-      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+      "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==",
       "dev": true
     },
+    "which-typed-array": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
+      "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
+      "requires": {
+        "available-typed-arrays": "^1.0.5",
+        "call-bind": "^1.0.2",
+        "for-each": "^0.3.3",
+        "gopd": "^1.0.1",
+        "has-tostringtag": "^1.0.0"
+      }
+    },
     "wide-align": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -23462,7 +23658,6 @@
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
       "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
-      "dev": true,
       "requires": {
         "errno": "~0.1.7"
       }
@@ -23515,7 +23710,7 @@
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
     },
     "write": {
       "version": "1.0.3",
@@ -23549,16 +23744,22 @@
       }
     },
     "ws": {
-      "version": "7.5.7",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
-      "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
-      "dev": true
+      "version": "7.5.8",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz",
+      "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==",
+      "dev": true,
+      "requires": {}
     },
     "xdg-basedir": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
       "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q=="
     },
+    "xml": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
+      "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="
+    },
     "xml-name-validator": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
@@ -23569,7 +23770,6 @@
       "version": "0.4.23",
       "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
       "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
-      "dev": true,
       "requires": {
         "sax": ">=0.6.0",
         "xmlbuilder": "~11.0.0"
@@ -23578,8 +23778,7 @@
     "xmlbuilder": {
       "version": "11.0.1",
       "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
-      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
-      "dev": true
+      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
     },
     "xmlchars": {
       "version": "2.2.0",
@@ -23588,9 +23787,9 @@
       "dev": true
     },
     "xss": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.11.tgz",
-      "integrity": "sha512-EimjrjThZeK2MO7WKR9mN5ZC1CSqivSl55wvUK5EtU6acf0rzEE1pN+9ZDrFXJ82BRp3JL38pPE6S4o/rpp1zQ==",
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.13.tgz",
+      "integrity": "sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q==",
       "dev": true,
       "requires": {
         "commander": "^2.20.3",
@@ -23608,8 +23807,7 @@
     "xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
-      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
-      "dev": true
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
     },
     "y18n": {
       "version": "5.0.8",
@@ -23625,7 +23823,7 @@
     "yaml-front-matter": {
       "version": "3.4.1",
       "resolved": "https://registry.npmjs.org/yaml-front-matter/-/yaml-front-matter-3.4.1.tgz",
-      "integrity": "sha1-5S6E/qaYO5N1XpsVZNupibAGtaU=",
+      "integrity": "sha512-/sDeHR8GD6JIJ8j/2h28QsjXS9XsWp2WnjU8RQODri/u6INSEF9Q5w4mZVl0KtXsM1UCBYQhOwTvJKTsnmusBQ==",
       "dev": true,
       "requires": {
         "commander": "1.0.0",
@@ -23635,7 +23833,7 @@
         "commander": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/commander/-/commander-1.0.0.tgz",
-          "integrity": "sha1-XmqI5wcP9ZCINurRkWlUjDD5C80=",
+          "integrity": "sha512-ypAKENwAvjA+utibuxSPeduXV/tIX73+9IyWMkFNnbxiJTeY2xdcM8C2KZo3KEGlDnO5tSm2BVZ65QfuRcR8DQ==",
           "dev": true
         }
       }
@@ -23698,7 +23896,7 @@
     "yauzl": {
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
-      "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
+      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
       "dev": true,
       "requires": {
         "buffer-crc32": "~0.2.3",
@@ -23726,7 +23924,7 @@
         "cross-spawn": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==",
           "dev": true,
           "requires": {
             "lru-cache": "^4.0.1",
@@ -23737,7 +23935,7 @@
         "execa": {
           "version": "0.8.0",
           "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
-          "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=",
+          "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==",
           "dev": true,
           "requires": {
             "cross-spawn": "^5.0.1",
@@ -23752,7 +23950,7 @@
         "get-stream": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+          "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==",
           "dev": true
         },
         "is-ci": {
@@ -23777,7 +23975,7 @@
         "normalize-path": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz",
-          "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=",
+          "integrity": "sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA==",
           "dev": true
         },
         "which": {
@@ -23792,7 +23990,7 @@
         "yallist": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
           "dev": true
         }
       }
diff --git a/ui/package.json b/ui/package.json
index 65fa230..8a9a37e 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,7 @@
 {
   "name": "cloudstack-ui",
   "description": "Modern role-based Apache CloudStack UI",
-  "version": "4.18.2",
+  "version": "4.19.0",
   "homepage": "https://cloudstack.apache.org/",
   "repository": {
     "type": "git",
@@ -34,23 +34,29 @@
     "test:unit": "vue-cli-service test:unit"
   },
   "dependencies": {
-    "@fortawesome/fontawesome-svg-core": "^1.3.0",
-    "@fortawesome/free-brands-svg-icons": "^5.15.2",
-    "@fortawesome/free-solid-svg-icons": "^5.15.2",
-    "@fortawesome/vue-fontawesome": "^3.0.0-4",
-    "ant-design-vue": "^2.2.3",
+    "@fortawesome/fontawesome-svg-core": "^6.5.2",
+    "@fortawesome/free-brands-svg-icons": "^6.5.2",
+    "@fortawesome/free-solid-svg-icons": "^6.5.2",
+    "@fortawesome/vue-fontawesome": "^3.0.6",
+    "@vue-js-cron/ant": "^1.1.3",
+    "@vue-js-cron/core": "^3.7.1",
+    "ant-design-vue": "^3.2.20",
+    "antd": "^4.24.16",
     "antd-theme-webpack-plugin": "^1.3.9",
-    "axios": "^0.21.1",
+    "axios": "^0.21.4",
     "babel-plugin-require-context-hook": "^1.0.0",
     "chart.js": "^3.7.1",
     "chartjs-adapter-moment": "^1.0.0",
     "core-js": "^3.21.1",
+    "cronstrue": "^2.26.0",
     "enquire.js": "^2.1.6",
     "js-cookie": "^2.2.1",
     "lodash": "^4.17.15",
     "md5": "^2.2.1",
+    "minio": "^7.0.33",
     "mitt": "^2.1.0",
     "moment": "^2.26.0",
+    "moment-timezone": "^0.5.43",
     "npm-check-updates": "^6.0.1",
     "nprogress": "^0.2.0",
     "qrious": "^4.0.2",
@@ -59,12 +65,14 @@
     "vue-clipboard2": "^0.3.1",
     "vue-cropper": "^1.0.2",
     "vue-i18n": "^9.1.6",
-    "vue-loader": "^16.2.0",
+    "vue-loader": "^16.8.3",
     "vue-qrious": "^3.1.0",
     "vue-router": "^4.0.14",
+    "vue-social-auth": "^1.4.9",
     "vue-uuid": "^3.0.0",
     "vue-web-storage": "^6.1.0",
     "vue3-clipboard": "^1.0.0",
+    "vue3-google-login": "^2.0.20",
     "vuedraggable": "^4.0.3",
     "vuex": "^4.0.0-0"
   },
diff --git a/ui/public/assets/banner.svg b/ui/public/assets/banner.svg
index 23eefed..d9a3e51 100644
--- a/ui/public/assets/banner.svg
+++ b/ui/public/assets/banner.svg
@@ -316,4 +316,4 @@
          id="path184"
          style="fill:#69afd8;fill-opacity:1;fill-rule:nonzero;stroke:none"
          d="m 0,0 h 15.311 v -47.158 l 16.944,19.598 h 18.781 l -19.7,-22.354 20.211,-27.253 H 32.255 l -16.74,25.109 H 15.311 V -77.167 H 0 Z"
-         inkscape:connector-curvature="0" /></g></g></g></g></g></svg>
\ No newline at end of file
+         inkscape:connector-curvature="0" /></g></g></g></g></g></svg>
diff --git a/ui/public/assets/github.svg b/ui/public/assets/github.svg
new file mode 100644
index 0000000..fd8a6c7
--- /dev/null
+++ b/ui/public/assets/github.svg
@@ -0,0 +1 @@
+<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 50 50" width="32px" height="32px">    <path d="M17.791,46.836C18.502,46.53,19,45.823,19,45v-5.4c0-0.197,0.016-0.402,0.041-0.61C19.027,38.994,19.014,38.997,19,39 c0,0-3,0-3.6,0c-1.5,0-2.8-0.6-3.4-1.8c-0.7-1.3-1-3.5-2.8-4.7C8.9,32.3,9.1,32,9.7,32c0.6,0.1,1.9,0.9,2.7,2c0.9,1.1,1.8,2,3.4,2 c2.487,0,3.82-0.125,4.622-0.555C21.356,34.056,22.649,33,24,33v-0.025c-5.668-0.182-9.289-2.066-10.975-4.975 c-3.665,0.042-6.856,0.405-8.677,0.707c-0.058-0.327-0.108-0.656-0.151-0.987c1.797-0.296,4.843-0.647,8.345-0.714 c-0.112-0.276-0.209-0.559-0.291-0.849c-3.511-0.178-6.541-0.039-8.187,0.097c-0.02-0.332-0.047-0.663-0.051-0.999 c1.649-0.135,4.597-0.27,8.018-0.111c-0.079-0.5-0.13-1.011-0.13-1.543c0-1.7,0.6-3.5,1.7-5c-0.5-1.7-1.2-5.3,0.2-6.6 c2.7,0,4.6,1.3,5.5,2.1C21,13.4,22.9,13,25,13s4,0.4,5.6,1.1c0.9-0.8,2.8-2.1,5.5-2.1c1.5,1.4,0.7,5,0.2,6.6c1.1,1.5,1.7,3.2,1.6,5 c0,0.484-0.045,0.951-0.11,1.409c3.499-0.172,6.527-0.034,8.204,0.102c-0.002,0.337-0.033,0.666-0.051,0.999 c-1.671-0.138-4.775-0.28-8.359-0.089c-0.089,0.336-0.197,0.663-0.325,0.98c3.546,0.046,6.665,0.389,8.548,0.689 c-0.043,0.332-0.093,0.661-0.151,0.987c-1.912-0.306-5.171-0.664-8.879-0.682C35.112,30.873,31.557,32.75,26,32.969V33 c2.6,0,5,3.9,5,6.6V45c0,0.823,0.498,1.53,1.209,1.836C41.37,43.804,48,35.164,48,25C48,12.318,37.683,2,25,2S2,12.318,2,25 C2,35.164,8.63,43.804,17.791,46.836z"/></svg>
diff --git a/ui/public/assets/google.svg b/ui/public/assets/google.svg
new file mode 100644
index 0000000..6ce064d
--- /dev/null
+++ b/ui/public/assets/google.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 48 48" width="100px" height="100px"><path fill="#fbc02d" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12	s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20	s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#e53935" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039	l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4caf50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36	c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1565c0" d="M43.611,20.083L43.595,20L42,20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571	c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
diff --git a/ui/public/assets/logo.svg b/ui/public/assets/logo.svg
index ed6cb89..5a60be9 100644
--- a/ui/public/assets/logo.svg
+++ b/ui/public/assets/logo.svg
@@ -329,4 +329,4 @@
          id="path184"
          style="fill:#69afd8;fill-opacity:1;fill-rule:nonzero;stroke:none"
          d="m 0,0 h 15.311 v -47.158 l 16.944,19.598 h 18.781 l -19.7,-22.354 20.211,-27.253 H 32.255 l -16.74,25.109 H 15.311 V -77.167 H 0 Z"
-         inkscape:connector-curvature="0" /></g></g></g></g></g></svg>
\ No newline at end of file
+         inkscape:connector-curvature="0" /></g></g></g></g></g></svg>
diff --git a/ui/public/assets/mini-logo.svg b/ui/public/assets/mini-logo.svg
index 72b4df7..e59b434 100644
--- a/ui/public/assets/mini-logo.svg
+++ b/ui/public/assets/mini-logo.svg
@@ -218,4 +218,4 @@
 </g>
 </g>
 </g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/ui/public/config.json b/ui/public/config.json
index 6c3acb9..57d120a 100644
--- a/ui/public/config.json
+++ b/ui/public/config.json
@@ -14,6 +14,8 @@
   "logo": "assets/logo.svg",
   "minilogo": "assets/mini-logo.svg",
   "banner": "assets/banner.svg",
+  "loginPageTitle": "CloudStack",
+  "loginPageFavicon": "assets/logo.svg",
   "error": {
     "403": "assets/403.png",
     "404": "assets/404.png",
@@ -59,9 +61,39 @@
     "jp": "label.japanese.keyboard",
     "sc": "label.simplified.chinese.keyboard"
   },
+  "userCard": {
+    "title": "label.help",
+    "icon": "question-circle-outlined",
+    "links": [
+      {
+        "title": "Documentation",
+        "text": "CloudStack documentation website",
+        "link": "https://docs.cloudstack.apache.org/en/latest/",
+        "icon": "read-outlined"
+      },
+      {
+        "title": "API Documentation",
+        "text": "Refer to API documentation",
+        "link": "https://cloudstack.apache.org/api.html",
+        "icon": "api-outlined"
+      },
+      {
+        "title": "Email Support",
+        "text": "Join CloudStack users mailing list to seek and provide support",
+        "link": "mailto:users-subscribe@cloudstack.apache.org",
+        "icon": "mail-outlined"
+      },
+      {
+        "title": "Report Issue",
+        "text": "Submit a bug or improvement request",
+        "link": "https://github.com/apache/cloudstack/issues/new",
+        "icon": "bug-outlined"
+      }
+    ]
+  },
   "plugins": [],
   "basicZoneEnabled": true,
   "multipleServer": false,
   "allowSettingTheme": true,
   "docHelpMappings": {}
-}
\ No newline at end of file
+}
diff --git a/ui/public/index.html b/ui/public/index.html
index 54ec6da..1a09e46 100644
--- a/ui/public/index.html
+++ b/ui/public/index.html
@@ -22,8 +22,8 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>cloud.ico">
-    <title>Apache CloudStack</title>
+    <link id="favicon" rel="icon" href="<%= BASE_URL %>cloud.ico">
+    <title id="title" >Apache CloudStack</title>
     <style>
       .loader {
         border: 16px solid #F3F3F3;
@@ -54,4 +54,12 @@
       <div class="loader"></div>
     </div>
   </body>
+  <script type="text/javascript">
+    fetch('./config.json')
+      .then(response => response.json())
+      .then(data => {
+        document.getElementById("favicon").setAttribute("href", data.loginPageFavicon);
+        document.getElementById("title").innerHTML = data.loginPageTitle;
+      }).catch((err) => {});
+  </script>
 </html>
diff --git a/ui/public/js/less.min.js b/ui/public/js/less.min.js
index 6319704..3605915 100644
--- a/ui/public/js/less.min.js
+++ b/ui/public/js/less.min.js
@@ -14,4 +14,4 @@
 "../tree/quoted":73,"./function-registry":22}],28:[function(a,b,c){b.exports=function(b){var c=a("../tree/dimension"),d=a("../tree/color"),e=a("../tree/expression"),f=a("../tree/quoted"),g=a("../tree/url"),h=a("./function-registry");h.add("svg-gradient",function(a){function b(){throw{type:"Argument",message:"svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position] or direction, color list"}}var h,i,j,k,l,m,n,o,p="linear",q='x="0" y="0" width="1" height="1"',r={compress:!1},s=a.toCSS(r);switch(2==arguments.length?(arguments[1].value.length<2&&b(),h=arguments[1].value):arguments.length<3?b():h=Array.prototype.slice.call(arguments,1),s){case"to bottom":i='x1="0%" y1="0%" x2="0%" y2="100%"';break;case"to right":i='x1="0%" y1="0%" x2="100%" y2="0%"';break;case"to bottom right":i='x1="0%" y1="0%" x2="100%" y2="100%"';break;case"to top right":i='x1="0%" y1="100%" x2="100%" y2="0%"';break;case"ellipse":case"ellipse at center":p="radial",i='cx="50%" cy="50%" r="75%"',q='x="-50" y="-50" width="101" height="101"';break;default:throw{type:"Argument",message:"svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"}}for(j='<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><'+p+'Gradient id="gradient" gradientUnits="userSpaceOnUse" '+i+">",k=0;k<h.length;k+=1)h[k]instanceof e?(l=h[k].value[0],m=h[k].value[1]):(l=h[k],m=void 0),l instanceof d&&((0===k||k+1===h.length)&&void 0===m||m instanceof c)||b(),n=m?m.toCSS(r):0===k?"0%":"100%",o=l.alpha,j+='<stop offset="'+n+'" stop-color="'+l.toRGB()+'"'+(o<1?' stop-opacity="'+o+'"':"")+"/>";return j+="</"+p+"Gradient><rect "+q+' fill="url(#gradient)" /></svg>',j=encodeURIComponent(j),j="data:image/svg+xml,"+j,new g(new f("'"+j+"'",j,(!1),this.index,this.currentFileInfo),this.index,this.currentFileInfo)})}},{"../tree/color":50,"../tree/dimension":56,"../tree/expression":59,"../tree/quoted":73,"../tree/url":80,"./function-registry":22}],29:[function(a,b,c){var d=a("../tree/keyword"),e=a("../tree/detached-ruleset"),f=a("../tree/dimension"),g=a("../tree/color"),h=a("../tree/quoted"),i=a("../tree/anonymous"),j=a("../tree/url"),k=a("../tree/operation"),l=a("./function-registry"),m=function(a,b){return a instanceof b?d.True:d.False},n=function(a,b){if(void 0===b)throw{type:"Argument",message:"missing the required second argument to isunit."};if(b="string"==typeof b.value?b.value:b,"string"!=typeof b)throw{type:"Argument",message:"Second argument to isunit should be a unit or a string."};return a instanceof f&&a.unit.is(b)?d.True:d.False},o=function(a){var b=Array.isArray(a.value)?a.value:Array(a);return b};l.addMultiple({isruleset:function(a){return m(a,e)},iscolor:function(a){return m(a,g)},isnumber:function(a){return m(a,f)},isstring:function(a){return m(a,h)},iskeyword:function(a){return m(a,d)},isurl:function(a){return m(a,j)},ispixel:function(a){return n(a,"px")},ispercentage:function(a){return n(a,"%")},isem:function(a){return n(a,"em")},isunit:n,unit:function(a,b){if(!(a instanceof f))throw{type:"Argument",message:"the first argument to unit must be a number"+(a instanceof k?". Have you forgotten parenthesis?":"")};return b=b?b instanceof d?b.value:b.toCSS():"",new f(a.value,b)},"get-unit":function(a){return new i(a.unit)},extract:function(a,b){return b=b.value-1,o(a)[b]},length:function(a){return new f(o(a).length)}})},{"../tree/anonymous":46,"../tree/color":50,"../tree/detached-ruleset":55,"../tree/dimension":56,"../tree/keyword":65,"../tree/operation":71,"../tree/quoted":73,"../tree/url":80,"./function-registry":22}],30:[function(a,b,c){var d=a("./contexts"),e=a("./parser/parser"),f=a("./plugins/function-importer");b.exports=function(a){var b=function(a,b){this.rootFilename=b.filename,this.paths=a.paths||[],this.contents={},this.contentsIgnoredChars={},this.mime=a.mime,this.error=null,this.context=a,this.queue=[],this.files={}};return b.prototype.push=function(b,c,g,h,i){var j=this;this.queue.push(b);var k=function(a,c,d){j.queue.splice(j.queue.indexOf(b),1);var e=d===j.rootFilename;h.optional&&a?i(null,{rules:[]},!1,null):(j.files[d]=c,a&&!j.error&&(j.error=a),i(a,c,e,d))},l={relativeUrls:this.context.relativeUrls,entryPath:g.entryPath,rootpath:g.rootpath,rootFilename:g.rootFilename},m=a.getFileManager(b,g.currentDirectory,this.context,a);if(!m)return void k({message:"Could not find a file-manager for "+b});c&&(b=m.tryAppendExtension(b,h.plugin?".js":".less"));var n=function(a){var b=a.filename,c=a.contents.replace(/^\uFEFF/,"");l.currentDirectory=m.getPath(b),l.relativeUrls&&(l.rootpath=m.join(j.context.rootpath||"",m.pathDiff(l.currentDirectory,l.entryPath)),!m.isPathAbsolute(l.rootpath)&&m.alwaysMakePathsAbsolute()&&(l.rootpath=m.join(l.entryPath,l.rootpath))),l.filename=b;var i=new d.Parse(j.context);i.processImports=!1,j.contents[b]=c,(g.reference||h.reference)&&(l.reference=!0),h.plugin?new f(i,l).eval(c,function(a,c){k(a,c,b)}):h.inline?k(null,c,b):new e(i,j,l).parse(c,function(a,c){k(a,c,b)})},o=m.loadFile(b,g.currentDirectory,this.context,a,function(a,b){a?k(a):n(b)});o&&o.then(n,k)},b}},{"./contexts":11,"./parser/parser":38,"./plugins/function-importer":40}],31:[function(a,b,c){b.exports=function(b,c){var d,e,f,g,h,i={version:[2,7,2],data:a("./data"),tree:a("./tree"),Environment:h=a("./environment/environment"),AbstractFileManager:a("./environment/abstract-file-manager"),environment:b=new h(b,c),visitors:a("./visitors"),Parser:a("./parser/parser"),functions:a("./functions")(b),contexts:a("./contexts"),SourceMapOutput:d=a("./source-map-output")(b),SourceMapBuilder:e=a("./source-map-builder")(d,b),ParseTree:f=a("./parse-tree")(e),ImportManager:g=a("./import-manager")(b),render:a("./render")(b,f,g),parse:a("./parse")(b,f,g),LessError:a("./less-error"),transformTree:a("./transform-tree"),utils:a("./utils"),PluginManager:a("./plugin-manager"),logger:a("./logger")};return i}},{"./contexts":11,"./data":13,"./environment/abstract-file-manager":15,"./environment/environment":16,"./functions":23,"./import-manager":30,"./less-error":32,"./logger":33,"./parse":35,"./parse-tree":34,"./parser/parser":38,"./plugin-manager":39,"./render":41,"./source-map-builder":42,"./source-map-output":43,"./transform-tree":44,"./tree":62,"./utils":83,"./visitors":87}],32:[function(a,b,c){var d=a("./utils"),e=b.exports=function(a,b,c){Error.call(this);var e=a.filename||c;if(b&&e){var f=b.contents[e],g=d.getLocation(a.index,f),h=g.line,i=g.column,j=a.call&&d.getLocation(a.call,f).line,k=f.split("\n");this.type=a.type||"Syntax",this.filename=e,this.index=a.index,this.line="number"==typeof h?h+1:null,this.callLine=j+1,this.callExtract=k[j],this.column=i,this.extract=[k[h-1],k[h],k[h+1]]}this.message=a.message,this.stack=a.stack};if("undefined"==typeof Object.create){var f=function(){};f.prototype=Error.prototype,e.prototype=new f}else e.prototype=Object.create(Error.prototype);e.prototype.constructor=e},{"./utils":83}],33:[function(a,b,c){b.exports={error:function(a){this._fireEvent("error",a)},warn:function(a){this._fireEvent("warn",a)},info:function(a){this._fireEvent("info",a)},debug:function(a){this._fireEvent("debug",a)},addListener:function(a){this._listeners.push(a)},removeListener:function(a){for(var b=0;b<this._listeners.length;b++)if(this._listeners[b]===a)return void this._listeners.splice(b,1)},_fireEvent:function(a,b){for(var c=0;c<this._listeners.length;c++){var d=this._listeners[c][a];d&&d(b)}},_listeners:[]}},{}],34:[function(a,b,c){var d=a("./less-error"),e=a("./transform-tree"),f=a("./logger");b.exports=function(a){var b=function(a,b){this.root=a,this.imports=b};return b.prototype.toCSS=function(b){var c,g,h={};try{c=e(this.root,b)}catch(i){throw new d(i,this.imports)}try{var j=Boolean(b.compress);j&&f.warn("The compress option has been deprecated. We recommend you use a dedicated css minifier, for instance see less-plugin-clean-css.");var k={compress:j,dumpLineNumbers:b.dumpLineNumbers,strictUnits:Boolean(b.strictUnits),numPrecision:8};b.sourceMap?(g=new a(b.sourceMap),h.css=g.toCSS(c,k,this.imports)):h.css=c.toCSS(k)}catch(i){throw new d(i,this.imports)}if(b.pluginManager)for(var l=b.pluginManager.getPostProcessors(),m=0;m<l.length;m++)h.css=l[m].process(h.css,{sourceMap:g,options:b,imports:this.imports});b.sourceMap&&(h.map=g.getExternalSourceMap()),h.imports=[];for(var n in this.imports.files)this.imports.files.hasOwnProperty(n)&&n!==this.imports.rootFilename&&h.imports.push(n);return h},b}},{"./less-error":32,"./logger":33,"./transform-tree":44}],35:[function(a,b,c){var d,e=a("./contexts"),f=a("./parser/parser"),g=a("./plugin-manager");b.exports=function(b,c,h){var i=function(b,c,j){if(c=c||{},"function"==typeof c&&(j=c,c={}),!j){d||(d="undefined"==typeof Promise?a("promise"):Promise);var k=this;return new d(function(a,d){i.call(k,b,c,function(b,c){b?d(b):a(c)})})}var l,m,n=new g(this);if(n.addPlugins(c.plugins),c.pluginManager=n,l=new e.Parse(c),c.rootFileInfo)m=c.rootFileInfo;else{var o=c.filename||"input",p=o.replace(/[^\/\\]*$/,"");m={filename:o,relativeUrls:l.relativeUrls,rootpath:l.rootpath||"",currentDirectory:p,entryPath:p,rootFilename:o},m.rootpath&&"/"!==m.rootpath.slice(-1)&&(m.rootpath+="/")}var q=new h(l,m);new f(l,q,m).parse(b,function(a,b){return a?j(a):void j(null,b,q,c)},c)};return i}},{"./contexts":11,"./parser/parser":38,"./plugin-manager":39,promise:void 0}],36:[function(a,b,c){b.exports=function(a,b){function c(b){var c=h-q;c<512&&!b||!c||(p.push(a.slice(q,h+1)),q=h+1)}var d,e,f,g,h,i,j,k,l,m=a.length,n=0,o=0,p=[],q=0;for(h=0;h<m;h++)if(j=a.charCodeAt(h),!(j>=97&&j<=122||j<34))switch(j){case 40:o++,e=h;continue;case 41:if(--o<0)return b("missing opening `(`",h);continue;case 59:o||c();continue;case 123:n++,d=h;continue;case 125:if(--n<0)return b("missing opening `{`",h);n||o||c();continue;case 92:if(h<m-1){h++;continue}return b("unescaped `\\`",h);case 34:case 39:case 96:for(l=0,i=h,h+=1;h<m;h++)if(k=a.charCodeAt(h),!(k>96)){if(k==j){l=1;break}if(92==k){if(h==m-1)return b("unescaped `\\`",h);h++}}if(l)continue;return b("unmatched `"+String.fromCharCode(j)+"`",i);case 47:if(o||h==m-1)continue;if(k=a.charCodeAt(h+1),47==k)for(h+=2;h<m&&(k=a.charCodeAt(h),!(k<=13)||10!=k&&13!=k);h++);else if(42==k){for(f=i=h,h+=2;h<m-1&&(k=a.charCodeAt(h),125==k&&(g=h),42!=k||47!=a.charCodeAt(h+1));h++);if(h==m-1)return b("missing closing `*/`",i);h++}continue;case 42:if(h<m-1&&47==a.charCodeAt(h+1))return b("unmatched `/*`",h);continue}return 0!==n?f>d&&g>f?b("missing closing `}` or `*/`",d):b("missing closing `}`",d):0!==o?b("missing closing `)`",e):(c(!0),p)}},{}],37:[function(a,b,c){var d=a("./chunker");b.exports=function(){function a(d){for(var e,f,j,p=k.i,q=c,s=k.i-i,t=k.i+h.length-s,u=k.i+=d,v=b;k.i<t;k.i++){if(e=v.charCodeAt(k.i),k.autoCommentAbsorb&&e===r){if(f=v.charAt(k.i+1),"/"===f){j={index:k.i,isLineComment:!0};var w=v.indexOf("\n",k.i+2);w<0&&(w=t),k.i=w,j.text=v.substr(j.index,k.i-j.index),k.commentStore.push(j);continue}if("*"===f){var x=v.indexOf("*/",k.i+2);if(x>=0){j={index:k.i,text:v.substr(k.i,x+2-k.i),isLineComment:!1},k.i+=j.text.length-1,k.commentStore.push(j);continue}}break}if(e!==l&&e!==n&&e!==m&&e!==o)break}if(h=h.slice(d+k.i-u+s),i=k.i,!h.length){if(c<g.length-1)return h=g[++c],a(0),!0;k.finished=!0}return p!==k.i||q!==c}var b,c,e,f,g,h,i,j=[],k={},l=32,m=9,n=10,o=13,p=43,q=44,r=47,s=57;return k.save=function(){i=k.i,j.push({current:h,i:k.i,j:c})},k.restore=function(a){(k.i>e||k.i===e&&a&&!f)&&(e=k.i,f=a);var b=j.pop();h=b.current,i=k.i=b.i,c=b.j},k.forget=function(){j.pop()},k.isWhitespace=function(a){var c=k.i+(a||0),d=b.charCodeAt(c);return d===l||d===o||d===m||d===n},k.$re=function(b){k.i>i&&(h=h.slice(k.i-i),i=k.i);var c=b.exec(h);return c?(a(c[0].length),"string"==typeof c?c:1===c.length?c[0]:c):null},k.$char=function(c){return b.charAt(k.i)!==c?null:(a(1),c)},k.$str=function(c){for(var d=c.length,e=0;e<d;e++)if(b.charAt(k.i+e)!==c.charAt(e))return null;return a(d),c},k.$quoted=function(){var c=b.charAt(k.i);if("'"===c||'"'===c){for(var d=b.length,e=k.i,f=1;f+e<d;f++){var g=b.charAt(f+e);switch(g){case"\\":f++;continue;case"\r":case"\n":break;case c:var h=b.substr(e,f+1);return a(f+1),h}}return null}},k.autoCommentAbsorb=!0,k.commentStore=[],k.finished=!1,k.peek=function(a){if("string"==typeof a){for(var c=0;c<a.length;c++)if(b.charAt(k.i+c)!==a.charAt(c))return!1;return!0}return a.test(h)},k.peekChar=function(a){return b.charAt(k.i)===a},k.currentChar=function(){return b.charAt(k.i)},k.getInput=function(){return b},k.peekNotNumeric=function(){var a=b.charCodeAt(k.i);return a>s||a<p||a===r||a===q},k.start=function(f,j,l){b=f,k.i=c=i=e=0,g=j?d(f,l):[f],h=g[0],a(0)},k.end=function(){var a,c=k.i>=b.length;return k.i<e&&(a=f,k.i=e),{isFinished:c,furthest:k.i,furthestPossibleErrorMessage:a,furthestReachedEnd:k.i>=b.length-1,furthestChar:b[k.i]}},k}},{"./chunker":36}],38:[function(a,b,c){var d=a("../less-error"),e=a("../tree"),f=a("../visitors"),g=a("./parser-input"),h=a("../utils"),i=function j(a,b,c){function i(a,e){throw new d({index:o.i,filename:c.filename,type:e||"Syntax",message:a},b)}function k(a,b,c){var d=a instanceof Function?a.call(n):o.$re(a);return d?d:void i(b||("string"==typeof a?"expected '"+a+"' got '"+o.currentChar()+"'":"unexpected token"))}function l(a,b){return o.$char(a)?a:void i(b||"expected '"+a+"' got '"+o.currentChar()+"'")}function m(a){var b=c.filename;return{lineNumber:h.getLocation(a,o.getInput()).line+1,fileName:b}}var n,o=g();return{parse:function(g,h,i){var k,l,m,n,p=null,q="";if(l=i&&i.globalVars?j.serializeVars(i.globalVars)+"\n":"",m=i&&i.modifyVars?"\n"+j.serializeVars(i.modifyVars):"",a.pluginManager)for(var r=a.pluginManager.getPreProcessors(),s=0;s<r.length;s++)g=r[s].process(g,{context:a,imports:b,fileInfo:c});(l||i&&i.banner)&&(q=(i&&i.banner?i.banner:"")+l,n=b.contentsIgnoredChars,n[c.filename]=n[c.filename]||0,n[c.filename]+=q.length),g=g.replace(/\r\n?/g,"\n"),g=q+g.replace(/^\uFEFF/,"")+m,b.contents[c.filename]=g;try{o.start(g,a.chunkInput,function(a,e){throw new d({index:e,type:"Parse",message:a,filename:c.filename},b)}),k=new e.Ruleset(null,this.parsers.primary()),k.root=!0,k.firstRoot=!0}catch(t){return h(new d(t,b,c.filename))}var u=o.end();if(!u.isFinished){var v=u.furthestPossibleErrorMessage;v||(v="Unrecognised input","}"===u.furthestChar?v+=". Possibly missing opening '{'":")"===u.furthestChar?v+=". Possibly missing opening '('":u.furthestReachedEnd&&(v+=". Possibly missing something")),p=new d({type:"Parse",message:v,index:u.furthest,filename:c.filename},b)}var w=function(a){return a=p||a||b.error,a?(a instanceof d||(a=new d(a,b,c.filename)),h(a)):h(null,k)};return a.processImports===!1?w():void new f.ImportVisitor(b,w).run(k)},parsers:n={primary:function(){for(var a,b=this.mixin,c=[];;){for(;;){if(a=this.comment(),!a)break;c.push(a)}if(o.finished)break;if(o.peek("}"))break;if(a=this.extendRule())c=c.concat(a);else if(a=b.definition()||this.rule()||this.ruleset()||b.call()||this.rulesetCall()||this.entities.call()||this.directive())c.push(a);else{for(var d=!1;o.$char(";");)d=!0;if(!d)break}}return c},comment:function(){if(o.commentStore.length){var a=o.commentStore.shift();return new e.Comment(a.text,a.isLineComment,a.index,c)}},entities:{quoted:function(){var a,b=o.i,d=!1;return o.save(),o.$char("~")&&(d=!0),(a=o.$quoted())?(o.forget(),new e.Quoted(a.charAt(0),a.substr(1,a.length-2),d,b,c)):void o.restore()},keyword:function(){var a=o.$char("%")||o.$re(/^[_A-Za-z-][_A-Za-z0-9-]*/);if(a)return e.Color.fromKeyword(a)||new e.Keyword(a)},call:function(){var a,b,d,f,g=o.i;if(!o.peek(/^url\(/i))return o.save(),(a=o.$re(/^([\w-]+|%|progid:[\w\.]+)\(/))?(a=a[1],b=a.toLowerCase(),"alpha"===b&&(f=n.alpha())?(o.forget(),f):(d=this.arguments(),o.$char(")")?(o.forget(),new e.Call(a,d,g,c)):void o.restore("Could not parse call arguments or missing ')'"))):void o.forget()},arguments:function(){var a,b,c,d=[],f=[],g=[];for(o.save();;){if(c=n.detachedRuleset()||this.assignment()||n.expression(),!c)break;b=c,c.value&&1==c.value.length&&(b=c.value[0]),b&&g.push(b),f.push(b),o.$char(",")||(o.$char(";")||a)&&(a=!0,g.length>1&&(b=new e.Value(g)),d.push(b),g=[])}return o.forget(),a?d:f},literal:function(){return this.dimension()||this.color()||this.quoted()||this.unicodeDescriptor()},assignment:function(){var a,b;return o.save(),(a=o.$re(/^\w+(?=\s?=)/i))&&o.$char("=")&&(b=n.entity())?(o.forget(),new e.Assignment(a,b)):void o.restore()},url:function(){var a,b=o.i;return o.autoCommentAbsorb=!1,o.$str("url(")?(a=this.quoted()||this.variable()||o.$re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/)||"",o.autoCommentAbsorb=!0,l(")"),new e.URL(null!=a.value||a instanceof e.Variable?a:new e.Anonymous(a),b,c)):void(o.autoCommentAbsorb=!0)},variable:function(){var a,b=o.i;if("@"===o.currentChar()&&(a=o.$re(/^@@?[\w-]+/)))return new e.Variable(a,b,c)},variableCurly:function(){var a,b=o.i;if("@"===o.currentChar()&&(a=o.$re(/^@\{([\w-]+)\}/)))return new e.Variable("@"+a[1],b,c)},color:function(){var a;if("#"===o.currentChar()&&(a=o.$re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))){var b=a.input.match(/^#([\w]+).*/);return b=b[1],b.match(/^[A-Fa-f0-9]+$/)||i("Invalid HEX color code"),new e.Color(a[1],(void 0),"#"+b)}},colorKeyword:function(){o.save();var a=o.autoCommentAbsorb;o.autoCommentAbsorb=!1;var b=o.$re(/^[_A-Za-z-][_A-Za-z0-9-]+/);if(o.autoCommentAbsorb=a,!b)return void o.forget();o.restore();var c=e.Color.fromKeyword(b);return c?(o.$str(b),c):void 0},dimension:function(){if(!o.peekNotNumeric()){var a=o.$re(/^([+-]?\d*\.?\d+)(%|[a-z_]+)?/i);return a?new e.Dimension(a[1],a[2]):void 0}},unicodeDescriptor:function(){var a;if(a=o.$re(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/))return new e.UnicodeDescriptor(a[0])},javascript:function(){var a,b=o.i;o.save();var d=o.$char("~"),f=o.$char("`");return f?(a=o.$re(/^[^`]*`/))?(o.forget(),new e.JavaScript(a.substr(0,a.length-1),Boolean(d),b,c)):void o.restore("invalid javascript definition"):void o.restore()}},variable:function(){var a;if("@"===o.currentChar()&&(a=o.$re(/^(@[\w-]+)\s*:/)))return a[1]},rulesetCall:function(){var a;if("@"===o.currentChar()&&(a=o.$re(/^(@[\w-]+)\(\s*\)\s*;/)))return new e.RulesetCall(a[1])},extend:function(a){var b,d,f,g,h,j=o.i;if(o.$str(a?"&:extend(":":extend(")){do{for(f=null,b=null;!(f=o.$re(/^(all)(?=\s*(\)|,))/))&&(d=this.element());)b?b.push(d):b=[d];f=f&&f[1],b||i("Missing target selector for :extend()."),h=new e.Extend(new e.Selector(b),f,j,c),g?g.push(h):g=[h]}while(o.$char(","));return k(/^\)/),a&&k(/^;/),g}},extendRule:function(){return this.extend(!0)},mixin:{call:function(){var a,b,d,f,g,h,i=o.currentChar(),j=!1,k=o.i;if("."===i||"#"===i){for(o.save();;){if(a=o.i,f=o.$re(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/),!f)break;d=new e.Element(g,f,a,c),b?b.push(d):b=[d],g=o.$char(">")}return b&&(o.$char("(")&&(h=this.args(!0).args,l(")")),n.important()&&(j=!0),n.end())?(o.forget(),new e.mixin.Call(b,h,k,c,j)):void o.restore()}},args:function(a){var b,c,d,f,g,h,j,k=n.entities,l={args:null,variadic:!1},m=[],p=[],q=[];for(o.save();;){if(a)h=n.detachedRuleset()||n.expression();else{if(o.commentStore.length=0,o.$str("...")){l.variadic=!0,o.$char(";")&&!b&&(b=!0),(b?p:q).push({variadic:!0});break}h=k.variable()||k.literal()||k.keyword()}if(!h)break;f=null,h.throwAwayComments&&h.throwAwayComments(),g=h;var r=null;if(a?h.value&&1==h.value.length&&(r=h.value[0]):r=h,r&&r instanceof e.Variable)if(o.$char(":")){if(m.length>0&&(b&&i("Cannot mix ; and , as delimiter types"),c=!0),g=n.detachedRuleset()||n.expression(),!g){if(!a)return o.restore(),l.args=[],l;i("could not understand value for named argument")}f=d=r.name}else if(o.$str("...")){if(!a){l.variadic=!0,o.$char(";")&&!b&&(b=!0),(b?p:q).push({name:h.name,variadic:!0});break}j=!0}else a||(d=f=r.name,g=null);g&&m.push(g),q.push({name:f,value:g,expand:j}),o.$char(",")||(o.$char(";")||b)&&(c&&i("Cannot mix ; and , as delimiter types"),b=!0,m.length>1&&(g=new e.Value(m)),p.push({name:d,value:g,expand:j}),d=null,m=[],c=!1)}return o.forget(),l.args=b?p:q,l},definition:function(){var a,b,c,d,f=[],g=!1;if(!("."!==o.currentChar()&&"#"!==o.currentChar()||o.peek(/^[^{]*\}/)))if(o.save(),b=o.$re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){a=b[1];var h=this.args(!1);if(f=h.args,g=h.variadic,!o.$char(")"))return void o.restore("Missing closing ')'");if(o.commentStore.length=0,o.$str("when")&&(d=k(n.conditions,"expected condition")),c=n.block())return o.forget(),new e.mixin.Definition(a,f,c,d,g);o.restore()}else o.forget()}},entity:function(){var a=this.entities;return this.comment()||a.literal()||a.variable()||a.url()||a.call()||a.keyword()||a.javascript()},end:function(){return o.$char(";")||o.peek("}")},alpha:function(){var a;if(o.$re(/^opacity=/i))return a=o.$re(/^\d+/),a||(a=k(this.entities.variable,"Could not parse alpha")),l(")"),new e.Alpha(a)},element:function(){var a,b,d,f=o.i;if(b=this.combinator(),a=o.$re(/^(?:\d+\.\d+|\d+)%/)||o.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||o.$char("*")||o.$char("&")||this.attribute()||o.$re(/^\([^&()@]+\)/)||o.$re(/^[\.#:](?=@)/)||this.entities.variableCurly(),a||(o.save(),o.$char("(")?(d=this.selector())&&o.$char(")")?(a=new e.Paren(d),o.forget()):o.restore("Missing closing ')'"):o.forget()),a)return new e.Element(b,a,f,c)},combinator:function(){var a=o.currentChar();if("/"===a){o.save();var b=o.$re(/^\/[a-z]+\//i);if(b)return o.forget(),new e.Combinator(b);o.restore()}if(">"===a||"+"===a||"~"===a||"|"===a||"^"===a){for(o.i++,"^"===a&&"^"===o.currentChar()&&(a="^^",o.i++);o.isWhitespace();)o.i++;return new e.Combinator(a)}return new e.Combinator(o.isWhitespace(-1)?" ":null)},lessSelector:function(){return this.selector(!0)},selector:function(a){for(var b,d,f,g,h,j,l,m=o.i;(a&&(d=this.extend())||a&&(j=o.$str("when"))||(g=this.element()))&&(j?l=k(this.conditions,"expected condition"):l?i("CSS guard can only be used at the end of selector"):d?h=h?h.concat(d):d:(h&&i("Extend can only be used at the end of selector"),f=o.currentChar(),b?b.push(g):b=[g],g=null),"{"!==f&&"}"!==f&&";"!==f&&","!==f&&")"!==f););return b?new e.Selector(b,h,l,m,c):void(h&&i("Extend must be used to extend a selector, it cannot be used on its own"))},attribute:function(){if(o.$char("[")){var a,b,c,d=this.entities;return(a=d.variableCurly())||(a=k(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/)),c=o.$re(/^[|~*$^]?=/),c&&(b=d.quoted()||o.$re(/^[0-9]+%/)||o.$re(/^[\w-]+/)||d.variableCurly()),l("]"),new e.Attribute(a,c,b)}},block:function(){var a;if(o.$char("{")&&(a=this.primary())&&o.$char("}"))return a},blockRuleset:function(){var a=this.block();return a&&(a=new e.Ruleset(null,a)),a},detachedRuleset:function(){var a=this.blockRuleset();if(a)return new e.DetachedRuleset(a)},ruleset:function(){var b,c,d,f;for(o.save(),a.dumpLineNumbers&&(f=m(o.i));;){if(c=this.lessSelector(),!c)break;if(b?b.push(c):b=[c],o.commentStore.length=0,c.condition&&b.length>1&&i("Guards are only currently allowed on a single selector."),!o.$char(","))break;c.condition&&i("Guards are only currently allowed on a single selector."),o.commentStore.length=0}if(b&&(d=this.block())){o.forget();var g=new e.Ruleset(b,d,a.strictImports);return a.dumpLineNumbers&&(g.debugInfo=f),g}o.restore()},rule:function(b){var d,f,g,h,i,j=o.i,k=o.currentChar();if("."!==k&&"#"!==k&&"&"!==k&&":"!==k)if(o.save(),d=this.variable()||this.ruleProperty()){if(i="string"==typeof d,i&&(f=this.detachedRuleset()),o.commentStore.length=0,!f){h=!i&&d.length>1&&d.pop().value;var l=!b&&(a.compress||i);if(l&&(f=this.value()),!f&&(f=this.anonymousValue()))return o.forget(),new e.Rule(d,f,(!1),h,j,c);l||f||(f=this.value()),g=this.important()}if(f&&this.end())return o.forget(),new e.Rule(d,f,g,h,j,c);if(o.restore(),f&&!b)return this.rule(!0)}else o.forget()},anonymousValue:function(){var a=o.$re(/^([^@+\/'"*`(;{}-]*);/);if(a)return new e.Anonymous(a[1])},"import":function(){var a,b,d=o.i,f=o.$re(/^@import?\s+/);if(f){var g=(f?this.importOptions():null)||{};if(a=this.entities.quoted()||this.entities.url())return b=this.mediaFeatures(),o.$char(";")||(o.i=d,i("missing semi-colon or unrecognised media features on import")),b=b&&new e.Value(b),new e.Import(a,b,g,d,c);o.i=d,i("malformed import statement")}},importOptions:function(){var a,b,c,d={};if(!o.$char("("))return null;do if(a=this.importOption()){switch(b=a,c=!0,b){case"css":b="less",c=!1;break;case"once":b="multiple",c=!1}if(d[b]=c,!o.$char(","))break}while(a);return l(")"),d},importOption:function(){var a=o.$re(/^(less|css|multiple|once|inline|reference|optional)/);if(a)return a[1]},mediaFeature:function(){var a,b,d=this.entities,f=[];o.save();do a=d.keyword()||d.variable(),a?f.push(a):o.$char("(")&&(b=this.property(),a=this.value(),o.$char(")")?b&&a?f.push(new e.Paren(new e.Rule(b,a,null,null,o.i,c,(!0)))):a?f.push(new e.Paren(a)):i("badly formed media feature definition"):i("Missing closing ')'","Parse"));while(a);if(o.forget(),f.length>0)return new e.Expression(f)},mediaFeatures:function(){var a,b=this.entities,c=[];do if(a=this.mediaFeature()){if(c.push(a),!o.$char(","))break}else if(a=b.variable(),a&&(c.push(a),!o.$char(",")))break;while(a);return c.length>0?c:null},media:function(){var b,d,f,g,h=o.i;return a.dumpLineNumbers&&(g=m(h)),o.save(),o.$str("@media")?(b=this.mediaFeatures(),d=this.block(),d||i("media definitions require block statements after any features"),o.forget(),f=new e.Media(d,b,h,c),a.dumpLineNumbers&&(f.debugInfo=g),f):void o.restore()},plugin:function(){var a,b=o.i,d=o.$re(/^@plugin?\s+/);if(d){var f={plugin:!0};if(a=this.entities.quoted()||this.entities.url())return o.$char(";")||(o.i=b,i("missing semi-colon on plugin")),new e.Import(a,null,f,b,c);o.i=b,i("malformed plugin statement")}},directive:function(){var b,d,f,g,h,j,k,l=o.i,n=!0,p=!0;if("@"===o.currentChar()){if(d=this["import"]()||this.plugin()||this.media())return d;if(o.save(),b=o.$re(/^@[a-z-]+/)){switch(g=b,"-"==b.charAt(1)&&b.indexOf("-",2)>0&&(g="@"+b.slice(b.indexOf("-",2)+1)),g){case"@charset":h=!0,n=!1;break;case"@namespace":j=!0,n=!1;break;case"@keyframes":case"@counter-style":h=!0;break;case"@document":case"@supports":k=!0,p=!1;break;default:k=!0}return o.commentStore.length=0,h?(d=this.entity(),d||i("expected "+b+" identifier")):j?(d=this.expression(),d||i("expected "+b+" expression")):k&&(d=(o.$re(/^[^{;]+/)||"").trim(),n="{"==o.currentChar(),d&&(d=new e.Anonymous(d))),n&&(f=this.blockRuleset()),f||!n&&d&&o.$char(";")?(o.forget(),new e.Directive(b,d,f,l,c,a.dumpLineNumbers?m(l):null,p)):void o.restore("directive options not recognised")}}},value:function(){var a,b=[];do if(a=this.expression(),a&&(b.push(a),!o.$char(",")))break;while(a);if(b.length>0)return new e.Value(b)},important:function(){if("!"===o.currentChar())return o.$re(/^! *important/)},sub:function(){var a,b;return o.save(),o.$char("(")?(a=this.addition(),a&&o.$char(")")?(o.forget(),b=new e.Expression([a]),b.parens=!0,b):void o.restore("Expected ')'")):void o.restore()},multiplication:function(){var a,b,c,d,f;if(a=this.operand()){for(f=o.isWhitespace(-1);;){if(o.peek(/^\/[*\/]/))break;if(o.save(),c=o.$char("/")||o.$char("*"),!c){o.forget();break}if(b=this.operand(),!b){o.restore();break}o.forget(),a.parensInOp=!0,b.parensInOp=!0,d=new e.Operation(c,[d||a,b],f),f=o.isWhitespace(-1)}return d||a}},addition:function(){var a,b,c,d,f;if(a=this.multiplication()){for(f=o.isWhitespace(-1);;){if(c=o.$re(/^[-+]\s+/)||!f&&(o.$char("+")||o.$char("-")),!c)break;if(b=this.multiplication(),!b)break;a.parensInOp=!0,b.parensInOp=!0,d=new e.Operation(c,[d||a,b],f),f=o.isWhitespace(-1)}return d||a}},conditions:function(){var a,b,c,d=o.i;if(a=this.condition()){for(;;){if(!o.peek(/^,\s*(not\s*)?\(/)||!o.$char(","))break;if(b=this.condition(),!b)break;c=new e.Condition("or",c||a,b,d)}return c||a}},condition:function(){function a(){return o.$str("or")}var b,c,d;if(b=this.conditionAnd(this)){if(c=a()){if(d=this.condition(),!d)return;b=new e.Condition(c,b,d)}return b}},conditionAnd:function(){function a(a){return a.negatedCondition()||a.parenthesisCondition()}function b(){return o.$str("and")}var c,d,f;if(c=a(this)){if(d=b()){if(f=this.conditionAnd(),!f)return;c=new e.Condition(d,c,f)}return c}},negatedCondition:function(){if(o.$str("not")){var a=this.parenthesisCondition();return a&&(a.negate=!a.negate),a}},parenthesisCondition:function(){function a(a){var b;return o.save(),(b=a.condition())&&o.$char(")")?(o.forget(),b):void o.restore()}var b;return o.save(),o.$str("(")?(b=a(this))?(o.forget(),b):(b=this.atomicCondition())?o.$char(")")?(o.forget(),b):void o.restore("expected ')' got '"+o.currentChar()+"'"):void o.restore():void o.restore()},atomicCondition:function(){var a,b,c,d,f=this.entities,g=o.i;if(a=this.addition()||f.keyword()||f.quoted())return o.$char(">")?d=o.$char("=")?">=":">":o.$char("<")?d=o.$char("=")?"<=":"<":o.$char("=")&&(d=o.$char(">")?"=>":o.$char("<")?"=<":"="),d?(b=this.addition()||f.keyword()||f.quoted(),b?c=new e.Condition(d,a,b,g,(!1)):i("expected expression")):c=new e.Condition("=",a,new e.Keyword("true"),g,(!1)),c},operand:function(){var a,b=this.entities;o.peek(/^-[@\(]/)&&(a=o.$char("-"));var c=this.sub()||b.dimension()||b.color()||b.variable()||b.call()||b.colorKeyword();return a&&(c.parensInOp=!0,c=new e.Negative(c)),c},expression:function(){var a,b,c=[];do a=this.comment(),a?c.push(a):(a=this.addition()||this.entity(),a&&(c.push(a),o.peek(/^\/[\/*]/)||(b=o.$char("/"),b&&c.push(new e.Anonymous(b)))));while(a);if(c.length>0)return new e.Expression(c)},property:function(){var a=o.$re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);if(a)return a[1]},ruleProperty:function(){function a(a){var b=o.i,c=o.$re(a);if(c)return g.push(b),f.push(c[1])}var b,d,f=[],g=[];o.save();var h=o.$re(/^([_a-zA-Z0-9-]+)\s*:/);if(h)return f=[new e.Keyword(h[1])],o.forget(),f;for(a(/^(\*?)/);;)if(!a(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/))break;if(f.length>1&&a(/^((?:\+_|\+)?)\s*:/)){for(o.forget(),""===f[0]&&(f.shift(),g.shift()),d=0;d<f.length;d++)b=f[d],f[d]="@"!==b.charAt(0)?new e.Keyword(b):new e.Variable("@"+b.slice(2,-1),g[d],c);return f}o.restore()}}}};i.serializeVars=function(a){var b="";for(var c in a)if(Object.hasOwnProperty.call(a,c)){var d=a[c];b+=("@"===c[0]?"":"@")+c+": "+d+(";"===String(d).slice(-1)?"":";")}return b},b.exports=i},{"../less-error":32,"../tree":62,"../utils":83,"../visitors":87,"./parser-input":37}],39:[function(a,b,c){var d=function(a){this.less=a,this.visitors=[],this.preProcessors=[],this.postProcessors=[],this.installedPlugins=[],this.fileManagers=[]};d.prototype.addPlugins=function(a){if(a)for(var b=0;b<a.length;b++)this.addPlugin(a[b])},d.prototype.addPlugin=function(a){this.installedPlugins.push(a),a.install(this.less,this)},d.prototype.addVisitor=function(a){this.visitors.push(a)},d.prototype.addPreProcessor=function(a,b){var c;for(c=0;c<this.preProcessors.length&&!(this.preProcessors[c].priority>=b);c++);this.preProcessors.splice(c,0,{preProcessor:a,priority:b})},d.prototype.addPostProcessor=function(a,b){var c;for(c=0;c<this.postProcessors.length&&!(this.postProcessors[c].priority>=b);c++);this.postProcessors.splice(c,0,{postProcessor:a,priority:b})},d.prototype.addFileManager=function(a){this.fileManagers.push(a)},d.prototype.getPreProcessors=function(){for(var a=[],b=0;b<this.preProcessors.length;b++)a.push(this.preProcessors[b].preProcessor);return a},d.prototype.getPostProcessors=function(){for(var a=[],b=0;b<this.postProcessors.length;b++)a.push(this.postProcessors[b].postProcessor);return a},d.prototype.getVisitors=function(){return this.visitors},d.prototype.getFileManagers=function(){return this.fileManagers},b.exports=d},{}],40:[function(a,b,c){var d=a("../less-error"),e=a("../tree"),f=b.exports=function(a,b){this.fileInfo=b};f.prototype.eval=function(a,b){var c,f,g={};f={add:function(a,b){g[a]=b},addMultiple:function(a){Object.keys(a).forEach(function(b){g[b]=a[b]})}};try{c=new Function("functions","tree","fileInfo",a),
 c(f,e,this.fileInfo)}catch(h){b(new d({message:"Plugin evaluation error: '"+h.name+": "+h.message.replace(/["]/g,"'")+"'",filename:this.fileInfo.filename}),null)}b(null,{functions:g})}},{"../less-error":32,"../tree":62}],41:[function(a,b,c){var d;b.exports=function(b,c,e){var f=function(b,e,g){if("function"==typeof e&&(g=e,e={}),!g){d||(d="undefined"==typeof Promise?a("promise"):Promise);var h=this;return new d(function(a,c){f.call(h,b,e,function(b,d){b?c(b):a(d)})})}this.parse(b,e,function(a,b,d,e){if(a)return g(a);var f;try{var h=new c(b,d);f=h.toCSS(e)}catch(a){return g(a)}g(null,f)})};return f}},{promise:void 0}],42:[function(a,b,c){b.exports=function(a,b){var c=function(a){this.options=a};return c.prototype.toCSS=function(b,c,d){var e=new a({contentsIgnoredCharsMap:d.contentsIgnoredChars,rootNode:b,contentsMap:d.contents,sourceMapFilename:this.options.sourceMapFilename,sourceMapURL:this.options.sourceMapURL,outputFilename:this.options.sourceMapOutputFilename,sourceMapBasepath:this.options.sourceMapBasepath,sourceMapRootpath:this.options.sourceMapRootpath,outputSourceFiles:this.options.outputSourceFiles,sourceMapGenerator:this.options.sourceMapGenerator,sourceMapFileInline:this.options.sourceMapFileInline}),f=e.toCSS(c);return this.sourceMap=e.sourceMap,this.sourceMapURL=e.sourceMapURL,this.options.sourceMapInputFilename&&(this.sourceMapInputFilename=e.normalizeFilename(this.options.sourceMapInputFilename)),f+this.getCSSAppendage()},c.prototype.getCSSAppendage=function(){var a=this.sourceMapURL;if(this.options.sourceMapFileInline){if(void 0===this.sourceMap)return"";a="data:application/json;base64,"+b.encodeBase64(this.sourceMap)}return a?"/*# sourceMappingURL="+a+" */":""},c.prototype.getExternalSourceMap=function(){return this.sourceMap},c.prototype.setExternalSourceMap=function(a){this.sourceMap=a},c.prototype.isInline=function(){return this.options.sourceMapFileInline},c.prototype.getSourceMapURL=function(){return this.sourceMapURL},c.prototype.getOutputFilename=function(){return this.options.sourceMapOutputFilename},c.prototype.getInputFilename=function(){return this.sourceMapInputFilename},c}},{}],43:[function(a,b,c){b.exports=function(a){var b=function(b){this._css=[],this._rootNode=b.rootNode,this._contentsMap=b.contentsMap,this._contentsIgnoredCharsMap=b.contentsIgnoredCharsMap,b.sourceMapFilename&&(this._sourceMapFilename=b.sourceMapFilename.replace(/\\/g,"/")),this._outputFilename=b.outputFilename,this.sourceMapURL=b.sourceMapURL,b.sourceMapBasepath&&(this._sourceMapBasepath=b.sourceMapBasepath.replace(/\\/g,"/")),b.sourceMapRootpath?(this._sourceMapRootpath=b.sourceMapRootpath.replace(/\\/g,"/"),"/"!==this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1)&&(this._sourceMapRootpath+="/")):this._sourceMapRootpath="",this._outputSourceFiles=b.outputSourceFiles,this._sourceMapGeneratorConstructor=a.getSourceMapGenerator(),this._lineNumber=0,this._column=0};return b.prototype.normalizeFilename=function(a){return a=a.replace(/\\/g,"/"),this._sourceMapBasepath&&0===a.indexOf(this._sourceMapBasepath)&&(a=a.substring(this._sourceMapBasepath.length),"\\"!==a.charAt(0)&&"/"!==a.charAt(0)||(a=a.substring(1))),(this._sourceMapRootpath||"")+a},b.prototype.add=function(a,b,c,d){if(a){var e,f,g,h,i;if(b){var j=this._contentsMap[b.filename];this._contentsIgnoredCharsMap[b.filename]&&(c-=this._contentsIgnoredCharsMap[b.filename],c<0&&(c=0),j=j.slice(this._contentsIgnoredCharsMap[b.filename])),j=j.substring(0,c),f=j.split("\n"),h=f[f.length-1]}if(e=a.split("\n"),g=e[e.length-1],b)if(d)for(i=0;i<e.length;i++)this._sourceMapGenerator.addMapping({generated:{line:this._lineNumber+i+1,column:0===i?this._column:0},original:{line:f.length+i,column:0===i?h.length:0},source:this.normalizeFilename(b.filename)});else this._sourceMapGenerator.addMapping({generated:{line:this._lineNumber+1,column:this._column},original:{line:f.length,column:h.length},source:this.normalizeFilename(b.filename)});1===e.length?this._column+=g.length:(this._lineNumber+=e.length-1,this._column=g.length),this._css.push(a)}},b.prototype.isEmpty=function(){return 0===this._css.length},b.prototype.toCSS=function(a){if(this._sourceMapGenerator=new this._sourceMapGeneratorConstructor({file:this._outputFilename,sourceRoot:null}),this._outputSourceFiles)for(var b in this._contentsMap)if(this._contentsMap.hasOwnProperty(b)){var c=this._contentsMap[b];this._contentsIgnoredCharsMap[b]&&(c=c.slice(this._contentsIgnoredCharsMap[b])),this._sourceMapGenerator.setSourceContent(this.normalizeFilename(b),c)}if(this._rootNode.genCSS(a,this),this._css.length>0){var d,e=JSON.stringify(this._sourceMapGenerator.toJSON());this.sourceMapURL?d=this.sourceMapURL:this._sourceMapFilename&&(d=this._sourceMapFilename),this.sourceMapURL=d,this.sourceMap=e}return this._css.join("")},b}},{}],44:[function(a,b,c){var d=a("./contexts"),e=a("./visitors"),f=a("./tree");b.exports=function(a,b){b=b||{};var c,g=b.variables,h=new d.Eval(b);"object"!=typeof g||Array.isArray(g)||(g=Object.keys(g).map(function(a){var b=g[a];return b instanceof f.Value||(b instanceof f.Expression||(b=new f.Expression([b])),b=new f.Value([b])),new f.Rule("@"+a,b,(!1),null,0)}),h.frames=[new f.Ruleset(null,g)]);var i,j=[],k=[new e.JoinSelectorVisitor,new e.MarkVisibleSelectorsVisitor((!0)),new e.ExtendVisitor,new e.ToCSSVisitor({compress:Boolean(b.compress)})];if(b.pluginManager){var l=b.pluginManager.getVisitors();for(i=0;i<l.length;i++){var m=l[i];m.isPreEvalVisitor?j.push(m):m.isPreVisitor?k.splice(0,0,m):k.push(m)}}for(i=0;i<j.length;i++)j[i].run(a);for(c=a.eval(h),i=0;i<k.length;i++)k[i].run(c);return c}},{"./contexts":11,"./tree":62,"./visitors":87}],45:[function(a,b,c){var d=a("./node"),e=function(a){this.value=a};e.prototype=new d,e.prototype.type="Alpha",e.prototype.accept=function(a){this.value=a.visit(this.value)},e.prototype.eval=function(a){return this.value.eval?new e(this.value.eval(a)):this},e.prototype.genCSS=function(a,b){b.add("alpha(opacity="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value),b.add(")")},b.exports=e},{"./node":70}],46:[function(a,b,c){var d=a("./node"),e=function(a,b,c,d,e,f){this.value=a,this.index=b,this.mapLines=d,this.currentFileInfo=c,this.rulesetLike="undefined"!=typeof e&&e,this.allowRoot=!0,this.copyVisibilityInfo(f)};e.prototype=new d,e.prototype.type="Anonymous",e.prototype.eval=function(){return new e(this.value,this.index,this.currentFileInfo,this.mapLines,this.rulesetLike,this.visibilityInfo())},e.prototype.compare=function(a){return a.toCSS&&this.toCSS()===a.toCSS()?0:void 0},e.prototype.isRulesetLike=function(){return this.rulesetLike},e.prototype.genCSS=function(a,b){b.add(this.value,this.currentFileInfo,this.index,this.mapLines)},b.exports=e},{"./node":70}],47:[function(a,b,c){var d=a("./node"),e=function(a,b){this.key=a,this.value=b};e.prototype=new d,e.prototype.type="Assignment",e.prototype.accept=function(a){this.value=a.visit(this.value)},e.prototype.eval=function(a){return this.value.eval?new e(this.key,this.value.eval(a)):this},e.prototype.genCSS=function(a,b){b.add(this.key+"="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value)},b.exports=e},{"./node":70}],48:[function(a,b,c){var d=a("./node"),e=function(a,b,c){this.key=a,this.op=b,this.value=c};e.prototype=new d,e.prototype.type="Attribute",e.prototype.eval=function(a){return new e(this.key.eval?this.key.eval(a):this.key,this.op,this.value&&this.value.eval?this.value.eval(a):this.value)},e.prototype.genCSS=function(a,b){b.add(this.toCSS(a))},e.prototype.toCSS=function(a){var b=this.key.toCSS?this.key.toCSS(a):this.key;return this.op&&(b+=this.op,b+=this.value.toCSS?this.value.toCSS(a):this.value),"["+b+"]"},b.exports=e},{"./node":70}],49:[function(a,b,c){var d=a("./node"),e=a("../functions/function-caller"),f=function(a,b,c,d){this.name=a,this.args=b,this.index=c,this.currentFileInfo=d};f.prototype=new d,f.prototype.type="Call",f.prototype.accept=function(a){this.args&&(this.args=a.visitArray(this.args))},f.prototype.eval=function(a){var b,c=this.args.map(function(b){return b.eval(a)}),d=new e(this.name,a,this.index,this.currentFileInfo);if(d.isValid()){try{b=d.call(c)}catch(g){throw{type:g.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(g.message?": "+g.message:""),index:this.index,filename:this.currentFileInfo.filename}}if(null!=b)return b.index=this.index,b.currentFileInfo=this.currentFileInfo,b}return new f(this.name,c,this.index,this.currentFileInfo)},f.prototype.genCSS=function(a,b){b.add(this.name+"(",this.currentFileInfo,this.index);for(var c=0;c<this.args.length;c++)this.args[c].genCSS(a,b),c+1<this.args.length&&b.add(", ");b.add(")")},b.exports=f},{"../functions/function-caller":21,"./node":70}],50:[function(a,b,c){function d(a,b){return Math.min(Math.max(a,0),b)}function e(a){return"#"+a.map(function(a){return a=d(Math.round(a),255),(a<16?"0":"")+a.toString(16)}).join("")}var f=a("./node"),g=a("../data/colors"),h=function(a,b,c){this.rgb=Array.isArray(a)?a:6==a.length?a.match(/.{2}/g).map(function(a){return parseInt(a,16)}):a.split("").map(function(a){return parseInt(a+a,16)}),this.alpha="number"==typeof b?b:1,"undefined"!=typeof c&&(this.value=c)};h.prototype=new f,h.prototype.type="Color",h.prototype.luma=function(){var a=this.rgb[0]/255,b=this.rgb[1]/255,c=this.rgb[2]/255;return a=a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4),b=b<=.03928?b/12.92:Math.pow((b+.055)/1.055,2.4),c=c<=.03928?c/12.92:Math.pow((c+.055)/1.055,2.4),.2126*a+.7152*b+.0722*c},h.prototype.genCSS=function(a,b){b.add(this.toCSS(a))},h.prototype.toCSS=function(a,b){var c,e,f=a&&a.compress&&!b;if(this.value)return this.value;if(e=this.fround(a,this.alpha),e<1)return"rgba("+this.rgb.map(function(a){return d(Math.round(a),255)}).concat(d(e,1)).join(","+(f?"":" "))+")";if(c=this.toRGB(),f){var g=c.split("");g[1]===g[2]&&g[3]===g[4]&&g[5]===g[6]&&(c="#"+g[1]+g[3]+g[5])}return c},h.prototype.operate=function(a,b,c){for(var d=[],e=this.alpha*(1-c.alpha)+c.alpha,f=0;f<3;f++)d[f]=this._operate(a,b,this.rgb[f],c.rgb[f]);return new h(d,e)},h.prototype.toRGB=function(){return e(this.rgb)},h.prototype.toHSL=function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=(g+h)/2,j=g-h;if(g===h)a=b=0;else{switch(b=i>.5?j/(2-g-h):j/(g+h),g){case c:a=(d-e)/j+(d<e?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,l:i,a:f}},h.prototype.toHSV=function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=g,j=g-h;if(b=0===g?0:j/g,g===h)a=0;else{switch(g){case c:a=(d-e)/j+(d<e?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,v:i,a:f}},h.prototype.toARGB=function(){return e([255*this.alpha].concat(this.rgb))},h.prototype.compare=function(a){return a.rgb&&a.rgb[0]===this.rgb[0]&&a.rgb[1]===this.rgb[1]&&a.rgb[2]===this.rgb[2]&&a.alpha===this.alpha?0:void 0},h.fromKeyword=function(a){var b,c=a.toLowerCase();if(g.hasOwnProperty(c)?b=new h(g[c].slice(1)):"transparent"===c&&(b=new h([0,0,0],0)),b)return b.value=a,b},b.exports=h},{"../data/colors":12,"./node":70}],51:[function(a,b,c){var d=a("./node"),e=function(a){" "===a?(this.value=" ",this.emptyOrWhitespace=!0):(this.value=a?a.trim():"",this.emptyOrWhitespace=""===this.value)};e.prototype=new d,e.prototype.type="Combinator";var f={"":!0," ":!0,"|":!0};e.prototype.genCSS=function(a,b){var c=a.compress||f[this.value]?"":" ";b.add(c+this.value+c)},b.exports=e},{"./node":70}],52:[function(a,b,c){var d=a("./node"),e=a("./debug-info"),f=function(a,b,c,d){this.value=a,this.isLineComment=b,this.index=c,this.currentFileInfo=d,this.allowRoot=!0};f.prototype=new d,f.prototype.type="Comment",f.prototype.genCSS=function(a,b){this.debugInfo&&b.add(e(a,this),this.currentFileInfo,this.index),b.add(this.value)},f.prototype.isSilent=function(a){var b=a.compress&&"!"!==this.value[2];return this.isLineComment||b},b.exports=f},{"./debug-info":54,"./node":70}],53:[function(a,b,c){var d=a("./node"),e=function(a,b,c,d,e){this.op=a.trim(),this.lvalue=b,this.rvalue=c,this.index=d,this.negate=e};e.prototype=new d,e.prototype.type="Condition",e.prototype.accept=function(a){this.lvalue=a.visit(this.lvalue),this.rvalue=a.visit(this.rvalue)},e.prototype.eval=function(a){var b=function(a,b,c){switch(a){case"and":return b&&c;case"or":return b||c;default:switch(d.compare(b,c)){case-1:return"<"===a||"=<"===a||"<="===a;case 0:return"="===a||">="===a||"=<"===a||"<="===a;case 1:return">"===a||">="===a;default:return!1}}}(this.op,this.lvalue.eval(a),this.rvalue.eval(a));return this.negate?!b:b},b.exports=e},{"./node":70}],54:[function(a,b,c){var d=function(a,b,c){var e="";if(a.dumpLineNumbers&&!a.compress)switch(a.dumpLineNumbers){case"comments":e=d.asComment(b);break;case"mediaquery":e=d.asMediaQuery(b);break;case"all":e=d.asComment(b)+(c||"")+d.asMediaQuery(b)}return e};d.asComment=function(a){return"/* line "+a.debugInfo.lineNumber+", "+a.debugInfo.fileName+" */\n"},d.asMediaQuery=function(a){var b=a.debugInfo.fileName;return/^[a-z]+:\/\//i.test(b)||(b="file://"+b),"@media -sass-debug-info{filename{font-family:"+b.replace(/([.:\/\\])/g,function(a){return"\\"==a&&(a="/"),"\\"+a})+"}line{font-family:\\00003"+a.debugInfo.lineNumber+"}}\n"},b.exports=d},{}],55:[function(a,b,c){var d=a("./node"),e=a("../contexts"),f=function(a,b){this.ruleset=a,this.frames=b};f.prototype=new d,f.prototype.type="DetachedRuleset",f.prototype.evalFirst=!0,f.prototype.accept=function(a){this.ruleset=a.visit(this.ruleset)},f.prototype.eval=function(a){var b=this.frames||a.frames.slice(0);return new f(this.ruleset,b)},f.prototype.callEval=function(a){return this.ruleset.eval(this.frames?new e.Eval(a,this.frames.concat(a.frames)):a)},b.exports=f},{"../contexts":11,"./node":70}],56:[function(a,b,c){var d=a("./node"),e=a("../data/unit-conversions"),f=a("./unit"),g=a("./color"),h=function(a,b){this.value=parseFloat(a),this.unit=b&&b instanceof f?b:new f(b?[b]:void 0)};h.prototype=new d,h.prototype.type="Dimension",h.prototype.accept=function(a){this.unit=a.visit(this.unit)},h.prototype.eval=function(a){return this},h.prototype.toColor=function(){return new g([this.value,this.value,this.value])},h.prototype.genCSS=function(a,b){if(a&&a.strictUnits&&!this.unit.isSingular())throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());var c=this.fround(a,this.value),d=String(c);if(0!==c&&c<1e-6&&c>-1e-6&&(d=c.toFixed(20).replace(/0+$/,"")),a&&a.compress){if(0===c&&this.unit.isLength())return void b.add(d);c>0&&c<1&&(d=d.substr(1))}b.add(d),this.unit.genCSS(a,b)},h.prototype.operate=function(a,b,c){var d=this._operate(a,b,this.value,c.value),e=this.unit.clone();if("+"===b||"-"===b)if(0===e.numerator.length&&0===e.denominator.length)e=c.unit.clone(),this.unit.backupUnit&&(e.backupUnit=this.unit.backupUnit);else if(0===c.unit.numerator.length&&0===e.denominator.length);else{if(c=c.convertTo(this.unit.usedUnits()),a.strictUnits&&c.unit.toString()!==e.toString())throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '"+e.toString()+"' and '"+c.unit.toString()+"'.");d=this._operate(a,b,this.value,c.value)}else"*"===b?(e.numerator=e.numerator.concat(c.unit.numerator).sort(),e.denominator=e.denominator.concat(c.unit.denominator).sort(),e.cancel()):"/"===b&&(e.numerator=e.numerator.concat(c.unit.denominator).sort(),e.denominator=e.denominator.concat(c.unit.numerator).sort(),e.cancel());return new h(d,e)},h.prototype.compare=function(a){var b,c;if(a instanceof h){if(this.unit.isEmpty()||a.unit.isEmpty())b=this,c=a;else if(b=this.unify(),c=a.unify(),0!==b.unit.compare(c.unit))return;return d.numericCompare(b.value,c.value)}},h.prototype.unify=function(){return this.convertTo({length:"px",duration:"s",angle:"rad"})},h.prototype.convertTo=function(a){var b,c,d,f,g,i=this.value,j=this.unit.clone(),k={};if("string"==typeof a){for(b in e)e[b].hasOwnProperty(a)&&(k={},k[b]=a);a=k}g=function(a,b){return d.hasOwnProperty(a)?(b?i/=d[a]/d[f]:i*=d[a]/d[f],f):a};for(c in a)a.hasOwnProperty(c)&&(f=a[c],d=e[c],j.map(g));return j.cancel(),new h(i,j)},b.exports=h},{"../data/unit-conversions":14,"./color":50,"./node":70,"./unit":79}],57:[function(a,b,c){var d=a("./node"),e=a("./selector"),f=a("./ruleset"),g=function(a,b,c,d,f,g,h,i){var j;if(this.name=a,this.value=b,c)for(Array.isArray(c)?this.rules=c:(this.rules=[c],this.rules[0].selectors=new e([],null,null,this.index,f).createEmptySelectors()),j=0;j<this.rules.length;j++)this.rules[j].allowImports=!0;this.index=d,this.currentFileInfo=f,this.debugInfo=g,this.isRooted=h||!1,this.copyVisibilityInfo(i),this.allowRoot=!0};g.prototype=new d,g.prototype.type="Directive",g.prototype.accept=function(a){var b=this.value,c=this.rules;c&&(this.rules=a.visitArray(c)),b&&(this.value=a.visit(b))},g.prototype.isRulesetLike=function(){return this.rules||!this.isCharset()},g.prototype.isCharset=function(){return"@charset"===this.name},g.prototype.genCSS=function(a,b){var c=this.value,d=this.rules;b.add(this.name,this.currentFileInfo,this.index),c&&(b.add(" "),c.genCSS(a,b)),d?this.outputRuleset(a,b,d):b.add(";")},g.prototype.eval=function(a){var b,c,d=this.value,e=this.rules;return b=a.mediaPath,c=a.mediaBlocks,a.mediaPath=[],a.mediaBlocks=[],d&&(d=d.eval(a)),e&&(e=[e[0].eval(a)],e[0].root=!0),a.mediaPath=b,a.mediaBlocks=c,new g(this.name,d,e,this.index,this.currentFileInfo,this.debugInfo,this.isRooted,this.visibilityInfo())},g.prototype.variable=function(a){if(this.rules)return f.prototype.variable.call(this.rules[0],a)},g.prototype.find=function(){if(this.rules)return f.prototype.find.apply(this.rules[0],arguments)},g.prototype.rulesets=function(){if(this.rules)return f.prototype.rulesets.apply(this.rules[0])},g.prototype.outputRuleset=function(a,b,c){var d,e=c.length;if(a.tabLevel=(0|a.tabLevel)+1,a.compress){for(b.add("{"),d=0;d<e;d++)c[d].genCSS(a,b);return b.add("}"),void a.tabLevel--}var f="\n"+Array(a.tabLevel).join("  "),g=f+"  ";if(e){for(b.add(" {"+g),c[0].genCSS(a,b),d=1;d<e;d++)b.add(g),c[d].genCSS(a,b);b.add(f+"}")}else b.add(" {"+f+"}");a.tabLevel--},b.exports=g},{"./node":70,"./ruleset":76,"./selector":77}],58:[function(a,b,c){var d=a("./node"),e=a("./paren"),f=a("./combinator"),g=function(a,b,c,d,e){this.combinator=a instanceof f?a:new f(a),this.value="string"==typeof b?b.trim():b?b:"",this.index=c,this.currentFileInfo=d,this.copyVisibilityInfo(e)};g.prototype=new d,g.prototype.type="Element",g.prototype.accept=function(a){var b=this.value;this.combinator=a.visit(this.combinator),"object"==typeof b&&(this.value=a.visit(b))},g.prototype.eval=function(a){return new g(this.combinator,this.value.eval?this.value.eval(a):this.value,this.index,this.currentFileInfo,this.visibilityInfo())},g.prototype.clone=function(){return new g(this.combinator,this.value,this.index,this.currentFileInfo,this.visibilityInfo())},g.prototype.genCSS=function(a,b){b.add(this.toCSS(a),this.currentFileInfo,this.index)},g.prototype.toCSS=function(a){a=a||{};var b=this.value,c=a.firstSelector;return b instanceof e&&(a.firstSelector=!0),b=b.toCSS?b.toCSS(a):b,a.firstSelector=c,""===b&&"&"===this.combinator.value.charAt(0)?"":this.combinator.toCSS(a)+b},b.exports=g},{"./combinator":51,"./node":70,"./paren":72}],59:[function(a,b,c){var d=a("./node"),e=a("./paren"),f=a("./comment"),g=function(a){if(this.value=a,!a)throw new Error("Expression requires an array parameter")};g.prototype=new d,g.prototype.type="Expression",g.prototype.accept=function(a){this.value=a.visitArray(this.value)},g.prototype.eval=function(a){var b,c=this.parens&&!this.parensInOp,d=!1;return c&&a.inParenthesis(),this.value.length>1?b=new g(this.value.map(function(b){return b.eval(a)})):1===this.value.length?(this.value[0].parens&&!this.value[0].parensInOp&&(d=!0),b=this.value[0].eval(a)):b=this,c&&a.outOfParenthesis(),this.parens&&this.parensInOp&&!a.isMathOn()&&!d&&(b=new e(b)),b},g.prototype.genCSS=function(a,b){for(var c=0;c<this.value.length;c++)this.value[c].genCSS(a,b),c+1<this.value.length&&b.add(" ")},g.prototype.throwAwayComments=function(){this.value=this.value.filter(function(a){return!(a instanceof f)})},b.exports=g},{"./comment":52,"./node":70,"./paren":72}],60:[function(a,b,c){var d=a("./node"),e=a("./selector"),f=function g(a,b,c,d,e){switch(this.selector=a,this.option=b,this.index=c,this.object_id=g.next_id++,this.parent_ids=[this.object_id],this.currentFileInfo=d||{},this.copyVisibilityInfo(e),this.allowRoot=!0,b){case"all":this.allowBefore=!0,this.allowAfter=!0;break;default:this.allowBefore=!1,this.allowAfter=!1}};f.next_id=0,f.prototype=new d,f.prototype.type="Extend",f.prototype.accept=function(a){this.selector=a.visit(this.selector)},f.prototype.eval=function(a){return new f(this.selector.eval(a),this.option,this.index,this.currentFileInfo,this.visibilityInfo())},f.prototype.clone=function(a){return new f(this.selector,this.option,this.index,this.currentFileInfo,this.visibilityInfo())},f.prototype.findSelfSelectors=function(a){var b,c,d=[];for(b=0;b<a.length;b++)c=a[b].elements,b>0&&c.length&&""===c[0].combinator.value&&(c[0].combinator.value=" "),d=d.concat(a[b].elements);this.selfSelectors=[new e(d)],this.selfSelectors[0].copyVisibilityInfo(this.visibilityInfo())},b.exports=f},{"./node":70,"./selector":77}],61:[function(a,b,c){var d=a("./node"),e=a("./media"),f=a("./url"),g=a("./quoted"),h=a("./ruleset"),i=a("./anonymous"),j=function(a,b,c,d,e,f){if(this.options=c,this.index=d,this.path=a,this.features=b,this.currentFileInfo=e,this.allowRoot=!0,void 0!==this.options.less||this.options.inline)this.css=!this.options.less||this.options.inline;else{var g=this.getPath();g&&/[#\.\&\?\/]css([\?;].*)?$/.test(g)&&(this.css=!0)}this.copyVisibilityInfo(f)};j.prototype=new d,j.prototype.type="Import",j.prototype.accept=function(a){this.features&&(this.features=a.visit(this.features)),this.path=a.visit(this.path),this.options.plugin||this.options.inline||!this.root||(this.root=a.visit(this.root))},j.prototype.genCSS=function(a,b){this.css&&void 0===this.path.currentFileInfo.reference&&(b.add("@import ",this.currentFileInfo,this.index),this.path.genCSS(a,b),this.features&&(b.add(" "),this.features.genCSS(a,b)),b.add(";"))},j.prototype.getPath=function(){return this.path instanceof f?this.path.value.value:this.path.value},j.prototype.isVariableImport=function(){var a=this.path;return a instanceof f&&(a=a.value),!(a instanceof g)||a.containsVariables()},j.prototype.evalForImport=function(a){var b=this.path;return b instanceof f&&(b=b.value),new j(b.eval(a),this.features,this.options,this.index,this.currentFileInfo,this.visibilityInfo())},j.prototype.evalPath=function(a){var b=this.path.eval(a),c=this.currentFileInfo&&this.currentFileInfo.rootpath;if(!(b instanceof f)){if(c){var d=b.value;d&&a.isPathRelative(d)&&(b.value=c+d)}b.value=a.normalizePath(b.value)}return b},j.prototype.eval=function(a){var b=this.doEval(a);return(this.options.reference||this.blocksVisibility())&&(b.length||0===b.length?b.forEach(function(a){a.addVisibilityBlock()}):b.addVisibilityBlock()),b},j.prototype.doEval=function(a){var b,c,d=this.features&&this.features.eval(a);if(this.options.plugin)return c=a.frames[0]&&a.frames[0].functionRegistry,c&&this.root&&this.root.functions&&c.addMultiple(this.root.functions),[];if(this.skip&&("function"==typeof this.skip&&(this.skip=this.skip()),this.skip))return[];if(this.options.inline){var f=new i(this.root,0,{filename:this.importedFilename,reference:this.path.currentFileInfo&&this.path.currentFileInfo.reference},(!0),(!0));return this.features?new e([f],this.features.value):[f]}if(this.css){var g=new j(this.evalPath(a),d,this.options,this.index);if(!g.css&&this.error)throw this.error;return g}return b=new h(null,this.root.rules.slice(0)),b.evalImports(a),this.features?new e(b.rules,this.features.value):b.rules},b.exports=j},{"./anonymous":46,"./media":66,"./node":70,"./quoted":73,"./ruleset":76,"./url":80}],62:[function(a,b,c){var d={};d.Node=a("./node"),d.Alpha=a("./alpha"),d.Color=a("./color"),d.Directive=a("./directive"),d.DetachedRuleset=a("./detached-ruleset"),d.Operation=a("./operation"),d.Dimension=a("./dimension"),d.Unit=a("./unit"),d.Keyword=a("./keyword"),d.Variable=a("./variable"),d.Ruleset=a("./ruleset"),d.Element=a("./element"),d.Attribute=a("./attribute"),d.Combinator=a("./combinator"),d.Selector=a("./selector"),d.Quoted=a("./quoted"),d.Expression=a("./expression"),d.Rule=a("./rule"),d.Call=a("./call"),d.URL=a("./url"),d.Import=a("./import"),d.mixin={Call:a("./mixin-call"),Definition:a("./mixin-definition")},d.Comment=a("./comment"),d.Anonymous=a("./anonymous"),d.Value=a("./value"),d.JavaScript=a("./javascript"),d.Assignment=a("./assignment"),d.Condition=a("./condition"),d.Paren=a("./paren"),d.Media=a("./media"),d.UnicodeDescriptor=a("./unicode-descriptor"),d.Negative=a("./negative"),d.Extend=a("./extend"),d.RulesetCall=a("./ruleset-call"),b.exports=d},{"./alpha":45,"./anonymous":46,"./assignment":47,"./attribute":48,"./call":49,"./color":50,"./combinator":51,"./comment":52,"./condition":53,"./detached-ruleset":55,"./dimension":56,"./directive":57,"./element":58,"./expression":59,"./extend":60,"./import":61,"./javascript":63,"./keyword":65,"./media":66,"./mixin-call":67,"./mixin-definition":68,"./negative":69,"./node":70,"./operation":71,"./paren":72,"./quoted":73,"./rule":74,"./ruleset":76,"./ruleset-call":75,"./selector":77,"./unicode-descriptor":78,"./unit":79,"./url":80,"./value":81,"./variable":82}],63:[function(a,b,c){var d=a("./js-eval-node"),e=a("./dimension"),f=a("./quoted"),g=a("./anonymous"),h=function(a,b,c,d){this.escaped=b,this.expression=a,this.index=c,this.currentFileInfo=d};h.prototype=new d,h.prototype.type="JavaScript",h.prototype.eval=function(a){var b=this.evaluateJavaScript(this.expression,a);return"number"==typeof b?new e(b):"string"==typeof b?new f('"'+b+'"',b,this.escaped,this.index):new g(Array.isArray(b)?b.join(", "):b)},b.exports=h},{"./anonymous":46,"./dimension":56,"./js-eval-node":64,"./quoted":73}],64:[function(a,b,c){var d=a("./node"),e=a("./variable"),f=function(){};f.prototype=new d,f.prototype.evaluateJavaScript=function(a,b){var c,d=this,f={};if(void 0!==b.javascriptEnabled&&!b.javascriptEnabled)throw{message:"You are using JavaScript, which has been disabled.",filename:this.currentFileInfo.filename,index:this.index};a=a.replace(/@\{([\w-]+)\}/g,function(a,c){return d.jsify(new e("@"+c,d.index,d.currentFileInfo).eval(b))});try{a=new Function("return ("+a+")")}catch(g){throw{message:"JavaScript evaluation error: "+g.message+" from `"+a+"`",filename:this.currentFileInfo.filename,index:this.index}}var h=b.frames[0].variables();for(var i in h)h.hasOwnProperty(i)&&(f[i.slice(1)]={value:h[i].value,toJS:function(){return this.value.eval(b).toCSS()}});try{c=a.call(f)}catch(g){throw{message:"JavaScript evaluation error: '"+g.name+": "+g.message.replace(/["]/g,"'")+"'",filename:this.currentFileInfo.filename,index:this.index}}return c},f.prototype.jsify=function(a){return Array.isArray(a.value)&&a.value.length>1?"["+a.value.map(function(a){return a.toCSS()}).join(", ")+"]":a.toCSS()},b.exports=f},{"./node":70,"./variable":82}],65:[function(a,b,c){var d=a("./node"),e=function(a){this.value=a};e.prototype=new d,e.prototype.type="Keyword",e.prototype.genCSS=function(a,b){if("%"===this.value)throw{type:"Syntax",message:"Invalid % without number"};b.add(this.value)},e.True=new e("true"),e.False=new e("false"),b.exports=e},{"./node":70}],66:[function(a,b,c){var d=a("./ruleset"),e=a("./value"),f=a("./selector"),g=a("./anonymous"),h=a("./expression"),i=a("./directive"),j=function(a,b,c,g,h){this.index=c,this.currentFileInfo=g;var i=new f([],null,null,this.index,this.currentFileInfo).createEmptySelectors();this.features=new e(b),this.rules=[new d(i,a)],this.rules[0].allowImports=!0,this.copyVisibilityInfo(h),this.allowRoot=!0};j.prototype=new i,j.prototype.type="Media",j.prototype.isRulesetLike=!0,j.prototype.accept=function(a){this.features&&(this.features=a.visit(this.features)),this.rules&&(this.rules=a.visitArray(this.rules))},j.prototype.genCSS=function(a,b){b.add("@media ",this.currentFileInfo,this.index),this.features.genCSS(a,b),this.outputRuleset(a,b,this.rules)},j.prototype.eval=function(a){a.mediaBlocks||(a.mediaBlocks=[],a.mediaPath=[]);var b=new j(null,[],this.index,this.currentFileInfo,this.visibilityInfo());this.debugInfo&&(this.rules[0].debugInfo=this.debugInfo,b.debugInfo=this.debugInfo);var c=!1;a.strictMath||(c=!0,a.strictMath=!0);try{b.features=this.features.eval(a)}finally{c&&(a.strictMath=!1)}return a.mediaPath.push(b),a.mediaBlocks.push(b),this.rules[0].functionRegistry=a.frames[0].functionRegistry.inherit(),a.frames.unshift(this.rules[0]),b.rules=[this.rules[0].eval(a)],a.frames.shift(),a.mediaPath.pop(),0===a.mediaPath.length?b.evalTop(a):b.evalNested(a)},j.prototype.evalTop=function(a){var b=this;if(a.mediaBlocks.length>1){var c=new f([],null,null,this.index,this.currentFileInfo).createEmptySelectors();b=new d(c,a.mediaBlocks),b.multiMedia=!0,b.copyVisibilityInfo(this.visibilityInfo())}return delete a.mediaBlocks,delete a.mediaPath,b},j.prototype.evalNested=function(a){var b,c,f=a.mediaPath.concat([this]);for(b=0;b<f.length;b++)c=f[b].features instanceof e?f[b].features.value:f[b].features,f[b]=Array.isArray(c)?c:[c];return this.features=new e(this.permute(f).map(function(a){for(a=a.map(function(a){return a.toCSS?a:new g(a)}),b=a.length-1;b>0;b--)a.splice(b,0,new g("and"));return new h(a)})),new d([],[])},j.prototype.permute=function(a){if(0===a.length)return[];if(1===a.length)return a[0];for(var b=[],c=this.permute(a.slice(1)),d=0;d<c.length;d++)for(var e=0;e<a[0].length;e++)b.push([a[0][e]].concat(c[d]));return b},j.prototype.bubbleSelectors=function(a){a&&(this.rules=[new d(a.slice(0),[this.rules[0]])])},b.exports=j},{"./anonymous":46,"./directive":57,"./expression":59,"./ruleset":76,"./selector":77,"./value":81}],67:[function(a,b,c){var d=a("./node"),e=a("./selector"),f=a("./mixin-definition"),g=a("../functions/default"),h=function(a,b,c,d,f){this.selector=new e(a),this.arguments=b||[],this.index=c,this.currentFileInfo=d,this.important=f,this.allowRoot=!0};h.prototype=new d,h.prototype.type="MixinCall",h.prototype.accept=function(a){this.selector&&(this.selector=a.visit(this.selector)),this.arguments.length&&(this.arguments=a.visitArray(this.arguments))},h.prototype.eval=function(a){function b(b,c){var d,e,f;for(d=0;d<2;d++){for(x[d]=!0,g.value(d),e=0;e<c.length&&x[d];e++)f=c[e],f.matchCondition&&(x[d]=x[d]&&f.matchCondition(null,a));b.matchCondition&&(x[d]=x[d]&&b.matchCondition(t,a))}return x[0]||x[1]?x[0]!=x[1]?x[1]?A:B:z:y}var c,d,e,h,i,j,k,l,m,n,o,p,q,r,s,t=[],u=[],v=!1,w=[],x=[],y=-1,z=0,A=1,B=2;for(j=0;j<this.arguments.length;j++)if(h=this.arguments[j],i=h.value.eval(a),h.expand&&Array.isArray(i.value))for(i=i.value,k=0;k<i.length;k++)t.push({value:i[k]});else t.push({name:h.name,value:i});for(s=function(b){return b.matchArgs(null,a)},j=0;j<a.frames.length;j++)if((c=a.frames[j].find(this.selector,null,s)).length>0){for(n=!0,k=0;k<c.length;k++){for(d=c[k].rule,e=c[k].path,m=!1,l=0;l<a.frames.length;l++)if(!(d instanceof f)&&d===(a.frames[l].originalRuleset||a.frames[l])){m=!0;break}m||d.matchArgs(t,a)&&(o={mixin:d,group:b(d,e)},o.group!==y&&w.push(o),v=!0)}for(g.reset(),q=[0,0,0],k=0;k<w.length;k++)q[w[k].group]++;if(q[z]>0)p=B;else if(p=A,q[A]+q[B]>1)throw{type:"Runtime",message:"Ambiguous use of `default()` found when matching for `"+this.format(t)+"`",index:this.index,filename:this.currentFileInfo.filename};for(k=0;k<w.length;k++)if(o=w[k].group,o===z||o===p)try{d=w[k].mixin,d instanceof f||(r=d.originalRuleset||d,d=new f("",[],d.rules,null,(!1),null,r.visibilityInfo()),d.originalRuleset=r);var C=d.evalCall(a,t,this.important).rules;this._setVisibilityToReplacement(C),Array.prototype.push.apply(u,C)}catch(D){throw{message:D.message,index:this.index,filename:this.currentFileInfo.filename,
 stack:D.stack}}if(v)return u}throw n?{type:"Runtime",message:"No matching definition was found for `"+this.format(t)+"`",index:this.index,filename:this.currentFileInfo.filename}:{type:"Name",message:this.selector.toCSS().trim()+" is undefined",index:this.index,filename:this.currentFileInfo.filename}},h.prototype._setVisibilityToReplacement=function(a){var b,c;if(this.blocksVisibility())for(b=0;b<a.length;b++)c=a[b],c.addVisibilityBlock()},h.prototype.format=function(a){return this.selector.toCSS().trim()+"("+(a?a.map(function(a){var b="";return a.name&&(b+=a.name+":"),b+=a.value.toCSS?a.value.toCSS():"???"}).join(", "):"")+")"},b.exports=h},{"../functions/default":20,"./mixin-definition":68,"./node":70,"./selector":77}],68:[function(a,b,c){var d=a("./selector"),e=a("./element"),f=a("./ruleset"),g=a("./rule"),h=a("./expression"),i=a("../contexts"),j=function(a,b,c,f,g,h,i){this.name=a,this.selectors=[new d([new e(null,a,this.index,this.currentFileInfo)])],this.params=b,this.condition=f,this.variadic=g,this.arity=b.length,this.rules=c,this._lookups={};var j=[];this.required=b.reduce(function(a,b){return!b.name||b.name&&!b.value?a+1:(j.push(b.name),a)},0),this.optionalParameters=j,this.frames=h,this.copyVisibilityInfo(i),this.allowRoot=!0};j.prototype=new f,j.prototype.type="MixinDefinition",j.prototype.evalFirst=!0,j.prototype.accept=function(a){this.params&&this.params.length&&(this.params=a.visitArray(this.params)),this.rules=a.visitArray(this.rules),this.condition&&(this.condition=a.visit(this.condition))},j.prototype.evalParams=function(a,b,c,d){var e,j,k,l,m,n,o,p,q=new f(null,null),r=this.params.slice(0),s=0;if(b.frames&&b.frames[0]&&b.frames[0].functionRegistry&&(q.functionRegistry=b.frames[0].functionRegistry.inherit()),b=new i.Eval(b,[q].concat(b.frames)),c)for(c=c.slice(0),s=c.length,k=0;k<s;k++)if(j=c[k],n=j&&j.name){for(o=!1,l=0;l<r.length;l++)if(!d[l]&&n===r[l].name){d[l]=j.value.eval(a),q.prependRule(new g(n,j.value.eval(a))),o=!0;break}if(o){c.splice(k,1),k--;continue}throw{type:"Runtime",message:"Named argument for "+this.name+" "+c[k].name+" not found"}}for(p=0,k=0;k<r.length;k++)if(!d[k]){if(j=c&&c[p],n=r[k].name)if(r[k].variadic){for(e=[],l=p;l<s;l++)e.push(c[l].value.eval(a));q.prependRule(new g(n,new h(e).eval(a)))}else{if(m=j&&j.value)m=m.eval(a);else{if(!r[k].value)throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+s+" for "+this.arity+")"};m=r[k].value.eval(b),q.resetCache()}q.prependRule(new g(n,m)),d[k]=m}if(r[k].variadic&&c)for(l=p;l<s;l++)d[l]=c[l].value.eval(a);p++}return q},j.prototype.makeImportant=function(){var a=this.rules?this.rules.map(function(a){return a.makeImportant?a.makeImportant(!0):a}):this.rules,b=new j(this.name,this.params,a,this.condition,this.variadic,this.frames);return b},j.prototype.eval=function(a){return new j(this.name,this.params,this.rules,this.condition,this.variadic,this.frames||a.frames.slice(0))},j.prototype.evalCall=function(a,b,c){var d,e,j=[],k=this.frames?this.frames.concat(a.frames):a.frames,l=this.evalParams(a,new i.Eval(a,k),b,j);return l.prependRule(new g("@arguments",new h(j).eval(a))),d=this.rules.slice(0),e=new f(null,d),e.originalRuleset=this,e=e.eval(new i.Eval(a,[this,l].concat(k))),c&&(e=e.makeImportant()),e},j.prototype.matchCondition=function(a,b){return!(this.condition&&!this.condition.eval(new i.Eval(b,[this.evalParams(b,new i.Eval(b,this.frames?this.frames.concat(b.frames):b.frames),a,[])].concat(this.frames||[]).concat(b.frames))))},j.prototype.matchArgs=function(a,b){var c,d=a&&a.length||0,e=this.optionalParameters,f=a?a.reduce(function(a,b){return e.indexOf(b.name)<0?a+1:a},0):0;if(this.variadic){if(f<this.required-1)return!1}else{if(f<this.required)return!1;if(d>this.params.length)return!1}c=Math.min(f,this.arity);for(var g=0;g<c;g++)if(!this.params[g].name&&!this.params[g].variadic&&a[g].value.eval(b).toCSS()!=this.params[g].value.eval(b).toCSS())return!1;return!0},b.exports=j},{"../contexts":11,"./element":58,"./expression":59,"./rule":74,"./ruleset":76,"./selector":77}],69:[function(a,b,c){var d=a("./node"),e=a("./operation"),f=a("./dimension"),g=function(a){this.value=a};g.prototype=new d,g.prototype.type="Negative",g.prototype.genCSS=function(a,b){b.add("-"),this.value.genCSS(a,b)},g.prototype.eval=function(a){return a.isMathOn()?new e("*",[new f((-1)),this.value]).eval(a):new g(this.value.eval(a))},b.exports=g},{"./dimension":56,"./node":70,"./operation":71}],70:[function(a,b,c){var d=function(){};d.prototype.toCSS=function(a){var b=[];return this.genCSS(a,{add:function(a,c,d){b.push(a)},isEmpty:function(){return 0===b.length}}),b.join("")},d.prototype.genCSS=function(a,b){b.add(this.value)},d.prototype.accept=function(a){this.value=a.visit(this.value)},d.prototype.eval=function(){return this},d.prototype._operate=function(a,b,c,d){switch(b){case"+":return c+d;case"-":return c-d;case"*":return c*d;case"/":return c/d}},d.prototype.fround=function(a,b){var c=a&&a.numPrecision;return null==c?b:Number((b+2e-16).toFixed(c))},d.compare=function(a,b){if(a.compare&&"Quoted"!==b.type&&"Anonymous"!==b.type)return a.compare(b);if(b.compare)return-b.compare(a);if(a.type===b.type){if(a=a.value,b=b.value,!Array.isArray(a))return a===b?0:void 0;if(a.length===b.length){for(var c=0;c<a.length;c++)if(0!==d.compare(a[c],b[c]))return;return 0}}},d.numericCompare=function(a,b){return a<b?-1:a===b?0:a>b?1:void 0},d.prototype.blocksVisibility=function(){return null==this.visibilityBlocks&&(this.visibilityBlocks=0),0!==this.visibilityBlocks},d.prototype.addVisibilityBlock=function(){null==this.visibilityBlocks&&(this.visibilityBlocks=0),this.visibilityBlocks=this.visibilityBlocks+1},d.prototype.removeVisibilityBlock=function(){null==this.visibilityBlocks&&(this.visibilityBlocks=0),this.visibilityBlocks=this.visibilityBlocks-1},d.prototype.ensureVisibility=function(){this.nodeVisible=!0},d.prototype.ensureInvisibility=function(){this.nodeVisible=!1},d.prototype.isVisible=function(){return this.nodeVisible},d.prototype.visibilityInfo=function(){return{visibilityBlocks:this.visibilityBlocks,nodeVisible:this.nodeVisible}},d.prototype.copyVisibilityInfo=function(a){a&&(this.visibilityBlocks=a.visibilityBlocks,this.nodeVisible=a.nodeVisible)},b.exports=d},{}],71:[function(a,b,c){var d=a("./node"),e=a("./color"),f=a("./dimension"),g=function(a,b,c){this.op=a.trim(),this.operands=b,this.isSpaced=c};g.prototype=new d,g.prototype.type="Operation",g.prototype.accept=function(a){this.operands=a.visit(this.operands)},g.prototype.eval=function(a){var b=this.operands[0].eval(a),c=this.operands[1].eval(a);if(a.isMathOn()){if(b instanceof f&&c instanceof e&&(b=b.toColor()),c instanceof f&&b instanceof e&&(c=c.toColor()),!b.operate)throw{type:"Operation",message:"Operation on an invalid type"};return b.operate(a,this.op,c)}return new g(this.op,[b,c],this.isSpaced)},g.prototype.genCSS=function(a,b){this.operands[0].genCSS(a,b),this.isSpaced&&b.add(" "),b.add(this.op),this.isSpaced&&b.add(" "),this.operands[1].genCSS(a,b)},b.exports=g},{"./color":50,"./dimension":56,"./node":70}],72:[function(a,b,c){var d=a("./node"),e=function(a){this.value=a};e.prototype=new d,e.prototype.type="Paren",e.prototype.genCSS=function(a,b){b.add("("),this.value.genCSS(a,b),b.add(")")},e.prototype.eval=function(a){return new e(this.value.eval(a))},b.exports=e},{"./node":70}],73:[function(a,b,c){var d=a("./node"),e=a("./js-eval-node"),f=a("./variable"),g=function(a,b,c,d,e){this.escaped=null==c||c,this.value=b||"",this.quote=a.charAt(0),this.index=d,this.currentFileInfo=e};g.prototype=new e,g.prototype.type="Quoted",g.prototype.genCSS=function(a,b){this.escaped||b.add(this.quote,this.currentFileInfo,this.index),b.add(this.value),this.escaped||b.add(this.quote)},g.prototype.containsVariables=function(){return this.value.match(/(`([^`]+)`)|@\{([\w-]+)\}/)},g.prototype.eval=function(a){function b(a,b,c){var d=a;do a=d,d=a.replace(b,c);while(a!==d);return d}var c=this,d=this.value,e=function(b,d){return String(c.evaluateJavaScript(d,a))},h=function(b,d){var e=new f("@"+d,c.index,c.currentFileInfo).eval(a,!0);return e instanceof g?e.value:e.toCSS()};return d=b(d,/`([^`]+)`/g,e),d=b(d,/@\{([\w-]+)\}/g,h),new g(this.quote+d+this.quote,d,this.escaped,this.index,this.currentFileInfo)},g.prototype.compare=function(a){return"Quoted"!==a.type||this.escaped||a.escaped?a.toCSS&&this.toCSS()===a.toCSS()?0:void 0:d.numericCompare(this.value,a.value)},b.exports=g},{"./js-eval-node":64,"./node":70,"./variable":82}],74:[function(a,b,c){function d(a,b){var c,d="",e=b.length,f={add:function(a){d+=a}};for(c=0;c<e;c++)b[c].eval(a).genCSS(a,f);return d}var e=a("./node"),f=a("./value"),g=a("./keyword"),h=function(a,b,c,d,g,h,i,j){this.name=a,this.value=b instanceof e?b:new f([b]),this.important=c?" "+c.trim():"",this.merge=d,this.index=g,this.currentFileInfo=h,this.inline=i||!1,this.variable=void 0!==j?j:a.charAt&&"@"===a.charAt(0),this.allowRoot=!0};h.prototype=new e,h.prototype.type="Rule",h.prototype.genCSS=function(a,b){b.add(this.name+(a.compress?":":": "),this.currentFileInfo,this.index);try{this.value.genCSS(a,b)}catch(c){throw c.index=this.index,c.filename=this.currentFileInfo.filename,c}b.add(this.important+(this.inline||a.lastRule&&a.compress?"":";"),this.currentFileInfo,this.index)},h.prototype.eval=function(a){var b,c=!1,e=this.name,f=this.variable;"string"!=typeof e&&(e=1===e.length&&e[0]instanceof g?e[0].value:d(a,e),f=!1),"font"!==e||a.strictMath||(c=!0,a.strictMath=!0);try{if(a.importantScope.push({}),b=this.value.eval(a),!this.variable&&"DetachedRuleset"===b.type)throw{message:"Rulesets cannot be evaluated on a property.",index:this.index,filename:this.currentFileInfo.filename};var i=this.important,j=a.importantScope.pop();return!i&&j.important&&(i=j.important),new h(e,b,i,this.merge,this.index,this.currentFileInfo,this.inline,f)}catch(k){throw"number"!=typeof k.index&&(k.index=this.index,k.filename=this.currentFileInfo.filename),k}finally{c&&(a.strictMath=!1)}},h.prototype.makeImportant=function(){return new h(this.name,this.value,"!important",this.merge,this.index,this.currentFileInfo,this.inline)},b.exports=h},{"./keyword":65,"./node":70,"./value":81}],75:[function(a,b,c){var d=a("./node"),e=a("./variable"),f=function(a){this.variable=a,this.allowRoot=!0};f.prototype=new d,f.prototype.type="RulesetCall",f.prototype.eval=function(a){var b=new e(this.variable).eval(a);return b.callEval(a)},b.exports=f},{"./node":70,"./variable":82}],76:[function(a,b,c){var d=a("./node"),e=a("./rule"),f=a("./selector"),g=a("./element"),h=a("./paren"),i=a("../contexts"),j=a("../functions/function-registry"),k=a("../functions/default"),l=a("./debug-info"),m=function(a,b,c,d){this.selectors=a,this.rules=b,this._lookups={},this.strictImports=c,this.copyVisibilityInfo(d),this.allowRoot=!0};m.prototype=new d,m.prototype.type="Ruleset",m.prototype.isRuleset=!0,m.prototype.isRulesetLike=!0,m.prototype.accept=function(a){this.paths?this.paths=a.visitArray(this.paths,!0):this.selectors&&(this.selectors=a.visitArray(this.selectors)),this.rules&&this.rules.length&&(this.rules=a.visitArray(this.rules))},m.prototype.eval=function(a){var b,c,d,f,g=this.selectors,h=!1;if(g&&(c=g.length)){for(b=[],k.error({type:"Syntax",message:"it is currently only allowed in parametric mixin guards,"}),f=0;f<c;f++)d=g[f].eval(a),b.push(d),d.evaldCondition&&(h=!0);k.reset()}else h=!0;var i,l,n=this.rules?this.rules.slice(0):null,o=new m(b,n,this.strictImports,this.visibilityInfo());o.originalRuleset=this,o.root=this.root,o.firstRoot=this.firstRoot,o.allowImports=this.allowImports,this.debugInfo&&(o.debugInfo=this.debugInfo),h||(n.length=0),o.functionRegistry=function(a){for(var b,c=0,d=a.length;c!==d;++c)if(b=a[c].functionRegistry)return b;return j}(a.frames).inherit();var p=a.frames;p.unshift(o);var q=a.selectors;q||(a.selectors=q=[]),q.unshift(this.selectors),(o.root||o.allowImports||!o.strictImports)&&o.evalImports(a);var r=o.rules,s=r?r.length:0;for(f=0;f<s;f++)r[f].evalFirst&&(r[f]=r[f].eval(a));var t=a.mediaBlocks&&a.mediaBlocks.length||0;for(f=0;f<s;f++)"MixinCall"===r[f].type?(n=r[f].eval(a).filter(function(a){return!(a instanceof e&&a.variable)||!o.variable(a.name)}),r.splice.apply(r,[f,1].concat(n)),s+=n.length-1,f+=n.length-1,o.resetCache()):"RulesetCall"===r[f].type&&(n=r[f].eval(a).rules.filter(function(a){return!(a instanceof e&&a.variable)}),r.splice.apply(r,[f,1].concat(n)),s+=n.length-1,f+=n.length-1,o.resetCache());for(f=0;f<r.length;f++)i=r[f],i.evalFirst||(r[f]=i=i.eval?i.eval(a):i);for(f=0;f<r.length;f++)if(i=r[f],i instanceof m&&i.selectors&&1===i.selectors.length&&i.selectors[0].isJustParentSelector()){r.splice(f--,1);for(var u=0;u<i.rules.length;u++)l=i.rules[u],l.copyVisibilityInfo(i.visibilityInfo()),l instanceof e&&l.variable||r.splice(++f,0,l)}if(p.shift(),q.shift(),a.mediaBlocks)for(f=t;f<a.mediaBlocks.length;f++)a.mediaBlocks[f].bubbleSelectors(b);return o},m.prototype.evalImports=function(a){var b,c,d=this.rules;if(d)for(b=0;b<d.length;b++)"Import"===d[b].type&&(c=d[b].eval(a),c&&(c.length||0===c.length)?(d.splice.apply(d,[b,1].concat(c)),b+=c.length-1):d.splice(b,1,c),this.resetCache())},m.prototype.makeImportant=function(){var a=new m(this.selectors,this.rules.map(function(a){return a.makeImportant?a.makeImportant():a}),this.strictImports,this.visibilityInfo());return a},m.prototype.matchArgs=function(a){return!a||0===a.length},m.prototype.matchCondition=function(a,b){var c=this.selectors[this.selectors.length-1];return!!c.evaldCondition&&!(c.condition&&!c.condition.eval(new i.Eval(b,b.frames)))},m.prototype.resetCache=function(){this._rulesets=null,this._variables=null,this._lookups={}},m.prototype.variables=function(){return this._variables||(this._variables=this.rules?this.rules.reduce(function(a,b){if(b instanceof e&&b.variable===!0&&(a[b.name]=b),"Import"===b.type&&b.root&&b.root.variables){var c=b.root.variables();for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},{}):{}),this._variables},m.prototype.variable=function(a){return this.variables()[a]},m.prototype.rulesets=function(){if(!this.rules)return[];var a,b,c=[],d=this.rules,e=d.length;for(a=0;a<e;a++)b=d[a],b.isRuleset&&c.push(b);return c},m.prototype.prependRule=function(a){var b=this.rules;b?b.unshift(a):this.rules=[a]},m.prototype.find=function(a,b,c){b=b||this;var d,e,g=[],h=a.toCSS();return h in this._lookups?this._lookups[h]:(this.rulesets().forEach(function(h){if(h!==b)for(var i=0;i<h.selectors.length;i++)if(d=a.match(h.selectors[i])){if(a.elements.length>d){if(!c||c(h)){e=h.find(new f(a.elements.slice(d)),b,c);for(var j=0;j<e.length;++j)e[j].path.push(h);Array.prototype.push.apply(g,e)}}else g.push({rule:h,path:[]});break}}),this._lookups[h]=g,g)},m.prototype.genCSS=function(a,b){function c(a){return"boolean"==typeof a.isRulesetLike?a.isRulesetLike:"function"==typeof a.isRulesetLike&&a.isRulesetLike()}var d,e,f,g,h,i=[],j=[];a.tabLevel=a.tabLevel||0,this.root||a.tabLevel++;var k,m=a.compress?"":Array(a.tabLevel+1).join("  "),n=a.compress?"":Array(a.tabLevel).join("  "),o=0,p=0;for(d=0;d<this.rules.length;d++)g=this.rules[d],"Comment"===g.type?(p===d&&p++,j.push(g)):g.isCharset&&g.isCharset()?(j.splice(o,0,g),o++,p++):"Import"===g.type?(j.splice(p,0,g),p++):j.push(g);if(j=i.concat(j),!this.root){f=l(a,this,n),f&&(b.add(f),b.add(n));var q,r=this.paths,s=r.length;for(k=a.compress?",":",\n"+n,d=0;d<s;d++)if(h=r[d],q=h.length)for(d>0&&b.add(k),a.firstSelector=!0,h[0].genCSS(a,b),a.firstSelector=!1,e=1;e<q;e++)h[e].genCSS(a,b);b.add((a.compress?"{":" {\n")+m)}for(d=0;d<j.length;d++){g=j[d],d+1===j.length&&(a.lastRule=!0);var t=a.lastRule;c(g)&&(a.lastRule=!1),g.genCSS?g.genCSS(a,b):g.value&&b.add(g.value.toString()),a.lastRule=t,a.lastRule?a.lastRule=!1:b.add(a.compress?"":"\n"+m)}this.root||(b.add(a.compress?"}":"\n"+n+"}"),a.tabLevel--),b.isEmpty()||a.compress||!this.firstRoot||b.add("\n")},m.prototype.joinSelectors=function(a,b,c){for(var d=0;d<c.length;d++)this.joinSelector(a,b,c[d])},m.prototype.joinSelector=function(a,b,c){function d(a,b){var c,d;if(0===a.length)c=new h(a[0]);else{var e=[];for(d=0;d<a.length;d++)e.push(new g(null,a[d],b.index,b.currentFileInfo));c=new h(new f(e))}return c}function e(a,b){var c,d;return c=new g(null,a,b.index,b.currentFileInfo),d=new f([c])}function i(a,b,c,d){var e,f,h;if(e=[],a.length>0?(e=a.slice(0),f=e.pop(),h=d.createDerived(f.elements.slice(0))):h=d.createDerived([]),b.length>0){var i=c.combinator,j=b[0].elements[0];i.emptyOrWhitespace&&!j.combinator.emptyOrWhitespace&&(i=j.combinator),h.elements.push(new g(i,j.value,c.index,c.currentFileInfo)),h.elements=h.elements.concat(b[0].elements.slice(1))}if(0!==h.elements.length&&e.push(h),b.length>1){var k=b.slice(1);k=k.map(function(a){return a.createDerived(a.elements,[])}),e=e.concat(k)}return e}function j(a,b,c,d,e){var f;for(f=0;f<a.length;f++){var g=i(a[f],b,c,d);e.push(g)}return e}function k(a,b){var c,d;if(0!==a.length){if(0===b.length)return void b.push([new f(a)]);for(c=0;c<b.length;c++)d=b[c],d.length>0?d[d.length-1]=d[d.length-1].createDerived(d[d.length-1].elements.concat(a)):d.push(new f(a))}}function l(a,b,c){function f(a){var b;return"Paren"!==a.value.type?null:(b=a.value.value,"Selector"!==b.type?null:b)}var h,m,n,o,p,q,r,s,t,u,v=!1;for(o=[],p=[[]],h=0;h<c.elements.length;h++)if(s=c.elements[h],"&"!==s.value){var w=f(s);if(null!=w){k(o,p);var x,y=[],z=[];for(x=l(y,b,w),v=v||x,n=0;n<y.length;n++){var A=e(d(y[n],s),s);j(p,[A],s,c,z)}p=z,o=[]}else o.push(s)}else{for(v=!0,q=[],k(o,p),m=0;m<p.length;m++)if(r=p[m],0===b.length)r.length>0&&r[0].elements.push(new g(s.combinator,"",s.index,s.currentFileInfo)),q.push(r);else for(n=0;n<b.length;n++){var B=i(r,b[n],s,c);q.push(B)}p=q,o=[]}for(k(o,p),h=0;h<p.length;h++)t=p[h].length,t>0&&(a.push(p[h]),u=p[h][t-1],p[h][t-1]=u.createDerived(u.elements,c.extendList));return v}function m(a,b){var c=b.createDerived(b.elements,b.extendList,b.evaldCondition);return c.copyVisibilityInfo(a),c}var n,o,p;if(o=[],p=l(o,b,c),!p)if(b.length>0)for(o=[],n=0;n<b.length;n++){var q=b[n].map(m.bind(this,c.visibilityInfo()));q.push(c),o.push(q)}else o=[[c]];for(n=0;n<o.length;n++)a.push(o[n])},b.exports=m},{"../contexts":11,"../functions/default":20,"../functions/function-registry":22,"./debug-info":54,"./element":58,"./node":70,"./paren":72,"./rule":74,"./selector":77}],77:[function(a,b,c){var d=a("./node"),e=a("./element"),f=function(a,b,c,d,e,f){this.elements=a,this.extendList=b,this.condition=c,this.currentFileInfo=e||{},c||(this.evaldCondition=!0),this.copyVisibilityInfo(f)};f.prototype=new d,f.prototype.type="Selector",f.prototype.accept=function(a){this.elements&&(this.elements=a.visitArray(this.elements)),this.extendList&&(this.extendList=a.visitArray(this.extendList)),this.condition&&(this.condition=a.visit(this.condition))},f.prototype.createDerived=function(a,b,c){var d=this.visibilityInfo();c=null!=c?c:this.evaldCondition;var e=new f(a,b||this.extendList,null,this.index,this.currentFileInfo,d);return e.evaldCondition=c,e.mediaEmpty=this.mediaEmpty,e},f.prototype.createEmptySelectors=function(){var a=new e("","&",this.index,this.currentFileInfo),b=[new f([a],null,null,this.index,this.currentFileInfo)];return b[0].mediaEmpty=!0,b},f.prototype.match=function(a){var b,c,d=this.elements,e=d.length;if(a.CacheElements(),b=a._elements.length,0===b||e<b)return 0;for(c=0;c<b;c++)if(d[c].value!==a._elements[c])return 0;return b},f.prototype.CacheElements=function(){if(!this._elements){var a=this.elements.map(function(a){return a.combinator.value+(a.value.value||a.value)}).join("").match(/[,&#\*\.\w-]([\w-]|(\\.))*/g);a?"&"===a[0]&&a.shift():a=[],this._elements=a}},f.prototype.isJustParentSelector=function(){return!this.mediaEmpty&&1===this.elements.length&&"&"===this.elements[0].value&&(" "===this.elements[0].combinator.value||""===this.elements[0].combinator.value)},f.prototype.eval=function(a){var b=this.condition&&this.condition.eval(a),c=this.elements,d=this.extendList;return c=c&&c.map(function(b){return b.eval(a)}),d=d&&d.map(function(b){return b.eval(a)}),this.createDerived(c,d,b)},f.prototype.genCSS=function(a,b){var c,d;if(a&&a.firstSelector||""!==this.elements[0].combinator.value||b.add(" ",this.currentFileInfo,this.index),!this._css)for(c=0;c<this.elements.length;c++)d=this.elements[c],d.genCSS(a,b)},f.prototype.getIsOutput=function(){return this.evaldCondition},b.exports=f},{"./element":58,"./node":70}],78:[function(a,b,c){var d=a("./node"),e=function(a){this.value=a};e.prototype=new d,e.prototype.type="UnicodeDescriptor",b.exports=e},{"./node":70}],79:[function(a,b,c){var d=a("./node"),e=a("../data/unit-conversions"),f=function(a,b,c){this.numerator=a?a.slice(0).sort():[],this.denominator=b?b.slice(0).sort():[],c?this.backupUnit=c:a&&a.length&&(this.backupUnit=a[0])};f.prototype=new d,f.prototype.type="Unit",f.prototype.clone=function(){return new f(this.numerator.slice(0),this.denominator.slice(0),this.backupUnit)},f.prototype.genCSS=function(a,b){var c=a&&a.strictUnits;1===this.numerator.length?b.add(this.numerator[0]):!c&&this.backupUnit?b.add(this.backupUnit):!c&&this.denominator.length&&b.add(this.denominator[0])},f.prototype.toString=function(){var a,b=this.numerator.join("*");for(a=0;a<this.denominator.length;a++)b+="/"+this.denominator[a];return b},f.prototype.compare=function(a){return this.is(a.toString())?0:void 0},f.prototype.is=function(a){return this.toString().toUpperCase()===a.toUpperCase()},f.prototype.isLength=function(){return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/))},f.prototype.isEmpty=function(){return 0===this.numerator.length&&0===this.denominator.length},f.prototype.isSingular=function(){return this.numerator.length<=1&&0===this.denominator.length},f.prototype.map=function(a){var b;for(b=0;b<this.numerator.length;b++)this.numerator[b]=a(this.numerator[b],!1);for(b=0;b<this.denominator.length;b++)this.denominator[b]=a(this.denominator[b],!0)},f.prototype.usedUnits=function(){var a,b,c,d={};b=function(b){return a.hasOwnProperty(b)&&!d[c]&&(d[c]=b),b};for(c in e)e.hasOwnProperty(c)&&(a=e[c],this.map(b));return d},f.prototype.cancel=function(){var a,b,c={};for(b=0;b<this.numerator.length;b++)a=this.numerator[b],c[a]=(c[a]||0)+1;for(b=0;b<this.denominator.length;b++)a=this.denominator[b],c[a]=(c[a]||0)-1;this.numerator=[],this.denominator=[];for(a in c)if(c.hasOwnProperty(a)){var d=c[a];if(d>0)for(b=0;b<d;b++)this.numerator.push(a);else if(d<0)for(b=0;b<-d;b++)this.denominator.push(a)}this.numerator.sort(),this.denominator.sort()},b.exports=f},{"../data/unit-conversions":14,"./node":70}],80:[function(a,b,c){var d=a("./node"),e=function(a,b,c,d){this.value=a,this.currentFileInfo=c,this.index=b,this.isEvald=d};e.prototype=new d,e.prototype.type="Url",e.prototype.accept=function(a){this.value=a.visit(this.value)},e.prototype.genCSS=function(a,b){b.add("url("),this.value.genCSS(a,b),b.add(")")},e.prototype.eval=function(a){var b,c=this.value.eval(a);if(!this.isEvald&&(b=this.currentFileInfo&&this.currentFileInfo.rootpath,b&&"string"==typeof c.value&&a.isPathRelative(c.value)&&(c.quote||(b=b.replace(/[\(\)'"\s]/g,function(a){return"\\"+a})),c.value=b+c.value),c.value=a.normalizePath(c.value),a.urlArgs&&!c.value.match(/^\s*data:/))){var d=c.value.indexOf("?")===-1?"?":"&",f=d+a.urlArgs;c.value.indexOf("#")!==-1?c.value=c.value.replace("#",f+"#"):c.value+=f}return new e(c,this.index,this.currentFileInfo,(!0))},b.exports=e},{"./node":70}],81:[function(a,b,c){var d=a("./node"),e=function(a){if(this.value=a,!a)throw new Error("Value requires an array argument")};e.prototype=new d,e.prototype.type="Value",e.prototype.accept=function(a){this.value&&(this.value=a.visitArray(this.value))},e.prototype.eval=function(a){return 1===this.value.length?this.value[0].eval(a):new e(this.value.map(function(b){return b.eval(a)}))},e.prototype.genCSS=function(a,b){var c;for(c=0;c<this.value.length;c++)this.value[c].genCSS(a,b),c+1<this.value.length&&b.add(a&&a.compress?",":", ")},b.exports=e},{"./node":70}],82:[function(a,b,c){var d=a("./node"),e=function(a,b,c){this.name=a,this.index=b,this.currentFileInfo=c||{}};e.prototype=new d,e.prototype.type="Variable",e.prototype.eval=function(a){var b,c=this.name;if(0===c.indexOf("@@")&&(c="@"+new e(c.slice(1),this.index,this.currentFileInfo).eval(a).value),this.evaluating)throw{type:"Name",message:"Recursive variable definition for "+c,filename:this.currentFileInfo.filename,index:this.index};if(this.evaluating=!0,b=this.find(a.frames,function(b){var d=b.variable(c);if(d){if(d.important){var e=a.importantScope[a.importantScope.length-1];e.important=d.important}return d.value.eval(a)}}))return this.evaluating=!1,b;throw{type:"Name",message:"variable "+c+" is undefined",filename:this.currentFileInfo.filename,index:this.index}},e.prototype.find=function(a,b){for(var c,d=0;d<a.length;d++)if(c=b.call(a,a[d]))return c;return null},b.exports=e},{"./node":70}],83:[function(a,b,c){b.exports={getLocation:function(a,b){for(var c=a+1,d=null,e=-1;--c>=0&&"\n"!==b.charAt(c);)e++;return"number"==typeof a&&(d=(b.slice(0,a).match(/\n/g)||"").length),{line:d,column:e}}}},{}],84:[function(a,b,c){var d=a("../tree"),e=a("./visitor"),f=a("../logger"),g=function(){this._visitor=new e(this),this.contexts=[],this.allExtendsStack=[[]]};g.prototype={run:function(a){return a=this._visitor.visit(a),a.allExtends=this.allExtendsStack[0],a},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(a,b){if(!a.root){var c,e,f,g,h=[],i=a.rules,j=i?i.length:0;for(c=0;c<j;c++)a.rules[c]instanceof d.Extend&&(h.push(i[c]),a.extendOnEveryPath=!0);var k=a.paths;for(c=0;c<k.length;c++){var l=k[c],m=l[l.length-1],n=m.extendList;for(g=n?n.slice(0).concat(h):h,g&&(g=g.map(function(a){return a.clone()})),e=0;e<g.length;e++)this.foundExtends=!0,f=g[e],f.findSelfSelectors(l),f.ruleset=a,0===e&&(f.firstExtendOnThisSelectorPath=!0),this.allExtendsStack[this.allExtendsStack.length-1].push(f)}this.contexts.push(a.selectors)}},visitRulesetOut:function(a){a.root||(this.contexts.length=this.contexts.length-1)},visitMedia:function(a,b){a.allExtends=[],this.allExtendsStack.push(a.allExtends)},visitMediaOut:function(a){this.allExtendsStack.length=this.allExtendsStack.length-1},visitDirective:function(a,b){a.allExtends=[],this.allExtendsStack.push(a.allExtends)},visitDirectiveOut:function(a){this.allExtendsStack.length=this.allExtendsStack.length-1}};var h=function(){this._visitor=new e(this)};h.prototype={run:function(a){var b=new g;if(this.extendIndices={},b.run(a),!b.foundExtends)return a;a.allExtends=a.allExtends.concat(this.doExtendChaining(a.allExtends,a.allExtends)),this.allExtendsStack=[a.allExtends];var c=this._visitor.visit(a);return this.checkExtendsForNonMatched(a.allExtends),c},checkExtendsForNonMatched:function(a){var b=this.extendIndices;a.filter(function(a){return!a.hasFoundMatches&&1==a.parent_ids.length}).forEach(function(a){var c="_unknown_";try{c=a.selector.toCSS({})}catch(d){}b[a.index+" "+c]||(b[a.index+" "+c]=!0,f.warn("extend '"+c+"' has no matches"))})},doExtendChaining:function(a,b,c){var e,f,g,h,i,j,k,l,m=[],n=this;for(c=c||0,e=0;e<a.length;e++)for(f=0;f<b.length;f++)j=a[e],k=b[f],j.parent_ids.indexOf(k.object_id)>=0||(i=[k.selfSelectors[0]],g=n.findMatch(j,i),g.length&&(j.hasFoundMatches=!0,j.selfSelectors.forEach(function(a){var b=k.visibilityInfo();h=n.extendSelector(g,i,a,j.isVisible()),l=new d.Extend(k.selector,k.option,0,k.currentFileInfo,b),l.selfSelectors=h,h[h.length-1].extendList=[l],m.push(l),l.ruleset=k.ruleset,l.parent_ids=l.parent_ids.concat(k.parent_ids,j.parent_ids),k.firstExtendOnThisSelectorPath&&(l.firstExtendOnThisSelectorPath=!0,k.ruleset.paths.push(h))})));if(m.length){if(this.extendChainCount++,c>100){var o="{unable to calculate}",p="{unable to calculate}";try{o=m[0].selfSelectors[0].toCSS(),p=m[0].selector.toCSS()}catch(q){}throw{message:"extend circular reference detected. One of the circular extends is currently:"+o+":extend("+p+")"}}return m.concat(n.doExtendChaining(m,b,c+1))}return m},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitSelector:function(a,b){b.visitDeeper=!1},visitRuleset:function(a,b){if(!a.root){var c,d,e,f,g=this.allExtendsStack[this.allExtendsStack.length-1],h=[],i=this;for(e=0;e<g.length;e++)for(d=0;d<a.paths.length;d++)if(f=a.paths[d],!a.extendOnEveryPath){var j=f[f.length-1].extendList;j&&j.length||(c=this.findMatch(g[e],f),c.length&&(g[e].hasFoundMatches=!0,g[e].selfSelectors.forEach(function(a){var b;b=i.extendSelector(c,f,a,g[e].isVisible()),h.push(b)})))}a.paths=a.paths.concat(h)}},findMatch:function(a,b){var c,d,e,f,g,h,i,j=this,k=a.selector.elements,l=[],m=[];for(c=0;c<b.length;c++)for(d=b[c],e=0;e<d.elements.length;e++)for(f=d.elements[e],(a.allowBefore||0===c&&0===e)&&l.push({pathIndex:c,index:e,matched:0,initialCombinator:f.combinator}),h=0;h<l.length;h++)i=l[h],g=f.combinator.value,""===g&&0===e&&(g=" "),!j.isElementValuesEqual(k[i.matched].value,f.value)||i.matched>0&&k[i.matched].combinator.value!==g?i=null:i.matched++,i&&(i.finished=i.matched===k.length,i.finished&&!a.allowAfter&&(e+1<d.elements.length||c+1<b.length)&&(i=null)),i?i.finished&&(i.length=k.length,i.endPathIndex=c,i.endPathElementIndex=e+1,l.length=0,m.push(i)):(l.splice(h,1),h--);return m},isElementValuesEqual:function(a,b){if("string"==typeof a||"string"==typeof b)return a===b;if(a instanceof d.Attribute)return a.op===b.op&&a.key===b.key&&(a.value&&b.value?(a=a.value.value||a.value,b=b.value.value||b.value,a===b):!a.value&&!b.value);if(a=a.value,b=b.value,a instanceof d.Selector){if(!(b instanceof d.Selector)||a.elements.length!==b.elements.length)return!1;for(var c=0;c<a.elements.length;c++){if(a.elements[c].combinator.value!==b.elements[c].combinator.value&&(0!==c||(a.elements[c].combinator.value||" ")!==(b.elements[c].combinator.value||" ")))return!1;if(!this.isElementValuesEqual(a.elements[c].value,b.elements[c].value))return!1}return!0}return!1},extendSelector:function(a,b,c,e){var f,g,h,i,j,k=0,l=0,m=[];for(f=0;f<a.length;f++)i=a[f],g=b[i.pathIndex],h=new d.Element(i.initialCombinator,c.elements[0].value,c.elements[0].index,c.elements[0].currentFileInfo),i.pathIndex>k&&l>0&&(m[m.length-1].elements=m[m.length-1].elements.concat(b[k].elements.slice(l)),l=0,k++),j=g.elements.slice(l,i.index).concat([h]).concat(c.elements.slice(1)),k===i.pathIndex&&f>0?m[m.length-1].elements=m[m.length-1].elements.concat(j):(m=m.concat(b.slice(k,i.pathIndex)),m.push(new d.Selector(j))),k=i.endPathIndex,l=i.endPathElementIndex,l>=b[k].elements.length&&(l=0,k++);return k<b.length&&l>0&&(m[m.length-1].elements=m[m.length-1].elements.concat(b[k].elements.slice(l)),k++),m=m.concat(b.slice(k,b.length)),m=m.map(function(a){var b=a.createDerived(a.elements);return e?b.ensureVisibility():b.ensureInvisibility(),b})},visitMedia:function(a,b){var c=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);c=c.concat(this.doExtendChaining(c,a.allExtends)),this.allExtendsStack.push(c)},visitMediaOut:function(a){var b=this.allExtendsStack.length-1;this.allExtendsStack.length=b},visitDirective:function(a,b){var c=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);c=c.concat(this.doExtendChaining(c,a.allExtends)),this.allExtendsStack.push(c)},visitDirectiveOut:function(a){var b=this.allExtendsStack.length-1;this.allExtendsStack.length=b}},b.exports=h},{"../logger":33,"../tree":62,"./visitor":91}],85:[function(a,b,c){function d(a){this.imports=[],this.variableImports=[],this._onSequencerEmpty=a,this._currentDepth=0}d.prototype.addImport=function(a){var b=this,c={callback:a,args:null,isReady:!1};return this.imports.push(c),function(){c.args=Array.prototype.slice.call(arguments,0),c.isReady=!0,b.tryRun()}},d.prototype.addVariableImport=function(a){this.variableImports.push(a)},d.prototype.tryRun=function(){this._currentDepth++;try{for(;;){for(;this.imports.length>0;){var a=this.imports[0];if(!a.isReady)return;
-this.imports=this.imports.slice(1),a.callback.apply(null,a.args)}if(0===this.variableImports.length)break;var b=this.variableImports[0];this.variableImports=this.variableImports.slice(1),b()}}finally{this._currentDepth--}0===this._currentDepth&&this._onSequencerEmpty&&this._onSequencerEmpty()},b.exports=d},{}],86:[function(a,b,c){var d=a("../contexts"),e=a("./visitor"),f=a("./import-sequencer"),g=function(a,b){this._visitor=new e(this),this._importer=a,this._finish=b,this.context=new d.Eval,this.importCount=0,this.onceFileDetectionMap={},this.recursionDetector={},this._sequencer=new f(this._onSequencerEmpty.bind(this))};g.prototype={isReplacing:!1,run:function(a){try{this._visitor.visit(a)}catch(b){this.error=b}this.isFinished=!0,this._sequencer.tryRun()},_onSequencerEmpty:function(){this.isFinished&&this._finish(this.error)},visitImport:function(a,b){var c=a.options.inline;if(!a.css||c){var e=new d.Eval(this.context,this.context.frames.slice(0)),f=e.frames[0];this.importCount++,a.isVariableImport()?this._sequencer.addVariableImport(this.processImportNode.bind(this,a,e,f)):this.processImportNode(a,e,f)}b.visitDeeper=!1},processImportNode:function(a,b,c){var d,e=a.options.inline;try{d=a.evalForImport(b)}catch(f){f.filename||(f.index=a.index,f.filename=a.currentFileInfo.filename),a.css=!0,a.error=f}if(!d||d.css&&!e)this.importCount--,this.isFinished&&this._sequencer.tryRun();else{d.options.multiple&&(b.importMultiple=!0);for(var g=void 0===d.css,h=0;h<c.rules.length;h++)if(c.rules[h]===a){c.rules[h]=d;break}var i=this.onImported.bind(this,d,b),j=this._sequencer.addImport(i);this._importer.push(d.getPath(),g,d.currentFileInfo,d.options,j)}},onImported:function(a,b,c,d,e,f){c&&(c.filename||(c.index=a.index,c.filename=a.currentFileInfo.filename),this.error=c);var g=this,h=a.options.inline,i=a.options.plugin,j=a.options.optional,k=e||f in g.recursionDetector;if(b.importMultiple||(a.skip=!!k||function(){return f in g.onceFileDetectionMap||(g.onceFileDetectionMap[f]=!0,!1)}),!f&&j&&(a.skip=!0),d&&(a.root=d,a.importedFilename=f,!h&&!i&&(b.importMultiple||!k))){g.recursionDetector[f]=!0;var l=this.context;this.context=b;try{this._visitor.visit(d)}catch(c){this.error=c}this.context=l}g.importCount--,g.isFinished&&g._sequencer.tryRun()},visitRule:function(a,b){"DetachedRuleset"===a.value.type?this.context.frames.unshift(a):b.visitDeeper=!1},visitRuleOut:function(a){"DetachedRuleset"===a.value.type&&this.context.frames.shift()},visitDirective:function(a,b){this.context.frames.unshift(a)},visitDirectiveOut:function(a){this.context.frames.shift()},visitMixinDefinition:function(a,b){this.context.frames.unshift(a)},visitMixinDefinitionOut:function(a){this.context.frames.shift()},visitRuleset:function(a,b){this.context.frames.unshift(a)},visitRulesetOut:function(a){this.context.frames.shift()},visitMedia:function(a,b){this.context.frames.unshift(a.rules[0])},visitMediaOut:function(a){this.context.frames.shift()}},b.exports=g},{"../contexts":11,"./import-sequencer":85,"./visitor":91}],87:[function(a,b,c){var d={Visitor:a("./visitor"),ImportVisitor:a("./import-visitor"),MarkVisibleSelectorsVisitor:a("./set-tree-visibility-visitor"),ExtendVisitor:a("./extend-visitor"),JoinSelectorVisitor:a("./join-selector-visitor"),ToCSSVisitor:a("./to-css-visitor")};b.exports=d},{"./extend-visitor":84,"./import-visitor":86,"./join-selector-visitor":88,"./set-tree-visibility-visitor":89,"./to-css-visitor":90,"./visitor":91}],88:[function(a,b,c){var d=a("./visitor"),e=function(){this.contexts=[[]],this._visitor=new d(this)};e.prototype={run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(a,b){var c,d=this.contexts[this.contexts.length-1],e=[];this.contexts.push(e),a.root||(c=a.selectors,c&&(c=c.filter(function(a){return a.getIsOutput()}),a.selectors=c.length?c:c=null,c&&a.joinSelectors(e,d,c)),c||(a.rules=null),a.paths=e)},visitRulesetOut:function(a){this.contexts.length=this.contexts.length-1},visitMedia:function(a,b){var c=this.contexts[this.contexts.length-1];a.rules[0].root=0===c.length||c[0].multiMedia},visitDirective:function(a,b){var c=this.contexts[this.contexts.length-1];a.rules&&a.rules.length&&(a.rules[0].root=a.isRooted||0===c.length||null)}},b.exports=e},{"./visitor":91}],89:[function(a,b,c){var d=function(a){this.visible=a};d.prototype.run=function(a){this.visit(a)},d.prototype.visitArray=function(a){if(!a)return a;var b,c=a.length;for(b=0;b<c;b++)this.visit(a[b]);return a},d.prototype.visit=function(a){return a?a.constructor===Array?this.visitArray(a):!a.blocksVisibility||a.blocksVisibility()?a:(this.visible?a.ensureVisibility():a.ensureInvisibility(),a.accept(this),a):a},b.exports=d},{}],90:[function(a,b,c){var d=a("../tree"),e=a("./visitor"),f=function(a){this._visitor=new e(this),this._context=a};f.prototype={containsSilentNonBlockedChild:function(a){var b;if(null==a)return!1;for(var c=0;c<a.length;c++)if(b=a[c],b.isSilent&&b.isSilent(this._context)&&!b.blocksVisibility())return!0;return!1},keepOnlyVisibleChilds:function(a){null!=a&&null!=a.rules&&(a.rules=a.rules.filter(function(a){return a.isVisible()}))},isEmpty:function(a){return null==a||null==a.rules||0===a.rules.length},hasVisibleSelector:function(a){return null!=a&&null!=a.paths&&a.paths.length>0},resolveVisibility:function(a,b){if(!a.blocksVisibility()){if(this.isEmpty(a)&&!this.containsSilentNonBlockedChild(b))return;return a}var c=a.rules[0];if(this.keepOnlyVisibleChilds(c),!this.isEmpty(c))return a.ensureVisibility(),a.removeVisibilityBlock(),a},isVisibleRuleset:function(a){return!!a.firstRoot||!this.isEmpty(a)&&!(!a.root&&!this.hasVisibleSelector(a))}};var g=function(a){this._visitor=new e(this),this._context=a,this.utils=new f(a)};g.prototype={isReplacing:!0,run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){if(!a.blocksVisibility()&&!a.variable)return a},visitMixinDefinition:function(a,b){a.frames=[]},visitExtend:function(a,b){},visitComment:function(a,b){if(!a.blocksVisibility()&&!a.isSilent(this._context))return a},visitMedia:function(a,b){var c=a.rules[0].rules;return a.accept(this._visitor),b.visitDeeper=!1,this.utils.resolveVisibility(a,c)},visitImport:function(a,b){if(!a.blocksVisibility())return a},visitDirective:function(a,b){return a.rules&&a.rules.length?this.visitDirectiveWithBody(a,b):this.visitDirectiveWithoutBody(a,b)},visitDirectiveWithBody:function(a,b){function c(a){var b=a.rules;return 1===b.length&&(!b[0].paths||0===b[0].paths.length)}function d(a){var b=a.rules;return c(a)?b[0].rules:b}var e=d(a);return a.accept(this._visitor),b.visitDeeper=!1,this.utils.isEmpty(a)||this._mergeRules(a.rules[0].rules),this.utils.resolveVisibility(a,e)},visitDirectiveWithoutBody:function(a,b){if(!a.blocksVisibility()){if("@charset"===a.name){if(this.charset){if(a.debugInfo){var c=new d.Comment("/* "+a.toCSS(this._context).replace(/\n/g,"")+" */\n");return c.debugInfo=a.debugInfo,this._visitor.visit(c)}return}this.charset=!0}return a}},checkValidNodes:function(a,b){if(a)for(var c=0;c<a.length;c++){var e=a[c];if(b&&e instanceof d.Rule&&!e.variable)throw{message:"Properties must be inside selector blocks. They cannot be in the root",index:e.index,filename:e.currentFileInfo&&e.currentFileInfo.filename};if(e instanceof d.Call)throw{message:"Function '"+e.name+"' is undefined",index:e.index,filename:e.currentFileInfo&&e.currentFileInfo.filename};if(e.type&&!e.allowRoot)throw{message:e.type+" node returned by a function is not valid here",index:e.index,filename:e.currentFileInfo&&e.currentFileInfo.filename}}},visitRuleset:function(a,b){var c,d=[];if(this.checkValidNodes(a.rules,a.firstRoot),a.root)a.accept(this._visitor),b.visitDeeper=!1;else{this._compileRulesetPaths(a);for(var e=a.rules,f=e?e.length:0,g=0;g<f;)c=e[g],c&&c.rules?(d.push(this._visitor.visit(c)),e.splice(g,1),f--):g++;f>0?a.accept(this._visitor):a.rules=null,b.visitDeeper=!1}return a.rules&&(this._mergeRules(a.rules),this._removeDuplicateRules(a.rules)),this.utils.isVisibleRuleset(a)&&(a.ensureVisibility(),d.splice(0,0,a)),1===d.length?d[0]:d},_compileRulesetPaths:function(a){a.paths&&(a.paths=a.paths.filter(function(a){var b;for(" "===a[0].elements[0].combinator.value&&(a[0].elements[0].combinator=new d.Combinator("")),b=0;b<a.length;b++)if(a[b].isVisible()&&a[b].getIsOutput())return!0;return!1}))},_removeDuplicateRules:function(a){if(a){var b,c,e,f={};for(e=a.length-1;e>=0;e--)if(c=a[e],c instanceof d.Rule)if(f[c.name]){b=f[c.name],b instanceof d.Rule&&(b=f[c.name]=[f[c.name].toCSS(this._context)]);var g=c.toCSS(this._context);b.indexOf(g)!==-1?a.splice(e,1):b.push(g)}else f[c.name]=c}},_mergeRules:function(a){if(a){for(var b,c,e,f={},g=0;g<a.length;g++)c=a[g],c instanceof d.Rule&&c.merge&&(e=[c.name,c.important?"!":""].join(","),f[e]?a.splice(g--,1):f[e]=[],f[e].push(c));Object.keys(f).map(function(a){function e(a){return new d.Expression(a.map(function(a){return a.value}))}function g(a){return new d.Value(a.map(function(a){return a}))}if(b=f[a],b.length>1){c=b[0];var h=[],i=[];b.map(function(a){"+"===a.merge&&(i.length>0&&h.push(e(i)),i=[]),i.push(a)}),h.push(e(i)),c.value=g(h)}})}},visitAnonymous:function(a,b){if(!a.blocksVisibility())return a.accept(this._visitor),a}},b.exports=g},{"../tree":62,"./visitor":91}],91:[function(a,b,c){function d(a){return a}function e(a,b){var c,d;for(c in a)if(a.hasOwnProperty(c))switch(d=a[c],typeof d){case"function":d.prototype&&d.prototype.type&&(d.prototype.typeIndex=b++);break;case"object":b=e(d,b)}return b}var f=a("../tree"),g={visitDeeper:!0},h=!1,i=function(a){this._implementation=a,this._visitFnCache=[],h||(e(f,1),h=!0)};i.prototype={visit:function(a){if(!a)return a;var b=a.typeIndex;if(!b)return a;var c,e=this._visitFnCache,f=this._implementation,h=b<<1,i=1|h,j=e[h],k=e[i],l=g;if(l.visitDeeper=!0,j||(c="visit"+a.type,j=f[c]||d,k=f[c+"Out"]||d,e[h]=j,e[i]=k),j!==d){var m=j.call(f,a,l);f.isReplacing&&(a=m)}return l.visitDeeper&&a&&a.accept&&a.accept(this),k!=d&&k.call(f,a),a},visitArray:function(a,b){if(!a)return a;var c,d=a.length;if(b||!this._implementation.isReplacing){for(c=0;c<d;c++)this.visit(a[c]);return a}var e=[];for(c=0;c<d;c++){var f=this.visit(a[c]);void 0!==f&&(f.splice?f.length&&this.flatten(f,e):e.push(f))}return e},flatten:function(a,b){b||(b=[]);var c,d,e,f,g,h;for(d=0,c=a.length;d<c;d++)if(e=a[d],void 0!==e)if(e.splice)for(g=0,f=e.length;g<f;g++)h=e[g],void 0!==h&&(h.splice?h.length&&this.flatten(h,b):b.push(h));else b.push(e);return b}},b.exports=i},{"../tree":62}],92:[function(a,b,c){"use strict";function d(){if(i.length)throw i.shift()}function e(a){var b;b=h.length?h.pop():new f,b.task=a,g(b)}function f(){this.task=null}var g=a("./raw"),h=[],i=[],j=g.makeRequestCallFromTimer(d);b.exports=e,f.prototype.call=function(){try{this.task.call()}catch(a){e.onerror?e.onerror(a):(i.push(a),j())}finally{this.task=null,h[h.length]=this}}},{"./raw":93}],93:[function(a,b,c){(function(a){"use strict";function c(a){h.length||(g(),i=!0),h[h.length]=a}function d(){for(;j<h.length;){var a=j;if(j+=1,h[a].call(),j>k){for(var b=0,c=h.length-j;b<c;b++)h[b]=h[b+j];h.length-=j,j=0}}h.length=0,j=0,i=!1}function e(a){var b=1,c=new l(a),d=document.createTextNode("");return c.observe(d,{characterData:!0}),function(){b=-b,d.data=b}}function f(a){return function(){function b(){clearTimeout(c),clearInterval(d),a()}var c=setTimeout(b,0),d=setInterval(b,50)}}b.exports=c;var g,h=[],i=!1,j=0,k=1024,l=a.MutationObserver||a.WebKitMutationObserver;g="function"==typeof l?e(d):f(d),c.requestFlush=g,c.makeRequestCallFromTimer=f}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],94:[function(a,b,c){"use strict";function d(){}function e(a){try{return a.then}catch(b){return r=b,s}}function f(a,b){try{return a(b)}catch(c){return r=c,s}}function g(a,b,c){try{a(b,c)}catch(d){return r=d,s}}function h(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._45=0,this._81=0,this._65=null,this._54=null,a!==d&&p(a,this)}function i(a,b,c){return new a.constructor(function(e,f){var g=new h(d);g.then(e,f),j(a,new o(b,c,g))})}function j(a,b){for(;3===a._81;)a=a._65;return h._10&&h._10(a),0===a._81?0===a._45?(a._45=1,void(a._54=b)):1===a._45?(a._45=2,void(a._54=[a._54,b])):void a._54.push(b):void k(a,b)}function k(a,b){q(function(){var c=1===a._81?b.onFulfilled:b.onRejected;if(null===c)return void(1===a._81?l(b.promise,a._65):m(b.promise,a._65));var d=f(c,a._65);d===s?m(b.promise,r):l(b.promise,d)})}function l(a,b){if(b===a)return m(a,new TypeError("A promise cannot be resolved with itself."));if(b&&("object"==typeof b||"function"==typeof b)){var c=e(b);if(c===s)return m(a,r);if(c===a.then&&b instanceof h)return a._81=3,a._65=b,void n(a);if("function"==typeof c)return void p(c.bind(b),a)}a._81=1,a._65=b,n(a)}function m(a,b){a._81=2,a._65=b,h._97&&h._97(a,b),n(a)}function n(a){if(1===a._45&&(j(a,a._54),a._54=null),2===a._45){for(var b=0;b<a._54.length;b++)j(a,a._54[b]);a._54=null}}function o(a,b,c){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.promise=c}function p(a,b){var c=!1,d=g(a,function(a){c||(c=!0,l(b,a))},function(a){c||(c=!0,m(b,a))});c||d!==s||(c=!0,m(b,r))}var q=a("asap/raw"),r=null,s={};b.exports=h,h._10=null,h._97=null,h._61=d,h.prototype.then=function(a,b){if(this.constructor!==h)return i(this,a,b);var c=new h(d);return j(this,new o(a,b,c)),c}},{"asap/raw":93}],95:[function(a,b,c){"use strict";function d(a){var b=new e(e._61);return b._81=1,b._65=a,b}var e=a("./core.js");b.exports=e;var f=d(!0),g=d(!1),h=d(null),i=d(void 0),j=d(0),k=d("");e.resolve=function(a){if(a instanceof e)return a;if(null===a)return h;if(void 0===a)return i;if(a===!0)return f;if(a===!1)return g;if(0===a)return j;if(""===a)return k;if("object"==typeof a||"function"==typeof a)try{var b=a.then;if("function"==typeof b)return new e(b.bind(a))}catch(c){return new e(function(a,b){b(c)})}return d(a)},e.all=function(a){var b=Array.prototype.slice.call(a);return new e(function(a,c){function d(g,h){if(h&&("object"==typeof h||"function"==typeof h)){if(h instanceof e&&h.then===e.prototype.then){for(;3===h._81;)h=h._65;return 1===h._81?d(g,h._65):(2===h._81&&c(h._65),void h.then(function(a){d(g,a)},c))}var i=h.then;if("function"==typeof i){var j=new e(i.bind(h));return void j.then(function(a){d(g,a)},c)}}b[g]=h,0===--f&&a(b)}if(0===b.length)return a([]);for(var f=b.length,g=0;g<b.length;g++)d(g,b[g])})},e.reject=function(a){return new e(function(b,c){c(a)})},e.race=function(a){return new e(function(b,c){a.forEach(function(a){e.resolve(a).then(b,c)})})},e.prototype["catch"]=function(a){return this.then(null,a)}},{"./core.js":94}],96:[function(a,b,c){"function"!=typeof Promise.prototype.done&&(Promise.prototype.done=function(a,b){var c=arguments.length?this.then.apply(this,arguments):this;c.then(null,function(a){setTimeout(function(){throw a},0)})})},{}],97:[function(a,b,c){a("asap");"undefined"==typeof Promise&&(Promise=a("./lib/core.js"),a("./lib/es6-extensions.js")),a("./polyfill-done.js")},{"./lib/core.js":94,"./lib/es6-extensions.js":95,"./polyfill-done.js":96,asap:92}]},{},[2])(2)});
\ No newline at end of file
+this.imports=this.imports.slice(1),a.callback.apply(null,a.args)}if(0===this.variableImports.length)break;var b=this.variableImports[0];this.variableImports=this.variableImports.slice(1),b()}}finally{this._currentDepth--}0===this._currentDepth&&this._onSequencerEmpty&&this._onSequencerEmpty()},b.exports=d},{}],86:[function(a,b,c){var d=a("../contexts"),e=a("./visitor"),f=a("./import-sequencer"),g=function(a,b){this._visitor=new e(this),this._importer=a,this._finish=b,this.context=new d.Eval,this.importCount=0,this.onceFileDetectionMap={},this.recursionDetector={},this._sequencer=new f(this._onSequencerEmpty.bind(this))};g.prototype={isReplacing:!1,run:function(a){try{this._visitor.visit(a)}catch(b){this.error=b}this.isFinished=!0,this._sequencer.tryRun()},_onSequencerEmpty:function(){this.isFinished&&this._finish(this.error)},visitImport:function(a,b){var c=a.options.inline;if(!a.css||c){var e=new d.Eval(this.context,this.context.frames.slice(0)),f=e.frames[0];this.importCount++,a.isVariableImport()?this._sequencer.addVariableImport(this.processImportNode.bind(this,a,e,f)):this.processImportNode(a,e,f)}b.visitDeeper=!1},processImportNode:function(a,b,c){var d,e=a.options.inline;try{d=a.evalForImport(b)}catch(f){f.filename||(f.index=a.index,f.filename=a.currentFileInfo.filename),a.css=!0,a.error=f}if(!d||d.css&&!e)this.importCount--,this.isFinished&&this._sequencer.tryRun();else{d.options.multiple&&(b.importMultiple=!0);for(var g=void 0===d.css,h=0;h<c.rules.length;h++)if(c.rules[h]===a){c.rules[h]=d;break}var i=this.onImported.bind(this,d,b),j=this._sequencer.addImport(i);this._importer.push(d.getPath(),g,d.currentFileInfo,d.options,j)}},onImported:function(a,b,c,d,e,f){c&&(c.filename||(c.index=a.index,c.filename=a.currentFileInfo.filename),this.error=c);var g=this,h=a.options.inline,i=a.options.plugin,j=a.options.optional,k=e||f in g.recursionDetector;if(b.importMultiple||(a.skip=!!k||function(){return f in g.onceFileDetectionMap||(g.onceFileDetectionMap[f]=!0,!1)}),!f&&j&&(a.skip=!0),d&&(a.root=d,a.importedFilename=f,!h&&!i&&(b.importMultiple||!k))){g.recursionDetector[f]=!0;var l=this.context;this.context=b;try{this._visitor.visit(d)}catch(c){this.error=c}this.context=l}g.importCount--,g.isFinished&&g._sequencer.tryRun()},visitRule:function(a,b){"DetachedRuleset"===a.value.type?this.context.frames.unshift(a):b.visitDeeper=!1},visitRuleOut:function(a){"DetachedRuleset"===a.value.type&&this.context.frames.shift()},visitDirective:function(a,b){this.context.frames.unshift(a)},visitDirectiveOut:function(a){this.context.frames.shift()},visitMixinDefinition:function(a,b){this.context.frames.unshift(a)},visitMixinDefinitionOut:function(a){this.context.frames.shift()},visitRuleset:function(a,b){this.context.frames.unshift(a)},visitRulesetOut:function(a){this.context.frames.shift()},visitMedia:function(a,b){this.context.frames.unshift(a.rules[0])},visitMediaOut:function(a){this.context.frames.shift()}},b.exports=g},{"../contexts":11,"./import-sequencer":85,"./visitor":91}],87:[function(a,b,c){var d={Visitor:a("./visitor"),ImportVisitor:a("./import-visitor"),MarkVisibleSelectorsVisitor:a("./set-tree-visibility-visitor"),ExtendVisitor:a("./extend-visitor"),JoinSelectorVisitor:a("./join-selector-visitor"),ToCSSVisitor:a("./to-css-visitor")};b.exports=d},{"./extend-visitor":84,"./import-visitor":86,"./join-selector-visitor":88,"./set-tree-visibility-visitor":89,"./to-css-visitor":90,"./visitor":91}],88:[function(a,b,c){var d=a("./visitor"),e=function(){this.contexts=[[]],this._visitor=new d(this)};e.prototype={run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(a,b){var c,d=this.contexts[this.contexts.length-1],e=[];this.contexts.push(e),a.root||(c=a.selectors,c&&(c=c.filter(function(a){return a.getIsOutput()}),a.selectors=c.length?c:c=null,c&&a.joinSelectors(e,d,c)),c||(a.rules=null),a.paths=e)},visitRulesetOut:function(a){this.contexts.length=this.contexts.length-1},visitMedia:function(a,b){var c=this.contexts[this.contexts.length-1];a.rules[0].root=0===c.length||c[0].multiMedia},visitDirective:function(a,b){var c=this.contexts[this.contexts.length-1];a.rules&&a.rules.length&&(a.rules[0].root=a.isRooted||0===c.length||null)}},b.exports=e},{"./visitor":91}],89:[function(a,b,c){var d=function(a){this.visible=a};d.prototype.run=function(a){this.visit(a)},d.prototype.visitArray=function(a){if(!a)return a;var b,c=a.length;for(b=0;b<c;b++)this.visit(a[b]);return a},d.prototype.visit=function(a){return a?a.constructor===Array?this.visitArray(a):!a.blocksVisibility||a.blocksVisibility()?a:(this.visible?a.ensureVisibility():a.ensureInvisibility(),a.accept(this),a):a},b.exports=d},{}],90:[function(a,b,c){var d=a("../tree"),e=a("./visitor"),f=function(a){this._visitor=new e(this),this._context=a};f.prototype={containsSilentNonBlockedChild:function(a){var b;if(null==a)return!1;for(var c=0;c<a.length;c++)if(b=a[c],b.isSilent&&b.isSilent(this._context)&&!b.blocksVisibility())return!0;return!1},keepOnlyVisibleChilds:function(a){null!=a&&null!=a.rules&&(a.rules=a.rules.filter(function(a){return a.isVisible()}))},isEmpty:function(a){return null==a||null==a.rules||0===a.rules.length},hasVisibleSelector:function(a){return null!=a&&null!=a.paths&&a.paths.length>0},resolveVisibility:function(a,b){if(!a.blocksVisibility()){if(this.isEmpty(a)&&!this.containsSilentNonBlockedChild(b))return;return a}var c=a.rules[0];if(this.keepOnlyVisibleChilds(c),!this.isEmpty(c))return a.ensureVisibility(),a.removeVisibilityBlock(),a},isVisibleRuleset:function(a){return!!a.firstRoot||!this.isEmpty(a)&&!(!a.root&&!this.hasVisibleSelector(a))}};var g=function(a){this._visitor=new e(this),this._context=a,this.utils=new f(a)};g.prototype={isReplacing:!0,run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){if(!a.blocksVisibility()&&!a.variable)return a},visitMixinDefinition:function(a,b){a.frames=[]},visitExtend:function(a,b){},visitComment:function(a,b){if(!a.blocksVisibility()&&!a.isSilent(this._context))return a},visitMedia:function(a,b){var c=a.rules[0].rules;return a.accept(this._visitor),b.visitDeeper=!1,this.utils.resolveVisibility(a,c)},visitImport:function(a,b){if(!a.blocksVisibility())return a},visitDirective:function(a,b){return a.rules&&a.rules.length?this.visitDirectiveWithBody(a,b):this.visitDirectiveWithoutBody(a,b)},visitDirectiveWithBody:function(a,b){function c(a){var b=a.rules;return 1===b.length&&(!b[0].paths||0===b[0].paths.length)}function d(a){var b=a.rules;return c(a)?b[0].rules:b}var e=d(a);return a.accept(this._visitor),b.visitDeeper=!1,this.utils.isEmpty(a)||this._mergeRules(a.rules[0].rules),this.utils.resolveVisibility(a,e)},visitDirectiveWithoutBody:function(a,b){if(!a.blocksVisibility()){if("@charset"===a.name){if(this.charset){if(a.debugInfo){var c=new d.Comment("/* "+a.toCSS(this._context).replace(/\n/g,"")+" */\n");return c.debugInfo=a.debugInfo,this._visitor.visit(c)}return}this.charset=!0}return a}},checkValidNodes:function(a,b){if(a)for(var c=0;c<a.length;c++){var e=a[c];if(b&&e instanceof d.Rule&&!e.variable)throw{message:"Properties must be inside selector blocks. They cannot be in the root",index:e.index,filename:e.currentFileInfo&&e.currentFileInfo.filename};if(e instanceof d.Call)throw{message:"Function '"+e.name+"' is undefined",index:e.index,filename:e.currentFileInfo&&e.currentFileInfo.filename};if(e.type&&!e.allowRoot)throw{message:e.type+" node returned by a function is not valid here",index:e.index,filename:e.currentFileInfo&&e.currentFileInfo.filename}}},visitRuleset:function(a,b){var c,d=[];if(this.checkValidNodes(a.rules,a.firstRoot),a.root)a.accept(this._visitor),b.visitDeeper=!1;else{this._compileRulesetPaths(a);for(var e=a.rules,f=e?e.length:0,g=0;g<f;)c=e[g],c&&c.rules?(d.push(this._visitor.visit(c)),e.splice(g,1),f--):g++;f>0?a.accept(this._visitor):a.rules=null,b.visitDeeper=!1}return a.rules&&(this._mergeRules(a.rules),this._removeDuplicateRules(a.rules)),this.utils.isVisibleRuleset(a)&&(a.ensureVisibility(),d.splice(0,0,a)),1===d.length?d[0]:d},_compileRulesetPaths:function(a){a.paths&&(a.paths=a.paths.filter(function(a){var b;for(" "===a[0].elements[0].combinator.value&&(a[0].elements[0].combinator=new d.Combinator("")),b=0;b<a.length;b++)if(a[b].isVisible()&&a[b].getIsOutput())return!0;return!1}))},_removeDuplicateRules:function(a){if(a){var b,c,e,f={};for(e=a.length-1;e>=0;e--)if(c=a[e],c instanceof d.Rule)if(f[c.name]){b=f[c.name],b instanceof d.Rule&&(b=f[c.name]=[f[c.name].toCSS(this._context)]);var g=c.toCSS(this._context);b.indexOf(g)!==-1?a.splice(e,1):b.push(g)}else f[c.name]=c}},_mergeRules:function(a){if(a){for(var b,c,e,f={},g=0;g<a.length;g++)c=a[g],c instanceof d.Rule&&c.merge&&(e=[c.name,c.important?"!":""].join(","),f[e]?a.splice(g--,1):f[e]=[],f[e].push(c));Object.keys(f).map(function(a){function e(a){return new d.Expression(a.map(function(a){return a.value}))}function g(a){return new d.Value(a.map(function(a){return a}))}if(b=f[a],b.length>1){c=b[0];var h=[],i=[];b.map(function(a){"+"===a.merge&&(i.length>0&&h.push(e(i)),i=[]),i.push(a)}),h.push(e(i)),c.value=g(h)}})}},visitAnonymous:function(a,b){if(!a.blocksVisibility())return a.accept(this._visitor),a}},b.exports=g},{"../tree":62,"./visitor":91}],91:[function(a,b,c){function d(a){return a}function e(a,b){var c,d;for(c in a)if(a.hasOwnProperty(c))switch(d=a[c],typeof d){case"function":d.prototype&&d.prototype.type&&(d.prototype.typeIndex=b++);break;case"object":b=e(d,b)}return b}var f=a("../tree"),g={visitDeeper:!0},h=!1,i=function(a){this._implementation=a,this._visitFnCache=[],h||(e(f,1),h=!0)};i.prototype={visit:function(a){if(!a)return a;var b=a.typeIndex;if(!b)return a;var c,e=this._visitFnCache,f=this._implementation,h=b<<1,i=1|h,j=e[h],k=e[i],l=g;if(l.visitDeeper=!0,j||(c="visit"+a.type,j=f[c]||d,k=f[c+"Out"]||d,e[h]=j,e[i]=k),j!==d){var m=j.call(f,a,l);f.isReplacing&&(a=m)}return l.visitDeeper&&a&&a.accept&&a.accept(this),k!=d&&k.call(f,a),a},visitArray:function(a,b){if(!a)return a;var c,d=a.length;if(b||!this._implementation.isReplacing){for(c=0;c<d;c++)this.visit(a[c]);return a}var e=[];for(c=0;c<d;c++){var f=this.visit(a[c]);void 0!==f&&(f.splice?f.length&&this.flatten(f,e):e.push(f))}return e},flatten:function(a,b){b||(b=[]);var c,d,e,f,g,h;for(d=0,c=a.length;d<c;d++)if(e=a[d],void 0!==e)if(e.splice)for(g=0,f=e.length;g<f;g++)h=e[g],void 0!==h&&(h.splice?h.length&&this.flatten(h,b):b.push(h));else b.push(e);return b}},b.exports=i},{"../tree":62}],92:[function(a,b,c){"use strict";function d(){if(i.length)throw i.shift()}function e(a){var b;b=h.length?h.pop():new f,b.task=a,g(b)}function f(){this.task=null}var g=a("./raw"),h=[],i=[],j=g.makeRequestCallFromTimer(d);b.exports=e,f.prototype.call=function(){try{this.task.call()}catch(a){e.onerror?e.onerror(a):(i.push(a),j())}finally{this.task=null,h[h.length]=this}}},{"./raw":93}],93:[function(a,b,c){(function(a){"use strict";function c(a){h.length||(g(),i=!0),h[h.length]=a}function d(){for(;j<h.length;){var a=j;if(j+=1,h[a].call(),j>k){for(var b=0,c=h.length-j;b<c;b++)h[b]=h[b+j];h.length-=j,j=0}}h.length=0,j=0,i=!1}function e(a){var b=1,c=new l(a),d=document.createTextNode("");return c.observe(d,{characterData:!0}),function(){b=-b,d.data=b}}function f(a){return function(){function b(){clearTimeout(c),clearInterval(d),a()}var c=setTimeout(b,0),d=setInterval(b,50)}}b.exports=c;var g,h=[],i=!1,j=0,k=1024,l=a.MutationObserver||a.WebKitMutationObserver;g="function"==typeof l?e(d):f(d),c.requestFlush=g,c.makeRequestCallFromTimer=f}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],94:[function(a,b,c){"use strict";function d(){}function e(a){try{return a.then}catch(b){return r=b,s}}function f(a,b){try{return a(b)}catch(c){return r=c,s}}function g(a,b,c){try{a(b,c)}catch(d){return r=d,s}}function h(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._45=0,this._81=0,this._65=null,this._54=null,a!==d&&p(a,this)}function i(a,b,c){return new a.constructor(function(e,f){var g=new h(d);g.then(e,f),j(a,new o(b,c,g))})}function j(a,b){for(;3===a._81;)a=a._65;return h._10&&h._10(a),0===a._81?0===a._45?(a._45=1,void(a._54=b)):1===a._45?(a._45=2,void(a._54=[a._54,b])):void a._54.push(b):void k(a,b)}function k(a,b){q(function(){var c=1===a._81?b.onFulfilled:b.onRejected;if(null===c)return void(1===a._81?l(b.promise,a._65):m(b.promise,a._65));var d=f(c,a._65);d===s?m(b.promise,r):l(b.promise,d)})}function l(a,b){if(b===a)return m(a,new TypeError("A promise cannot be resolved with itself."));if(b&&("object"==typeof b||"function"==typeof b)){var c=e(b);if(c===s)return m(a,r);if(c===a.then&&b instanceof h)return a._81=3,a._65=b,void n(a);if("function"==typeof c)return void p(c.bind(b),a)}a._81=1,a._65=b,n(a)}function m(a,b){a._81=2,a._65=b,h._97&&h._97(a,b),n(a)}function n(a){if(1===a._45&&(j(a,a._54),a._54=null),2===a._45){for(var b=0;b<a._54.length;b++)j(a,a._54[b]);a._54=null}}function o(a,b,c){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.promise=c}function p(a,b){var c=!1,d=g(a,function(a){c||(c=!0,l(b,a))},function(a){c||(c=!0,m(b,a))});c||d!==s||(c=!0,m(b,r))}var q=a("asap/raw"),r=null,s={};b.exports=h,h._10=null,h._97=null,h._61=d,h.prototype.then=function(a,b){if(this.constructor!==h)return i(this,a,b);var c=new h(d);return j(this,new o(a,b,c)),c}},{"asap/raw":93}],95:[function(a,b,c){"use strict";function d(a){var b=new e(e._61);return b._81=1,b._65=a,b}var e=a("./core.js");b.exports=e;var f=d(!0),g=d(!1),h=d(null),i=d(void 0),j=d(0),k=d("");e.resolve=function(a){if(a instanceof e)return a;if(null===a)return h;if(void 0===a)return i;if(a===!0)return f;if(a===!1)return g;if(0===a)return j;if(""===a)return k;if("object"==typeof a||"function"==typeof a)try{var b=a.then;if("function"==typeof b)return new e(b.bind(a))}catch(c){return new e(function(a,b){b(c)})}return d(a)},e.all=function(a){var b=Array.prototype.slice.call(a);return new e(function(a,c){function d(g,h){if(h&&("object"==typeof h||"function"==typeof h)){if(h instanceof e&&h.then===e.prototype.then){for(;3===h._81;)h=h._65;return 1===h._81?d(g,h._65):(2===h._81&&c(h._65),void h.then(function(a){d(g,a)},c))}var i=h.then;if("function"==typeof i){var j=new e(i.bind(h));return void j.then(function(a){d(g,a)},c)}}b[g]=h,0===--f&&a(b)}if(0===b.length)return a([]);for(var f=b.length,g=0;g<b.length;g++)d(g,b[g])})},e.reject=function(a){return new e(function(b,c){c(a)})},e.race=function(a){return new e(function(b,c){a.forEach(function(a){e.resolve(a).then(b,c)})})},e.prototype["catch"]=function(a){return this.then(null,a)}},{"./core.js":94}],96:[function(a,b,c){"function"!=typeof Promise.prototype.done&&(Promise.prototype.done=function(a,b){var c=arguments.length?this.then.apply(this,arguments):this;c.then(null,function(a){setTimeout(function(){throw a},0)})})},{}],97:[function(a,b,c){a("asap");"undefined"==typeof Promise&&(Promise=a("./lib/core.js"),a("./lib/es6-extensions.js")),a("./polyfill-done.js")},{"./lib/core.js":94,"./lib/es6-extensions.js":95,"./polyfill-done.js":96,asap:92}]},{},[2])(2)});
diff --git a/ui/public/locales/ar.json b/ui/public/locales/ar.json
index df4afa7..2cf51c2 100644
--- a/ui/public/locales/ar.json
+++ b/ui/public/locales/ar.json
@@ -651,7 +651,7 @@
 "label.level": "Level",
 "label.limitcpuuse": "CPU Cap",
 "label.link.domain.to.ldap": "Link Domain to LDAP",
-"label.linklocalip": "Link Local IP Address",
+"label.linklocalip": "Control IP Address",
 "label.load.balancer": "Load Balancer",
 "label.loadbalancerinstance": "Assigned VMs",
 "label.loadbalancerrule": "Load balancing rule",
@@ -1480,4 +1480,4 @@
 "state.stopped": "\u062a\u0648\u0642\u0641",
 "state.stopping": "Stopping",
 "state.suspended": "\u062a\u0645 \u0627\u0644\u0625\u064a\u0642\u0627\u0641"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/ca.json b/ui/public/locales/ca.json
index 465ce78..e97d11d 100644
--- a/ui/public/locales/ca.json
+++ b/ui/public/locales/ca.json
@@ -651,7 +651,7 @@
 "label.level": "Level",
 "label.limitcpuuse": "CPU Cap",
 "label.link.domain.to.ldap": "Link Domain to LDAP",
-"label.linklocalip": "Link Local IP Address",
+"label.linklocalip": "Control IP Address",
 "label.load.balancer": "Load Balancer",
 "label.loadbalancerinstance": "Assigned VMs",
 "label.loadbalancerrule": "Load balancing rule",
@@ -1480,4 +1480,4 @@
 "state.stopped": "Stopped",
 "state.stopping": "Stopping",
 "state.suspended": "Susp\u00e9s"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/de_DE.json b/ui/public/locales/de_DE.json
index 55b3be4..d9adc43 100644
--- a/ui/public/locales/de_DE.json
+++ b/ui/public/locales/de_DE.json
@@ -2198,4 +2198,4 @@
 "state.suspended": "Suspendiert",
 "user.login": "Anmelden",
 "user.logout": "Abmelden"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json
index ffe6aaf..754cde8 100644
--- a/ui/public/locales/el_GR.json
+++ b/ui/public/locales/el_GR.json
@@ -1578,6 +1578,8 @@
 "label.shared": "Κοινόχρηστο",
 "label.sharedexecutable": "Κοινόχρηστο",
 "label.sharedmountpoint": "Κοινόχρηστο μέσο",
+"label.sharedrouterip": "Διεύθυνση IPv4 του δρομολογητή στο κοινόχρηστο δίκτυο ",
+"label.sharedrouteripv6": "Διεύθυνση IPv6 του δρομολογητή στο κοινόχρηστο δίκτυο",
 "label.sharewith": "Κοινή χρήση με",
 "label.showing": "Προβολή",
 "label.shrinkok": "Συρρίκνωση OK",
@@ -2720,4 +2722,4 @@
 "state.suspended": "Ανεσταλμένο",
 "user.login": "Σύνδεση χρήστη",
 "user.logout": "Αποσύνδεση χρήστη"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index b11a8f8..be9d4e7 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -26,6 +26,8 @@
 "label.access": "Access",
 "label.access.kubernetes.nodes": "Access Kubernetes nodes",
 "label.accesskey": "Access key",
+"label.access.key": "Access key",
+"label.secret.key": "Secret key",
 "label.account": "Account",
 "label.account.and.security.group": "Account - security group",
 "label.account.id": "Account ID",
@@ -43,6 +45,7 @@
 "label.acquire.new.ip": "Acquire new IP",
 "label.acquire.new.secondary.ip": "Acquire new secondary IP",
 "label.acquiring.ip": "Acquiring IP",
+"label.associated.resource": "Associated resource",
 "label.action": "Action",
 "label.action.attach.disk": "Attach disk",
 "label.action.attach.iso": "Attach ISO",
@@ -52,147 +55,159 @@
 "label.action.bulk.delete.isos": "Bulk delete ISOs",
 "label.action.bulk.delete.load.balancer.rules": "Bulk delete load balancer rules",
 "label.action.bulk.delete.portforward.rules": "Bulk delete port forward rules",
-"label.action.bulk.delete.templates": "Bulk delete templates",
+"label.action.bulk.delete.snapshots": "Bulk delete snapshots",
+"label.action.bulk.delete.templates": "Bulk delete Templates",
 "label.action.bulk.release.public.ip.address": "Bulk release public IP addresses",
 "label.action.cancel.maintenance.mode": "Cancel maintenance mode",
 "label.action.change.password": "Change password",
 "label.action.configure.stickiness": "Stickiness",
 "label.action.copy.iso": "Copy ISO",
-"label.action.copy.template": "Copy template",
-"label.action.create.snapshot.from.vmsnapshot": "Create snapshot from VM snapshot",
-"label.action.create.template.from.volume": "Create template from volume",
+"label.action.copy.snapshot": "Copy Snapshot",
+"label.action.copy.template": "Copy Template",
+"label.action.create.snapshot.from.vmsnapshot": "Create Snapshot from Instance Snapshot",
+"label.action.create.template.from.volume": "Create Template from volume",
 "label.action.create.volume": "Create volume",
 "label.action.create.volume.add": "Create and Add Volume",
-"label.action.delete.account": "Delete account",
+"label.action.delete.account": "Delete Account",
 "label.action.delete.backup.offering": "Delete backup offering",
 "label.action.delete.cluster": "Delete cluster",
-"label.action.delete.disk.offering": "Delete disk offering",
 "label.action.delete.domain": "Delete domain",
 "label.action.delete.egress.firewall": "Delete egress firewall rule",
 "label.action.delete.firewall": "Delete firewall rule",
 "label.action.delete.interface.static.route": "Remove Tungsten Fabric interface static route",
+"label.action.delete.guest.os": "Delete guest os",
+"label.action.delete.guest.os.hypervisor.mapping": "Delete guest os hypervisor mapping",
 "label.action.delete.ip.range": "Delete IP range",
 "label.action.delete.iso": "Delete ISO",
 "label.action.delete.load.balancer": "Delete load balancer rule",
-"label.action.delete.network": "Delete network",
-"label.action.delete.network.static.route": "Remove Tungsten Fabric network static route",
-"label.action.delete.network.permission": "Delete network permission",
+"label.action.delete.network": "Delete Network",
+"label.action.delete.network.static.route": "Remove Tungsten Fabric Network static route",
+"label.action.delete.network.permission": "Delete Network permission",
 "label.action.delete.node": "Delete node",
-"label.action.delete.physical.network": "Delete physical network",
+"label.action.delete.oauth.provider": "Delete OAuth provider",
+"label.action.delete.physical.network": "Delete physical Network",
 "label.action.delete.pod": "Delete Pod",
 "label.action.delete.primary.storage": "Delete primary storage",
 "label.action.delete.secondary.storage": "Delete secondary storage",
 "label.action.delete.security.group": "Delete security group",
-"label.action.delete.service.offering": "Delete service offering",
-"label.action.delete.snapshot": "Delete snapshot",
-"label.action.delete.system.service.offering": "Delete system service offering",
-"label.action.delete.template": "Delete template",
-"label.action.delete.tungsten.router.table": "Remove Tungsten Fabric route table from network",
-"label.action.delete.user": "Delete user",
+"label.action.delete.snapshot": "Delete Snapshot",
+"label.action.delete.template": "Delete Template",
+"label.action.delete.tungsten.router.table": "Remove Tungsten Fabric route table from Network",
+"label.action.delete.user": "Delete User",
 "label.action.delete.volume": "Delete volume",
 "label.action.delete.zone": "Delete zone",
-"label.action.destroy.instance": "Destroy instance",
+"label.action.destroy.instance": "Destroy Instance",
 "label.action.destroy.systemvm": "Destroy system VM",
 "label.action.destroy.volume": "Destroy volume",
 "label.action.detach.disk": "Detach disk",
 "label.action.detach.iso": "Detach ISO",
-"label.action.disable.account": "Disable account",
+"label.action.disable.account": "Disable Account",
 "label.action.disable.cluster": "Disable cluster",
-"label.action.disable.physical.network": "Disable physical network",
+"label.action.disable.disk.offering": "Disable disk offering",
+"label.action.disable.physical.network": "Disable physical Network",
 "label.action.disable.pod": "Disable pod",
 "label.action.disable.static.nat": "Disable static NAT",
-"label.action.disable.user": "Disable user",
+"label.action.disable.service.offering": "Disable service offering",
+"label.action.disable.system.service.offering": "Disable system service offering",
+"label.action.disable.user": "Disable User",
 "label.action.disable.zone": "Disable zone",
 "label.action.download.iso": "Download ISO",
-"label.action.download.template": "Download template",
+"label.action.download.template": "Download Template",
 "label.action.download.volume": "Download volume",
-"label.action.edit.account": "Edit account",
+"label.action.edit.account": "Edit Account",
 "label.action.edit.domain": "Edit domain",
-"label.action.edit.instance": "Edit instance",
+"label.action.edit.instance": "Edit Instance",
 "label.action.edit.iso": "Edit ISO",
-"label.action.edit.template": "Edit template",
+"label.action.edit.template": "Edit Template",
 "label.action.edit.zone": "Edit zone",
 "label.action.enable.two.factor.authentication": "Enabled Two factor authentication",
 "label.action.verify.two.factor.authentication": "Verified Two factor authentication",
-"label.action.enable.account": "Enable account",
+"label.action.enable.account": "Enable Account",
 "label.action.enable.cluster": "Enable cluster",
+"label.action.enable.disk.offering": "Enable disk offering",
 "label.action.enable.maintenance.mode": "Enable maintenance mode",
-"label.action.enable.physical.network": "Enable physical network",
+"label.action.enable.physical.network": "Enable physical Network",
 "label.action.enable.pod": "Enable pod",
+"label.action.enable.service.offering": "Enable service offering",
+"label.action.enable.system.service.offering": "Enable system service offering",
 "label.action.enable.static.nat": "Enable static NAT",
-"label.action.enable.user": "Enable user",
+"label.action.enable.user": "Enable User",
 "label.action.enable.zone": "Enable zone",
-"label.action.expunge.instance": "Expunge instance",
+"label.action.expunge.instance": "Expunge Instance",
 "label.action.force.reconnect": "Force reconnect",
 "label.action.generate.keys": "Generate keys",
+"label.action.generate.api.secret.keys": "Generate New API/Secret Keys",
 "label.action.get.diagnostics": "Get diagnostics data",
 "label.action.health.monitor": "Health monitor",
 "label.action.image.store.read.only": "Make image store read-only",
 "label.action.image.store.read.write": "Make image store read-write",
-"label.action.import.export.instances": "Import-Export instances",
+"label.action.import.export.instances": "Import-Export Instances",
+"label.action.ingest.instances": "Ingest instances",
 "label.action.iso.permission": "Update ISO permissions",
 "label.action.iso.share": "Update ISO sharing",
-"label.action.lock.account": "Lock account",
+"label.action.lock.account": "Lock Account",
 "label.action.manage.cluster": "Manage cluster",
 "label.action.migrate.router": "Migrate router",
-"label.action.migrate.systemvm": "Migrate system VM",
-"label.action.migrate.systemvm.to.ps": "Migrate system VM to another primary storage",
+"label.action.migrate.systemvm": "Migrate System VM",
+"label.action.migrate.systemvm.to.ps": "Migrate System VM to another primary storage",
 "label.action.patch.systemvm": "Patch system VM",
-"label.action.patch.systemvm.vpc": "Patch system VM - VPC Router",
-"label.action.patch.systemvm.processing": "Patching system VM....",
-"label.action.project.add.account": "Add account to project",
-"label.action.project.add.user": "Add user to project",
-"label.action.reboot.instance": "Reboot instance",
+"label.action.patch.systemvm.vpc": "Patch System VM - VPC Router",
+"label.action.patch.systemvm.processing": "Patching System VM....",
+"label.action.project.add.account": "Add Account to project",
+"label.action.project.add.user": "Add User to project",
+"label.action.reboot.instance": "Reboot Instance",
 "label.action.reboot.router": "Reboot router",
-"label.action.reboot.systemvm": "Reboot system VM",
+"label.action.reboot.systemvm": "Reboot System VM",
 "label.action.recover.volume": "Recover volume",
-"label.action.recurring.snapshot": "Recurring snapshots",
+"label.action.recurring.snapshot": "Recurring Snapshots",
 "label.action.disable.2FA.user.auth": "Disable User Two Factor Authentication",
 "label.action.register.iso": "Register ISO",
-"label.action.register.template": "Register template from URL",
+"label.action.register.template": "Register Template from URL",
 "label.action.release.ip": "Release IP",
 "label.action.release.reserved.ip": "Release reserved IP",
 "label.action.remove.host": "Remove host",
 "label.action.remove.logical.router": "Remove Logical Router",
 "label.action.remove.network.policy": "Remove Network Policy",
 "label.action.remove.router.table.from.interface": "Remove Tungsten Fabric route table from interface",
-"label.action.remove.tungsten.routing.policy": "Remove Tungsten-Fabric routing policy from network",
+"label.action.remove.tungsten.routing.policy": "Remove Tungsten-Fabric routing policy from Network",
 "label.action.remove.nic": "Remove NIC",
 "label.action.reserve.ip": "Reserve Public IP",
-"label.action.reset.network.permissions": "Reset network permissions",
+"label.action.reset.network.permissions": "Reset Network permissions",
 "label.action.reset.password": "Reset password",
 "label.action.resize.volume": "Resize volume",
-"label.action.revert.snapshot": "Revert to snapshot",
+"label.action.revert.snapshot": "Revert to Snapshot",
 "label.action.router.health.checks": "Get health checks result",
 "label.action.run.diagnostics": "Run diagnostics",
 "label.action.secure.host": "Provision host security keys",
+"label.action.set.as.source.nat.ip": "make source NAT",
 "label.action.setup.2FA.user.auth": "Setup User Two Factor Authentication",
-"label.action.start.instance": "Start instance",
+"label.action.start.instance": "Start Instance",
 "label.action.start.router": "Start router",
 "label.action.start.systemvm": "Start system VM",
-"label.action.stop.instance": "Stop instance",
+"label.action.stop.instance": "Stop Instance",
 "label.action.stop.router": "Stop router",
 "label.action.stop.systemvm": "Stop system VM",
-"label.action.take.snapshot": "Take snapshot",
-"label.action.template.permission": "Update template permissions",
-"label.action.template.share": "Update template sharing",
+"label.action.take.snapshot": "Take Snapshot",
+"label.action.template.permission": "Update Template permissions",
+"label.action.template.share": "Update Template sharing",
 "label.action.unmanage.cluster": "Unmanage cluster",
-"label.action.unmanage.instance": "Unmanage instance",
-"label.action.unmanage.instances": "Unmanage instances",
-"label.action.unmanage.virtualmachine": "Unmanage VM",
+"label.action.unmanage.instance": "Unmanage Instance",
+"label.action.unmanage.instances": "Unmanage Instances",
+"label.action.unmanage.virtualmachine": "Unmanage Instance",
 "label.action.update.offering.access": "Update offering access",
 "label.action.update.resource.count": "Update resource count",
 "label.action.value": "Action/Value",
-"label.action.userdata.reset": "Reset userdata",
-"label.action.vmsnapshot.create": "Take VM snapshot",
-"label.action.vmsnapshot.delete": "Delete VM snapshot",
-"label.action.vmsnapshot.revert": "Revert to VM snapshot",
-"label.action.vmstoragesnapshot.create": "Take VM volume snapshot",
+"label.action.userdata.reset": "Reset Userdata",
+"label.action.vmsnapshot.create": "Take Instance Snapshot",
+"label.action.vmsnapshot.delete": "Delete Instance Snapshot",
+"label.action.vmsnapshot.revert": "Revert to Instance Snapshot",
+"label.action.vmstoragesnapshot.create": "Take Instance volume Snapshot",
 "label.actions": "Actions",
+"label.active": "Active",
 "label.activate.project": "Activate project",
 "label.activeviewersessions": "Active sessions",
 "label.add": "Add",
-"label.add.account": "Add account",
+"label.add.account": "Add Account",
 "label.add.acl": "Add ACL",
 "label.add.acl.list": "Add ACL list",
 "label.add.affinity.group": "Add new affinity group",
@@ -211,33 +226,35 @@
 "label.add.f5.device": "Add F5 device",
 "label.add.firewall": "Add firewall rule",
 "label.add.firewallrule": "Add Firewall Rule",
-"label.add.guest.network": "Add guest network",
+"label.add.guest.network": "Add guest Network",
+"label.add.guest.os": "Add guest os",
+"label.add.guest.os.hypervisor.mapping": "Add guest os hypervisor mapping",
 "label.add.host": "Add host",
 "label.add.ingress.rule": "Add ingress rule",
 "label.add.intermediate.certificate": "Add intermediate certificate",
 "label.add.internal.lb": "Add internal LB",
 "label.add.ip.range": "Add IP range",
 "label.add.ip.v6.prefix": "Add IPv6 prefix",
-"label.add.isolated.network": "Add isolated network",
+"label.add.isolated.network": "Add isolated Network",
 "label.add.kubernetes.cluster": "Add Kubernetes cluster",
-"label.add.ldap.account": "Add LDAP account",
+"label.add.ldap.account": "Add LDAP Account",
 "label.add.list.name": "ACL List name",
-"label.add.logical.router": "Add Logical Router to this network",
+"label.add.logical.router": "Add Logical Router to this Network",
 "label.add.more": "Add more",
 "label.add.netscaler.device": "Add Netscaler device",
-"label.add.network": "Add network",
-"label.add.network.acl": "Add network ACL",
-"label.add.network.acl.list": "Add network ACL list",
-"label.add.network.offering": "Add network offering",
-"label.add.network.permission": "Add network permission",
+"label.add.network": "Add Network",
+"label.add.network.acl": "Add Network ACL",
+"label.add.network.acl.list": "Add Network ACL list",
+"label.add.network.offering": "Add Network offering",
+"label.add.network.permission": "Add Network permission",
 "label.add.new.gateway": "Add new gateway",
-"label.add.new.tier": "Add new tier",
+"label.add.new.tier": "Add new Network Tier",
 "label.add.niciranvp.device": "Add Nvp controller",
 "label.add.note": "Add comment",
 "label.add.opendaylight.device": "Add OpenDaylight controller",
 "label.add.pa.device": "Add Palo Alto device",
 "label.add.param": "Add param",
-"label.add.physical.network": "Add physical network",
+"label.add.physical.network": "Add physical Network",
 "label.add.pod": "Add pod",
 "label.add.prefix": "Add prefix",
 "label.add.policy": "Add policy",
@@ -246,7 +263,7 @@
 "label.add.resources": "Add resources",
 "label.add.role": "Add role",
 "label.add.route": "Add route",
-"label.add.router.table.to.instance": "Add router table to this instance",
+"label.add.router.table.to.instance": "Add router table to this Instance",
 "label.add.routing.policy": "Add routing policy",
 "label.add.rule": "Add rule",
 "label.add.secondary.ip": "Add secondary IP",
@@ -260,34 +277,34 @@
 "label.add.traffic": "Add traffic",
 "label.add.traffic.type": "Add traffic type",
 "label.add.tungsten.address.group": "Add Address Group",
-"label.add.tungsten.fabric.route": "Add Tungsten Fabric network routing table",
+"label.add.tungsten.fabric.route": "Add Tungsten Fabric Network routing table",
 "label.add.tungsten.firewall.policy": "Add Firewall Policy",
 "label.add.tungsten.firewall.rule": "Add Firewall Rule",
 "label.add.tungsten.interface.route": "Add Tungsten Fabric interface routing table",
 "label.add.tungsten.interface.static.route": "Add Tungsten Fabric interface static route",
 "label.add.tungsten.logical.route": "Add Logical Router",
-"label.add.tungsten.network.static.route": "Add Tungsten Fabric network static route",
+"label.add.tungsten.network.static.route": "Add Tungsten Fabric Network static route",
 "label.add.tungsten.policy": "Add Policy",
 "label.add.tungsten.policy.set": "Add Application Policy Set",
-"label.add.tungsten.router.table": "Add route table to this network",
-"label.add.tungsten.routing.policy": "Add routing policy to this network",
+"label.add.tungsten.router.table": "Add route table to this Network",
+"label.add.tungsten.routing.policy": "Add routing policy to this Network",
 "label.add.tungsten.service.group": "Add Service Group",
 "label.add.tungsten.tag": "Add Tag",
 "label.add.tungsten.tag.type": "Add Tag Type",
-"label.add.user": "Add user",
+"label.add.user": "Add User",
 "label.add.upstream.ipv6.routes": "Add upstream IPv6 routes",
-"label.add.vm": "Add VM",
-"label.add.vms": "Add VMs",
+"label.add.vm": "Add Instance",
+"label.add.vms": "Add Instances",
 "label.add.vmware.datacenter": "Add VMware datacenter",
 "label.add.vnmc.device": "Add VNMC device",
 "label.add.vpc": "Add VPC",
 "label.add.vpc.offering": "Add VPC offering",
 "label.add.vpn.customer.gateway": "Add VPN customer gateway",
 "label.add.vpn.gateway": "Add VPN gateway",
-"label.add.vpn.user": "Add VPN user",
+"label.add.vpn.user": "Add VPN User",
 "label.add.zone": "Add zone",
 "label.adding": "Adding",
-"label.adding.user": "Adding user...",
+"label.adding.user": "Adding User...",
 "label.address": "Address",
 "label.address.group": "Address group",
 "label.admin": "Domain admin",
@@ -314,7 +331,8 @@
 "label.allocatedonly": "Allocated",
 "label.allocationstate": "Allocation state",
 "label.allow": "Allow",
-"label.allowuserdrivenbackups": "Allow user driven backups",
+"label.allow.duplicate.macaddresses": "Allow duplicate MAC addresses",
+"label.allowuserdrivenbackups": "Allow User driven backups",
 "label.annotation": "Comment",
 "label.annotations": "Comments",
 "label.adminsonly": "Only visible to administrators",
@@ -331,42 +349,44 @@
 "label.apply.tungsten.network.policy": "Apply Network Policy",
 "label.apply.tungsten.tag": "Apply tag",
 "label.archive": "Archive",
+"label.archived": "Archived",
 "label.archive.alerts": "Archive alerts",
 "label.archive.events": "Archive events",
 "label.as.default": "as default",
 "label.assign": "Assign",
-"label.assign.instance.another": "Assign instance to another account",
-"label.assign.vms": "Assign VMs",
-"label.assigning.vms": "Assigning VMs",
-"label.associatednetwork": "Associated network",
-"label.associatednetworkid": "Associated network ID",
+"label.assign.instance.another": "Assign Instance to another Account",
+"label.assign.vms": "Assign Instances",
+"label.assigning.vms": "Assigning Instances",
+"label.associatednetwork": "Associated Network",
+"label.associatednetworkid": "Associated Network ID",
 "label.associatednetworkname": "Network name",
 "label.asyncbackup": "Async backup",
 "label.attaching": "Attaching",
 "label.authentication.method": "Authentication Method",
 "label.authentication.sshkey": "System SSH Key",
 "label.autoscale": "AutoScale",
-"label.autoscalevmgroupname": "AutoScale VM Group",
+"label.autoscalevmgroupname": "AutoScale Instance Group",
 "label.author.email": "Author e-mail",
 "label.author.name": "Author name",
 "label.auto.assign": "Automatically assign",
 "label.auto.assign.diskoffering.disk.size": "Automatically assign offering matching the disk size",
 "label.auto.assign.random.ip": "Automatically assign a random IP address",
 "label.automigrate.volume": "Auto migrate volume to another storage pool if required",
-"label.autoscale.vm.groups": "AutoScale VM Groups",
-"label.autoscale.vm.profile": "AutoScale VM Profile",
+"label.autoscale.vm.groups": "AutoScale Instance Groups",
+"label.autoscale.vm.profile": "AutoScale Instance Profile",
 "label.autoscalingenabled": "Auto scaling",
 "label.availability": "Availability",
 "label.available": "Available",
 "label.availableprocessors": "Available processor cores",
-"label.availablevirtualmachinecount": "Available VMs",
+"label.availablevirtualmachinecount": "Available Instances",
 "label.back": "Back",
 "label.backup": "Backups",
 "label.backup.attach.restore": "Restore and attach backup volume",
-"label.backup.offering.assign": "Assign VM to backup offering",
-"label.backup.offering.remove": "Remove VM from backup offering",
+"label.backup.configure.schedule": "Configure Backup Schedule",
+"label.backup.offering.assign": "Assign Instance to backup offering",
+"label.backup.offering.remove": "Remove Instance from backup offering",
 "label.backup.offerings": "Backup offerings",
-"label.backup.restore": "Restore VM backup",
+"label.backup.restore": "Restore Instance backup",
 "label.backupofferingid": "Backup offering",
 "label.backupofferingname": "Backup offering",
 "label.balance": "Balance",
@@ -393,8 +413,9 @@
 "label.broadcastdomaintype": "Broadcast domain type",
 "label.broadcasturi": "Broadcast URI",
 "label.brocade.vcs.address": "Vcs switch address",
+"label.browser": "Browser",
 "label.bucket": "Bucket",
-"label.by.account": "By account",
+"label.by.account": "By Account",
 "label.by.domain": "By domain",
 "label.by.level": "By level",
 "label.by.pod": "By pod",
@@ -404,6 +425,7 @@
 "label.bypassvlanoverlapcheck": "Bypass VLAN id/range overlap",
 "label.cachemode": "Write-cache type",
 "label.cancel": "Cancel",
+"label.cancel.shutdown": "Cancel Shutdown",
 "label.cancelmaintenance": "Cancel maintenance",
 "label.cancel.host.as.degraded": "Cancel host as degraded",
 "label.capacity": "Capacity",
@@ -427,7 +449,7 @@
 "label.choose.resource.icon": "Choose icon",
 "label.choose.saml.identity": "Choose SAML identity provider",
 "label.cidr": "CIDR",
-"label.cidr.destination.network": "Destination network CIDR",
+"label.cidr.destination.network": "Destination Network CIDR",
 "label.cidrlist": "CIDR list",
 "label.cisco.nexus1000v.ip.address": "Nexus 1000v IP address",
 "label.cisco.nexus1000v.password": "Nexus 1000v password",
@@ -440,7 +462,9 @@
 "label.clear": "Clear",
 "label.clear.list": "Clear list",
 "label.clear.notification": "Clear notification",
+"label.clientid": "Provider Client ID",
 "label.close": "Close",
+"label.cloud.managed": "CloudManaged",
 "label.cloudian.storage": "Cloudian storage",
 "label.cluster": "Cluster",
 "label.cluster.name": "Cluster name",
@@ -455,6 +479,7 @@
 "label.collectiontime": "Collection time",
 "label.columns": "Columns",
 "label.comma.separated.list.description": "Enter comma-separated list of commands",
+"label.command": "Command",
 "label.comments": "Comments",
 "label.communities": "Communities",
 "label.community": "Community",
@@ -477,7 +502,8 @@
 "label.confirm.delete.isos": "Please confirm you wish to delete the selected ISOs.",
 "label.confirm.delete.loadbalancer.rules": "Please confirm you wish to delete the selected load balancing rules.",
 "label.confirm.delete.portforward.rules": "Please confirm you wish to delete the selected port-forward rules.",
-"label.confirm.delete.templates": "Please confirm you wish to delete the selected templates.",
+"label.confirm.delete.snapshot.zones": "Please confirm you wish to delete the Snapshot in the selected zones.",
+"label.confirm.delete.templates": "Please confirm you wish to delete the selected Templates.",
 "label.confirm.delete.tungsten.address.group": "Please confirm that you would like to delete this Address Group",
 "label.confirm.delete.tungsten.firewall.policy": "Please confirm that you would like to delete this Firewall Policy",
 "label.confirm.delete.tungsten.policy": "Please confirm that you would like to delete this Policy",
@@ -487,13 +513,14 @@
 "label.confirm.delete.tungsten.tag.type": "Please confirm that you would like to delete this Tag Type",
 "label.confirm.release.public.ip.addresses": "Please confirm you wish to release the selected public IP addresses.",
 "label.confirm.remove.logical.router": "Please confirm that you would like to delete this Logical Router",
-"label.confirm.remove.network.route.table": "Please confirm that you would like to delete this network Route Table",
+"label.confirm.remove.network.route.table": "Please confirm that you would like to delete this Network Route Table",
 "label.confirm.remove.route.table": "Please confirm that you would like to delete this Interface Route Table",
 "label.confirmacceptinvitation": "Please confirm you wish to join this project.",
 "label.confirmation": "Confirmation",
 "label.confirmdeclineinvitation": "Are you sure you want to decline this project invitation?",
 "label.confirmpassword": "Confirm password",
 "label.confirmpassword.description": "Please type the same password again.",
+"label.connect": "Connect",
 "label.connectiontimeout": "Connection timeout",
 "label.conservemode": "Conserve mode",
 "label.considerlasthost": "Consider Last Host",
@@ -506,7 +533,9 @@
 "label.copied.clipboard": "Copied to clipboard",
 "label.copy": "Copy",
 "label.copy.clipboard": "Copy to clipboard",
+"label.copy.consoleurl": "Copy console URL to clipboard",
 "label.copyid": "Copy ID",
+"label.copy.password": "Copy password",
 "label.core": "Core",
 "label.core.zone.type": "Core zone type",
 "label.counter": "Counter",
@@ -514,7 +543,7 @@
 "label.cpu": "CPU",
 "label.cpu.sockets": "CPU sockets",
 "label.cpu.usage.info": "CPU usage information",
-"label.cpuallocated": "CPU allocated for VMs",
+"label.cpuallocated": "CPU allocated for Instances",
 "label.cpuallocatedghz": "CPU allocated",
 "label.cpulimit": "CPU limits",
 "label.cpuload": "% CPU in use",
@@ -527,31 +556,34 @@
 "label.cpuused": "CPU utilized",
 "label.cpuusedghz": "CPU used",
 "label.create": "Create",
-"label.create.account": "Create account",
+"label.create.instance": "Create cloud server",
+"label.create.account": "Create Account",
 "label.create.backup": "Start backup",
-"label.create.network": "Create new network",
+"label.create.network": "Create new Network",
 "label.create.nfs.secondary.staging.storage": "Create NFS secondary staging storage",
 "label.create.project": "Create project",
 "label.create.project.role": "Create project role",
 "label.create.routing.policy": "Create Routing Policy",
 "label.create.site.vpn.connection": "Create site-to-site VPN connection",
 "label.create.site.vpn.gateway": "Create site-to-site VPN gateway",
-"label.create.snapshot.for.volume": "Created snapshot for volume",
+"label.create.snapshot.for.volume": "Created Snapshot for volume",
 "label.create.ssh.key.pair": "Create a SSH Key Pair",
-"label.create.template": "Create template",
-"label.create.tier.aclid.description": "The ACL associated with the tier.",
-"label.create.tier.externalid.description": "ID of the network in an external system.",
-"label.create.tier.gateway.description": "The tier's gateway in the super CIDR range, not overlapping with the CIDR of other tiers in this VPC.",
-"label.create.tier.name.description": "A unique name for the tier.",
-"label.create.tier.netmask.description": "The tier's netmask. For example 255.255.255.0",
-"label.create.tier.networkofferingid.description": "The network offering for the tier.",
+"label.create.template": "Create Template",
+"label.create.tier.aclid.description": "The ACL associated with the Network Tier.",
+"label.create.tier.externalid.description": "ID of the Network in an external system.",
+"label.create.tier.gateway.description": "The Network Tier's gateway in the super CIDR range, not overlapping with the CIDR of other Network Tiers in this VPC.",
+"label.create.tier.name.description": "A unique name for the Network Tier.",
+"label.create.tier.netmask.description": "The Network Tier's netmask. For example 255.255.255.0",
+"label.create.tier.networkofferingid.description": "The Network offering for the Network Tier.",
 "label.create.tungsten.routing.policy": "Create Tungsten-Fabric routing policy",
-"label.create.user": "Create user",
+"label.create.user": "Create User",
 "label.create.vpn.connection": "Create VPN connection",
 "label.created": "Created",
 "label.creating": "Creating",
 "label.creating.iprange": "Creating IP ranges",
 "label.credit": "Credit",
+"label.cron": "Cron expression",
+"label.cron.mode": "Cron mode",
 "label.crosszones": "Cross zones",
 "label.currency": "Currency",
 "label.current": "Current",
@@ -585,12 +617,12 @@
 "label.default": "Default",
 "label.default.use": "Default use",
 "label.default.view": "Default view",
-"label.defaultnetwork": "Default network",
+"label.defaultnetwork": "Default Network",
 "label.delete": "Delete",
 "label.delete.acl.list": "Delete ACL list",
 "label.delete.affinity.group": "Delete affinity group",
 "label.delete.alerts": "Delete alerts",
-"label.delete.autoscale.vmgroup": "Delete AutoScale VM Group",
+"label.delete.autoscale.vmgroup": "Delete AutoScale Instance Group",
 "label.delete.backup": "Delete backup",
 "label.delete.bigswitchbcf": "Remove BigSwitch BCF controller",
 "label.delete.brocadevcs": "Remove Brocade Vcs switch",
@@ -604,7 +636,7 @@
 "label.delete.f5": "Delete F5",
 "label.delete.gateway": "Delete gateway",
 "label.delete.icon": "Delete icon",
-"label.delete.instance.group": "Delete instance group",
+"label.delete.instance.group": "Delete Instance group",
 "label.delete.internal.lb": "Delete internal LB",
 "label.delete.ip.v6.prefix": "Delete IPv6 prefix",
 "label.delete.netscaler": "Delete NetScaler",
@@ -617,11 +649,12 @@
 "label.delete.role": "Delete role",
 "label.delete.rule": "Delete rule",
 "label.delete.setting": "Delete setting",
-"label.delete.snapshot.policy": "Delete snapshot policy",
+"label.delete.snapshot.policy": "Delete Snapshot policy",
 "label.delete.srx": "Delete SRX",
 "label.delete.sslcertificate": "Delete SSL certificate",
 "label.delete.tag": "Remove tag",
 "label.delete.term": "Delete term",
+"label.delete.traffic.type": "Delete traffic type",
 "label.delete.tungsten.address.group": "Delete Address Group",
 "label.delete.tungsten.fabric.tag": "Delete Tag",
 "label.delete.tungsten.fabric.tag.type": "Delete Tag type",
@@ -633,26 +666,37 @@
 "label.delete.vpn.connection": "Delete VPN connection",
 "label.delete.vpn.customer.gateway": "Delete VPN customer gateway",
 "label.delete.vpn.gateway": "Delete VPN gateway",
-"label.delete.vpn.user": "Delete VPN user",
+"label.delete.vpn.user": "Delete VPN User",
 "label.deleteconfirm": "Please confirm that you would like to delete this",
 "label.deleting": "Deleting",
 "label.deleting.failed": "Deleting failed",
 "label.deleting.iso": "Deleting ISO",
-"label.deleting.template": "Deleting template",
-"label.demote.project.owner": "Demote account to regular role",
-"label.demote.project.owner.user": "Demote user to regular role",
+"label.deleting.snapshot": "Deleting Snapshot",
+"label.deleting.template": "Deleting Template",
+"label.demote.project.owner": "Demote Account to regular role",
+"label.demote.project.owner.user": "Demote User to regular role",
 "label.deny": "Deny",
-"label.deployasis": "Read VM settings from OVA",
+"label.deployasis": "Read Instance settings from OVA",
 "label.deploymentplanner": "Deployment planner",
 "label.desc.db.stats": "Database Statistics",
-"label.desc.importexportinstancewizard": "Import and export instances to/from an existing VMware cluster.",
+"label.desc.importexportinstancewizard": "Import and export Instances to/from an existing VMware or KVM cluster.",
+"label.desc.import.ext.kvm.wizard": "Import Instance from remote KVM host",
+"label.desc.import.local.kvm.wizard": "Import QCOW2 image from Local Storage",
+"label.desc.import.shared.kvm.wizard": "Import QCOW2 image from Shared Storage",
+"label.desc.ingesttinstancewizard": "Ingest instances from an external KVM host",
+"label.desc.importmigratefromvmwarewizard": "Import instances from VMware into a KVM cluster",
 "label.desc.usage.stats": "Usage Server Statistics",
 "label.description": "Description",
 "label.destaddressgroupuuid": "Destination Address Group",
 "label.destcidr": "Destination CIDR",
 "label.destendport": "Destination End Port",
+"label.desthost": "Destination host",
 "label.destination": "Destination",
-"label.destinationphysicalnetworkid": "Destination physical network ID",
+"label.destination.cluster":  "Destination Cluster",
+"label.destination.pod":  "Destination Pod",
+"label.destination.zone":  "Destination Zone",
+"label.destinationphysicalnetworkid": "Destination physical Network ID",
+"label.destination.hypervisor":  "Destination Hypervisor",
 "label.destinationtype": "Destination Type",
 "label.destipprefix": "Destination Network Address",
 "label.destipprefixlen": "Destination Prefix Length",
@@ -669,12 +713,12 @@
 "label.devices": "Devices",
 "label.dhcp": "DHCP",
 "label.direct.attached.public.ip": "Direct attached public IP",
-"label.direct.ips": "Shared network IPs",
+"label.direct.ips": "Shared Network IPs",
 "label.directdownload": "Direct download",
 "label.direction": "Direction",
-"label.disable.autoscale.vmgroup": "Disable AutoScale VM Group",
+"label.disable.autoscale.vmgroup": "Disable AutoScale Instance Group",
 "label.disable.host": "Disable host",
-"label.disable.network.offering": "Disable network offering",
+"label.disable.network.offering": "Disable Network offering",
 "label.disable.provider": "Disable provider",
 "label.disable.storage": "Disable storage pool",
 "label.disable.vpc.offering": "Disable VPC offering",
@@ -683,6 +727,8 @@
 "label.disconnected": "Last disconnected",
 "label.disk": "Disk",
 "label.disk.offerings": "Disk offerings",
+"label.disk.path": "Disk Path",
+"label.disk.tooltip": "Disk Image filename in the selected Storage Pool",
 "label.disk.selection": "Disk selection",
 "label.disk.size": "Disk size",
 "label.disk.usage.info": "Disk usage information",
@@ -713,7 +759,7 @@
 "label.disksizeusedgb": "Used",
 "label.display.text": "Display text",
 "label.displayname": "Display name",
-"label.displaynetwork": "Display network",
+"label.displaynetwork": "Display Network",
 "label.displaytext": "Description",
 "label.distributedvpcrouter": "Distributed VPC router",
 "label.dns": "DNS",
@@ -740,6 +786,11 @@
 "label.download.state": "Download state",
 "label.dpd": "Dead peer detection",
 "label.driver": "Driver",
+"label.drs": "DRS",
+"label.drsimbalance": "DRS imbalance",
+"label.drs.plan": "DRS Plan",
+"label.drs.generate.plan": "Generate DRS plan",
+"label.drs.no.plan.generated": "No DRS plan has been generated as the cluster is not imbalanced according to the threshold set",
 "label.duration": "Duration (in sec)",
 "label.duration.custom": "Custom",
 "label.duration.1hour": "1 hour",
@@ -748,7 +799,7 @@
 "label.duration.24hours": "24 hours",
 "label.duration.7days": "7 days",
 "label.dynamicscalingenabled": "Dynamic scaling enabled",
-"label.dynamicscalingenabled.tooltip": "VM can dynamically scale only when dynamic scaling is enabled on template, service offering and global setting.",
+"label.dynamicscalingenabled.tooltip": "Instance can dynamically scale only when dynamic scaling is enabled on Template, service offering and global setting.",
 "label.diskofferingstrictness": "Disk offering strictness",
 "label.disksizestrictness": "Disk size strictness",
 "label.edge": "Edge",
@@ -756,7 +807,7 @@
 "label.edit": "Edit",
 "label.edit.acl.list": "Edit ACL list",
 "label.edit.acl.rule": "Edit ACL rule",
-"label.edit.autoscale.vmprofile": "Edit AutoScale VM Profile",
+"label.edit.autoscale.vmprofile": "Edit AutoScale Instance Profile",
 "label.edit.project.details": "Edit project details",
 "label.edit.project.role": "Edit project role",
 "label.edit.role": "Edit role",
@@ -764,16 +815,17 @@
 "label.edit.secondary.ips": "Edit secondary IPs",
 "label.edit.tags": "Edit tags",
 "label.edit.traffic.type": "Edit traffic type",
-"label.edit.user": "Edit user",
+"label.edit.user": "Edit User",
 "label.egress": "Egress",
 "label.egress.rule": "Egress rule",
 "label.egress.rules": "Egress rules",
 "label.egressdefaultpolicy": "Default egress policy",
 "label.elastic": "Elastic",
 "label.email": "Email",
-"label.enable.autoscale.vmgroup": "Enable AutoScale VM Group",
+"label.enable.autoscale.vmgroup": "Enable AutoScale Instance Group",
 "label.enable.host": "Enable Host",
-"label.enable.network.offering": "Enable network offering",
+"label.enable.network.offering": "Enable Network offering",
+"label.enable.oauth": "Enable OAuth Login",
 "label.enable.provider": "Enable provider",
 "label.enable.storage": "Enable storage pool",
 "label.enable.vpc.offering": "Enable VPC offering",
@@ -820,11 +872,16 @@
 "label.every": "Every",
 "label.example": "Example",
 "label.example.plugin": "ExamplePlugin",
+"label.existing": "Existing",
+"label.execute": "Execute",
 "label.expunge": "Expunge",
-"label.expungevmgraceperiod": "Expunge VM grace period (in sec)",
+"label.expungevmgraceperiod": "Expunge Instance grace period (in sec)",
 "label.expunged": "Expunged",
 "label.expunging": "Expunging",
 "label.export.rules": "Export Rules",
+"label.ext.hostname.tooltip": "External Host Name or IP Address",
+"label.external.managed": "ExternalManaged",
+"label.external": "External",
 "label.external.link": "External link",
 "label.externalid": "External Id",
 "label.externalloadbalanceripaddress": "External load balancer IP address.",
@@ -833,6 +890,7 @@
 "label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.",
 "label.failed": "Failed",
 "label.featured": "Featured",
+"label.fetch.instances": "Fetch Instances",
 "label.fetch.latest": "Fetch latest",
 "label.files": "Alternate files to retrieve",
 "label.filter": "Filter",
@@ -853,6 +911,8 @@
 "label.for": "for",
 "label.forbidden": "Forbidden",
 "label.forced": "Force",
+"label.force.stop": "Force stop",
+"label.force.reboot": "Force reboot",
 "label.forceencap": "Force UDP encapsulation of ESP packets",
 "label.forgedtransmits": "Forged transmits",
 "label.format": "Format",
@@ -889,7 +949,9 @@
 "label.guest.ip": "Guest IP address",
 "label.guest.ip.range": "Guest IP range",
 "label.guest.netmask": "Guest netmask",
-"label.guest.networks": "Guest networks",
+"label.guest.networks": "Guest Networks",
+"label.guest.os": "Guest OS",
+"label.guest.os.hypervisor.mappings": "Guest OS mappings",
 "label.guest.start.ip": "Guest start IP",
 "label.guest.traffic": "Guest traffic",
 "label.guestcidraddress": "Guest CIDR",
@@ -898,7 +960,7 @@
 "label.guestipaddress": "Guest IP address",
 "label.guestiptype": "Guest type",
 "label.guestnetmask": "Guest netmask",
-"label.guestnetwork": "Guest network",
+"label.guestnetwork": "Guest Network",
 "label.guestnetworkid": "Network ID",
 "label.guestnetworkname": "Network name",
 "label.gueststartip": "Guest start IP",
@@ -912,10 +974,11 @@
 "label.haenable": "HA enabled",
 "label.haprovider": "HA provider",
 "label.hardware": "Hardware",
+"label.hasrules":"FW rules defined",
 "label.hastate": "HA state",
 "label.header.backup.schedule": "You can set up recurring backup schedules by selecting from the available options below and applying your policy preference.",
-"label.header.volume.snapshot": "You can set up recurring snapshot schedules by selecting from the available options below and applying your policy preference.",
-"label.header.volume.take.snapshot": "Please confirm that you want to take a snapshot of this volume.",
+"label.header.volume.snapshot": "You can set up recurring Snapshot schedules by selecting from the available options below and applying your policy preference.",
+"label.header.volume.take.snapshot": "Please confirm that you want to take a Snapshot of this volume.",
 "label.health.check": "Health check",
 "label.heapmemoryused": "Heap-memory used",
 "label.heapmemorytotal": "Heap-memory available",
@@ -929,6 +992,7 @@
 "label.hostcontrolstate": "Control Plane Status",
 "label.hostid": "Host",
 "label.hostname": "Host",
+"label.hostname.tooltip": "Destination Host. Volume should be located in local storage of this Host",
 "label.hostnamelabel": "Host name",
 "label.hosts": "Hosts",
 "label.hosttags": "Host tags",
@@ -937,7 +1001,7 @@
 "label.hypervisor.capabilities": "Hypervisor capabilities",
 "label.hypervisor.type": "Hypervisor type",
 "label.hypervisors": "Hypervisors",
-"label.hypervisorsnapshotreserve": "Hypervisor snapshot reserve",
+"label.hypervisorsnapshotreserve": "Hypervisor Snapshot reserve",
 "label.hypervisortype": "Hypervisor",
 "label.hypervisorversion": "Hypervisor version",
 "label.hypervnetworklabel": "HyperV traffic label",
@@ -955,15 +1019,18 @@
 "label.ikepolicy": "IKE policy",
 "label.ikeversion": "IKE version",
 "label.images": "Images",
+"label.imagestoreid": "Secondary Storage",
 "label.import.backup.offering": "Import backup offering",
-"label.import.instance": "Import instance",
+"label.import.instance": "Import Instance",
 "label.import.offering": "Import offering",
 "label.import.role": "Import role",
+"label.inactive": "Inactive",
 "label.in.progress": "in progress",
 "label.in.progress.for": "in progress for",
 "label.info": "Info",
 "label.info.upper": "INFO",
 "label.infrastructure": "Infrastructure",
+"label.ingest.instance": "Ingest Instance",
 "label.ingress": "Ingress",
 "label.ingress.rule": "Ingress Rule",
 "label.initial": "Inital",
@@ -998,14 +1065,14 @@
 "label.invitations": "Invitations",
 "label.invite": "Invite",
 "label.iodriverpolicy" : "IO driver policy",
-"label.iodriverpolicy.tooltip" : "IO driver policy could be native, io_uring or threads. Choosing the IO policy for a VM will override the storage pool option 'kvm.storage.pool.io.policy' if set (only if iothreads is enabled)",
+"label.iodriverpolicy.tooltip" : "IO driver policy could be native, io_uring or threads. Choosing the IO policy for an Instance will override the storage pool option 'kvm.storage.pool.io.policy' if set (only if iothreads is enabled)",
 "label.iops": "IOPS",
 "label.iothreadsenabled" : "IOThreads",
 "label.iothreadsenabled.tooltip" : "Enable iothreads allocation for KVM hypervisor",
 "label.ip": "IP address",
 "label.ip6firewall": "IPv6 firewall",
 "label.ip6routes": "IPv6 routes",
-"label.ip6routing": "IPv6 fouting",
+"label.ip6routing": "IPv6 routing",
 "label.ip.range.type": "IP range type",
 "label.ip.v4": "IPv4",
 "label.ip.v6": "IPv6",
@@ -1046,6 +1113,7 @@
 "label.isdedicated": "Dedicated",
 "label.isdefault": "Is default",
 "label.isdynamicallyscalable": "Dynamically scalable",
+"label.istagarule": "Tag as JS rule",
 "label.isextractable": "Extractable",
 "label.isfeatured": "Featured",
 "label.isforced": "Force delete",
@@ -1055,7 +1123,7 @@
 "label.iso.name": "ISO name",
 "label.isoid": "ISO",
 "label.isolated": "Isolated",
-"label.isolated.networks": "Isolated networks",
+"label.isolated.networks": "Isolated Networks",
 "label.isolatedpvlanid": "Secondary VLAN ID",
 "label.isolatedpvlantype": "Secondary VLAN type",
 "label.isolation.method": "Isolation method",
@@ -1073,6 +1141,7 @@
 "label.issourcenat": "Source NAT",
 "label.isstaticnat": "Static NAT",
 "label.issystem": "Is system",
+"label.isuserdefined": "User defined",
 "label.isvolatile": "Volatile",
 "label.items": "items",
 "label.items.selected": "item(s) selected",
@@ -1098,7 +1167,7 @@
 "label.kubernetes.cluster.upgrade": "Upgrade Kubernetes cluster",
 "label.kubernetes.dashboard": "Kubernetes dashboard UI",
 "label.kubernetes.dashboard.create.token": "Create token for Kubernetes dashboard",
-"label.kubernetes.dashboard.create.token.desc": "Since Kubernetes v1.24.0, there is no auto-generation of secret-based service account token due to security reason. You need to create a service account and an optional long-lived Bearer Token for the service account.",
+"label.kubernetes.dashboard.create.token.desc": "Since Kubernetes v1.24.0, there is no auto-generation of secret-based service Account token due to security reason. You need to create a service Account and an optional long-lived Bearer Token for the service Account.",
 "label.kubernetes.isos": "Kubernetes ISOs",
 "label.kubernetes.service": "Kubernetes service",
 "label.kubernetes.version.add": "Add Kubernetes version",
@@ -1106,6 +1175,7 @@
 "label.kubernetes.version.update": "Manage Kubernetes version",
 "label.kubernetesversionid": "Kubernetes version",
 "label.kubernetesversionname": "Kubernetes version",
+"label.kvm": "KVM",
 "label.kvmnetworklabel": "KVM traffic label",
 "label.l2": "L2",
 "label.l2gatewayserviceuuid": "L2 Gateway Service UUID",
@@ -1119,8 +1189,10 @@
 "label.lastserverstart": "Last Management Server start time",
 "label.lastserverstop": "Last stop time for this management server",
 "label.launch": "Launch",
-"label.launch.vm": "Launch instance",
-"label.launch.vm.and.stay": "Launch instance & stay on this page",
+"label.launch.vm": "Launch Instance",
+"label.launch.vm.and.stay": "Launch Instance & stay on this page",
+"label.launch.vnf.appliance": "Launch VNF appliance",
+"label.launch.vnf.appliance.and.stay": "Launch VNF appliance & stay on this page",
 "label.launch.zone": "Launch zone",
 "label.lb.algorithm.leastconn": "Least connections",
 "label.lb.algorithm.roundrobin": "Round-robin",
@@ -1142,6 +1214,7 @@
 "label.limitcpuuse": "CPU cap",
 "label.limits": "Limits",
 "label.limits.configure": "Configure limits",
+"label.link": "Link",
 "label.link.domain.to.ldap": "Link domain to LDAP",
 "label.linklocalip": "Link-local/Control IP address",
 "label.linux": "Linux",
@@ -1150,17 +1223,18 @@
 "label.list.nodes": "List nodes",
 "label.list.pods": "List pods",
 "label.list.services": "List services",
-"label.livepatch": "Live patch network's router(s)",
+"label.list.vmware.vcenter.vms": "List VMware Instances",
+"label.livepatch": "Live patch Network's router(s)",
 "label.load.balancer": "Load balancer",
-"label.loadbalancerinstance": "Assigned VMs",
+"label.loadbalancerinstance": "Assigned Instances",
 "label.loadbalancerrule": "Load balancing rule",
 "label.loadbalancing": "Load balancing",
 "label.loading": "Loading",
 "label.local": "Local",
 "label.local.storage": "Local storage",
-"label.local.storage.enabled": "Enable local storage for user VMs",
+"label.local.storage.enabled": "Enable local storage for User Instances",
 "label.local.storage.enabled.system.vms": "Enable local storage for system VMs",
-"label.localstorageenabled": "Enable local storage for user VMs",
+"label.localstorageenabled": "Enable local storage for User Instances",
 "label.localstorageenabledforsystemvm": "Enable local storage for system VMs",
 "label.locked": "Locked",
 "label.login": "Login",
@@ -1170,6 +1244,7 @@
 "label.logout": "Logout",
 "label.lun": "LUN",
 "label.lun.number": "LUN #",
+"label.lxc": "LXC",
 "label.lxcnetworklabel": "LXC traffic label",
 "label.macaddress": "MAC address",
 "label.macaddress.example": "The MAC address. Example: 01:23:45:67:89:ab",
@@ -1179,12 +1254,12 @@
 "label.maintenance": "Maintenance",
 "label.majorsequence": "Major Sequence",
 "label.make": "Make",
-"label.make.project.owner": "Make account project owner",
-"label.make.user.project.owner": "Make user project owner",
+"label.make.project.owner": "Make Account project owner",
+"label.make.user.project.owner": "Make User project owner",
 "label.makeredundant": "Make redundant",
 "label.manage": "Manage",
-"label.manage.vpn.user": "Manage VPN users",
-"label.managed.instances": "Managed instances",
+"label.manage.vpn.user": "Manage VPN Users",
+"label.managed.instances": "Managed Instances",
 "label.managedstate": "Managed state",
 "label.management": "Management",
 "label.management.ips": "Management IP addresses",
@@ -1194,6 +1269,7 @@
 "label.matchall": "Match all",
 "label.max.primary.storage": "Max. primary (GiB)",
 "label.max.secondary.storage": "Max. secondary (GiB)",
+"label.max.migrations": "Max. migrations",
 "label.maxcpu": "Max. CPU cores",
 "label.maxcpunumber": "Max CPU cores",
 "label.maxdatavolumeslimit": "Max data volumes limit",
@@ -1201,19 +1277,19 @@
 "label.maxguestslimit": "Max guest limit",
 "label.maxhostspercluster": "Max hosts per cluster",
 "label.maximum": "Maximum",
-"label.maxinstance": "Max instances",
+"label.maxinstance": "Max Instances",
 "label.maxiops": "Max IOPS",
 "label.maxmembers": "Max members",
 "label.maxmemory": "Max. memory (MiB)",
-"label.maxnetwork": "Max. networks",
+"label.maxnetwork": "Max. Networks",
 "label.maxprimarystorage": "Max. primary storage (GiB)",
 "label.maxproject": "Max. projects",
 "label.maxpublicip": "Max. public IPs",
 "label.maxsecondarystorage": "Max. secondary storage (GiB)",
 "label.maxsize": "Maximum size",
-"label.maxsnapshot": "Max. snapshots",
-"label.maxtemplate": "Max. templates",
-"label.maxuservm": "Max. user VMs",
+"label.maxsnapshot": "Max. Snapshots",
+"label.maxtemplate": "Max. Templates",
+"label.maxuservm": "Max. User Instances",
 "label.maxvolume": "Max. volumes",
 "label.maxvpc": "Max. VPCs",
 "label.may.continue": "You may now continue.",
@@ -1229,25 +1305,28 @@
 "label.memoryallocatedgb": "Memory allocated",
 "label.memorylimit": "Memory limits (MiB)",
 "label.memorymaxdeviation": "Deviation",
-"label.memorytotal": "Memory allocated",
+"label.memorytotal": "Memory total",
 "label.memorytotalgb": "Memory total",
 "label.memoryused": "Used memory",
 "label.memoryusedgb": "Memory used",
 "label.memused": "Memory usage",
 "label.menu.security.groups": "Security groups",
 "label.menu.service.offerings": "Service offerings",
+"label.metadata": "Metadata",
+"label.metadata.description": "Metadata of the Object",
+"label.metadata.upload.description": "Set metadata for the object",
 "label.metrics": "Metrics",
 "label.migrate.allowed": "Migrate allowed",
 "label.migrate.data.from.image.store": "Migrate data from image store",
-"label.migrate.instance.to": "Migrate instance to",
-"label.migrate.instance.to.host": "Migrate instance to another host",
-"label.migrate.instance.to.ps": "Migrate instance to another primary storage",
-"label.migrate.instance.single.storage": "Migrate all volume(s) of the instance to a single primary storage",
-"label.migrate.instance.specific.storages": "Migrate volume(s) of the instance to specific primary storages",
+"label.migrate.instance.to": "Migrate Instance to",
+"label.migrate.instance.to.host": "Migrate Instance to another host",
+"label.migrate.instance.to.ps": "Migrate Instance to another primary storage",
+"label.migrate.instance.single.storage": "Migrate all volume(s) of the Instance to a single primary storage",
+"label.migrate.instance.specific.storages": "Migrate volume(s) of the Instance to specific primary storages",
 "label.migrate.systemvm.to": "Migrate system VM to",
 "label.migrate.volume": "Migrate volume",
-"message.memory.usage.info.hypervisor.additionals": "The data shown may not reflect the actual memory usage if the VM does not have the additional hypervisor tools installed",
-"message.memory.usage.info.negative.value": "If the VM's memory usage cannot be obtained from the hypervisor, the lines for free memory in the raw data graph and memory usage in the percentage graph will be disabled",
+"message.memory.usage.info.hypervisor.additionals": "The data shown may not reflect the actual memory usage if the Instance does not have the additional hypervisor tools installed",
+"message.memory.usage.info.negative.value": "If the Instance's memory usage cannot be obtained from the hypervisor, the lines for free memory in the raw data graph and memory usage in the percentage graph will be disabled",
 "message.migrate.volume.tooltip": "Volume can be migrated to any suitable storage pool. Admin has to choose the appropriate disk offering to replace, that supports the new storage pool",
 "label.migrate.with.storage": "Migrate with storage",
 "label.migrating": "Migrating",
@@ -1280,7 +1359,7 @@
 "label.move.to.top": "Move to top",
 "label.move.up.row": "Move up one row",
 "label.my.isos": "My ISOs",
-"label.my.templates": "My templates",
+"label.my.templates": "My Templates",
 "label.na": "N/A",
 "label.name": "Name",
 "label.name.optional": "Name (Optional)",
@@ -1294,7 +1373,7 @@
 "label.network": "Network",
 "label.network.acl": "Network ACL",
 "label.network.acl.lists": "Network ACL lists",
-"label.network.addvm": "Add network to VM",
+"label.network.addvm": "Add Network to Instance",
 "label.network.desc": "Network desc",
 "label.network.domain": "Network domain",
 "label.network.label.display.for.blank.value": "Use default gateway",
@@ -1325,28 +1404,32 @@
 "label.networktype": "Network type",
 "label.networkwrite": "Network write",
 "label.new": "New",
-"label.new.autoscale.vmgroup": "New AutoScale VM Group",
-"label.new.instance.group": "New instance group",
+"label.new.autoscale.vmgroup": "New AutoScale Instance Group",
+"label.new.instance.group": "New Instance group",
 "label.new.password": "New password",
 "label.new.project": "New project",
 "label.new.secondaryip.description": "Enter new secondary IP address",
 "label.new.tag": "New tag",
-"label.new.vm": "New VM",
+"label.new.vm": "New Instance",
 "label.newdiskoffering": "New offering",
-"label.newinstance": "New instance",
+"label.newinstance": "New Instance",
 "label.newname": "New name",
 "label.next": "Next",
 "label.nfs": "NFS",
 "label.nfsserver": "NFS server",
 "label.nic": "NIC",
 "label.nicadaptertype": "NIC adapter type",
+"label.nicmultiqueuenumber" : "NIC multiqueue number",
+"label.nicmultiqueuenumber.tooltip" : "NIC multiqueue number. This supports only KVM. The value \"-1\" indicates the NIC multiqueue number will be set to the vCPU number of the Instance.",
+"label.nicpackedvirtqueuesenabled" : "NIC packed virtqueues enabled",
+"label.nicpackedvirtqueuesenabled.tooltip" : "Enable NIC packed virtqueues or not. This supports only KVM with QEMU >= 4.2.0 and Libvirt >=6.3.0.",
 "label.nics": "NICs",
 "label.no": "No",
 "label.no.data": "No data to show",
 "label.no.errors": "No recent errors",
 "label.no.items": "No available Items",
 "label.no.matching.offering": "No matching offering found",
-"label.no.matching.network": "No matching networks found",
+"label.no.matching.network": "No matching Networks found",
 "label.noderootdisksize": "Node root disk size (in GB)",
 "label.nodiskcache": "No disk cache",
 "label.none": "None",
@@ -1358,7 +1441,19 @@
 "label.number": "#Rule",
 "label.numretries": "Number of retries",
 "label.nvpdeviceid": "ID",
+"label.oauth.configuration": "OAuth configuration",
+"label.oauth.verification": "OAuth verification",
 "label.ocfs2": "OCFS2",
+"label.object.storage" : "Object Storage",
+"label.object.presigned.url": "Presigned URL",
+"label.object.presigned.url.description" : "Presigned URL of the object in order to access it without authentication.",
+"label.object.url.description" : "URL of the object",
+"label.objectstore" : "Object Storage",
+"label.objectstore.search" : "Prefix based search in current directory",
+"label.add.object.storage" : "Add Object Storage",
+"label.add.key.value": "Add key value pair",
+"label.action.update.object.storage" : "Update Object Storage",
+"label.action.delete.object.storage" : "Delete Object Storage",
 "label.of": "of",
 "label.of.month": "of month",
 "label.offerha": "Offer HA",
@@ -1380,7 +1475,12 @@
 "label.operator.equal": "Equals to",
 "label.optional": "Optional",
 "label.order": "Order",
-"label.oscategoryid": "OS preference",
+"label.oscategoryid": "OS category",
+"label.oscategoryname": "OS category name",
+"label.osname": "OS name",
+"label.osdisplayname": "OS name",
+"label.osmappingcheckenabled": "Check OS name with hypervisor",
+"label.osnameforhypervisor": "Hypervisor mapping name",
 "label.ostypeid": "OS type",
 "label.osdistribution": "OS distribution",
 "label.ostypename": "OS type",
@@ -1399,13 +1499,14 @@
 "label.overridepublictraffic": "Override public-traffic",
 "label.override.root.diskoffering": "Override root disk offering",
 "label.ovf.properties": "vApp properties",
+"label.ovm3": "OVM3",
 "label.ovm3cluster": "Native Clustering",
 "label.ovm3networklabel": "OVM3 traffic label",
 "label.ovm3pool": "Native pooling",
 "label.ovm3vip": "Primary VIP",
 "label.ovmnetworklabel": "OVM traffic label",
 "label.ovs": "OVS",
-"label.owner.account": "Owner account",
+"label.owner.account": "Owner Account",
 "label.owners": "Owners",
 "label.pa": "Palo Alto",
 "label.page": "page",
@@ -1418,14 +1519,17 @@
 "label.parentname": "Parent",
 "label.passive": "Passive",
 "label.password": "Password",
+"label.password.default": "Default Password",
 "label.password.reset.confirm": "Password has been reset to ",
+"label.password.tooltip": "The password for the Host",
 "label.passwordenabled": "Password enabled",
 "label.path": "Path",
 "label.patp": "Palo Alto threat profile",
 "label.pavr": "Virtual router",
 "label.payload": "Payload",
 "label.pcidevice": "GPU",
-"label.per.account": "Per account",
+"label.pending.jobs": "Pending Jobs",
+"label.per.account": "Per Account",
 "label.per.zone": "Per zone",
 "label.percentage": "Percentage",
 "label.perfectforwardsecrecy": "Perfect forward secrecy",
@@ -1433,9 +1537,9 @@
 "label.performfreshchecks": "Perform fresh checks",
 "label.permission": "Permission",
 "label.permissions": "permissions",
-"label.physical.network": "Physical network",
-"label.physicalnetworkid": "Physical network",
-"label.physicalnetworkname": "Physical network name",
+"label.physical.network": "Physical Network",
+"label.physicalnetworkid": "Physical Network",
+"label.physicalnetworkname": "Physical Network name",
 "label.physicalsize": "Physical size",
 "label.ping.path": "Ping path",
 "label.pkcs.private.certificate": "PKCS#8 private certificate",
@@ -1463,10 +1567,15 @@
 "label.preferred": "Preferred",
 "label.prefix": "Prefix",
 "label.prefix.type": "Prefix type",
+"label.prepare.for.shutdown": "Prepare for Shutdown",
 "label.prepareformaintenance": "Prepare for Maintenance",
 "label.presetup": "PreSetup",
 "label.prev": "Prev",
 "label.previous": "Previous",
+"label.primera.username.tooltip": "The username with edit privileges",
+"label.primera.url.tooltip": "URL designating the Primera storage array endpoint, formatted as: http[s]://HOSTNAME:PORT?cpg=NAME&hostset=NAME[&skipTlsValidation=true][&snapCPG=NAME][&taskWaitTimeoutMs=#][&keyttl=#][&connectTimeoutMs=#] where values in [] are optional.",
+"label.flashArray.username.tooltip": "The username with edit privileges",
+"label.flashArray.url.tooltip": "URL designating the Flash Array endpoint, formatted as: http[s]://HOSTNAME:PORT?pod=NAME&hostgroup=NAME[&skipTlsValidation=true][&postCopyWaitMs=#][&keyttl=#][&connectTimeoutMs=#][&apiLoginVersion=#][&apiVersion=#] where values in [] are optional.",
 "label.primary": "Primary",
 "label.primary.storage": "Primary storage",
 "label.primary.storage.allocated": "Primary storage allocated",
@@ -1480,7 +1589,7 @@
 "label.privateinterface": "Private interface",
 "label.privateip": "Private IP address",
 "label.privatekey": "Private key",
-"label.privatenetwork": "Private network",
+"label.privatenetwork": "Private Network",
 "label.privateport": "Private port",
 "label.profilename": "Profile",
 "label.project": "Project",
@@ -1490,7 +1599,7 @@
 "label.project.role": "Project role",
 "label.project.roles": "Project roles",
 "label.project.view": "Project view",
-"label.projectaccountname": "Project account name",
+"label.projectaccountname": "Project Account name",
 "label.projectid": "Project ID",
 "label.projectlimit": "Project limits",
 "label.projectname": "Project",
@@ -1519,18 +1628,18 @@
 "label.publicip": "IP address",
 "label.publicipid": "IP address ID",
 "label.publickey": "Public key",
-"label.publicnetwork": "Public network",
+"label.publicnetwork": "Public Network",
 "label.publicport": "Public port",
 "label.purpose": "Purpose",
 "label.qostype": "QoS type",
 "label.quickview": "Quick view",
-"label.quiescevm": "Quiesce VM",
+"label.quiescevm": "Quiesce Instance",
 "label.quiettime": "Quiet time (in sec)",
 "label.quota": "Quota",
 "label.quota.add.credits": "Add credits",
 "label.quota.configuration": "Quota configuration",
 "label.quota.credits": "Credits",
-"label.quota.email.edit": "Edit Email template",
+"label.quota.email.edit": "Edit Email Template",
 "label.quota.enforce": "Enforce Quota",
 "label.quota.statement": "Statement",
 "label.quota.statement.balance": "Quota balance",
@@ -1565,26 +1674,29 @@
 "label.reason": "Reason",
 "label.reboot": "Reboot",
 "label.receivedbytes": "Bytes received",
-"label.recover.vm": "Recover VM",
+"label.recover.vm": "Recover Instance",
 "label.redirect": "Redirect to:",
+"label.redirecturi": "Redirect URI",
 "label.redundantrouter": "Redundant router",
 "label.redundantstate": "Redundant state",
 "label.redundantvpcrouter": "Redundant VPC",
 "label.refresh": "Refresh",
 "label.region": "Region",
-"label.register.template": "Register template",
+"label.register.oauth": "Register OAuth",
+"label.register.template": "Register Template",
 "label.register.user.data": "Register a userdata",
-"label.reinstall.vm": "Reinstall VM",
+"label.reinstall.vm": "Reinstall Instance",
 "label.reject": "Reject",
 "label.related": "Related",
 "label.relationaloperator": "Operator",
 "label.release": "Release",
-"label.release.account": "Release from account",
+"label.release.account": "Release from Account",
 "label.release.dedicated.cluster": "Release dedicated cluster",
 "label.release.dedicated.host": "Release dedicated host",
 "label.release.dedicated.pod": "Release dedicated pod",
 "label.release.dedicated.zone": "Release dedicated zone",
 "label.releasing.ip": "Releasing IP",
+"label.remote.instances": "Remote Instances",
 "label.remove": "Remove",
 "label.remove.annotation": "Remove comment",
 "label.remove.egress.rule": "Remove egress rule",
@@ -1593,19 +1705,19 @@
 "label.remove.ldap": "Remove LDAP",
 "label.remove.logical.network": "Remove Network from logical router",
 "label.remove.logical.router": "Remove logical router",
-"label.remove.network.offering": "Remove network offering",
-"label.remove.network.route.table": "Remove Tungsten Fabric network routing table",
+"label.remove.network.offering": "Remove Network offering",
+"label.remove.network.route.table": "Remove Tungsten Fabric Network routing table",
 "label.remove.pf": "Remove port forwarding rule",
 "label.remove.policy": "Remove policy",
-"label.remove.project.account": "Remove account from project",
+"label.remove.project.account": "Remove Account from project",
 "label.remove.project.role": "Remove project role",
-"label.remove.project.user": "Remove user from project",
+"label.remove.project.user": "Remove User from project",
 "label.remove.routing.policy": "Remove Tungsten-Fabric routing policy",
 "label.remove.rule": "Remove rule",
 "label.remove.ssh.key.pair": "Remove SSH Key pair",
 "label.remove.tungsten.tag": "Remove Tag",
-"label.remove.user.data": "Remove userdata",
-"label.remove.vm.from.lb": "Remove VM from load balancer rule",
+"label.remove.user.data": "Remove Userdata",
+"label.remove.vm.from.lb": "Remove Instance from load balancer rule",
 "label.remove.vmware.datacenter": "Remove VMware Datacenter",
 "label.remove.vpc": "Remove VPC",
 "label.remove.vpc.offering": "Remove VPC offering",
@@ -1631,7 +1743,7 @@
 "label.reset.ssh.key.pair": "Reset SSH key pair",
 "label.reset.to.default": "Reset to default",
 "label.reset.userdata.on.autoscale.vm.group": "Reset Userdata on AutoScale VM Group",
-"label.reset.userdata.on.vm": "Reset Userdata on VM",
+"label.reset.userdata.on.vm": "Reset Userdata on Instance",
 "label.reset.vpn.connection": "Reset VPN connection",
 "label.resource": "Resource",
 "label.resource.limit.exceeded": "Resource limit exceeded",
@@ -1641,7 +1753,7 @@
 "label.resources": "Resources",
 "label.resourcestate": "Resource state",
 "label.resourcetype": "Resource type",
-"label.restart.network": "Restart network",
+"label.restart.network": "Restart Network",
 "label.restart.vpc": "Restart VPC",
 "label.restartrequired": "Restart required",
 "label.restore": "Restore",
@@ -1665,8 +1777,8 @@
 "label.router.health.check.success": "Success",
 "label.router.health.checks": "Health checks",
 "label.routercount": "Total of virtual routers",
-"label.routerip": "IPv4 address for the VR in this shared network.",
-"label.routeripv6": "IPv6 address for the VR in this shared network.",
+"label.routerip": "IPv4 address for the VR in this Network.",
+"label.routeripv6": "IPv6 address for the VR in this Network.",
 "label.resourcegroup": "Resource group",
 "label.routing.policy": "Routing policy",
 "label.routing.policy.terms": "Routing policy terms",
@@ -1679,7 +1791,7 @@
 "label.rules.file.to.import": "Rule definitions CSV file to import",
 "label.run.proxy.locally": "Run proxy locally",
 "label.running": "Running",
-"label.running.vms": "Running VMs",
+"label.running.vms": "Running Instances",
 "label.s2scustomergatewayid": "Site to site customer gateway ID",
 "label.s2svpngatewayid": "Site to site VPN gateway ID",
 "label.s3.access.key": "Access key",
@@ -1699,15 +1811,18 @@
 "label.saturday": "Saturday",
 "label.save": "Save",
 "label.save.new.rule": "Save new rule",
-"label.scale.vm": "Scale VM",
+"label.scale.vm": "Scale Instance",
 "label.scaledown.policies": "ScaleDown policies",
 "label.scaledown.policy": "ScaleDown policy",
 "label.scaleup.policies": "ScaleUp policies",
 "label.scaleup.policy": "ScaleUp policy",
 "label.schedule": "Schedule",
+"label.schedule.add": "Add schedule",
 "label.scheduled.backups": "Scheduled backups",
-"label.scheduled.snapshots": "Scheduled snapshots",
+"label.scheduled.snapshots": "Scheduled Snapshots",
+"label.schedules": "Schedules",
 "label.scope": "Scope",
+"label.scope.tooltip": "Primary Storage Pool Scope",
 "label.search": "Search",
 "label.secondary.isolated.vlan.type.isolated": "Isolated",
 "label.secondary.isolated.vlan.type.promiscuous": "Promiscuous",
@@ -1725,18 +1840,21 @@
 "label.select": "Select",
 "label.see.more.info.cpu.usage": "See more info about CPU usage",
 "label.see.more.info.memory.usage": "See more info about memory usage",
-"label.see.more.info.network.usage": "See more info about network usage",
+"label.see.more.info.network.usage": "See more info about Network usage",
 "label.see.more.info.disk.usage": "See more info about disk usage",
 "label.see.more.info.shown.charts": "See more info about the shown charts",
 "label.select-view": "Select view",
 "label.select.a.zone": "Select a zone",
 "label.select.deployment.infrastructure": "Select deployment infrastructure",
+"label.select.guest.os.type": "Please select the guest OS type",
 "label.select.network": "Select Network",
 "label.select.period": "Select period",
 "label.select.project": "Select project",
 "label.select.projects": "Select projects",
 "label.select.ps": "Select primary storage",
-"label.select.tier": "Select tier",
+"label.select.root.disk": "Select the ROOT disk",
+"label.select.source.vcenter.datacenter": "Select the source VMware vCenter Datacenter",
+"label.select.tier": "Select Network Tier",
 "label.select.zones": "Select zones",
 "label.select.2fa.provider": "Select the provider",
 "label.self": "Mine",
@@ -1765,13 +1883,15 @@
 "label.sessions": "Active client sessions",
 "label.set.default.nic": "Set default NIC",
 "label.set.reservation": "Set reservation",
-"label.set.reservation.desc": "(optional) Please specify an account to be associated with this IP range.<br/><br/>System VMs: Enable dedication of public IP range for SSVM and CPVM, account field disabled. Reservation strictness defined on 'system.vm.public.ip.reservation.mode.strictness'.",
+"label.set.reservation.desc": "(optional) Please specify an Account to be associated with this IP range.<br/><br/>System VMs: Enable dedication of public IP range for SSVM and CPVM, Account field disabled. Reservation strictness defined on 'system.vm.public.ip.reservation.mode.strictness'.",
 "label.setting": "Setting",
 "label.settings": "Settings",
 "label.setup": "Setup",
 "label.shared": "Shared",
 "label.sharedexecutable": "Shared",
 "label.sharedmountpoint": "SharedMountPoint",
+"label.sharedrouterip": "IPv4 address for the VR in this shared Network.",
+"label.sharedrouteripv6": "IPv6 address for the VR in this shared Network.",
 "label.sharewith": "Share with",
 "label.showing": "Showing",
 "label.shrinkok": "Shrink OK",
@@ -1792,15 +1912,18 @@
 "label.snapshot.name": "Snapshot name",
 "label.snapshotlimit": "Snapshot limits",
 "label.snapshotmemory": "Snapshot memory",
-"label.snapshots": "Snapshots",
+"label.snapshots": "Volume Snapshots",
 "label.snapshottype": "Snapshot Type",
 "label.sockettimeout": "Socket timeout",
 "label.softwareversion": "Software version",
+"label.source": "Select Import-Export Source Hypervisor",
 "label.source.based": "SourceBased",
 "label.sourcecidr": "Source CIDR",
+"label.sourcehost": "Source host",
 "label.sourceipaddress": "Source IP address",
 "label.sourceipaddressnetworkid": "Network ID of source IP address",
 "label.sourcenat": "Source NAT",
+"label.sourcenatipaddress": "Source NAT IP address",
 "label.sourcenatsupported": "Source NAT supported",
 "label.sourcenattype": "Supported source NAT type",
 "label.sourceport": "Source port",
@@ -1830,7 +1953,7 @@
 "label.userdata.registered": "Stored Userdata",
 "label.userdata.do.override": "Userdata override",
 "label.userdata.do.append": "Userdata append",
-"label.userdatapolicy.tooltip": "Userdata linked to the template can be overridden by userdata provided during VM deploy. Select the override policy as required.",
+"label.userdatapolicy.tooltip": "Userdata linked to the Template can be overridden by Userdata provided during Instance deploy. Select the override policy as required.",
 "label.user.data": "User Data",
 "label.ssh.port": "SSH port",
 "label.sshkeypair": "New SSH key pair",
@@ -1840,10 +1963,10 @@
 "label.start": "Start",
 "label.start.date.and.time": "Start date and time",
 "label.start.ip": "Start IP",
-"label.start.lb.vm": "Start LB VM",
+"label.start.lb.vm": "Start LB Instance",
 "label.start.reserved.system.ip": "Start reserved system IP",
 "label.start.rolling.maintenance": "Start rolling maintenance",
-"label.start.vm": "Start VM",
+"label.start.vm": "Start Instance",
 "label.startdate": "By date (start)",
 "label.starting": "Starting",
 "label.startip": "Start IP",
@@ -1852,6 +1975,7 @@
 "label.startport": "Start port",
 "label.startquota": "Quota value",
 "label.state": "State",
+"label.staticnat": "Static NAT",
 "label.static.routes": "Static routes",
 "label.status": "Status",
 "label.step.1": "Step 1",
@@ -1874,7 +1998,7 @@
 "label.sticky.tablesize": "Table size",
 "label.stop": "Stop",
 "label.stopped": "Stopped",
-"label.stopped.vms": "Stopped VMs",
+"label.stopped.vms": "Stopped Instances",
 "label.stopping": "Stopping",
 "label.storage": "Storage",
 "label.storage.migration.required": "Storage migration required",
@@ -1884,6 +2008,7 @@
 "label.storagemotionenabled": "Storage motion enabled",
 "label.storagepolicy": "Storage policy",
 "label.storagepool": "Storage pool",
+"label.storagepool.tooltip": "Destination Storage Pool. Volume should be located in this Storage Pool",
 "label.storagetags": "Storage tags",
 "label.storagetype": "Storage type",
 "label.strict": "Strict",
@@ -1891,6 +2016,7 @@
 "label.submit": "Submit",
 "label.succeeded": "Succeeded",
 "label.success": "Success",
+"label.success.migrations": "Successful migrations",
 "label.success.set": "Successfully set",
 "label.success.updated": "Successfully updated",
 "label.suitability": "Suitability",
@@ -1902,7 +2028,7 @@
 "label.supportsha": "Supports HA",
 "label.supportspublicaccess": "Supports public access",
 "label.supportsstrechedl2subnet": "Supports stretched L2 subnet",
-"label.supportsvmautoscaling": "Supports VM auto scaling",
+"label.supportsvmautoscaling": "Supports auto scaling",
 "label.suspend.project": "Suspend project",
 "label.switch.type": "Switch type",
 "label.sync.storage": "Sync storage pool",
@@ -1935,12 +2061,12 @@
 "label.tariffvalue": "Tariff value",
 "label.tcp": "TCP",
 "label.tcp.proxy": "TCP proxy",
-"label.template": "Select a template",
-"label.template.select.existing": "Select an existing template",
-"label.template.temporary.import": "Use a temporary template for import",
+"label.template": "Select a Template",
+"label.template.select.existing": "Select an existing Template",
+"label.template.temporary.import": "Use a temporary Template for import",
 "label.templatebody": "Body",
 "label.templatefileupload": "Local file",
-"label.templateid": "Select a template",
+"label.templateid": "Select a Template",
 "label.templateiso": "Template/ISO",
 "label.templatelimit": "Template limits",
 "label.templatename": "Template",
@@ -1972,19 +2098,22 @@
 "label.timeout": "Timeout",
 "label.timeout.in.second ": " Timeout (seconds)",
 "label.timezone": "Timezone",
+"label.tmppath": "Temp Path",
+"label.tmppath.tooltip": "Temporary Path to store disk images on External Host before copying to destination storage pool. Default is /tmp",
 "label.to": "to",
 "label.token": "Token",
 "label.token.for.dashboard.login": "Token for dashboard login can be retrieved using following command",
 "label.tools": "Tools",
 "label.total": "Total",
-"label.total.network": "Total networks",
-"label.total.vms": "Total VMs",
+"label.total.network": "Total Networks",
+"label.total.vms": "Total Instances",
 "label.total.volume": "Total volumes",
 "label.totalcpu": "Total CPU",
 "label.traffic.label": "Traffic label",
 "label.traffic.types": "Traffic types",
 "label.traffictype": "Traffic type",
 "label.transportzoneuuid": "Transport zone UUID",
+"label.trigger.shutdown": "Trigger Safe Shutdown",
 "label.try.again": "Try again",
 "label.tuesday": "Tuesday",
 "label.two.factor.authentication.secret.key": "Your Two factor authentication secret key",
@@ -2013,10 +2142,10 @@
 "label.tungstenproviderhostname": "Provider hostname",
 "label.tungstenproviderintrospectport": "Provider introspect port",
 "label.tungstenproviderport": "Provider port",
-"label.tungstenprovideruuid": "Provider Uuid",
+"label.tungstenprovideruuid": "Provider UUID",
 "label.tungstenprovidervrouterport": "Provider vrouter port",
 "label.tungstenroutingpolicyterm": "Network",
-"label.tungstenvms": "VMs",
+"label.tungstenvms": "Instances",
 "label.type": "Type",
 "label.type.id": "Type ID",
 "label.ucs": "UCS",
@@ -2029,30 +2158,33 @@
 "label.unit": "Usage unit",
 "label.unknown": "Unknown",
 "label.unlimited": "Unlimited",
-"label.unmanage.instance": "Unmanage instance",
-"label.unmanaged.instance": "Unmanaged instance",
-"label.unmanaged.instances": "Unmanaged instances",
+"label.unmanaged": "Unmanaged",
+"label.unmanage.instance": "Unmanage Instance",
+"label.unmanaged.instance": "Unmanaged Instance",
+"label.unmanaged.instances": "Unmanaged Instances",
 "label.untagged": "Untagged",
 "label.up": "Up",
 "label.updateinsequence": "Update in sequence",
-"label.update.autoscale.vmgroup": "Update AutoScale VM group",
+"label.update.autoscale.vmgroup": "Update AutoScale Instance group",
 "label.update.condition": "Update condition",
-"label.update.instance.group": "Update instance group",
+"label.update.instance.group": "Update Instance group",
 "label.update.ip.range": "Update IP range",
-"label.update.network": "Update network",
-"label.update.physical.network": "Update physical network",
+"label.update.network": "Update Network",
+"label.update.physical.network": "Update physical Network",
 "label.update.project.role": "Update project role",
 "label.update.ssl": " SSL certificate",
 "label.update.to": "updated to",
 "label.update.traffic.label": "Update traffic labels",
-"label.update.vmware.datacenter": "Update VMware datacenter",
+"label.update.vmware.datacenter": "Update VMWare datacenter",
 "label.updating": "Updating",
-"label.upgrade.router.newer.template": "Upgrade router to use newer template",
+"label.upgrade.router.newer.template": "Upgrade router to use newer Template",
 "label.upload": "Upload",
+"label.upload.description": "Path to upload objects at",
+"label.upload.path": "Upload path",
 "label.upload.icon": "Upload icon",
 "label.upload.iso.from.local": "Upload ISO from local",
 "label.upload.resource.icon": "Upload icon",
-"label.upload.template.from.local": "Upload template from local",
+"label.upload.template.from.local": "Upload Template from local",
 "label.upload.volume": "Upload volume",
 "label.upload.volume.from.local": "Upload volume from local",
 "label.upload.volume.from.url": "Upload volume from URL",
@@ -2073,6 +2205,7 @@
 "label.userdata": "Userdata",
 "label.userdatal2": "User data",
 "label.username": "Username",
+"label.username.tooltip": "The Username for the Host",
 "label.users": "Users",
 "label.usersource": "User type",
 "label.using.cli": "Using CLI",
@@ -2100,44 +2233,83 @@
 "label.view.console": "View console",
 "label.viewing": "Viewing",
 "label.virtualmachine": "Instance",
-"label.virtualmachinecount": "VM Count",
+"label.virtualmachinecount": "Instances Count",
 "label.virtual.machine": "Virtual machine",
 "label.virtual.machines": "Virtual machines",
-"label.virtual.network": "Virtual network",
-"label.virtual.networking": "Virtual networking",
+"label.virtual.network": "Virtual Network",
+"label.virtual.networking": "Virtual Networking",
 "label.virtual.routers": "Virtual routers",
-"label.virtualmachineid": "VM ID",
-"label.virtualmachinename": "VM name",
+"label.virtualmachineid": "Instance ID",
+"label.virtualmachinename": "Instance name",
 "label.virtualsize": "Virtual Size",
 "label.vlan": "VLAN/VNI",
 "label.vlan.range": "VLAN/VNI range",
 "label.vlan.vni.ranges": "VLAN/VNI range(s)",
 "label.vlanid": "VLAN/VNI ID",
 "label.vlanrange": "VLAN/VNI range",
-"label.vm": "VM",
-"label.vm.add": "Add instance",
-"label.vm.password": "Password of the VM is",
-"label.vm.snapshots": "VM snapshots",
+"label.vm": "Instance",
+"label.vm.add": "Add Instance",
+"label.vm.password": "Password of the Instance is",
+"label.vm.snapshots": "Instance Snapshots",
 "label.vm.start": "Start",
 "label.vm.stats.filter.period": "From <b>{startDate}</b> to <b>{endDate}</b>",
 "label.vm.stats.filter.starting": "Starting <b>{startDate}</b>.",
 "label.vm.stats.filter.up.to": "Up to <b>{endDate}</b>.",
 "label.vmfs": "VMFS",
-"label.vmipaddress": "VM IP address",
+"label.vmipaddress": "Instance IP address",
 "label.vmlimit": "Instance limits",
-"label.vmname": "VM name",
-"label.vms": "VMs",
-"label.vmstate": "VM state",
-"label.vmtotal": "Total of VMs",
+"label.vmname": "Instance name",
+"label.vms": "Instances",
+"label.vmscheduleactions": "Actions",
+"label.vmstate": "Instance state",
+"label.vmtotal": "Total of Instances",
+"label.vmware": "VMware",
 "label.vmware.storage.policy": "VMWare storage policy",
 "label.vmwaredcid": "VMware datacenter ID",
 "label.vmwaredcname": "VMware datacenter name",
 "label.vmwaredcvcenter": "VMware datacenter vCenter",
 "label.vmwarenetworklabel": "VMware traffic label",
+"label.vnf.appliance": "VNF Appliance",
+"label.vnf.appliances": "VNF appliances",
+"label.vnf.appliance.add": "Add VNF Appliance",
+"label.vnf.appliance.access.methods": "Management access information for this VNF appliance",
+"label.vnf.app.action.destroy": "Destroy VNF appliance",
+"label.vnf.app.action.edit": "Edit VNF appliance",
+"label.vnf.app.action.expunge": "Expunge VNF appliance",
+"label.vnf.app.action.migrate.to.host": "Migrate VNF appliance to another host",
+"label.vnf.app.action.migrate.to.ps": "Migrate VNF appliance to another primary storage",
+"label.vnf.app.action.recover": "Recover VNF appliance",
+"label.vnf.app.action.scale": "Scale VNF appliance",
+"label.vnf.app.action.start": "Start VNF appliance",
+"label.vnf.app.action.stop": "Stop VNF appliance",
+"label.vnf.app.action.reboot": "Reboot VNF appliance",
+"label.vnf.app.action.reinstall": "Reinstall VNF appliance",
+"label.vnf.cidr.list": "CIDR from which access to the VNF appliance’s Management interface should be allowed from",
+"label.vnf.cidr.list.tooltip": "the CIDR list to forward traffic from to the VNF management interface. Multiple entries must be separated by a single comma character (,). The default value is 0.0.0.0/0.",
+"label.vnf.configure.management": "Configure Firewall and Port Forwarding rules for VNF's management interfaces",
+"label.vnf.configure.management.tooltip": "True by default, security group or network rules (source nat and firewall rules) will be configured for VNF management interfaces. False otherwise. Learn what rules are configured at http://docs.cloudstack.apache.org/en/latest/adminguide/networking/vnf_templates_appliances.html#deploying-vnf-appliances",
+"label.vnf.detail.add": "Add VNF detail",
+"label.vnf.detail.remove": "Remove VNF detail",
+"label.vnf.details": "VNF Details",
+"label.vnf.nic.add": "Add VNF nic",
+"label.vnf.nic.delete": "Delete VNF nic",
+"label.vnf.nic.description": "Description of VNF nic",
+"label.vnf.nic.deviceid": "Device ID of VNF nic. It starts with 0. The NIC with deviceid as 0 for VNF appliance will be the default NIC.",
+"label.vnf.nic.edit": "Edit VNF nic",
+"label.vnf.nic.management": "Management NIC",
+"label.vnf.nic.management.description": "True if the VNF nic is a management interface. False otherwise",
+"label.vnf.nic.mappings": "VNF NIC mappings",
+"label.vnf.nic.name": "Name of VNF nic",
+"label.vnf.nic.remove": "Remove VNF nic",
+"label.vnf.nic.required": "True if VNF nic is required. Otherwise optional",
+"label.vnf.nics": "VNF Nics",
+"label.vnf.settings": "VNF settings",
+"label.vnf.templates": "VNF templates",
+"label.vnf.template.register": "Register VNF template",
 "label.vnmc": "VNMC",
 "label.volgroup": "Volume group",
 "label.volume": "Volume",
-"label.volume.empty": "No data volumes attached to this VM",
+"label.volume.empty": "No data volumes attached to this Instance",
 "label.volume.volumefileupload.description": "Click or drag file to this area to upload.",
 "label.volume.encryption.support": "Volume Encryption Supported",
 "label.volumechecksum": "MD5 checksum",
@@ -2164,7 +2336,7 @@
 "label.vpn": "VPN",
 "label.vpn.connection": "VPN connection",
 "label.vpn.gateway": "VPN gateway",
-"label.vpn.users": "VPN users",
+"label.vpn.users": "VPN Users",
 "label.vpncustomergateway": "IP address of the remote gateway",
 "label.vpncustomergatewayid": "VPN customer gateway",
 "label.vsmipaddress": "Nexus 1000v IP address",
@@ -2186,17 +2358,18 @@
 "label.welcome": "Welcome",
 "label.what.is.cloudstack": "What is CloudStack™?",
 "label.windows": "Windows",
-"label.with.snapshotid": "with snapshot ID",
+"label.with.snapshotid": "with Snapshot ID",
 "label.write": "Write",
 "label.writeback": "Write-back disk caching",
 "label.writecachetype": "Write-cache Type",
 "label.writeio": "Write (IO)",
 "label.writethrough": "Write-through",
 "label.xennetworklabel": "XenServer Traffic Label",
+"label.xenserver": "XenServer",
 "label.xenservertoolsversion61plus": "Original XS Version is 6.1+",
 "label.yes": "Yes",
-"label.yourinstance": "Your instance",
-"label.your.autoscale.vmgroup": "Your autoscale vm group",
+"label.yourinstance": "Your Instance",
+"label.your.autoscale.vmgroup": "Your autoscale Instance group",
 "label.zone": "Zone",
 "label.zone.dedicated": "Zone dedicated",
 "label.zone.details": "Zone details",
@@ -2207,93 +2380,110 @@
 "label.zonename": "Zone",
 "label.zonenamelabel": "Zone name",
 "label.zones": "Zones",
-"label.zonewizard.traffictype.storage": "Storage: Traffic between primary and secondary storage servers, such as VM templates and snapshots.",
+"label.zonewizard.traffictype.storage": "Storage: Traffic between primary and secondary storage servers, such as Instance Templates and Snapshots.",
+"label.buckets": "Buckets",
+"label.objectstorageid": "Object Storage Pool",
+"label.bucket.update": "Update Bucket",
+"label.bucket.delete": "Delete Bucket",
+"label.quotagb": "Quota in GB",
+"label.encryption": "Encryption",
+"label.versioning": "Versioning",
+"label.objectlocking": "Object Lock",
+"label.bucket.policy": "Bucket Policy",
+"label.usersecretkey": "Secret Key",
+"label.create.bucket": "Create Bucket",
 "message.acquire.ip.failed": "Failed to acquire IP.",
 "message.action.acquire.ip": "Please confirm that you want to acquire new IP.",
 "message.action.cancel.maintenance": "Your host has been successfully canceled for maintenance. This process can take up to several minutes.",
 "message.action.cancel.maintenance.mode": "Please confirm that you want to cancel this maintenance.",
-"message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create snapshot from VM Snapshot",
-"message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscale vm group.",
+"message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create Snapshot from Instance Snapshot",
+"message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscale Instance group.",
 "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?",
 "message.action.delete.cluster": "Please confirm that you want to delete this cluster.",
-"message.action.delete.disk.offering": "Please confirm that you want to delete this disk offering.",
 "message.action.delete.domain": "Please confirm that you want to delete this domain.",
 "message.action.delete.external.firewall": "Please confirm that you would like to remove this external firewall. Warning: If you are planning to add back the same external firewall, you must reset usage data on the device.",
 "message.action.delete.external.load.balancer": "Please confirm that you would like to remove this external load balancer. Warning: If you are planning to add back the same external load balancer, you must reset usage data on the device.",
 "message.action.delete.ingress.rule": "Please confirm that you want to delete this ingress rule.",
-"message.action.delete.instance.group": "Please confirm that you want to delete the instance group.",
+"message.action.delete.guest.os": "Please confirm that you want to delete this guest os. System defined entry cannot be deleted.",
+"message.action.delete.guest.os.hypervisor.mapping": "Please confirm that you want to delete this guest os hypervisor mapping. System defined entry cannot be deleted.",
+"message.action.delete.instance.group": "Please confirm that you want to delete the Instance group.",
 "message.action.delete.interface.static.route": "Please confirm that you want to remove this interface Static Route?",
 "message.action.delete.iso": "Please confirm that you want to delete this ISO.",
-"message.action.delete.network": "Please confirm that you want to delete this network.",
-"message.action.delete.network.static.route": "Please confirm that you want to remove this network Static Route",
+"message.action.delete.network": "Please confirm that you want to delete this Network.",
+"message.action.delete.network.static.route": "Please confirm that you want to remove this Network Static Route",
 "message.action.delete.nexusvswitch": "Please confirm that you want to delete this nexus 1000v",
 "message.action.delete.node": "Please confirm that you want to delete this node.",
-"message.action.delete.physical.network": "Please confirm that you want to delete this physical network.",
+"message.action.delete.oauth.provider": "Please confirm that you want to delete the OAuth provider.",
+"message.action.delete.physical.network": "Please confirm that you want to delete this physical Network.",
 "message.action.delete.pod": "Please confirm that you want to delete this pod.",
 "message.action.delete.secondary.storage": "Please confirm that you want to delete this secondary storage.",
 "message.action.delete.security.group": "Please confirm that you want to delete this security group.",
-"message.action.delete.service.offering": "Please confirm that you want to delete this service offering.",
-"message.action.delete.snapshot": "Please confirm that you want to delete this snapshot.",
-"message.action.delete.system.service.offering": "Please confirm that you want to delete this system service offering.",
-"message.action.delete.template": "Please confirm that you want to delete this template.",
-"message.action.delete.tungsten.router.table": "Please confirm that you want to remove Route Table from this network?",
-"message.action.delete.volume": "Please confirm that you want to delete this volume. Note: this will not delete any snapshots of this volume.",
+"message.action.delete.snapshot": "Please confirm that you want to delete this Snapshot.",
+"message.action.delete.template": "Please confirm that you want to delete this Template.",
+"message.action.delete.tungsten.router.table": "Please confirm that you want to remove Route Table from this Network?",
+"message.action.delete.volume": "Please confirm that you want to delete this volume. Note: this will not delete any Snapshots of this volume.",
 "message.action.delete.vpn.user": "Please confirm that you want to delete the VPN user.",
 "message.action.delete.zone": "Please confirm that you want to delete this zone.",
-"message.action.destroy.instance": "Please confirm that you want to destroy the instance.",
-"message.action.destroy.instance.with.backups": "Please confirm that you want to destroy the instance. There may be backups associated with the instance which will not be deleted.",
+"message.action.destroy.instance": "Please confirm that you want to destroy the Instance.",
+"message.action.destroy.instance.with.backups": "Please confirm that you want to destroy the Instance. There may be backups associated with the Instance which will not be deleted.",
 "message.action.destroy.systemvm": "Please confirm that you want to destroy the System VM.",
 "message.action.destroy.volume": "Please confirm that you want to destroy the volume.",
-"message.action.disable.2FA.user.auth": "Please confirm that you want to disable user two factor authentication.",
-"message.action.about.mandate.and.disable.2FA.user.auth": "Two factor authentication is mandated for the user, if this is disabled now user will need to setup two factor authentication again during next login. <br><br>Please confirm that you want to disable.",
+"message.action.disable.2FA.user.auth": "Please confirm that you want to disable User two factor authentication.",
+"message.action.about.mandate.and.disable.2FA.user.auth": "Two factor authentication is mandated for the User, if this is disabled now User will need to setup two factor authentication again during next login. <br><br>Please confirm that you want to disable.",
 "message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
-"message.action.disable.physical.network": "Please confirm that you want to disable this physical network.",
+"message.action.disable.disk.offering": "Please confirm that you want to disable this disk offering.",
+"message.action.disable.service.offering": "Please confirm that you want to disable this service offering.",
+"message.action.disable.system.service.offering": "Please confirm that you want to disable this system service offering.",
+"message.action.disable.physical.network": "Please confirm that you want to disable this physical Network.",
 "message.action.disable.pod": "Please confirm that you want to disable this pod.",
 "message.action.disable.static.nat": "Please confirm that you want to disable static NAT.",
 "message.action.disable.zone": "Please confirm that you want to disable this zone.",
 "message.action.download.iso": "Please confirm that you want to download this ISO.",
-"message.action.download.template": "Please confirm that you want to download this template.",
+"message.action.download.template": "Please confirm that you want to download this Template.",
 "message.action.enable.cluster": "Please confirm that you want to enable this cluster.",
-"message.action.enable.physical.network": "Please confirm that you want to enable this physical network.",
+"message.action.enable.disk.offering": "Please confirm that you want to enable this disk offering.",
+"message.action.enable.service.offering": "Please confirm that you want to enable this service offering.",
+"message.action.enable.system.service.offering": "Please confirm that you want to enable this system service offering.",
+"message.action.enable.physical.network": "Please confirm that you want to enable this physical Network.",
 "message.action.enable.pod": "Please confirm that you want to enable this pod.",
 "message.action.enable.zone": "Please confirm that you want to enable this zone.",
-"message.action.expunge.instance": "Please confirm that you want to expunge this instance.",
-"message.action.expunge.instance.with.backups": "Please confirm that you want to expunge this instance. There may be backups associated with the instance which will not be deleted.",
-"message.action.host.enable.maintenance.mode": "Enabling maintenance mode will cause a live migration of all running instances on this host to any available host.",
-"message.action.instance.reset.password": "Please confirm that you want to change the ROOT password for this virtual machine.",
+"message.action.expunge.instance": "Please confirm that you want to expunge this Instance.",
+"message.action.expunge.instance.with.backups": "Please confirm that you want to expunge this Instance. There may be backups associated with the Instance which will not be deleted.",
+"message.action.host.enable.maintenance.mode": "Enabling maintenance mode will cause a live migration of all running Instances on this host to any available host.",
+"message.action.instance.reset.password": "Please confirm that you want to change the ROOT password for this Instance.",
 "message.action.manage.cluster": "Please confirm that you want to manage the cluster.",
-"message.action.patch.router": "Please confirm that you want to live patch the router. <br> This operation is equivalent updating the router packages and restarting the network without cleanup.",
+"message.action.patch.router": "Please confirm that you want to live patch the router. <br> This operation is equivalent updating the router packages and restarting the Network without cleanup.",
 "message.action.patch.systemvm": "Please confirm that you want to patch the System VM.",
-"message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all VMs using volumes from it to be stopped.  Do you want to continue?",
-"message.action.reboot.instance": "Please confirm that you want to reboot this instance.",
+"message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all Instances using volumes from it to be stopped.  Do you want to continue?",
+"message.action.reboot.instance": "Please confirm that you want to reboot this Instance.",
 "message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.",
 "message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.",
 "message.action.recover.volume": "Please confirm that you would like to recover this volume.",
 "message.action.release.ip": "Please confirm that you want to release this IP.",
 "message.action.remove.host": "Please confirm that you want to remove this host.",
 "message.action.remove.logical.router": "Please confirm that you want to remove Logical Router?",
-"message.action.remove.routing.policy": "Please confirm that you want to remove Routing Policy from this network",
+"message.action.remove.routing.policy": "Please confirm that you want to remove Routing Policy from this Network",
 "message.action.release.reserved.ip": "Please confirm that you want to release this reserved IP.",
 "message.action.reserve.ip": "Please confirm that you want to reserve this IP.",
-"message.action.revert.snapshot": "Please confirm that you want to revert the owning volume to this snapshot.",
+"message.action.revert.snapshot": "Please confirm that you want to revert the owning volume to this Snapshot.",
 "message.action.router.health.checks": "Health checks result will be fetched from router.",
 "message.action.router.health.checks.disabled.warning": "Please enable router health checks.",
 "message.action.scale.kubernetes.cluster.warning": "Please do not manually scale the cluster if cluster auto scaling is enabled.",
 "message.action.secondary.storage.read.only": "Please confirm that you want to make this secondary storage read only.",
 "message.action.secondary.storage.read.write": "Please confirm that you want to make this secondary storage read write.",
 "message.action.secure.host": "This will restart the host agent and libvirtd process after applying new X509 certificates, please confirm?",
-"message.action.settings.warning.vm.running": "Please stop the virtual machine to access settings.",
-"message.action.start.instance": "Please confirm that you want to start this instance.",
+"message.action.settings.warning.vm.running": "Please stop the Instance to access settings.",
+"message.action.start.instance": "Please confirm that you want to start this Instance.",
 "message.action.start.router": "Please confirm that you want to start this router.",
 "message.action.start.systemvm": "Please confirm that you want to start this system VM.",
-"message.action.stop.instance": "Please confirm that you want to stop this instance.",
+"message.action.stop.instance": "Please confirm that you want to stop this Instance.",
 "message.action.stop.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to stop this router.",
 "message.action.stop.systemvm": "Please confirm that you want to stop this system VM.",
 "message.action.unmanage.cluster": "Please confirm that you want to unmanage the cluster.",
-"message.action.unmanage.instance": "Please confirm that you want to unmanage the instance.",
-"message.action.unmanage.instances": "Please confirm that you want to unmanage the instances.",
-"message.action.unmanage.virtualmachine": "Please confirm that you want to unmanage the virtual machine.",
-"message.action.vmsnapshot.delete": "Please confirm that you want to delete this VM snapshot. <br>Please notice that the instance will be paused before the snapshot deletion, and resumed after deletion, if it runs on KVM.",
+"message.action.unmanage.instance": "Please confirm that you want to unmanage the Instance.",
+"message.action.unmanage.instances": "Please confirm that you want to unmanage the Instances.",
+"message.action.unmanage.virtualmachine": "Please confirm that you want to unmanage the Instance.",
+"message.action.vmsnapshot.delete": "Please confirm that you want to delete this Instance Snapshot. <br>Please notice that the Instance will be paused before the Snapshot deletion, and resumed after deletion, if it runs on KVM.",
 "message.activate.project": "Are you sure you want to activate this project?",
 "message.add.egress.rule.failed": "Adding new egress rule failed.",
 "message.add.egress.rule.processing": "Adding new egress rule...",
@@ -2312,12 +2502,14 @@
 "message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule",
 "message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...",
 "message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule",
-"message.add.network": "Add a new network for zone: <b><span id=\"zone_name\"></span></b>",
-"message.add.network.acl.failed": "Adding network ACL list failed.",
-"message.add.network.acl.processing": "Adding network ACL list...",
-"message.add.network.failed": "Adding network failed.",
-"message.add.network.processing": "Adding network...",
+"message.add.network": "Add a new Network for zone: <b><span id=\"zone_name\"></span></b>",
+"message.add.network.acl.failed": "Adding Network ACL list failed.",
+"message.add.network.acl.processing": "Adding Network ACL list...",
+"message.add.network.failed": "Adding Network failed.",
+"message.add.network.processing": "Adding Network...",
 "message.add.new.gateway.to.vpc": "Please specify the information to add a new gateway to this VPC.",
+"message.add.physical.network.failed": "Adding physical network failed",
+"message.add.physical.network.processing": "Adding a new physical network...",
 "message.add.pod": "Add a new pod for zone <b><span id=\"add_pod_zone_name\"></span></b>",
 "message.add.pod.during.zone.creation": "Each zone must contain one or more pods. We will add the first pod now. A pod contains hosts and primary storage servers, which you will add in a later step. First, configure a range of reserved IP addresses for CloudStack's internal management traffic. The reserved IP range must be unique for each zone in the cloud.",
 "message.add.port.forward.failed": "Adding new port forwarding rule failed.",
@@ -2334,9 +2526,9 @@
 "message.add.tag.failed": "Failed to add new tag.",
 "message.add.tag.for.networkacl": "Add tag for NetworkACL",
 "message.add.tag.processing": "Adding new tag...",
-"message.add.template": "Please enter the following data to create your new template",
+"message.add.template": "Please enter the following data to create your new Template",
 "message.add.tungsten.routing.policy.available": "The Tungsten-Fabric routing policy is ready to launch. Please proceed to the next step.",
-"message.add.user.to.project": "This form is to enable adding specific users of an account to a project.<br>Furthermore, a ProjectRole may be added to the added user/account to allow/disallow API access at project level.<br> We can also specify the role with which the user should be added to a project - Admin/Regular; if not specified, it defaults to 'Regular'.",
+"message.add.user.to.project": "This form is to enable adding specific Users of an Account to a project.<br>Furthermore, a ProjectRole may be added to the added User/Account to allow/disallow API access at project level.<br> We can also specify the role with which the User should be added to a project - Admin/Regular; if not specified, it defaults to 'Regular'.",
 "message.add.volume": "Please fill in the following data to add a new volume.",
 "message.add.vpn.connection.failed": "Adding VPN connection failed",
 "message.add.vpn.connection.processing": "Adding VPN Connection...",
@@ -2350,28 +2542,29 @@
 "message.adding.host": "Adding host",
 "message.adding.netscaler.device": "Adding Netscaler device",
 "message.adding.netscaler.provider": "Adding Netscaler provider",
-"message.advanced.security.group": "Choose this if you wish to use security groups to provide guest VM isolation.",
+"message.advanced.security.group": "Choose this if you wish to use security groups to provide guest Instance isolation.",
 "message.allowed": "Allowed",
 "message.alert.show.all.stats.data": "This may return a lot of data depending on VM statistics and retention settings",
 "message.apply.success": "Apply Successfully",
-"message.assign.instance.another": "Please specify the account type, domain, account name and network (optional) of the new account. <br> If the default nic of the vm is on a shared network, CloudStack will check if the network can be used by the new account if you do not specify one network. <br> If the default nic of the vm is on a isolated network, and the new account has more one isolated networks, you should specify one.",
-"message.assign.vm.failed": "Failed to assign VM",
-"message.assign.vm.processing": "Assigning VM...",
-"message.attach.volume": "Please fill in the following data to attach a new volume. If you are attaching a disk volume to a Windows based virtual machine, you will need to reboot the instance to see the attached disk.",
+"message.assign.instance.another": "Please specify the Account type, domain, Account name and Network (optional) of the new Account. <br> If the default NIC of the Instance is on a shared Network, CloudStack will check if the Network can be used by the new Account if you do not specify one Network. <br> If the default NIC of the Instance is on a isolated Network, and the new Account has more one isolated Networks, you should specify one.",
+"message.assign.vm.failed": "Failed to assign Instance",
+"message.assign.vm.processing": "Assigning Instance...",
+"message.attach.volume": "Please fill in the following data to attach a new volume. If you are attaching a disk volume to a Windows based Instance, you will need to reboot the Instance to see the attached disk.",
 "message.attach.volume.failed": "Failed to attach volume.",
 "message.attach.volume.progress": "Attaching volume",
 "message.attach.volume.success": "Successfully attached the volume to the instance",
 "message.authorization.failed": "Session expired, authorization verification failed.",
-"message.autoscale.loadbalancer.update": "The load balancer rule can be updated only when autoscale VM group is DISABLED.",
-"message.autoscale.policies.update": "The scale up/down policies can be updated only when autoscale VM group is DISABLED.",
-"message.autoscale.vm.networks": "Please choose at least one network for VMs in the autoscale VM group. The default network must be an Isolated network or VPC tier which supports VM AutoScaling and has load balancing rules.",
-"message.autoscale.vmprofile.update": "The autoscale vm profile can be updated only when autoscale VM group is DISABLED.",
+"message.autoscale.loadbalancer.update": "The load balancer rule can be updated only when autoscale Instance group is DISABLED.",
+"message.autoscale.policies.update": "The scale up/down policies can be updated only when autoscale Instance group is DISABLED.",
+"message.autoscale.vm.networks": "Please choose at least one Network for Instances in the autoscale Instance group. The default Network must be an Isolated Network or VPC Network Tier which supports Instance AutoScaling and has load balancing rules.",
+"message.autoscale.vmprofile.update": "The autoscale Instance profile can be updated only when autoscale Instance group is DISABLED.",
 "message.backup.attach.restore": "Please confirm that you want to restore and attach the volume from the backup?",
-"message.backup.create": "Are you sure you want create a VM backup?",
-"message.backup.offering.remove": "Are you sure you want to remove VM from backup offering and delete the backup chain?",
-"message.backup.restore": "Please confirm that you want to restore the vm backup?",
+"message.backup.create": "Are you sure you want create an Instance backup?",
+"message.backup.offering.remove": "Are you sure you want to remove Instance from backup offering and delete the backup chain?",
+"message.backup.restore": "Please confirm that you want to restore the Instance backup?",
+"message.cancel.shutdown": "Please confirm that you would like to cancel the  shutdown on this Management server. It will resume accepting any new Async Jobs.",
 "message.certificate.upload.processing": "Certificate upload in progress",
-"message.change.offering.confirm": "Please confirm that you wish to change the service offering of this virtual instance.",
+"message.change.offering.confirm": "Please confirm that you wish to change the service offering of this virtual Instance.",
 "message.change.offering.for.volume": "Successfully changed offering for the volume",
 "message.change.offering.for.volume.failed": "Change offering for the volume failed",
 "message.change.offering.for.volume.processing": "Changing offering for the volume...",
@@ -2381,13 +2574,15 @@
 "message.config.health.monitor.failed": "Configure Health Monitor failed",
 "message.config.sticky.policy.failed": "Failed to configure sticky policy.",
 "message.config.sticky.policy.processing": "Updating sticky policy...",
+"message.configure.network.ip.and.mac": "Please configure the IP address and mac address of networks if needed.",
+"message.configure.network.select.default.network": "Please configure the IP address and mac address of networks if needed. Please select a network as the default network.",
 "message.configuring.guest.traffic": "Configuring guest traffic",
-"message.configuring.physical.networks": "Configuring physical networks",
+"message.configuring.physical.networks": "Configuring physical Networks",
 "message.configuring.public.traffic": "Configuring public traffic",
 "message.configuring.storage.traffic": "Configuring storage traffic",
 "message.confirm.action.force.reconnect": "Please confirm that you want to force reconnect this host.",
-"message.confirm.add.router.table.to.instance": "Please confirm that you want to add Route Table to this nic",
-"message.confirm.add.routing.policy": "Please confirm that you want to add Routing Policy to this network",
+"message.confirm.add.router.table.to.instance": "Please confirm that you want to add Route Table to this NIC",
+"message.confirm.add.routing.policy": "Please confirm that you want to add Routing Policy to this Network",
 "message.confirm.archive.selected.alerts": "Please confirm you would like to archive the selected alerts",
 "message.confirm.archive.selected.events": "Please confirm you would like to archive the selected events",
 "message.confirm.attach.disk": "Are you sure you want to attach disk?",
@@ -2405,22 +2600,23 @@
 "message.confirm.delete.pa": "Please confirm that you would like to delete Palo Alto.",
 "message.confirm.delete.provider": "Please confirm that you would like to delete this provider?",
 "message.confirm.delete.srx": "Please confirm that you would like to delete SRX.",
+"message.confirm.delete.traffic.type": "Please confirm that you would like to delete traffic type.",
 "message.confirm.destroy.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to stop this router. Please confirm that you would like to destroy this router.",
-"message.confirm.disable.autoscale.vmgroup": "Please confirm that you want to disable this autoscale vm group.",
+"message.confirm.disable.autoscale.vmgroup": "Please confirm that you want to disable this autoscale Instance group.",
 "message.confirm.disable.host": "Please confirm that you want to disable the host.",
-"message.confirm.disable.network.offering": "Are you sure you want to disable this network offering?",
+"message.confirm.disable.network.offering": "Are you sure you want to disable this Network offering?",
 "message.confirm.disable.provider": "Please confirm that you would like to disable this provider.",
 "message.confirm.disable.storage": "Please confirm that you want to disable the storage pool.",
 "message.confirm.disable.vpc.offering": "Are you sure you want to disable this VPC offering?",
-"message.confirm.enable.autoscale.vmgroup": "Please confirm that you want to enable this autoscale vm group.",
+"message.confirm.enable.autoscale.vmgroup": "Please confirm that you want to enable this autoscale Instance group.",
 "message.confirm.enable.host": "Please confirm that you want to enable the host.",
-"message.confirm.enable.network.offering": "Are you sure you want to enable this network offering?",
+"message.confirm.enable.network.offering": "Are you sure you want to enable this Network offering?",
 "message.confirm.enable.provider": "Please confirm that you would like to enable this provider.",
 "message.confirm.enable.storage": "Please confirm that you want to enable the storage pool.",
 "message.confirm.enable.vpc.offering": "Are you sure you want to enable this VPC offering?",
 "message.confirm.remove.firewall.rule": "Please confirm that you want to delete this Firewall Rule?",
 "message.confirm.remove.ip.range": "Please confirm that you would like to remove this IP range.",
-"message.confirm.remove.network.offering": "Are you sure you want to remove this network offering?",
+"message.confirm.remove.network.offering": "Are you sure you want to remove this Network offering?",
 "message.confirm.remove.network.policy": "Please confirm that you want to remove this Network Policy?",
 "message.confirm.remove.routing.policy": "Please confirm that you want to delete this Routing Policy?",
 "message.confirm.remove.selected.alerts": "Please confirm you would like to remove the selected alerts.",
@@ -2428,34 +2624,39 @@
 "message.confirm.remove.vmware.datacenter": "Please confirm you want to remove VMware datacenter.",
 "message.confirm.remove.vpc.offering": "Are you sure you want to remove this VPC offering?",
 "message.confirm.replace.acl.new.one": "Do you want to replace the ACL with a new one?",
-"message.confirm.reset.network.permissions": "Are you sure you want to reset this network permissions?",
-"message.confirm.scale.up.router.vm": "Do you really want to scale up the router VM?",
+"message.confirm.reset.network.permissions": "Are you sure you want to reset this Network permissions?",
+"message.confirm.scale.up.router.vm": "Do you really want to scale up the router Instance?",
 "message.confirm.scale.up.system.vm": "Do you really want to scale up the system VM?",
-"message.confirm.start.lb.vm": "Please confirm you want to start LB VM.",
+"message.confirm.start.lb.vm": "Please confirm you want to start LB Instance.",
 "message.confirm.sync.storage": "Please confirm you want to sync the storage pool",
-"message.confirm.upgrade.router.newer.template": "Please confirm that you want to upgrade router to use newer template.",
-"message.cpu.usage.info": "The CPU usage percentage can exceed 100% if the VM has more than 1 vCPU or when CPU Cap is not enabled. This behavior happens according to the hypervisor being used (e.g: in KVM), due to how they account the stats",
+"message.confirm.type": "To confirm, please type",
+"message.confirm.upgrade.router.newer.template": "Please confirm that you want to upgrade router to use newer Template.",
+"message.cpu.usage.info": "The CPU usage percentage can exceed 100% if the Instance has more than 1 vCPU or when CPU Cap is not enabled. This behavior happens according to the hypervisor being used (e.g: in KVM), due to how they account the stats",
+"message.create.bucket.failed": "Failed to create bucket.",
+"message.create.bucket.processing": "Bucket creation in progress",
 "message.create.compute.offering": "Compute offering created",
-"message.create.tungsten.public.network": "Create Tungsten-Fabric public network",
+"message.create.tungsten.public.network": "Create Tungsten-Fabric public Network",
 "message.create.internallb": "Creating internal LB",
 "message.create.internallb.failed": "Failed to create internal LB.",
 "message.create.internallb.processing": "Creation of internal LB is in progress",
 "message.create.service.offering": "Service offering created.",
-"message.create.snapshot.from.vmsnapshot.failed": "Failed to create Snapshot from VM Snapshot.",
+"message.create.snapshot.from.vmsnapshot.failed": "Failed to create Snapshot from Instance Snapshot.",
 "message.create.snapshot.from.vmsnapshot.progress": "Snapshot creation in progress",
+"message.create.template.failed": "Failed to create template.",
+"message.create.template.processing": "Template creation in progress",
 "message.create.volume.failed": "Failed to create volume.",
 "message.create.volume.processing": "Volume creation in progress",
 "message.create.vpc.offering": "VPC offering created.",
 "message.create.vpn.customer.gateway.failed": "VPN customer gateway creation failed.",
-"message.creating.autoscale.vmgroup": "Creating AutoScale VM group",
-"message.creating.autoscale.vmprofile": "Creating AutoScale VM profile",
+"message.creating.autoscale.vmgroup": "Creating AutoScale Instance group",
+"message.creating.autoscale.vmprofile": "Creating AutoScale Instance profile",
 "message.creating.autoscale.scaledown.conditions": "Creating ScaleDown conditions",
 "message.creating.autoscale.scaledown.policy": "Creating ScaleDown policy",
 "message.creating.autoscale.scaleup.conditions": "Creating ScaleUp conditions",
 "message.creating.autoscale.scaleup.policy": "Creating ScaleUp policy",
 "message.creating.cluster": "Creating cluster",
-"message.creating.guest.network": "Creating guest network",
-"message.creating.physical.networks": "Creating physical networks",
+"message.creating.guest.network": "Creating guest Network",
+"message.creating.physical.networks": "Creating physical Networks",
 "message.creating.pod": "Creating pod",
 "message.creating.primary.storage": "Creating primary storage",
 "message.creating.secondary.storage": "Creating secondary storage",
@@ -2470,7 +2671,7 @@
 "message.dedicating.host": "Dedicating host...",
 "message.dedicating.pod": "Dedicating pod...",
 "message.dedicating.zone": "Dedicating zone...",
-"message.delete.account": "Please confirm that you want to delete this account.",
+"message.delete.account": "Please confirm that you want to delete this Account.",
 "message.delete.acl.processing": "Removing ACL rule...",
 "message.delete.acl.rule": "Remove ACL rule",
 "message.delete.acl.rule.failed": "Failed to remove ACL rule.",
@@ -2487,45 +2688,53 @@
 "message.delete.tag.failed": "Failed to delete tag",
 "message.delete.tag.for.networkacl": "Remove tag for NetworkACL",
 "message.delete.tag.processing": "Deleting tag...",
+"message.delete.traffic.type.processing": "Deleting traffic type...",
 "message.delete.tungsten.policy.rule": "Please confirm that you want to delete Policy Rule?",
 "message.delete.tungsten.tag": "Are you sure you want to remove this Tag from this Policy?",
-"message.delete.user": "Please confirm that you would like to delete this user.",
+"message.delete.user": "Please confirm that you would like to delete this User.",
 "message.delete.vpn.connection": "Please confirm that you want to delete VPN connection.",
 "message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN customer gateway.",
 "message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway.",
 "message.deleting.firewall.policy": "Deleting Firewall Policy",
 "message.deleting.node": "Deleting Node",
-"message.deleting.vm": "Deleting VM",
+"message.deleting.vm": "Deleting Instance",
 "message.denied": "Denied",
-"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.",
-"message.desc.advanced.zone": "This is recommended and allows more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.",
-"message.desc.basic.zone": "Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).",
-"message.desc.core.zone": "Core Zones are intended for Datacenter based deployments and allow the full range of networking and other functionality in Apache CloudStack. Core zones have a number of prerequisites and rely on the presence of shared storage and helper VMs.",
+"message.deployasis": "Selected Template is Deploy As-Is i.e., the Instance is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped Instances for such Templates.",
+"message.desc.advanced.zone": "This is recommended and allows more sophisticated Network topologies. This Network model provides the most flexibility in defining guest Networks and providing custom Network offerings such as firewall, VPN, or load balancer support.",
+"message.desc.basic.zone": "Provide a single Network where each Instance is assigned an IP directly from the Network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).",
+"message.desc.core.zone": "Core Zones are intended for Datacenter based deployments and allow the full range of Networking and other functionality in Apache CloudStack. Core zones have a number of prerequisites and rely on the presence of shared storage and helper Instances.",
 "message.desc.edge.zone": "Edge Zones are lightweight zones, designed for deploying in edge computing scenarios. They are limited in functionality but have far fewer prerequisites than core zones.<br><br>Please refer to the Apache CloudStack documentation for more information on Zone Types<br><a href='http://docs.cloudstack.apache.org/en/latest/installguide/configuration.html#adding-a-zone'>http://docs.cloudstack.apache.org/en/latest/installguide/configuration.html#adding-a-zone</a>",
 "message.desc.cluster": "Each pod must contain one or more clusters. We will add the first cluster now. A cluster provides a way to group hosts. The hosts in a cluster all have identical hardware, run the same hypervisor, are on the same subnet, and access the same shared storage. Each cluster consists of one or more hosts and one or more primary storage servers.",
 "message.desc.create.ssh.key.pair": "Please fill in the following data to create or register a ssh key pair.<br><br>(1) If public key is set, CloudStack will register the public key. You can use it through your private key.<br><br>(2) If public key is not set, CloudStack will create a new SSH key pair. In this case, please copy and save the private key. CloudStack will not keep it.<br>",
 "message.desc.created.ssh.key.pair": "Created a SSH key pair.",
-"message.desc.host": "Each cluster must contain at least one host (computer) for guest VMs to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.<br/><br/>Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.",
-"message.desc.importexportinstancewizard": "This feature only applies Cloudstack VMware clusters. By choosing to manage an instance, CloudStack takes over the orchestration of that instance. The instance is left running and not physically moved. Unmanaging instances, removes CloudStack ability to manage them (but they are left running and not destroyed).",
-"message.desc.primary.storage": "Each cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the VMs running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.",
-"message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this VM.",
-"message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores VM templates, ISO images, and VM disk volume snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.",
-"message.desc.register.user.data": "Please fill in the following data to register a user data.",
+"message.desc.host": "Each cluster must contain at least one host (computer) for guest Instances to run on. We will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.<br/><br/>Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.",
+"message.desc.importingestinstancewizard": "This feature only applies to libvirt based KVM instances. Only Stopped instances can be ingested",
+"message.desc.import.ext.kvm.wizard": "Import libvirt domain from External KVM Host not managed by CloudStack",
+"message.desc.import.local.kvm.wizard": "Import QCOW2 image from Local Storage of selected KVM Host",
+"message.desc.import.shared.kvm.wizard": "Import QCOW2 image from selected Primary Storage Pool",
+"message.desc.importexportinstancewizard": "By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. Unmanaging an Instance removes CloudStack ability to manage it. In both cases, the Instance is left running and no changes are done to the VM on the hypervisor.<br><br>For KVM, managing a VM is an experimental feature.",
+"message.desc.importmigratefromvmwarewizard": "By selecting an existing or external VMware Datacenter and an instance to import, CloudStack migrates the selected instance from VMware to KVM on a conversion host using virt-v2v and imports it into a KVM cluster",
+"message.desc.primary.storage": "Each cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the Instances running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.",
+"message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this Instance.",
+"message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.",
+"message.desc.register.user.data": "Please fill in the following data to register a User data.",
 "message.desc.registered.user.data": "Registered a User Data.",
 "message.desc.zone": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.",
-"message.desc.zone.edge": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. An edge zone consists of one or more hosts (each of which provides local storage as primary storage servers). Only shared and L2 networks can be deployed in such zones and functionalities that require secondary storages are not supported.",
-"message.zone.edge.local.storage": "Local storage will be used by default for user VMs and virtual routers",
+"message.desc.zone.edge": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. An edge zone consists of one or more hosts (each of which provides local storage as primary storage servers). Only shared and L2 Networks can be deployed in such zones and functionalities that require secondary storages are not supported.",
+"message.drs.plan.description": "The maximum number of live migrations allowed for DRS. Configure DRS under the settings tab before generating a plan or to enable automatic DRS for the cluster.",
+"message.drs.plan.executed": "DRS plan executed successfully.",
+"message.zone.edge.local.storage": "Local storage will be used by default for User Instances and virtual routers",
 "message.detach.disk": "Are you sure you want to detach this disk?",
-"message.detach.iso.confirm": "Please confirm that you want to detach the ISO from this virtual instance.",
-"message.disable.account": "Please confirm that you want to disable this account. By disabling the account, all users for this account will no longer have access to their cloud resources. All running virtual machines will be immediately shut down.",
-"message.disable.user": "Please confirm that you would like to disable this user.",
+"message.detach.iso.confirm": "Please confirm that you want to detach the ISO from this virtual Instance.",
+"message.disable.account": "Please confirm that you want to disable this Account. By disabling the Account, all Users for this Account will no longer have access to their cloud resources. All running Instances will be immediately shut down.",
+"message.disable.user": "Please confirm that you would like to disable this User.",
 "message.disable.vpn": "Are you sure you want to disable VPN?",
 "message.disable.vpn.failed": "Failed to disable VPN.",
 "message.disable.vpn.processing": "Disabling VPN...",
 "message.discovering.feature": "Discovering features, please wait...",
 "message.disk.offering.created": "Disk offering created:",
 "message.disk.usage.info.data.points": "Each data point represents the difference in read/write data since the last data point.",
-"message.disk.usage.info.sum.of.disks": "The disk usage shown is made up of the sum of read/write data from all the disks in the VM.",
+"message.disk.usage.info.sum.of.disks": "The disk usage shown is made up of the sum of read/write data from all the disks in the Instance.",
 "message.download.volume": "Please click the link to download the volume:<p><a href=\"#\">00000</a>",
 "message.download.volume.confirm": "Please confirm that you want to download this volume.",
 "message.edit.acl.failed": "Failed to edit ACL rule",
@@ -2535,11 +2744,11 @@
 "message.edit.traffic.type": "Please specify the traffic label you want associated with this traffic type.",
 "message.egress.rules.allow": "Allow (traffic matching the egress rules added will be denied)",
 "message.egress.rules.deny": "Deny (traffic matching the egress rules added will be allowed)",
-"message.egress.rules.info.for.network": "The default egress policy of this network is %x. <br> Outgoing traffic matching the following egress rules will be %y",
-"message.enable.account": "Please confirm that you want to enable this account.",
+"message.egress.rules.info.for.network": "The default egress policy of this Network is %x. <br> Outgoing traffic matching the following egress rules will be %y",
+"message.enable.account": "Please confirm that you want to enable this Account.",
 "message.enable.netsacler.provider.failed": "failed to enable Netscaler provider",
 "message.enable.securitygroup.provider.failed": "failed to enable security group provider",
-"message.enable.user": "Please confirm that you would like to enable this user.",
+"message.enable.user": "Please confirm that you would like to enable this User.",
 "message.enable.vpn": "Please confirm that you want remote access VPN enabled for this IP address.",
 "message.enable.vpn.failed": "Failed to enable VPN.",
 "message.enable.vpn.processing": "Enabling VPN...",
@@ -2548,10 +2757,10 @@
 "message.enabling.security.group.provider": "Enabling security group provider",
 "message.enter.valid.nic.ip": "Please enter a valid IP address for NIC",
 "message.error.access.key": "Please enter access key.",
-"message.error.add.guest.network": "Either IPv4 fields or IPv6 fields need to be filled when adding a guest network.",
+"message.error.add.guest.network": "Either IPv4 fields or IPv6 fields need to be filled when adding a guest Network.",
 "message.error.add.interface.static.route": "Adding interface Static Route failed",
 "message.error.add.logical.router": "Adding Logical Router failed",
-"message.error.add.network.static.route": "Adding network Static Route failed",
+"message.error.add.network.static.route": "Adding Network Static Route failed",
 "message.error.add.policy.rule": "Adding Policy rule failed",
 "message.error.add.secondary.ipaddress": "There was an error adding the secondary IP Address.",
 "message.error.add.tungsten.router.table": "Adding Router Table failed",
@@ -2567,11 +2776,12 @@
 "message.error.cluster.description": "Please enter Kubernetes cluster description.",
 "message.error.cluster.name": "Please enter cluster name.",
 "message.error.confirm.password": "Please confirm new password.",
+"message.error.confirm.text": "Please enter the confirmation text",
 "message.error.current.password": "Please enter current password.",
 "message.error.custom.disk.size": "Please enter custom disk size.",
 "message.error.date": "Please select a date.",
 "message.error.delete.interface.static.route": "Removing interface Static Route failed",
-"message.error.delete.network.static.route": "Removing network Static Route failed",
+"message.error.delete.network.static.route": "Removing Network Static Route failed",
 "message.error.delete.tungsten.policy.rule": "Deleting Policy rule failed",
 "message.error.delete.tungsten.router.table": "Removing Router Table failed",
 "message.error.delete.tungsten.tag": "Removing Tag failed",
@@ -2592,10 +2802,10 @@
 "message.error.hypervisor.type": "Please select hypervisor type.",
 "message.error.input.value": "Please enter value.",
 "message.error.internal.dns1": "Please enter internal DNS 1",
-"message.error.internallb.instance.port": "Please specify a instance port.",
+"message.error.internallb.instance.port": "Please specify a Instance port.",
 "message.error.internallb.name": "Please specify a name for the internal LB.",
 "message.error.internallb.source.port": "Please specify a source port.",
-"message.error.invalid.autoscale.vmgroup.name": "Invalid AutoScale VM group name. It can contain the ASCII letters 'a' through 'z', 'A' through 'Z', the digits '0' through '9' and the hyphen ('-'), must be between 1 and 255 characters long.",
+"message.error.invalid.autoscale.vmgroup.name": "Invalid AutoScale Instance group name. It can contain the ASCII letters 'a' through 'z', 'A' through 'Z', the digits '0' through '9' and the hyphen ('-'), must be between 1 and 255 characters long.",
 "message.error.ip.range": "Please enter valid range.",
 "message.error.ipv4.address": "Please enter a valid IPv4 address.",
 "message.error.ipv4.dns1": "Please enter IpV4 DNS 1",
@@ -2615,7 +2825,7 @@
 "message.error.mtu.private.max.exceed": "The value entered exceeds the maximum allowed private MTU for this Zone, your value will be automatically lowered to match it.",
 "message.error.name": "Please enter name.",
 "message.error.netmask": "Please enter Netmask.",
-"message.error.network.offering": "Please select network offering.",
+"message.error.network.offering": "Please select Network offering.",
 "message.error.new.password": "Please enter new password.",
 "message.error.nexus1000v.ipaddress": "Please enter Nexus 1000v IP address.",
 "message.error.nexus1000v.password": "Please enter Nexus 1000v password.",
@@ -2627,12 +2837,13 @@
 "message.error.rados.monitor": "Please enter RADOS monitor",
 "message.error.rados.pool": "Please enter RADOS pool",
 "message.error.rados.secret": "Please enter RADOS secret",
-"message.error.rados.user": "Please enter RADOS user",
+"message.error.rados.user": "Please enter RADOS User",
 "message.error.remove.logical.router": "Removing Logical Router failed",
 "message.error.remove.network.policy": "Removing Network Policy failed",
 "message.error.remove.nic": "There was an error",
 "message.error.remove.secondary.ipaddress": "There was an error removing the secondary IP Address",
-"message.error.remove.tungsten.routing.policy": "Removing Tungsten-Fabric Routing Policy from network failed",
+"message.error.remove.tungsten.routing.policy": "Removing Tungsten-Fabric Routing Policy from Network failed",
+"message.error.remove.vm.schedule": "Removing Instance Schedule failed",
 "message.error.required.input": "Please enter input",
 "message.error.reset.config": "Unable to reset config to default value",
 "message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes cluster config",
@@ -2640,10 +2851,10 @@
 "message.error.s3nfs.path": "Please enter S3 NFS Path",
 "message.error.s3nfs.server": "Please enter S3 NFS Server",
 "message.error.select.load.balancer": "Please select a load balancer",
-"message.error.select.network": "Please select a network",
-"message.error.select.network.supports.vm.autoscaling": "The default network you selected does not support VM AutoScaling, please select a default network which supports VM AutoScaling.",
-"message.error.select.user": "Please select a user",
-"message.error.swift.account": "Please enter account",
+"message.error.select.network": "Please select a Network",
+"message.error.select.network.supports.vm.autoscaling": "The default Network you selected does not support Instance AutoScaling, please select a default Network which supports Instance AutoScaling.",
+"message.error.select.user": "Please select a User",
+"message.error.swift.account": "Please enter Account",
 "message.error.swift.key": "Please enter key",
 "message.error.swift.username": "Please enter username",
 "message.error.save.setting": "There was an error saving this setting.",
@@ -2672,9 +2883,9 @@
 "message.error.try.save.setting": "There was an error saving this setting. Please try again later.",
 "message.error.upload.iso.description": "Only one ISO can be uploaded at a time.",
 "message.error.upload.template": "Template upload failed.",
-"message.error.upload.template.description": "Only one template can be uploaded at a time.",
+"message.error.upload.template.description": "Only one Template can be uploaded at a time.",
 "message.error.url": "Please enter URL.",
-"message.error.userdata": "Please enter userdata",
+"message.error.userdata": "Please enter Userdata",
 "message.error.username": "Enter your username.",
 "message.error.valid.iops.range": "Please enter a valid IOPS range.",
 "message.error.vcenter.datacenter": "Please enter vCenter datacenter.",
@@ -2693,19 +2904,20 @@
 "message.error.zone.name": "Please enter zone name.",
 "message.error.zone.type": "Please select zone type.",
 "message.error.linstor.resourcegroup": "Please enter the Linstor Resource-Group.",
-"message.error.fixed.offering.kvm": "It's not possible to scale up VMs that utilize KVM hypervisor with a fixed compute offering.",
+"message.error.fixed.offering.kvm": "It's not possible to scale up Instances that utilize KVM hypervisor with a fixed compute offering.",
 "message.fail.to.delete": "Failed to delete.",
 "message.failed.to.add": "Failed to add",
-"message.failed.to.assign.vms": "Failed to assign VMs",
+"message.failed.to.assign.vms": "Failed to assign Instances",
 "message.failed.to.remove": "Failed to remove",
-"message.generate.keys": "Please confirm that you would like to generate new keys for this user.",
+"message.generate.keys": "Please confirm that you would like to generate new API/Secret keys for this User.",
 "message.chart.statistic.info": "The shown charts are self-adjustable, that means, if the value gets close to the limit or overpass it, it will grow to adjust the shown value",
-"message.guest.traffic.in.advanced.zone": "Guest network traffic is communication between end-user virtual machines. Specify a range of VLAN IDs or VXLAN network identifiers (VNIs) to carry guest traffic for each physical network.",
-"message.guest.traffic.in.basic.zone": "Guest network traffic is communication between end-user virtual machines. Specify a range of IP addresses that CloudStack can assign to guest VMs. Make sure this range does not overlap the reserved system IP range.",
-"message.host.controlstate": "The Control Plane Status of this instance is ",
-"message.host.controlstate.retry": "Some actions on this instance will fail, if so please wait a while and retry.",
+"message.guest.traffic.in.advanced.zone": "Guest Network traffic is communication between end-user Instances. Specify a range of VLAN IDs or VXLAN Network identifiers (VNIs) to carry guest traffic for each physical Network.",
+"message.guest.traffic.in.basic.zone": "Guest Network traffic is communication between end-user Instances. Specify a range of IP addresses that CloudStack can assign to guest Instances. Make sure this range does not overlap the reserved system IP range.",
+"message.host.controlstate": "The Control Plane Status of this Instance is ",
+"message.host.controlstate.retry": "Some actions on this Instance will fail, if so please wait a while and retry.",
 "message.host.dedicated": "Host Dedicated",
 "message.host.dedication.released": "Host dedication released.",
+"message.import.running.instance.warning": "The selected VM is powered-on on the VMware Datacenter. The recommended state to convert a VMware VM into KVM is powered-off after a graceful shutdown of the guest OS.",
 "message.info.cloudian.console": "Cloudian Management Console should open in another window.",
 "message.installwizard.cloudstack.helptext.website": " * Project website:\t ",
 "message.infra.setup.tungsten.description": "This zone must contain a Tungsten-Fabric provider because the isolation method is TF",
@@ -2715,11 +2927,11 @@
 "message.installwizard.cloudstack.helptext.mailinglists": " * Join mailing lists:\t ",
 "message.installwizard.cloudstack.helptext.releasenotes": " * Release notes:\t ",
 "message.installwizard.cloudstack.helptext.survey": " * Take the survey:\t ",
-"message.installwizard.copy.whatiscloudstack": "CloudStack™ is a software platform that pools computing resources to build public, private, and hybrid Infrastructure as a Service (IaaS) clouds. CloudStack™ manages the network, storage, and compute nodes that make up a cloud infrastructure. Use CloudStack™ to deploy, manage, and configure cloud computing environments.\n\nExtending beyond individual virtual machine images running on commodity hardware, CloudStack™ provides a turnkey cloud infrastructure software stack for delivering virtual datacenters as a service - delivering all of the essential components to build, deploy, and manage multi-tier and multi-tenant cloud applications. Both open-source and Premium versions are available, with the open-source version offering nearly identical features.",
+"message.installwizard.copy.whatiscloudstack": "CloudStack™ is a software platform that pools computing resources to build public, private, and hybrid Infrastructure as a Service (IaaS) clouds. CloudStack™ manages the Network, storage, and compute nodes that make up a cloud infrastructure. Use CloudStack™ to deploy, manage, and configure cloud computing environments.\n\nExtending beyond individual Instance images running on commodity hardware, CloudStack™ provides a turnkey cloud infrastructure software stack for delivering virtual datacenters as a service - delivering all of the essential components to build, deploy, and manage multi-tier and multi-tenant cloud applications. Both open-source and Premium versions are available, with the open-source version offering nearly identical features.",
 "message.installwizard.tooltip.addpod.name": "A name for the pod.",
-"message.installwizard.tooltip.addpod.reservedsystemendip": "This is the IP range in the private network that the CloudStack uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers.",
+"message.installwizard.tooltip.addpod.reservedsystemendip": "This is the IP range in the private Network that the CloudStack uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers.",
 "message.installwizard.tooltip.addpod.reservedsystemgateway": "The gateway for the hosts in that pod.",
-"message.installwizard.tooltip.addpod.reservedsystemstartip": "This is the IP range in the private network that the CloudStack uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers.",
+"message.installwizard.tooltip.addpod.reservedsystemstartip": "This is the IP range in the private Network that the CloudStack uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers.",
 "message.installwizard.tooltip.configureguesttraffic.guestendip": "The range of IP addresses that will be available for allocation to guests in this zone. If one NIC is used, these IPs should be in the same CIDR as the pod CIDR.",
 "message.installwizard.tooltip.configureguesttraffic.guestgateway": "The gateway that the guests should use.",
 "message.installwizard.tooltip.configureguesttraffic.guestnetmask": "The netmask in use on the subnet that the guests should use.",
@@ -2730,10 +2942,11 @@
 "message.installwizard.tooltip.tungsten.provider.name": "Tungsten provider name is required",
 "message.installwizard.tooltip.tungsten.provider.port": "Tungsten provider port is required",
 "message.installwizard.tooltip.tungsten.provider.vrouterport": "Tungsten provider vrouter port is required",
-"message.instances.managed": "Instances or VMs controlled by CloudStack.",
-"message.instances.unmanaged": "Instances or VMs not controlled by CloudStack.",
+"message.instances.managed": "Instances controlled by CloudStack.",
+"message.instances.unmanaged": "Instances not controlled by CloudStack.",
+"message.instances.migrate.vmware": "Instances that can be migrated from VMware.",
 "message.interloadbalance.not.return.elementid": "error: listInternalLoadBalancerElements API doesn't return internal LB element ID.",
-"message.ip.address.changes.effect.after.vm.restart": "IP address changes takes effect only after VM restart.",
+"message.ip.address.changes.effect.after.vm.restart": "IP address changes takes effect only after Instance restart.",
 "message.ip.v6.prefix.delete": "IPv6 prefix deleted",
 "message.iso.desc": "Disc image containing data or bootable media for OS.",
 "message.kubeconfig.cluster.not.available": "Kubernetes cluster kubeconfig not available currently.",
@@ -2745,80 +2958,85 @@
 "message.kubernetes.version.delete": "Please confirm that you want to delete this Kubernetes version.",
 "message.launch.zone": "Zone is ready to launch; please proceed to the next step.",
 "message.launch.zone.description": "Zone is ready to launch; please proceed to the next step.",
-"message.launch.zone.hint": "Configure network components and traffic including IP addresses.",
+"message.launch.zone.hint": "Configure Network components and traffic including IP addresses.",
 "message.license.agreements.not.accepted": "License agreements not accepted.",
 "message.linstor.resourcegroup.description": "Linstor resource group to use for primary storage.",
+"message.list.zone.vmware.datacenter.empty": "No VMware Datacenter exists in the selected Zone",
 "message.listnsp.not.return.providerid": "error: listNetworkServiceProviders API doesn't return VirtualRouter provider ID.",
 "message.load.host.failed": "Failed to load hosts.",
+"message.loadbalancer.stickypolicy.configuration": "Customize the load balancer stickiness policy:",
 "message.loading.add.interface.static.route": "Adding interface Static Route...",
-"message.loading.add.network.static.route": "Adding network Static Route...",
+"message.loading.add.network.static.route": "Adding Network Static Route...",
 "message.loading.add.policy.rule": "Adding Policy rule...",
 "message.loading.add.tungsten.router.table": "Adding Router Table...",
 "message.loading.apply.tungsten.tag": "Applying Tag...",
 "message.loading.delete.interface.static.route": "Removing interface Static Route...",
-"message.loading.delete.network.static.route": "Removing network Static Route...",
+"message.loading.delete.network.static.route": "Removing Network Static Route...",
 "message.loading.delete.tungsten.policy.rule": "Deleting Policy rule...",
 "message.loading.delete.tungsten.router.table": "Removing Router Table...",
 "message.loading.delete.tungsten.tag": "Removing Tag...",
-"message.lock.account": "Please confirm that you want to lock this account. By locking the account, all users for this account will no longer be able to manage their cloud resources. Existing resources can still be accessed.",
+"message.lock.account": "Please confirm that you want to lock this Account. By locking the Account, all Users for this Account will no longer be able to manage their cloud resources. Existing resources can still be accessed.",
 "message.login.failed": "Login Failed",
-"message.migrate.instance.host.auto.assign": "Host for the instance will be automatically chosen based on the suitability within the same cluster",
-"message.migrate.instance.to.host": "Please confirm that you want to migrate this instance to another host. When migration is between hosts of different clusters volume(s) of the instance may get migrated to suitable storage pools.",
-"message.migrate.instance.to.ps": "Please confirm that you want to migrate this instance to another primary storage.",
+"message.migrate.instance.host.auto.assign": "Host for the Instance will be automatically chosen based on the suitability within the same cluster",
+"message.migrate.instance.to.host": "Please confirm that you want to migrate this Instance to another host. When migration is between hosts of different clusters volume(s) of the Instance may get migrated to suitable storage pools.",
+"message.migrate.instance.to.ps": "Please confirm that you want to migrate this Instance to another primary storage.",
+"message.migrate.resource.to.ss": "Please confirm that you want to migrate this resource to another secondary storage.",
 "message.migrate.router.confirm": "Please confirm the host you wish to migrate the router to:",
 "message.migrate.systemvm.confirm": "Please confirm the host you wish to migrate the system VM to:",
 "message.migrate.volume": "Please confirm that you want to migrate this volume to another primary storage.",
 "message.migrate.volume.failed": "Migrating volume failed.",
-"message.migrate.volume.pool.auto.assign": "Primary storage for the volume will be automatically chosen based on the suitability and VM destination",
+"message.migrate.volume.pool.auto.assign": "Primary storage for the volume will be automatically chosen based on the suitability and Instance destination",
 "message.migrate.volume.processing": "Migrating volume...",
-"message.migrate.with.storage": "Specify storage pool for volumes of the instance.",
+"message.migrate.with.storage": "Specify storage pool for volumes of the Instance.",
 "message.migrating.failed": "Migration failed.",
 "message.migrating.processing": "Migration in progress for",
-"message.migrating.vm.to.storage.failed": "Failed to migrate VM to storage",
+"message.migrating.vm.to.storage.failed": "Failed to migrate Instance to storage",
 "message.move.acl.order": "Move ACL rule order",
 "message.move.acl.order.failed": "Failed to move ACL rule",
 "message.move.acl.order.processing": "Moving ACL rule...",
-"message.network.acl.default.allow": "Warning: With this policy all traffic will be allowed through the firewall to this VPC tier. You should consider securing your network.",
-"message.network.acl.default.deny": "Warning: With this policy all traffic will be denied through the firewall to this VPC tier. In order to allow traffic through you will need to change policies.",
-"message.network.addvm.desc": "Please specify the network that you would like to add this VM to. A new NIC will be added for this network.",
-"message.network.description": "Setup network and traffic.",
+"message.network.acl.default.allow": "Warning: With this policy all traffic will be allowed through the firewall to this VPC Network Tier. You should consider securing your Network.",
+"message.network.acl.default.deny": "Warning: With this policy all traffic will be denied through the firewall to this VPC Network Tier. In order to allow traffic through you will need to change policies.",
+"message.network.addvm.desc": "Please specify the Network that you would like to add this Instance to. A new NIC will be added for this Network.",
+"message.network.description": "Setup Network and traffic.",
 "message.network.error": "Network Error",
-"message.network.error.description": "Unable to reach the management server or a browser extension may be blocking the network request.",
-"message.network.hint": "Configure network components and public/guest/management traffic including IP addresses.",
-"message.network.offering.change.warning": "WARNING: Changing the offering will cause connectivity downtime for the VMs with NICs in the network.",
-"message.network.offering.forged.transmits": "Applicable for guest networks on VMware hypervisor only.\nReject - The switch drops any outbound frame from a virtual machine adapter with a source MAC address that is different from the one in the .vmx configuration file.\nAccept - The switch does not perform filtering, and permits all outbound frames.\nNone - Default to value from global setting.",
-"message.network.offering.mac.address.changes": "Applicable for guest networks on VMware hypervisor only.\nReject - If the guest OS changes the effective MAC address of the virtual machine to a value that is different from the MAC address of the VM network adapter (set in the .vmx configuration file), the switch drops all inbound frames to the adapter.\nIf the guest OS changes the effective MAC address of the virtual machine back to the MAC address of the VM network adapter, the virtual machine receives frames again.\nAccept - If the guest OS changes the effective MAC address of the virtual machine to a value that is different from the MAC address of the VM network adapter, the switch allows frames to the new address to pass.\nNone - Default to value from global setting.",
-"message.network.offering.mac.learning": "Applicable for guest networks on VMware hypervisor only with VMware Distributed Virtual Switches version 6.6.0 & above and vSphere version 6.7 & above.\nMAC learning enables network connectivity for multiple MAC addresses behind a single vNIC.\nNone - Default to value from global setting.",
-"message.network.offering.mac.learning.warning": "WARNING: In order to use MAC Learning you must ensure your hypervisor hosts are running ESXi 6.7+ and the network uses distributed vSwitch 6.6.0+.",
-"message.network.offering.promiscuous.mode": "Applicable for guest networks on VMware hypervisor only.\nReject - The switch drops any outbound frame from a virtual machine adapter with a source MAC address that is different from the one in the .vmx configuration file.\nAccept - The switch does not perform filtering, and permits all outbound frames.\nNone - Default to value from global setting.",
-"message.network.removenic": "Please confirm that want to remove this NIC, which will also remove the associated network from the VM.",
+"message.network.error.description": "Unable to reach the management server or a browser extension may be blocking the Network request.",
+"message.network.hint": "Configure Network components and public/guest/management traffic including IP addresses.",
+"message.network.offering.change.warning": "WARNING: Changing the offering will cause connectivity downtime for the Instances with NICs in the Network.",
+"message.network.offering.forged.transmits": "Applicable for guest Networks on VMware hypervisor only.\nReject - The switch drops any outbound frame from a Instance adapter with a source MAC address that is different from the one in the .vmx configuration file.\nAccept - The switch does not perform filtering, and permits all outbound frames.\nNone - Default to value from global setting.",
+"message.network.offering.mac.address.changes": "Applicable for guest Networks on VMware hypervisor only.\nReject - If the guest OS changes the effective MAC address of the Instance to a value that is different from the MAC address of the Instance Network adapter (set in the .vmx configuration file), the switch drops all inbound frames to the adapter.\nIf the guest OS changes the effective MAC address of the Instance back to the MAC address of the Instance Network adapter, the virtual machine receives frames again.\nAccept - If the guest OS changes the effective MAC address of the virtual machine to a value that is different from the MAC address of the Instance Network adapter, the switch allows frames to the new address to pass.\nNone - Default to value from global setting.",
+"message.network.offering.mac.learning": "Applicable for guest Networks on VMware hypervisor only with VMware Distributed Virtual Switches version 6.6.0 & above and vSphere version 6.7 & above.\nMAC learning enables Network connectivity for multiple MAC addresses behind a single vNIC.\nNone - Default to value from global setting.",
+"message.network.offering.mac.learning.warning": "WARNING: In order to use MAC Learning you must ensure your hypervisor hosts are running ESXi 6.7+ and the Network uses distributed vSwitch 6.6.0+.",
+"message.network.offering.promiscuous.mode": "Applicable for guest Networks on VMware hypervisor only.\nReject - The switch drops any outbound frame from a virtual machine adapter with a source MAC address that is different from the one in the .vmx configuration file.\nAccept - The switch does not perform filtering, and permits all outbound frames.\nNone - Default to value from global setting.",
+"message.network.removenic": "Please confirm that want to remove this NIC, which will also remove the associated Network from the Instance.",
 "message.network.secondaryip": "Please confirm that you would like to acquire a new secondary IP for this NIC. \n NOTE: You need to manually configure the newly-acquired secondary IP inside the virtual machine.",
-"message.network.updateip": "Please confirm that you would like to change the IP address for this NIC on VM.",
+"message.network.selection": "Choose one or more Networks to attach the Instance to. A new Network can also be created here.",
+"message.network.updateip": "Please confirm that you would like to change the IP address for this NIC on the Instance.",
 "message.network.usage.info.data.points": "Each data point represents the difference in data traffic since the last data point.",
-"message.network.usage.info.sum.of.vnics": "The network usage shown is made up of the sum of data traffic from all the vNICs in the VM.",
+"message.network.usage.info.sum.of.vnics": "The Network usage shown is made up of the sum of data traffic from all the vNICs in the Instance.",
 "message.no.data.to.show.for.period": "No data to show for the selected period.",
 "message.no.description": "No description entered.",
-"message.offering.internet.protocol.warning": "WARNING: IPv6 supported networks use static routing and will require upstream routes to be configured manually.",
-"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled network/VPC offering <a href='http://docs.cloudstack.apache.org/en/latest/plugins/ipv6.html#isolated-network-and-vpc-tier'>IPv6 support in CloudStack - Isolated networks and VPC tiers</a>",
+"message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.",
+"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering <a href='http://docs.cloudstack.apache.org/en/latest/plugins/ipv6.html#isolated-network-and-vpc-tier'>IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers</a>",
 "message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.",
 "message.path.description": "NFS: exported path from the server. VMFS: /datacenter name/datastore name. SharedMountPoint: path where primary storage is mounted, such as /mnt/primary.",
 "message.please.confirm.remove.ssh.key.pair": "Please confirm that you want to remove this SSH key pair.",
-"message.please.confirm.remove.user.data": "Please confirm that you want to remove this userdata",
+"message.please.confirm.remove.user.data": "Please confirm that you want to remove this Userdata",
 "message.please.enter.valid.value": "Please enter a valid value.",
 "message.please.enter.value": "Please enter values.",
-"message.please.wait.while.autoscale.vmgroup.is.being.created": "Please wait while your AutoScale VM group is being created; this may take a while...",
+"message.please.wait.while.autoscale.vmgroup.is.being.created": "Please wait while your AutoScale Instance group is being created; this may take a while...",
 "message.please.wait.while.zone.is.being.created": "Please wait while your zone is being created; this may take a while...",
 "message.pod.dedicated": "Pod dedicated.",
 "message.pod.dedication.released": "Pod dedication released.",
+"message.prepare.for.shutdown": "Please confirm that you would like to prep this Management server for shutdown. It will not accept any new Async Jobs but will NOT terminate after there are no pending jobs.",
 "message.primary.storage.invalid.state": "Primary storage is not in Up state",
 "message.processing.complete": "Processing complete!",
 "message.protocol.description": "For XenServer, choose NFS, iSCSI, or PreSetup. For KVM, choose NFS, SharedMountPoint, RDB, CLVM or Gluster. For vSphere, choose NFS, PreSetup (VMFS or iSCSI or FiberChannel or vSAN or vVols) or DatastoreCluster. For Hyper-V, choose SMB/CIFS. For LXC, choose NFS or SharedMountPoint. For OVM, choose NFS or OCFS2.",
-"message.public.traffic.in.advanced.zone": "Public traffic is generated when VMs in the cloud access the internet. Publicly-accessible IPs must be allocated for this purpose. End users can use the CloudStack UI to acquire these IPs to implement NAT between their guest network and their public network.<br/><br/>Provide at least one range of IP addresses for internet traffic.",
-"message.public.traffic.in.basic.zone": "Public traffic is generated when VMs in the cloud access the Internet or provide services to clients over the Internet. Publicly accessible IPs must be allocated for this purpose. When a instance is created, an IP from this set of Public IPs will be allocated to the instance in addition to the guest IP address. Static 1-1 NAT will be set up automatically between the public IP and the guest IP. End users can also use the CloudStack UI to acquire additional IPs to implement static NAT between their instances and the public IP.",
+"message.public.traffic.in.advanced.zone": "Public traffic is generated when Instances in the cloud access the internet. Publicly-accessible IPs must be allocated for this purpose. End Users can use the CloudStack UI to acquire these IPs to implement NAT between their guest Network and their public Network.<br/><br/>Provide at least one range of IP addresses for internet traffic.",
+"message.public.traffic.in.basic.zone": "Public traffic is generated when Instances in the cloud access the Internet or provide services to clients over the Internet. Publicly accessible IPs must be allocated for this purpose. When a Instance is created, an IP from this set of Public IPs will be allocated to the Instance in addition to the guest IP address. Static 1-1 NAT will be set up automatically between the public IP and the guest IP. End Users can also use the CloudStack UI to acquire additional IPs to implement static NAT between their Instances and the public IP.",
 "message.read.accept.license.agreements": "Please read and accept the terms for the license agreements.",
 "message.read.admin.guide.scaling.up": "Please read the dynamic scaling section in the admin guide before scaling up.",
-"message.recover.vm": "Please confirm that you would like to recover this VM.",
-"message.reinstall.vm": "NOTE: Proceed with caution. This will cause the VM to be reinstalled from the template; data on the root disk will be lost. Extra data volumes, if any, will not be touched.",
+"message.recover.vm": "Please confirm that you would like to recover this Instance.",
+"message.reinstall.vm": "NOTE: Proceed with caution. This will cause the Instance to be reinstalled from the Template; data on the root disk will be lost. Extra data volumes, if any, will not be touched.",
 "message.release.ip.failed": "Failed to release IP",
 "message.releasing.dedicated.cluster": "Releasing dedicated cluster...",
 "message.releasing.dedicated.host": "Releasing dedicated host...",
@@ -2830,13 +3048,13 @@
 "message.remove.failed": "Removing failed",
 "message.remove.firewall.rule.failed": "Removing firewall rule failed",
 "message.remove.firewall.rule.processing": "Deleting firewall rule...",
-"message.remove.instance.failed": "Failed to remove instance",
+"message.remove.instance.failed": "Failed to remove Instance",
 "message.remove.instance.processing": "Removing...",
 "message.remove.iprange.processing": "Removing IP range...",
 "message.remove.ldap": "Are you sure you want to delete the LDAP configuration?",
 "message.remove.nic.processing": "Removing NIC...",
 "message.remove.port.forward.failed": "Removing port forwarding rule failed",
-"message.remove.router.table.from.interface": "Please confirm that you want to remove Route Table from this nic",
+"message.remove.router.table.from.interface": "Please confirm that you want to remove Route Table from this NIC",
 "message.remove.router.table.from.interface.failed": "Removing Router Table from interface failed",
 "message.remove.rule.failed": "Failed to delete rule",
 "message.remove.secondary.ipaddress.processing": "Removing secondary IP address...",
@@ -2853,125 +3071,137 @@
 "message.resize.volume.processing": "Volume resize is in progress",
 "message.resource.not.found": "Resource not found.",
 "message.restart.mgmt.server": "Please restart your management server(s) for your new settings to take effect.",
-"message.restart.network": "All services provided by this network will be interrupted. Please confirm that you want to restart this network.",
-"message.restart.vm.to.update.settings": "Update in fields other than name and display name will require the VM to be restarted.",
+"message.restart.network": "All services provided by this Network will be interrupted. Please confirm that you want to restart this Network.",
+"message.restart.vm.to.update.settings": "Update in fields other than name and display name will require the Instance to be restarted.",
 "message.restart.vpc": "Please confirm that you want to restart the VPC.",
-"message.restart.vpc.remark": "Please confirm that you want to restart the VPC <p><i>Remark: making a non-redundant VPC redundant will force a clean up. The networks will not be available for a couple of minutes</i>.</p>",
+"message.restart.vpc.remark": "Please confirm that you want to restart the VPC <p><i>Remark: making a non-redundant VPC redundant will force a clean up. The Networks will not be available for a couple of minutes</i>.</p>",
 "message.scale.processing": "Scale in progress",
-"message.scaledown.policies": "Please add at least a ScaleDown policy. The AutoScale VM group will be scaled down when all conditions in a ScaleDown policy are matched. ScaleDown policies will be checked after ScaleUp policies.",
+"message.scaledown.policies": "Please add at least a ScaleDown policy. The AutoScale Instance group will be scaled down when all conditions in a ScaleDown policy are matched. ScaleDown policies will be checked after ScaleUp policies.",
 "message.scaledown.policy.continue": "Please add at least condition to ScaleDown policy to continue",
 "message.scaledown.policy.duration.continue": "Please input a valid duration to ScaleDown policy to continue",
 "message.scaledown.policy.name.continue": "Please input a name to ScaleDown policy to continue",
-"message.scaleup.policies": "Please add at least a ScaleUp policy. The AutoScale VM group will be scaled up when all conditions in a ScaleUp policy are matched. ScaleUp policies will be checked before ScaleDown policies.",
+"message.scaleup.policies": "Please add at least a ScaleUp policy. The AutoScale Instance group will be scaled up when all conditions in a ScaleUp policy are matched. ScaleUp policies will be checked before ScaleDown policies.",
 "message.scaleup.policy.continue": "Please add at least a condition to ScaleUp policy to continue",
 "message.scaleup.policy.duration.continue": "Please input a valid duration to ScaleUp policy to continue",
 "message.scaleup.policy.name.continue": "Please input a name to ScaleUp policy to continue",
 "message.select.a.zone": "A zone typically corresponds to a single datacenter. Multiple zones help make the cloud more reliable by providing physical isolation and redundancy.",
-"message.select.affinity.groups": "Please select any affinity groups you want this VM to belong to:",
+"message.select.affinity.groups": "Please select any affinity groups you want this Instance to belong to:",
 "message.select.deselect.to.sort": "Please select / deselect to sort the values",
 "message.select.destination.image.stores": "Please select Image Store(s) to which data is to be migrated to",
 "message.select.disk.offering": "Please select a disk offering for disk",
 "message.select.end.date.and.time": "Select an end date & time.",
-"message.select.load.balancer.rule": "Please select a load balancer rule for your AutoScale VM group.",
+"message.select.load.balancer.rule": "Please select a load balancer rule for your AutoScale Instance group.",
 "message.select.migration.policy": "Please select a migration policy.",
-"message.select.nic.network": "Please select a network for NIC",
-"message.select.security.groups": "Please select security group(s) for your new VM.",
+"message.select.nic.network": "Please select a Network for NIC",
+"message.select.security.groups": "Please select security group(s) for your new Instance.",
 "message.select.start.date.and.time": "Select a start date & time.",
 "message.select.zone.description": "Select type of zone basic/advanced.",
-"message.select.zone.hint": "This is the type of zone deployment that you want to use. Basic zone: provides a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering). Advanced zone: For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.",
+"message.select.zone.hint": "This is the type of zone deployment that you want to use. Basic zone: provides a single Network where each Instance is assigned an IP directly from the Network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering). Advanced zone: For more sophisticated Network topologies. This Network model provides the most flexibility in defining guest Networks and providing custom Network offerings such as firewall, VPN, or load balancer support.",
 "message.server.description": "NFS, iSCSI, or PreSetup: IP address or DNS name of the storage device. VMWare PreSetup: IP address or DNS name of the vCenter server. Linstor: http(s) url of the linstor-controller.",
-"message.set.default.nic": "Please confirm that you would like to make this NIC the default for this VM.",
-"message.set.default.nic.manual": "Please manually update the default NIC on the VM now.",
+"message.set.default.nic": "Please confirm that you would like to make this NIC the default for this Instance.",
+"message.set.default.nic.manual": "Please manually update the default NIC on the Instance now.",
 "message.setting.updated": "Setting Updated:",
 "message.setup.physical.network.during.zone.creation": "When adding a zone, you need to set up one or more physical networks. Each physical network can carry one or more types of traffic, with certain restrictions on how they may be combined. Add or remove one or more traffic types onto each physical network.",
-"message.setup.physical.network.during.zone.creation.basic": "When adding a basic zone, you can set up one physical network, which corresponds to a NIC on the hypervisor. The network carries several types of traffic.<br/><br/>You may also <strong>add</strong> other traffic types onto the physical network.",
-"message.shared.network.offering.warning": "Domain admins and regular users can only create shared networks from network offering with the setting specifyvlan=false. Please contact an administrator to create a network offering if this list is empty.",
+"message.setup.physical.network.during.zone.creation.basic": "When adding a basic zone, you can set up one physical Network, which corresponds to a NIC on the hypervisor. The Network carries several types of traffic.<br/><br/>You may also <strong>add</strong> other traffic types onto the physical Network.",
+"message.shared.network.offering.warning": "Domain admins and regular Users can only create shared Networks from Network offering with the setting specifyvlan=false. Please contact an administrator to create a Network offering if this list is empty.",
+"message.shutdown.triggered": "A shutdown has been triggered. CloudStack will not accept new jobs",
+"message.snapshot.additional.zones": "Snapshots will always be created in its native zone - %x, here you can select additional zone(s) where it will be copied to at creation time",
+"message.sourcenatip.change.warning": "WARNING: Changing the sourcenat IP address of the network will cause connectivity downtime for the Instances with NICs in the Network.",
+"message.sourcenatip.change.inhibited": "Changing the sourcenat to this IP of the Network to this address is inhibited as firewall rules are defined for it. This can include port forwarding or load balancing rules.\n - If this is an Isolated Network, please use updateNetwork/click the edit button.\n - If this is a VPC, first clear all other rules for this address.",
 "message.specify.tag.key": "Please specify a tag key.",
 "message.specify.tag.value": "Please specify a tag value.",
 "message.step.2.continue": "Please select a service offering to continue.",
 "message.step.3.continue": "Please select a disk offering to continue.",
-"message.step.4.continue": "Please select at least one network to continue.",
+"message.step.4.continue": "Please select at least one Network to continue.",
 "message.step.license.agreements.continue": "Please accept all license agreements to continue.",
 "message.success.acquire.ip": "Successfully acquired IP",
 "message.success.add.egress.rule": "Successfully added new egress rule",
 "message.success.add.firewall.rule": "Successfully added new firewall rule",
-"message.success.add.guest.network": "Successfully created guest network",
+"message.success.add.guest.network": "Successfully created guest Network",
 "message.success.add.interface.static.route": "Successfully added interface Static Route",
 "message.success.add.iprange": "Successfully added IP range",
 "message.success.add.ip.v6.prefix": "Successfully added IPv6 Prefix",
 "message.success.add.kuberversion": "Successfully added Kubernetes version",
 "message.success.add.logical.router": "Successfully added Logical Router",
-"message.success.add.network": "Successfully added network",
+"message.success.add.network": "Successfully added Network",
 "message.success.add.network.acl": "Successfully added Network ACL list",
-"message.success.add.network.static.route": "Successfully added network Static Route",
-"message.success.add.network.permissions": "Successfully added network permissions",
+"message.success.add.network.static.route": "Successfully added Network Static Route",
+"message.success.add.network.permissions": "Successfully added Network permissions",
+"message.success.add.physical.network": "Successfully added Physical Network",
+"message.success.add.object.storage": "Successfully added Object Storage",
 "message.success.add.policy.rule": "Successfully added Policy rule",
 "message.success.add.port.forward": "Successfully added new port forwarding rule",
 "message.success.add.private.gateway": "Successfully added private gateway",
-"message.success.add.router.table.to.instance": "Successfully added Router Table to instance",
+"message.success.add.router.table.to.instance": "Successfully added Router Table to Instance",
 "message.success.add.rule": "Successfully added new rule",
 "message.success.add.secondary.ipaddress": "Successfully added secondary IP address",
 "message.success.add.static.route": "Successfully added static route",
 "message.success.add.tag": "Successfully added new tag",
 "message.success.add.tungsten.router.table": "Successfully added Router Table",
 "message.success.add.tungsten.routing.policy": "Successfully added Tungsten-Fabric routing policy",
-"message.success.add.vpc.network": "Successfully added VPC network",
+"message.success.add.vpc.network": "Successfully added VPC Network",
 "message.success.add.vpn.customer.gateway": "Successfully added VPN customer gateway",
 "message.success.add.vpn.gateway": "Successfully added VPN gateway",
-"message.success.assign.vm": "Successfully assigned VM",
+"message.success.assign.vm": "Successfully assigned Instance",
 "message.success.apply.network.policy": "Successfully applied Network Policy",
 "message.success.apply.tungsten.tag": "Successfully applied Tag",
-"message.success.asign.vm": "Successfully assigned VM",
-"message.success.assigned.vms": "Successfully assigned VMs",
+"message.success.asign.vm": "Successfully assigned Instance",
+"message.success.assigned.vms": "Successfully assigned Instances",
 "message.success.certificate.upload": "Certificate successfully uploaded",
 "message.success.change.affinity.group": "Successfully changed affinity groups",
 "message.success.change.offering": "Successfully changed offering",
-"message.success.change.password": "Successfully changed password for user",
-"message.success.config.backup.schedule": "Successfully configured VM backup schedule",
+"message.success.change.password": "Successfully changed password for User",
+"message.success.config.backup.schedule": "Successfully configured Instance backup schedule",
 "message.success.config.health.monitor": "Successfully Configure Health Monitor",
 "message.success.config.sticky.policy": "Successfully configured sticky policy",
+"message.success.config.vm.schedule": "Successfully configured Instance schedule",
 "message.success.copy.clipboard": "Successfully copied to clipboard",
-"message.success.create.account": "Successfully created account",
+"message.success.create.account": "Successfully created Account",
+"message.success.create.bucket": "Successfully created bucket",
 "message.success.create.internallb": "Successfully created Internal Load Balancer",
-"message.success.create.isolated.network": "Successfully created isolated network",
+"message.success.create.isolated.network": "Successfully created isolated Network",
 "message.success.create.keypair": "Successfully created SSH key pair",
 "message.success.create.kubernetes.cluter": "Successfully created Kubernetes cluster",
-"message.success.create.l2.network": "Successfully created L2 network",
-"message.success.create.snapshot.from.vmsnapshot": "Successfully created snapshot from VM snapshot",
-"message.success.create.user": "Successfully created user",
+"message.success.create.l2.network": "Successfully created L2 Network",
+"message.success.create.snapshot.from.vmsnapshot": "Successfully created Snapshot from Instance Snapshot",
+"message.success.create.template": "Successfully created Template",
+"message.success.create.user": "Successfully created User",
 "message.success.create.volume": "Successfully created volume",
 "message.success.delete": "Successfully deleted",
 "message.success.delete.acl.rule": "Successfully removed ACL rule",
-"message.success.delete.backup.schedule": "Successfully deleted configure VM backup schedule",
+"message.success.delete.backup.schedule": "Successfully deleted configure Instance backup schedule",
 "message.success.delete.icon": "Successfully deleted icon of",
 "message.success.delete.interface.static.route": "Successfully removed interface Static Route",
-"message.success.delete.network.static.route": "Successfully removed network Static Route",
+"message.success.delete.network.static.route": "Successfully removed Network Static Route",
 "message.success.delete.node": "Successfully deleted node",
-"message.success.delete.snapshot.policy": "Successfully deleted snapshot policy",
+"message.success.delete.snapshot.policy": "Successfully deleted Snapshot policy",
 "message.success.delete.static.route": "Successfully deleted static route",
 "message.success.delete.tag": "Successfully deleted tag",
-"message.success.delete.tungsten.policy.rule": "Successfully deledted Policy rule",
+"message.success.delete.tungsten.policy.rule": "Successfully deleted Policy rule",
 "message.success.delete.tungsten.router.table": "Successfully removed Router Table",
 "message.success.delete.tungsten.tag": "Successfully removed Tag",
-"message.success.delete.vm": "Successfully deleted VM",
+"message.success.delete.vm": "Successfully deleted Instance",
 "message.success.disable.saml.auth": "Successfully disabled SAML authorization",
 "message.success.disable.vpn": "Successfully disabled VPN",
 "message.success.edit.acl": "Successfully edited ACL rule",
 "message.success.edit.rule": "Successfully edited rule",
 "message.success.enable.saml.auth": "Successfully enabled SAML Authorization",
-"message.success.import.instance": "Successfully imported instance",
+"message.success.import.instance": "Successfully imported Instance",
 "message.success.migrate.volume": "Successfully migrated volume",
 "message.success.migrating": "Migration completed successfully for",
+"message.success.migration": "Migration completed successfully",
 "message.success.move.acl.order": "Successfully moved ACL rule",
-"message.success.recurring.snapshot": "Successfully recurring snapshots",
+"message.success.recurring.snapshot": "Successfully recurring Snapshots",
 "message.success.register.iso": "Successfully registered ISO",
 "message.success.register.keypair": "Successfully registered SSH key pair",
-"message.success.register.template": "Successfully registered template",
+"message.success.register.template": "Successfully registered Template",
 "message.success.register.user.data": "Successfully registered Userdata",
 "message.success.release.ip": "Successfully released IP",
 "message.success.remove.egress.rule": "Successfully removed egress rule",
+"message.success.remove.objectstore.objects": "Successfully removed selected object(s)",
+"message.success.remove.objectstore.directory": "Successfully removed selected directory",
 "message.success.remove.firewall.rule": "Successfully removed firewall rule",
-"message.success.remove.instance.rule": "Successfully removed instance from rule",
+"message.success.remove.instance.rule": "Successfully removed Instance from rule",
 "message.success.remove.ip": "Successfully removed IP",
 "message.success.remove.iprange": "Successfully removed IP Range",
 "message.success.remove.logical.router": "Successfully removed Logical Router",
@@ -2983,65 +3213,68 @@
 "message.success.remove.rule": "Successfully deleted rule",
 "message.success.remove.secondary.ipaddress": "Successfully removed secondary IP address",
 "message.success.remove.sticky.policy": "Successfully removed sticky policy",
-"message.success.remove.tungsten.routing.policy": "Successfully removed Tungsten-Fabric Routing Policy from network",
+"message.success.remove.tungsten.routing.policy": "Successfully removed Tungsten-Fabric Routing Policy from Network",
 "message.success.reset.network.permissions": "Successfully reset Network Permissions",
 "message.success.resize.volume": "Successfully resized volume",
 "message.success.scale.kubernetes": "Successfully scaled Kubernetes cluster",
-"message.success.unmanage.instance": "Successfully unmanaged instance",
+"message.success.unmanage.instance": "Successfully unmanaged Instance",
+"message.success.update.bucket": "Successfully updated bucket",
 "message.success.update.condition": "Successfully updated condition",
 "message.success.update.ipaddress": "Successfully updated IP address",
 "message.success.update.iprange": "Successfully updated IP range",
 "message.success.update.kubeversion": "Successfully updated Kubernetes supported version",
-"message.success.update.network": "Successfully updated network",
+"message.success.update.network": "Successfully updated Network",
 "message.success.update.template": "Successfully updated Template",
-"message.success.update.user": "Successfully updated user",
+"message.success.update.user": "Successfully updated User",
 "message.success.upgrade.kubernetes": "Successfully upgraded Kubernetes cluster",
 "message.success.upload": "Successfully uploaded",
-"message.success.upload.description": "This ISO file has been uploaded. Please check its status in the templates menu.",
+"message.success.upload.description": "This ISO file has been uploaded. Please check its status in the Templates menu.",
 "message.success.upload.icon": "Successfully uploaded icon for ",
 "message.success.upload.iso.description": "This ISO file has been uploaded. Please check its status in the images > ISOs menu.",
-"message.success.upload.template.description": "This template file has been uploaded. Please check its status in the templates menu.",
+"message.success.upload.template.description": "This Template file has been uploaded. Please check its status in the Templates menu.",
 "message.success.upload.volume.description": "This volume has been uploaded. Please check its status in the volumes menu.",
 "message.suspend.project": "Are you sure you want to suspend this project?",
 "message.sussess.discovering.feature": "Discovered all available features!",
 "message.switch.to": "Switched to",
-"message.template.desc": "OS image that can be used to boot VMs.",
-"message.template.import.vm.temporary": "If a temporary template is used, the reset VM operation will not work after importing it.",
-"message.template.iso": "Please select a template or ISO to continue.",
-"message.template.type.change.warning": "WARNING: Changing the template type to SYSTEM will disable further changes to the template.",
-"message.tooltip.reserved.system.netmask": "The network prefix that defines the pod subnet. Uses CIDR notation.",
+"message.template.desc": "OS image that can be used to boot Instances.",
+"message.template.import.vm.temporary": "If a temporary Template is used, the reset Instance operation will not work after importing it.",
+"message.template.iso": "Please select a Template or ISO to continue.",
+"message.template.type.change.warning": "WARNING: Changing the Template type to SYSTEM will disable further changes to the Template.",
+"message.tooltip.reserved.system.netmask": "The Network prefix that defines the pod subnet. Uses CIDR notation.",
+"message.traffic.type.deleted": "Successfully deleted traffic type",
 "message.traffic.type.to.basic.zone": "traffic type to basic zone",
-"message.type.values.to.add": "Please add additonal values by typing them in",
+"message.trigger.shutdown": "Please confirm that you would like to trigger a shutdown on this Management server. It will not accept any new Async Jobs and will terminate after there are no pending jobs.",
+"message.type.values.to.add": "Please add additional values by typing them in",
 "message.update.autoscale.policy.failed": "Failed to update autoscale policy",
-"message.update.autoscale.vmgroup.failed": "Failed to update autoscale vm group",
-"message.update.autoscale.vm.profile.failed": "Failed to update autoscale vm profile",
+"message.update.autoscale.vmgroup.failed": "Failed to update autoscale Instance group",
+"message.update.autoscale.vm.profile.failed": "Failed to update autoscale Instance profile",
 "message.update.condition.failed": "Failed to update condition",
 "message.update.condition.processing": "Updating condition...",
 "message.two.factor.authorization.failed": "Unable to verify 2FA with provided code, please retry.",
 "message.two.fa.auth": "Open the two-factor authentication app on your mobile device to view your authentication code.",
-"message.two.fa.auth.register.account": "Open the two-factor authentication application and scan the QR code add the user account.",
+"message.two.fa.auth.register.account": "Open the two-factor authentication application and scan the QR code add the User Account.",
 "message.two.fa.static.pin.part1": "If you can't scan the QR code, ",
 "message.two.fa.static.pin.part2": "Click here to view the secret code",
 "message.two.fa.auth.staticpin": "<br> You have configured 2FA for security verification. <br> Enter the static PIN generated during the 2FA setup to verify.",
 "message.two.fa.auth.totp": "<br> You have configured 2FA for security verification. <br> Open the TOTP authenticator application on your device and enter the authentication code.",
-"message.two.fa.register.account": "1. Open the TOTP authenticator application on your device. <br>2. Scan the below QR code to add the user. <br>3. If you cannot scan the QR code, enter the setup key manually. <br>4. Verification of the 2FA code is mandatory to complete the 2FA setup.",
+"message.two.fa.register.account": "1. Open the TOTP authenticator application on your device. <br>2. Scan the below QR code to add the User. <br>3. If you cannot scan the QR code, enter the setup key manually. <br>4. Verification of the 2FA code is mandatory to complete the 2FA setup.",
 "message.two.fa.staticpin": "1. Use the generated static PIN as 2FA code for two factor authentication.<br>2. Save this static PIN / 2FA code and do not share it. This code will be used for subsequent logins.<br>3. Verification of the 2FA code is mandatory to complete the 2FA setup.",
-"message.two.fa.register.account.login.page": "1. Open the TOTP authenticator application on your device. <br>2. Scan the below QR code to add the user. <br>3. If you cannot scan the QR code, enter the setup key manually. <br>4. Verify the 2FA code to continue to login.",
+"message.two.fa.register.account.login.page": "1. Open the TOTP authenticator application on your device. <br>2. Scan the below QR code to add the User. <br>3. If you cannot scan the QR code, enter the setup key manually. <br>4. Verify the 2FA code to continue to login.",
 "message.two.fa.staticpin.login.page": "1. Use the generated static PIN as 2FA code for two factor authentication.<br>2. Save this static PIN / 2FA code and do not share it. This code will be used for subsequent logins.<br>3. Verify the 2FA code to continue to login.",
-"message.two.fa.login.page": "Two factor authentication (2FA) is enabled on your account, you need to select a 2FA provider and setup. 2FA is an extra layer of security to your account. Once setup is done, on every login you will be prompted to enter 2FA code.<br>",
+"message.two.fa.login.page": "Two factor authentication (2FA) is enabled on your Account, you need to select a 2FA provider and setup. 2FA is an extra layer of security to your account. Once setup is done, on every login you will be prompted to enter 2FA code.<br>",
 "message.two.fa.setup.page": "Two factor authentication (2FA) is an extra layer of security to your account.<br> Once setup is done, on every login you will be prompted to enter the 2FA code.<br>",
 "message.two.fa.view.setup.key": "Click here to view the setup key",
 "message.two.fa.view.static.pin": "Click here to view the static PIN",
 "message.update.ipaddress.processing": "Updating IP Address...",
-"message.update.resource.count": "Please confirm that you want to update resource counts for this account.",
+"message.update.resource.count": "Please confirm that you want to update resource counts for this Account.",
 "message.update.resource.count.domain": "Please confirm that you want to update resource counts for this domain.",
-"message.update.ssl": "Please submit a new X.509 compliant SSL certificate chain to be updated in each console proxy and secondary storage virtual instance:",
+"message.update.ssl": "Please submit a new X.509 compliant SSL certificate chain to be updated in each console proxy and secondary storage virtual Instance:",
 "message.upload.failed": "Upload Failed",
 "message.upload.file.limit": "Only one file can be uploaded at a time.",
 "message.upload.file.processing": "Please do not close this form or refresh your browser, file upload is in progress...",
 "message.upload.iso.failed": "ISO upload failed",
 "message.upload.iso.failed.description": "Failed to upload ISO.",
-"message.upload.template.failed.description": "Failed to upload template",
+"message.upload.template.failed.description": "Failed to upload Template",
 "message.upload.volume.failed": "Volume upload failed",
 "message.user.not.permitted.api": "User is not permitted to use the API",
 "message.validate.equalto": "Please enter the same value again.",
@@ -3053,17 +3286,30 @@
 "message.validate.range": "Please enter a value between {0} and {1}.",
 "message.validate.range.length": "Please enter a value between {0} and {1} characters long.",
 "message.virtual.router.not.return.elementid": "error: listVirtualRouterElements API doesn't return virtual router element ID.",
-"message.vm.review.launch": "Please review the following information and confirm that your virtual instance is correct before launch.",
-"message.vm.state.destroyed": "VM is marked to be destroyed.",
-"message.vm.state.error": "VM is in error.",
-"message.vm.state.expunging": "VM is being expunged.",
-"message.vm.state.migrating": "VM is being migrated.",
-"message.vm.state.running": "VM is running.",
-"message.vm.state.shutdown": "VM state is shutdown from inside.",
-"message.vm.state.starting": "VM is starting.",
-"message.vm.state.stopped": "VM is stopped.",
-"message.vm.state.stopping": "VM is being stopped.",
-"message.vm.state.unknown": "VM state is unknown.",
+"message.vm.review.launch": "Please review the following information and confirm that your Instance is correct before launch.",
+"message.vm.state.destroyed": "Instance is marked to be destroyed.",
+"message.vm.state.error": "Instance is in error.",
+"message.vm.state.expunging": "Instance is being expunged.",
+"message.vm.state.migrating": "Instance is being migrated.",
+"message.vm.state.running": "Instance is running.",
+"message.vm.state.shutdown": "Instance state is shutdown from inside.",
+"message.vm.state.starting": "Instance is starting.",
+"message.vm.state.stopped": "Instance is stopped.",
+"message.vm.state.stopping": "Instance is being stopped.",
+"message.vm.state.unknown": "Instance state is unknown.",
+"message.vnf.appliance.networks": "Please select the networks for the new VNF appliance.",
+"message.vnf.credentials.change": "Please change the password(s) of the VNF appliance immediately.",
+"message.vnf.credentials.default": "The default credentials(s) of the VNF appliance",
+"message.vnf.credentials.in.template.vnf.details": "Please find the default credentials for this VNF in the details of the VNF template.",
+"message.vnf.error.deviceid.should.be.consecutive": "The deviceid of selected VNF NICs should be consecutive.",
+"message.vnf.error.network.is.already.used": "Network has been used by multiple NICs of the new VNF appliance.",
+"message.vnf.error.network.should.be.used": "All selected networks should be used as VNF Nics",
+"message.vnf.error.no.networks": "Please select the networks for the new VNF appliance.",
+"message.vnf.error.no.network.for.required.deviceid": "Please select a Network for required NIC of the new VNF appliance.",
+"message.vnf.nic.move.up.fail": "Failed to move up this NIC",
+"message.vnf.nic.move.down.fail": "Failed to move down this NIC",
+"message.vnf.no.credentials": "No credentials found for the VNF appliance.",
+"message.vnf.select.networks": "Please select the relevant network for each VNF NIC.",
 "message.volume.state.allocated": "The volume is allocated but has not been created yet.",
 "message.volume.state.attaching": "The volume is attaching to a volume from Ready state.",
 "message.volume.state.copying": "The volume is being copied from the image store to primary storage, in case it's an uploaded volume.",
@@ -3076,23 +3322,25 @@
 "message.volume.state.notuploaded": "The volume entry is just created in DB, not yet uploaded.",
 "message.volume.state.ready": "The volume is ready to be used.",
 "message.volume.state.resizing": "The volume is being resized.",
-"message.volume.state.revertsnapshotting": "There is a snapshot of this volume, the volume is being reverted from a snapshot.",
-"message.volume.state.snapshotting": "There is a snapshot being created of this volume but not copied to secondary storage yet.",
+"message.volume.state.revertsnapshotting": "There is a Snapshot of this volume, the volume is being reverted from a Snapshot.",
+"message.volume.state.snapshotting": "There is a Snapshot being created of this volume but not copied to secondary storage yet.",
 "message.volume.state.uploadabandoned": "Volume upload has been abandoned as the upload wasn't initiated within the specified time.",
 "message.volume.state.uploaded": "Volume is uploaded.",
 "message.volume.state.uploaderror": "Volume upload encountered some error.",
 "message.volume.state.uploadinprogress": "Volume upload is in progress.",
 "message.volume.state.uploadop": "The volume upload operation is in progress and will be on secondary storage shortly.",
 "message.volume.state.primary.storage.suitability": "The suitability of a primary storage for a volume depends on the disk offering of the volume and on the virtual machine allocation (if the volume is attached to a virtual machine).",
-"message.vr.alert.upon.network.offering.creation.l2": "As virtual routers are not created for L2 networks, the compute offering will not be used.",
+"message.vr.alert.upon.network.offering.creation.l2": "As virtual routers are not created for L2 Networks, the compute offering will not be used.",
 "message.vr.alert.upon.network.offering.creation.others": "As none of the obligatory services for creating a virtual router (VPN, DHCP, DNS, Firewall, LB, UserData, SourceNat, StaticNat, PortForwarding) are enabled, the virtual router will not be created and the compute offering will not be used.",
 "message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats.",
-"message.warn.importing.instance.without.nic": "WARNING: this instance is being imported without NICs and many network resources will not be available. Consider creating a NIC via VCenter before importing or as soon as the instance is imported.",
-"message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing network’s MTU settings",
+"message.warn.importing.instance.without.nic": "WARNING: This Instance is being imported without NICs and many Network resources will not be available. Consider creating a NIC via vCenter before importing or as soon as the Instance is imported.",
+"message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Network’s MTU settings",
 "message.zone.creation.complete": "Zone creation complete.",
 "message.zone.detail.description": "Populate zone details.",
 "message.zone.detail.hint": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.",
 "message.validate.min": "Please enter a value greater than or equal to {0}.",
+"message.action.delete.object.storage": "Please confirm that you want to delete this Object Store",
+"message.bucket.delete": "Please confirm that you want to delete this Bucket",
 "migrate.from": "Migrate from",
 "migrate.to": "Migrate to",
 "migrationPolicy": "Migration policy",
diff --git a/ui/public/locales/es.json b/ui/public/locales/es.json
index 939aa5c..7648893 100644
--- a/ui/public/locales/es.json
+++ b/ui/public/locales/es.json
@@ -1512,4 +1512,4 @@
 "state.stopped": "Detenidas",
 "state.stopping": "Parando",
 "state.suspended": "Suspendido"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/fr_FR.json b/ui/public/locales/fr_FR.json
index 6aaafb5..f11f4ac 100644
--- a/ui/public/locales/fr_FR.json
+++ b/ui/public/locales/fr_FR.json
@@ -1482,4 +1482,4 @@
 "state.stopped": "Arr\u00eat\u00e9e",
 "state.stopping": "Arr\u00eat en cours",
 "state.suspended": "Suspendu"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/hi.json b/ui/public/locales/hi.json
index 45c6ee6..0559093 100644
--- a/ui/public/locales/hi.json
+++ b/ui/public/locales/hi.json
@@ -461,4 +461,4 @@
 "label.zone": "ज़ोन",
 "label.zoneid": "ज़ोन",
 "label.zonename": "ज़ोन नाम"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/hu.json b/ui/public/locales/hu.json
index 8e58fa6..445d3a1 100644
--- a/ui/public/locales/hu.json
+++ b/ui/public/locales/hu.json
@@ -651,7 +651,7 @@
 "label.level": "Szint",
 "label.limitcpuuse": "CPU Cap",
 "label.link.domain.to.ldap": "Link Domain to LDAP",
-"label.linklocalip": "Link Local IP Address",
+"label.linklocalip": "Control IP Address",
 "label.load.balancer": "Terhel\u00e9seloszt\u00f3",
 "label.loadbalancerinstance": "Hozz\u00e1rendelt VM-ek",
 "label.loadbalancerrule": "Terhel\u00e9seloszt\u00f3 szab\u00e1ly",
@@ -1480,4 +1480,4 @@
 "state.stopped": "Le\u00e1ll\u00edtva",
 "state.stopping": "Le\u00e1ll\u00e1s folyamatban",
 "state.suspended": "Felf\u00fcggesztett"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/it_IT.json b/ui/public/locales/it_IT.json
index 15faa8f..fa09e18 100644
--- a/ui/public/locales/it_IT.json
+++ b/ui/public/locales/it_IT.json
@@ -651,7 +651,7 @@
 "label.level": "Livello",
 "label.limitcpuuse": "Limite CPU",
 "label.link.domain.to.ldap": "Link Domain to LDAP",
-"label.linklocalip": "Link Local IP Address",
+"label.linklocalip": "Control IP Address",
 "label.load.balancer": "Load Balancer",
 "label.loadbalancerinstance": "Assigned VMs",
 "label.loadbalancerrule": "Load balancing rule",
@@ -1480,4 +1480,4 @@
 "state.stopped": "Arrestato",
 "state.stopping": "Arresto in corso",
 "state.suspended": "Sospeso"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json
index 19938b9..f151828 100644
--- a/ui/public/locales/ja_JP.json
+++ b/ui/public/locales/ja_JP.json
@@ -2046,6 +2046,8 @@
   "label.shared": "共有",
   "label.sharedexecutable": "共有",
   "label.sharedmountpoint": "SharedMountPoint",
+  "label.sharedrouterip": "共有ネットワークのルーターのIPv4アドレス",
+  "label.sharedrouteripv6": "共有ネットワークのルーターのIPv6アドレス",
   "label.sharewith": "共有",
   "label.show.ingress.rule": "受信ルールの表示",
   "label.showing": "表示中",
diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json
index 68c6fe9..a34cdea 100644
--- a/ui/public/locales/ko_KR.json
+++ b/ui/public/locales/ko_KR.json
@@ -1373,6 +1373,8 @@
 "label.shared": "shared",
 "label.sharedexecutable": "\uacf5\uc720",
 "label.sharedmountpoint": "\uacf5\uc720 \ub9c8\uc6b4\ud2b8 \ud3ec\uc778\ud2b8",
+"label.sharedrouterip": "\uc11c\ube44\uc2a4\uc6a9 \ub124\ud2b8\uc6cc\ud06c\uc758 \ub77c\uc6b0\ud130\uc5d0 \ub300\ud55c IPv4 \uc8fc\uc18c",
+"label.sharedrouteripv6": "\uc11c\ube44\uc2a4\uc6a9 \ub124\ud2b8\uc6cc\ud06c\uc758 \ub77c\uc6b0\ud130\uc5d0 \ub300\ud55c IPv6 \uc8fc\uc18c",
 "label.sharewith": "\uacf5\uc720",
 "label.showing": "\ubcf4\uae30",
 "label.shrinkok": "\ubcc0\uacbd \uc644\ub8cc",
@@ -2324,4 +2326,4 @@
 "state.suspended": "\uc77c\uc2dc\uc815\uc9c0",
 "user.login": "\ub85c\uadf8\uc778",
 "user.logout": "\ub85c\uadf8\uc544\uc6c3"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/nb_NO.json b/ui/public/locales/nb_NO.json
index b9e7664..a0fbd89 100644
--- a/ui/public/locales/nb_NO.json
+++ b/ui/public/locales/nb_NO.json
@@ -1480,4 +1480,4 @@
 "state.stopped": "Stoppet",
 "state.stopping": "Stopper",
 "state.suspended": "Pauset"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/nl_NL.json b/ui/public/locales/nl_NL.json
index 07951cc..8e637f1 100644
--- a/ui/public/locales/nl_NL.json
+++ b/ui/public/locales/nl_NL.json
@@ -652,7 +652,7 @@
 "label.level": "Level",
 "label.limitcpuuse": "CPU Cap",
 "label.link.domain.to.ldap": "link domein aan LDAP",
-"label.linklocalip": "Link Local IP Adres",
+"label.linklocalip": "Control IP Adres",
 "label.load.balancer": "Load Balancer",
 "label.loadbalancerinstance": "toegewezen VMs",
 "label.loadbalancerrule": "load balancing regel",
@@ -1481,4 +1481,4 @@
 "state.stopped": "Gestopt",
 "state.stopping": "Stoppen",
 "state.suspended": "Gepauzeerd"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/pl.json b/ui/public/locales/pl.json
index e91eae4..6838b5e 100644
--- a/ui/public/locales/pl.json
+++ b/ui/public/locales/pl.json
@@ -651,7 +651,7 @@
 "label.level": "Poziom",
 "label.limitcpuuse": "CPU Cap",
 "label.link.domain.to.ldap": "Link Domain to LDAP",
-"label.linklocalip": "Link Local IP Address",
+"label.linklocalip": "Control IP Address",
 "label.load.balancer": "Load Balancer",
 "label.loadbalancerinstance": "Assigned VMs",
 "label.loadbalancerrule": "Load balancing rule",
@@ -1481,4 +1481,4 @@
 "state.stopped": "Zatrzymano",
 "state.stopping": "Stopping",
 "state.suspended": "Zawieszono"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json
index c95bb5b..c36a5c7 100644
--- a/ui/public/locales/pt_BR.json
+++ b/ui/public/locales/pt_BR.json
@@ -844,6 +844,7 @@
 "label.isdedicated": "Dedicado",
 "label.isdefault": "\u00c9\u0089 padr\u00e3o",
 "label.isdynamicallyscalable": "Dinamicamente escal\u00e1vel",
+"label.istagarule": "Tag como regra JS",
 "label.isextractable": "Extra\u00edvel",
 "label.isfeatured": "Em destaque",
 "label.isforced": "For\u00e7ar exclus\u00e3o",
@@ -928,7 +929,7 @@
 "label.limitcpuuse": "Limite da CPU",
 "label.limits": "Configurar limites",
 "label.link.domain.to.ldap": "Link dom\u00ednio para LDAP",
-"label.linklocalip": "Endere\u00e7o IP do link local",
+"label.linklocalip": "Endere\u00e7o IP do Control",
 "label.linux": "Linux",
 "label.list.ciscoasa1000v": "ASA 1000v",
 "label.list.ciscovnmc": "Cisco VNMC",
@@ -1468,6 +1469,8 @@
 "label.shared": "Compatilhado",
 "label.sharedexecutable": "Compatilhado",
 "label.sharedmountpoint": "SharedMountPoint",
+"label.sharedrouterip": "Endere\u00e7os IPv4 para o roteador dentro da rede compartilhada",
+"label.sharedrouteripv6": "Endere\u00e7os IPv6 para o roteador dentro da rede compartilhada",
 "label.sharewith": "Compartilhar com",
 "label.showing": "Exibindo",
 "label.shrinkok": "Encolhimento OK",
@@ -2505,4 +2508,4 @@
 "state.suspended": "Suspendido",
 "user.login": "Entrar",
 "user.logout": "Sair"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/ru_RU.json b/ui/public/locales/ru_RU.json
index f855035..f68d767 100644
--- a/ui/public/locales/ru_RU.json
+++ b/ui/public/locales/ru_RU.json
@@ -1479,4 +1479,4 @@
 "state.stopped": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e",
 "state.stopping": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c",
 "state.suspended": "\u041f\u0440\u0438\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e"
-}
\ No newline at end of file
+}
diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json
index 7ba77fd..91353d2 100644
--- a/ui/public/locales/zh_CN.json
+++ b/ui/public/locales/zh_CN.json
@@ -2333,6 +2333,8 @@
   "label.shared": "\u5DF2\u5171\u4EAB",
   "label.sharedexecutable": "\u5DF2\u5171\u4EAB",
   "label.sharedmountpoint": "\u5171\u4EAB\u6302\u8F7D\u70B9",
+  "label.sharedrouterip": "\u5171\u4EAB\u7F51\u7EDC\u4E2D\u8DEF\u7531\u5668\u7684 IPv4 \u5730\u5740",
+  "label.sharedrouteripv6": "\u5171\u4EAB\u7F51\u7EDC\u4E2D\u8DEF\u7531\u5668\u7684 IPv6 \u5730\u5740",
   "label.sharewith": "\u5206\u4EAB",
 
   "label.show.ingress.rule": "\u663E\u793A\u5165\u53E3\u89C4\u5219",
@@ -4016,4 +4018,4 @@
   "title.upload.volume": "\u4E0A\u4F20\u5377",
   "user.login": "\u767B\u5F55",
   "user.logout": "\u6CE8\u9500"
-}
\ No newline at end of file
+}
diff --git a/ui/src/api/index.js b/ui/src/api/index.js
index 5e42862..1db4166 100644
--- a/ui/src/api/index.js
+++ b/ui/src/api/index.js
@@ -70,3 +70,28 @@
   notification.destroy()
   return api('logout')
 }
+
+export function oauthlogin (arg) {
+  if (!sourceToken.checkExistSource()) {
+    sourceToken.init()
+  }
+
+  // Logout before login is called to purge any duplicate sessionkey cookies
+  api('logout')
+
+  const params = new URLSearchParams()
+  params.append('command', 'oauthlogin')
+  params.append('email', arg.email)
+  params.append('secretcode', arg.secretcode)
+  params.append('provider', arg.provider)
+  params.append('domain', arg.domain)
+  params.append('response', 'json')
+  return axios({
+    url: '/',
+    method: 'post',
+    data: params,
+    headers: {
+      'content-type': 'application/x-www-form-urlencoded'
+    }
+  })
+}
diff --git a/ui/src/assets/icons/dark.svg b/ui/src/assets/icons/dark.svg
index 9190c1d..b1dd4b3 100644
--- a/ui/src/assets/icons/dark.svg
+++ b/ui/src/assets/icons/dark.svg
@@ -36,4 +36,4 @@
             </g>
         </g>
     </g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/ui/src/assets/icons/debian.svg b/ui/src/assets/icons/debian.svg
deleted file mode 100644
index b28d3a2..0000000
--- a/ui/src/assets/icons/debian.svg
+++ /dev/null
@@ -1,153 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:ns4="http://ns.adobe.com/SaveForWeb/1.0/"
-   xmlns:ns3="http://ns.adobe.com/Variables/1.0/"
-   xmlns:ns2="http://ns.adobe.com/AdobeIllustrator/10.0/"
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   ns2:viewOrigin="262 450"
-   ns2:rulerOrigin="0 0"
-   ns2:pageBounds="0 792 612 0"
-   viewBox="0 0 55.999999 56.000069"
-   overflow="visible"
-   enable-background="new 0 0 87.041 108.445"
-   xml:space="preserve"
-   version="1.1"
-   id="svg31"
-   sodipodi:docname="debian.svg"
-   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
-   style="overflow:visible"><defs
-   id="defs35" /><sodipodi:namedview
-   pagecolor="#ffffff"
-   bordercolor="#666666"
-   borderopacity="1"
-   objecttolerance="10"
-   gridtolerance="10"
-   guidetolerance="10"
-   inkscape:pageopacity="0"
-   inkscape:pageshadow="2"
-   inkscape:window-width="1866"
-   inkscape:window-height="1017"
-   id="namedview33"
-   showgrid="false"
-   inkscape:zoom="2.1762184"
-   inkscape:cx="-62.475298"
-   inkscape:cy="28.002047"
-   inkscape:window-x="54"
-   inkscape:window-y="26"
-   inkscape:window-maximized="1"
-   inkscape:current-layer="g28"
-   fit-margin-top="0"
-   fit-margin-left="0"
-   fit-margin-right="0"
-   fit-margin-bottom="0" />
-	<metadata
-   id="metadata2">
-		<ns3:variableSets>
-			<ns3:variableSet
-   varSetName="binding1"
-   locked="none">
-				<ns3:variables />
-				<ns3:sampleDataSets />
-			</ns3:variableSet>
-		</ns3:variableSets>
-		<ns4:sfw>
-			<ns4:slices />
-			<ns4:sliceSourceBounds
-   y="341.555"
-   x="262"
-   width="87.041"
-   height="108.445"
-   bottomLeftOrigin="true" />
-		</ns4:sfw>
-	<rdf:RDF><cc:Work
-     rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
-       rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
-	<g
-   id="Layer_1"
-   ns2:layer="yes"
-   ns2:dimmedPercent="50"
-   ns2:rgbTrio="#4F008000FFFF"
-   transform="translate(-20.985947,-26.22447)">
-		<g
-   id="g28">
-			<path
-   ns2:knockout="Off"
-   d="m 53.872479,55.811055 c -0.927921,0.01291 0.175567,0.478161 1.386977,0.664571 0.334609,-0.261284 0.638236,-0.525667 0.908815,-0.78282 -0.75442,0.184861 -1.522266,0.188992 -2.295792,0.118249"
-   id="path4"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 58.852891,54.569696 c 0.552518,-0.762682 0.955289,-1.597656 1.097291,-2.461031 -0.123929,0.615516 -0.458022,1.146863 -0.772493,1.707644 -1.734495,1.092127 -0.163174,-0.648564 -10e-4,-1.310037 -1.865137,2.347429 -0.256121,1.407631 -0.323766,2.063424"
-   id="path6"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 60.691176,49.786022 c 0.112053,-1.670981 -0.328929,-1.142732 -0.477128,-0.505012 0.172985,0.08985 0.309824,1.177846 0.477128,0.505012"
-   id="path8"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 50.353918,26.946873 c 0.495201,0.08882 1.069924,0.156977 0.98937,0.275226 0.541674,-0.118765 0.664571,-0.228236 -0.98937,-0.275226"
-   id="path10"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 51.343288,27.222099 -0.350101,0.07229 0.325831,-0.02892 0.02427,-0.04338"
-   id="path12"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 66.784887,50.419611 c 0.05525,1.500578 -0.438917,2.228663 -0.884546,3.517529 l -0.801926,0.400705 c -0.65631,1.274407 0.06351,0.809155 -0.406386,1.822794 -1.024482,0.910881 -3.109077,2.850376 -3.776231,3.027491 -0.486939,-0.01085 0.329962,-0.574722 0.436851,-0.79573 -1.371485,0.941864 -1.100389,1.413828 -3.197894,1.985969 l -0.06145,-0.136323 c -5.173018,2.433663 -12.358856,-2.389255 -12.26436,-8.969904 -0.05525,0.417745 -0.156977,0.313438 -0.271612,0.482292 -0.266964,-3.385854 1.563576,-6.786682 4.650966,-8.175207 3.019746,-1.494898 6.559995,-0.881448 8.723078,1.13447 -1.188172,-1.556347 -3.553158,-3.206156 -6.356027,-3.051761 -2.745552,0.04337 -5.313988,1.788197 -6.171166,3.682251 -1.406598,0.885579 -1.569772,3.413738 -2.182706,3.876408 -0.824647,6.060662 1.551183,8.679186 5.570109,11.759347 0.632556,0.426524 0.178148,0.49107 0.263866,0.815869 -1.335339,-0.625327 -2.558109,-1.569256 -3.563486,-2.724897 0.533413,0.780755 1.109168,1.539822 1.853261,2.136232 -1.258916,-0.426523 -2.940741,-3.050728 -3.431811,-3.157617 2.170313,3.885702 8.805182,6.814566 12.279335,5.361494 -1.607467,0.05938 -3.64972,0.03305 -5.455991,-0.634621 -0.758551,-0.390378 -1.790263,-1.199017 -1.605918,-1.350314 4.741331,1.771157 9.639123,1.341535 13.741702,-1.94724 1.043588,-0.81277 2.183738,-2.195615 2.513184,-2.214721 -0.496234,0.746158 0.08469,0.358879 -0.296398,1.01777 1.039974,-1.677178 -0.451826,-0.682645 1.075087,-2.896333 l 0.563879,0.776624 c -0.209647,-1.39214 1.728815,-3.082743 1.532077,-5.284555 0.444596,-0.673349 0.496234,0.724471 0.02427,2.273588 0.654761,-1.718487 0.172469,-1.994746 0.340806,-3.412705 0.181763,0.476612 0.420327,0.983173 0.542707,1.48612 -0.426523,-1.660654 0.437884,-2.796673 0.651662,-3.761773 -0.21068,-0.09346 -0.658374,0.734282 -0.760616,-1.227417 0.01497,-0.852014 0.237015,-0.446662 0.322733,-0.656309 -0.167305,-0.09605 -0.606222,-0.749257 -0.873186,-2.001976 0.19364,-0.294332 0.517405,0.763198 0.780755,0.806574 -0.16937,-0.996083 -0.461121,-1.755666 -0.472997,-2.519897 -0.769395,-1.607984 -0.272128,0.214294 -0.896423,-0.69039 -0.818966,-2.554494 0.679547,-0.592796 0.780755,-1.753601 1.24136,1.798525 1.949306,4.585903 2.274104,5.740512 -0.247858,-1.407631 -0.648563,-2.771371 -1.137568,-4.090702 0.376952,0.158526 -0.607254,-2.896333 0.490037,-0.873186 -1.172165,-4.312742 -5.016557,-8.342512 -8.553191,-10.233467 0.43272,0.396057 0.979042,0.893324 0.78282,0.971296 -1.758764,-1.047203 -1.449457,-1.12879 -1.701447,-1.571321 -1.432933,-0.582984 -1.526913,0.04699 -2.476005,0.001 -2.70063,-1.432419 -3.221133,-1.280089 -5.706433,-2.177544 l 0.113085,0.528249 c -1.78923,-0.595894 -2.084595,0.226171 -4.018409,0.0021 -0.117733,-0.09191 0.619646,-0.332544 1.226384,-0.420843 -1.729847,0.228236 -1.648777,-0.340806 -3.341446,0.063 0.417229,-0.292783 0.858211,-0.486423 1.303324,-0.735314 -1.410729,0.08572 -3.36778,0.821032 -2.763625,0.15233 -2.300955,1.026548 -6.387526,2.467743 -8.680735,4.617918 l -0.07229,-0.481776 c -1.050818,1.261498 -4.582289,3.767453 -4.863712,5.401255 l -0.280906,0.06558 c -0.546839,0.925856 -0.900553,1.975125 -1.334306,2.927832 -0.715176,1.218638 -1.048236,0.468866 -0.946511,0.659924 -1.406598,2.851924 -2.10525,5.248408 -2.708889,7.213721 0.430138,0.642884 0.01033,3.870211 0.172985,6.453106 -0.706398,12.756463 8.952863,25.142168 19.511129,28.001841 1.547568,0.553548 3.849039,0.532381 5.806607,0.589182 -2.309733,-0.660445 -2.608197,-0.350104 -4.858031,-1.134472 -1.622958,-0.764231 -1.978739,-1.636901 -3.128184,-2.634532 l 0.454924,0.803992 c -2.25448,-0.7978 -1.311068,-0.987308 -3.145223,-1.568227 l 0.485907,-0.634622 c -0.730667,-0.05525 -1.935364,-1.231548 -2.26481,-1.882693 l -0.799344,0.0315 c -0.960453,-1.185074 -1.472178,-2.039154 -1.434999,-2.700627 l -0.258186,0.460088 C 37.555113,72.462514 34.31436,68.520528 35.995668,69.438122 35.683263,69.152568 35.2681,68.973386 34.817823,68.155453 l 0.342355,-0.391411 c -0.809156,-1.041006 -1.489218,-2.375312 -1.437581,-2.819909 0.431688,0.582984 0.731184,0.691939 1.027581,0.791599 -2.043285,-5.069744 -2.15792,-0.279358 -3.705488,-5.160626 l 0.32738,-0.02634 c -0.250956,-0.377984 -0.403286,-0.7885 -0.605188,-1.191271 l 0.142519,-1.420024 c -1.471145,-1.70093 -0.411549,-7.232311 -0.19932,-10.265999 0.147166,-1.233613 1.227933,-2.546748 2.049998,-4.606041 l -0.500881,-0.08623 c 0.957354,-1.669948 5.466318,-6.706644 7.554528,-6.447425 1.011573,-1.270793 -0.200869,-0.0046 -0.39864,-0.324799 2.22195,-2.299406 2.920602,-1.624507 4.420148,-2.038121 1.617278,-0.959937 -1.388009,0.37437 -0.621196,-0.366108 2.79564,-0.714143 1.98132,-1.623475 5.628458,-1.985968 0.384698,0.218941 -0.892807,0.338223 -1.213475,0.622228 2.329356,-1.139634 7.371216,-0.880415 10.646049,0.632556 3.799984,1.775805 8.069351,7.025246 8.237688,11.964348 l 0.191575,0.05164 c -0.09708,1.963248 0.300528,4.233737 -0.388312,6.319365 l 0.468866,-0.987304"
-   id="path14"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 43.744352,57.084946 -0.130126,0.650629 c 0.609836,0.828261 1.093677,1.725716 1.872366,2.373247 -0.560264,-1.093677 -0.97646,-1.545502 -1.74224,-3.023876"
-   id="path16"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 45.186064,57.028145 c -0.322733,-0.356814 -0.513791,-0.786435 -0.727569,-1.214508 0.204483,0.752354 0.623261,1.398853 1.013123,2.056195 l -0.285554,-0.841687"
-   id="path18"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 70.696924,51.483338 -0.136323,0.341839 c -0.249924,1.775288 -0.789533,3.531987 -1.616762,5.160625 0.91398,-1.718487 1.505226,-3.598082 1.753085,-5.502464"
-   id="path20"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 50.53723,26.50176 c 0.627393,-0.229786 1.542405,-0.125995 2.208009,-0.277292 -0.867506,0.07281 -1.730881,0.116184 -2.583411,0.226171 l 0.375402,0.05112"
-   id="path22"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 28.511368,38.214118 c 0.144584,1.338437 -1.006926,1.857908 0.255088,0.975427 0.676447,-1.523815 -0.264383,-0.420843 -0.255088,-0.975427"
-   id="path24"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-			<path
-   ns2:knockout="Off"
-   d="m 27.028346,44.408521 c 0.290718,-0.892292 0.343388,-1.428286 0.454408,-1.944659 -0.803476,1.027065 -0.369723,1.246007 -0.454408,1.944659"
-   id="path26"
-   style="fill:#000000;fill-opacity:1;stroke-width:0.51637238"
-   inkscape:connector-curvature="0" />
-		</g>
-	</g>
-</svg>
\ No newline at end of file
diff --git a/ui/src/assets/icons/kubernetes.svg b/ui/src/assets/icons/kubernetes.svg
deleted file mode 100644
index f4115cb..0000000
--- a/ui/src/assets/icons/kubernetes.svg
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xml:space="preserve"
-   width="160"
-   height="160"
-   version="1.1"
-   style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
-   viewBox="0 0 1666.6667 1666.8369"
-   id="svg38"
-   sodipodi:docname="kubernetes.svg"
-   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
-   id="metadata42"><rdf:RDF><cc:Work
-       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
-         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
-   pagecolor="#ffffff"
-   bordercolor="#666666"
-   borderopacity="1"
-   objecttolerance="10"
-   gridtolerance="10"
-   guidetolerance="10"
-   inkscape:pageopacity="0"
-   inkscape:pageshadow="2"
-   inkscape:window-width="1673"
-   inkscape:window-height="931"
-   id="namedview40"
-   showgrid="false"
-   units="px"
-   inkscape:zoom="1.11"
-   inkscape:cx="-5.8558558"
-   inkscape:cy="95.225335"
-   inkscape:window-x="54"
-   inkscape:window-y="26"
-   inkscape:window-maximized="0"
-   inkscape:current-layer="layer1" />
- <defs
-   id="defs4">
- </defs>
-
-<g
-   class="custom-icon"
-   transform="matrix(10.417731,0,0,10.417731,-3348.2599,-4287.0516)"
-   id="layer1"
-   inkscape:label="Layer 1"><path
-     inkscape:connector-curvature="0"
-     id="path3059"
-     d="m 402.68583,413.59883 c -2.48732,2.5e-4 -4.50412,2.24066 -4.50385,5.00427 4e-5,0.0425 0.01,0.0829 0.01,0.12498 -0.003,0.37553 -0.0216,0.82791 -0.01,1.15483 0.0593,1.59399 0.40679,2.81394 0.61591,4.28251 0.37888,3.14319 0.69635,5.7487 0.50043,8.17042 -0.19062,0.91322 -0.86313,1.74839 -1.46279,2.32891 l -0.10592,1.90548 c -2.70285,0.22389 -5.42381,0.63402 -8.14156,1.25107 -11.69445,2.65524 -21.7632,8.67911 -29.42894,16.81242 -0.49741,-0.33935 -1.36762,-0.96365 -1.6264,-1.15482 -0.80413,0.10868 -1.61682,0.3567 -2.67536,-0.25992 -2.01553,-1.35672 -3.85126,-3.2295 -6.0725,-5.48545 -1.01776,-1.07912 -1.75482,-2.10672 -2.96406,-3.1469 -0.2747,-0.2362 -0.69367,-0.55574 -1.00087,-0.79878 -0.94536,-0.75374 -2.06042,-1.14682 -3.13729,-1.18368 -1.3845,-0.0474 -2.71736,0.4939 -3.58957,1.58788 -1.55062,1.94485 -1.05417,4.91745 1.1067,6.64028 0.0216,0.0185 0.0453,0.0311 0.0674,0.048 0.29687,0.24082 0.66056,0.54913 0.93347,0.75065 1.28323,0.94742 2.45539,1.43242 3.73396,2.18454 2.69369,1.66351 4.92678,3.04288 6.69801,4.70595 0.6917,0.73727 0.81258,2.03647 0.90462,2.59836 l 1.44354,1.28957 c -7.72762,11.62948 -11.30404,25.99429 -9.19052,40.63081 l -1.88622,0.54854 c -0.49713,0.64195 -1.19961,1.6521 -1.93435,1.95358 -2.31739,0.72993 -4.92549,0.99797 -8.07418,1.32806 -1.47828,0.12291 -2.75379,0.0496 -4.32101,0.34645 -0.34491,0.0654 -0.82554,0.19064 -1.20294,0.27901 -0.0125,0.003 -0.0247,0.006 -0.0385,0.01 -0.0216,0.006 -0.0478,0.0155 -0.0674,0.0185 -2.6546,0.64143 -4.35993,3.0814 -3.81094,5.48546 0.54915,2.40459 3.14213,3.86693 5.81265,3.29126 0.0186,-0.003 0.0474,-0.006 0.0674,-0.01 0.0308,-0.006 0.0567,-0.0216 0.0865,-0.0278 0.37229,-0.0817 0.83881,-0.17276 1.16447,-0.25991 1.54079,-0.41256 2.65669,-1.01872 4.04191,-1.54942 2.98005,-1.06884 5.44825,-1.96173 7.85284,-2.30966 1.00431,-0.0785 2.06242,0.61966 2.58873,0.91427 l 1.96322,-0.33685 c 4.51777,14.00664 13.98554,25.32775 25.9741,32.4315 l -0.81803,1.96322 c 0.29502,0.76231 0.62008,1.79374 0.40044,2.54657 -0.87419,2.26691 -2.37156,4.65967 -4.07665,7.32723 -0.82557,1.23242 -1.6705,2.18882 -2.41551,3.59923 -0.1783,0.33748 -0.40532,0.8559 -0.57741,1.21258 -1.15754,2.4767 -0.30845,5.32921 1.91508,6.3997 2.23754,1.07716 5.01489,-0.0588 6.21685,-2.54063 10e-4,-0.003 0.01,-0.006 0.01,-0.01 10e-4,-0.003 -0.001,-0.006 0,-0.01 0.17122,-0.35186 0.41375,-0.81435 0.55817,-1.14522 0.63814,-1.46185 0.85047,-2.71462 1.29919,-4.1285 1.19167,-2.99338 1.84641,-6.13419 3.48685,-8.09124 0.44921,-0.53593 1.18157,-0.74202 1.94086,-0.94532 l 1.02009,-1.84774 c 10.45145,4.01167 22.15019,5.08819 33.83655,2.43478 2.66598,-0.60532 5.23968,-1.38871 7.72774,-2.3289 0.2867,0.50852 0.81954,1.4861 0.96236,1.73224 0.77161,0.25099 1.61384,0.38066 2.30005,1.39541 1.22733,2.09686 2.06666,4.57751 3.08916,7.57378 0.44882,1.41385 0.67063,2.66668 1.3088,4.12851 0.14537,0.3332 0.3868,0.8022 0.55818,1.15482 1.19945,2.4898 3.98561,3.62979 6.22648,2.55027 2.22329,-1.07103 3.07329,-3.92331 1.91508,-6.3997 -0.17214,-0.35664 -0.40876,-0.87509 -0.58702,-1.21257 -0.74511,-1.41034 -1.58989,-2.35724 -2.41554,-3.58959 -1.70522,-2.6675 -3.11947,-4.88346 -3.99378,-7.15034 -0.36557,-1.16918 0.0616,-1.8963 0.34644,-2.65612 -0.17059,-0.19554 -0.53546,-1.29959 -0.75064,-1.81883 12.45907,-7.35649 21.64881,-19.09978 25.96447,-32.66247 0.58277,0.0914 1.59566,0.27068 1.92472,0.33679 0.67734,-0.44674 1.30015,-1.02963 2.52137,-0.93346 2.40461,0.34777 4.87274,1.24096 7.85285,2.30966 1.38521,0.5306 2.50109,1.14655 4.04192,1.55902 0.32564,0.0871 0.79218,0.16845 1.16443,0.25006 0.0308,0.006 0.0563,0.0216 0.0866,0.0278 0.0216,0.003 0.048,0.006 0.0674,0.01 2.6707,0.57492 5.26414,-0.88651 5.81263,-3.29126 0.54841,-2.40421 -1.15614,-4.84475 -3.81094,-5.48546 -0.38614,-0.0878 -0.93378,-0.23682 -1.3088,-0.30795 -1.56722,-0.29686 -2.8427,-0.22358 -4.32098,-0.34646 -3.14871,-0.32994 -5.75676,-0.59825 -8.07422,-1.32804 -0.94487,-0.36657 -1.61707,-1.49092 -1.94395,-1.95361 l -1.81888,-0.52929 c 0.94304,-6.82258 0.68876,-13.92307 -0.94312,-21.02756 -1.64703,-7.17063 -4.55782,-13.72888 -8.43987,-19.50701 0.46655,-0.42413 1.34768,-1.20437 1.59751,-1.43393 0.073,-0.80813 0.01,-1.65541 0.84689,-2.55022 1.77113,-1.66317 4.00442,-3.04229 6.69801,-4.70597 1.27855,-0.75217 2.46041,-1.23705 3.74357,-2.18452 0.29008,-0.21435 0.68639,-0.5536 0.99124,-0.79879 2.16042,-1.72341 2.65784,-4.69582 1.1067,-6.64028 -1.55111,-1.94446 -4.55685,-2.1276 -6.71727,-0.40418 -0.30734,0.24358 -0.72478,0.56124 -1.00085,0.79876 -1.20918,1.04025 -1.95595,2.06773 -2.97367,3.14691 -2.22113,2.25607 -4.05704,4.13823 -6.0725,5.49508 -0.87337,0.50843 -2.15259,0.3325 -2.73311,0.29841 l -1.71301,1.22222 c -9.76795,-10.24277 -23.06727,-16.79141 -37.38759,-18.0635 -0.04,-0.60015 -0.0924,-1.68489 -0.10592,-2.01136 -0.58625,-0.56096 -1.29445,-1.0399 -1.47242,-2.25191 -0.19587,-2.42172 0.13126,-5.02725 0.51006,-8.17042 0.2091,-1.46857 0.5566,-2.68855 0.61593,-4.28251 0.0125,-0.36235 -0.01,-0.88815 -0.01,-1.27992 -3.3e-4,-2.76363 -2.0165,-5.00454 -4.50385,-5.00427 z m -5.63943,34.93365 -1.33765,23.62589 -0.0962,0.048 c -0.0897,2.11359 -1.82925,3.8013 -3.96493,3.8013 -0.87483,0 -1.68231,-0.28085 -2.33855,-0.76024 l -0.0385,0.0185 -19.37227,-13.73288 c 5.95388,-5.85455 13.56939,-10.1811 22.34598,-12.17382 1.60322,-0.36403 3.20572,-0.63412 4.80216,-0.82764 z m 11.28849,0 c 10.24674,1.26023 19.72298,5.90004 26.98456,13.01109 l -19.24719,13.64626 -0.0674,-0.0278 c -1.70835,1.24774 -4.11532,0.93814 -5.44696,-0.73139 -0.54547,-0.68398 -0.83167,-1.48817 -0.86612,-2.30006 l -0.0185,-0.01 z m -45.46185,21.8263 17.68817,15.82119 -0.0186,0.0962 c 1.59657,1.38792 1.832,3.79643 0.50044,5.4662 -0.54545,0.68396 -1.27559,1.14273 -2.05946,1.35691 l -0.0185,0.077 -22.67318,6.54403 c -1.154,-10.55214 1.33297,-20.80963 6.58253,-29.36156 z m 79.50048,0.01 c 2.62812,4.25979 4.61828,9.01757 5.80305,14.17554 1.17056,5.09613 1.46433,10.18315 0.98161,15.09944 l -22.78868,-6.56329 -0.0185,-0.096 c -2.04068,-0.55775 -3.29478,-2.63354 -2.81972,-4.71556 0.19461,-0.85294 0.6474,-1.57449 1.26069,-2.10758 l -0.01,-0.048 17.59192,-15.74421 z m -43.31578,17.03377 h 7.24656 l 4.50384,5.62978 -1.61677,7.02523 -6.50554,3.12767 -6.5248,-3.13729 -1.61676,-7.02522 z m 23.23136,19.26642 c 0.30795,-0.0155 0.61455,0.0125 0.91423,0.0674 l 0.0384,-0.048 23.45272,3.96493 c -3.43231,9.64301 -10.00017,17.99691 -18.77564,23.58741 l -9.10392,-21.9899 0.0278,-0.0384 c -0.83628,-1.94321 6.1e-4,-4.22195 1.92472,-5.14865 0.49264,-0.23713 1.00728,-0.36861 1.52053,-0.39455 z m -39.38937,0.096 c 1.78976,0.0247 3.39511,1.26729 3.81094,3.08916 0.19462,0.85291 0.0997,1.698 -0.22141,2.44438 l 0.0674,0.0865 -9.00769,21.76856 c -8.42167,-5.40418 -15.12958,-13.49585 -18.71788,-23.42383 l 23.2506,-3.94566 0.0385,0.048 c 0.26021,-0.0477 0.52383,-0.0708 0.7795,-0.0674 z m 19.64176,9.53699 c 0.62343,-0.0216 1.25603,0.10503 1.85733,0.39455 0.78825,0.37956 1.39714,0.97721 1.78039,1.69376 h 0.0865 l 11.46171,20.70997 c -1.48751,0.49864 -3.01676,0.92483 -4.58083,1.27995 -8.76578,1.99029 -17.50372,1.38722 -25.41592,-1.3088 L 399.1635,518.3999 h 0.0185 c 0.68603,-1.28244 1.98709,-2.04755 3.35864,-2.09794 z"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill-opacity:1;stroke:#ffffff;stroke-width:0.07698873;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
-     sodipodi:nodetypes="ccccccccsccccscssccsccccccccscccsccccccccccccccscccscsccsccccscscsccccccccscccscsccccsccccscscscccccccccccccccscccsccccccccccccscccccscccccccccccccccccccccccscccscccccccccscccscccc"
-     inkscape:export-filename="./path3059.png"
-     inkscape:export-xdpi="250.55"
-     inkscape:export-ydpi="250.55" /></g></svg>
\ No newline at end of file
diff --git a/ui/src/assets/icons/light.svg b/ui/src/assets/icons/light.svg
index fbb1000..eb68ad7 100644
--- a/ui/src/assets/icons/light.svg
+++ b/ui/src/assets/icons/light.svg
@@ -37,4 +37,4 @@
             </g>
         </g>
     </g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/ui/src/components/CheckBoxSelectPair.vue b/ui/src/components/CheckBoxSelectPair.vue
index 19dd8bd..de6aed4 100644
--- a/ui/src/components/CheckBoxSelectPair.vue
+++ b/ui/src/components/CheckBoxSelectPair.vue
@@ -34,13 +34,14 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             @change="val => { handleSelectChange(val) }">
             <a-select-option
               v-for="(opt) in selectSource"
               :key="opt.id"
-              :disabled="opt.enabled === false">
+              :disabled="opt.enabled === false"
+              :label="opt.displaytext || opt.name || opt.description">
               {{ opt.displaytext || opt.name || opt.description }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/components/KeyValuePairInput.vue b/ui/src/components/KeyValuePairInput.vue
new file mode 100644
index 0000000..03989de
--- /dev/null
+++ b/ui/src/components/KeyValuePairInput.vue
@@ -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.
+
+<template>
+   <a-form
+    ref="formRef"
+    name="form"
+    :model="dynamicValidateForm"
+  >
+    <a-space
+      v-for="(pair, index) in dynamicValidateForm.pairArray"
+      :key="pair.id"
+    >
+      <a-form-item :name="['pairArray', index, 'key']">
+        <a-input v-model:value="pair.key" :placeholder="$t('label.key')" />
+      </a-form-item>
+      <a-form-item :name="['pairArray', index, 'value']">
+        <a-input v-model:value="pair.value" :placeholder="$t('label.value')" />
+      </a-form-item>
+      <MinusCircleOutlined @click="removePair(pair)" />
+    </a-space>
+    <a-form-item>
+      <a-button type="dashed" block @click="addKeyValue()">
+        <PlusOutlined />
+        {{ $t('label.add.key.value') }}
+      </a-button>
+    </a-form-item>
+  </a-form>
+</template>
+
+<script>
+import { ref, reactive } from 'vue'
+
+export default {
+  name: 'KeyValuePairInput',
+  props: {
+    pairs: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data () {
+    var pairArray = []
+    for (var key in this.pairs) {
+      pairArray.push({ key: key, value: this.pairs[key] })
+    }
+    return {
+      dynamicValidateForm: reactive({
+        pairArray: reactive(pairArray)
+      }),
+      formRef: ref()
+    }
+  },
+  created () {},
+  watch: {
+    dynamicValidateForm: {
+      handler: function (val) {
+        this.$emit('update-pairs', val.pairArray.reduce((obj, item) => {
+          obj[item.key] = item.value
+          return obj
+        }, {}))
+      },
+      deep: true
+    }
+  },
+  computed: {},
+  methods: {
+    addKeyValue () {
+      this.dynamicValidateForm.pairArray.push({ id: Date.now(), key: '', value: '' })
+    },
+    removePair (pair) {
+      const index = this.dynamicValidateForm.pairArray.indexOf(pair)
+      this.dynamicValidateForm.pairArray.splice(index, 1)
+    }
+  }
+}
+</script>
diff --git a/ui/src/components/header/CreateMenu.vue b/ui/src/components/header/CreateMenu.vue
new file mode 100644
index 0000000..5ff6894
--- /dev/null
+++ b/ui/src/components/header/CreateMenu.vue
@@ -0,0 +1,171 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-dropdown>
+    <template #overlay>
+      <a-menu>
+        <a-menu-item style="width: 100%; padding: 12px">
+          <router-link :to="{ path: '/action/deployVirtualMachine'}">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <cloud-server-outlined/>
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.instance') }}
+                </h3>
+                <small>{{ $t('label.create.instance') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+        <a-menu-item style="width: 100%; padding: 12px" v-if="'listKubernetesClusters' in $store.getters.apis">
+          <router-link :to="{ path: '/kubernetes', query: { action: 'createKubernetesCluster' } }">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <font-awesome-icon :icon="['fa-solid', 'fa-dharmachakra']" />
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.kubernetes') }}
+                </h3>
+                <small>{{ $t('label.kubernetes.cluster.create') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+        <a-menu-item style="width: 100%; padding: 12px">
+          <router-link :to="{ path: '/volume', query: { action: 'createVolume' } }">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <hdd-outlined />
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.volume') }}
+                </h3>
+                <small>{{ $t('label.action.create.volume') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+        <a-menu-item style="width: 100%; padding: 12px">
+          <router-link :to="{ path: '/guestnetwork', query: { action: 'createNetwork' } }">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <apartment-outlined />
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.network') }}
+                </h3>
+                <small>{{ $t('label.add.network') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+        <a-menu-item style="width: 100%; padding: 12px">
+          <router-link :to="{ path: '/vpc', query: { action: 'createVPC' } }">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <deployment-unit-outlined />
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.vpc') }}
+                </h3>
+                <small>{{ $t('label.add.vpc') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+        <a-menu-item style="width: 100%; padding: 12px">
+          <router-link :to="{ path: '/template', query: { action: 'registerTemplate' } }">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <picture-outlined />
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.templatename') }}
+                </h3>
+                <small>{{ $t('label.action.register.template') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+        <a-menu-item style="width: 100%; padding: 12px" v-if="'deployVnfAppliance' in $store.getters.apis">
+          <router-link :to="{ path: '/action/deployVnfAppliance'}">
+            <a-row>
+              <a-col style="margin-right: 12px">
+                <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                  <template #icon>
+                    <font-awesome-icon :icon="['fa-solid', 'fa-dharmachakra']" />
+                  </template>
+                </a-avatar>
+              </a-col>
+              <a-col>
+                <h3 style="margin-bottom: 0px;">
+                  {{ $t('label.vnf.appliance') }}
+                </h3>
+                <small>{{ $t('label.vnf.appliance.add') }}</small>
+              </a-col>
+            </a-row>
+          </router-link>
+        </a-menu-item>
+      </a-menu>
+    </template>
+    <a-button type="primary">
+      {{ $t('label.create') }}
+      <DownOutlined />
+    </a-button>
+  </a-dropdown>
+</template>
+
+<script>
+
+export default {
+  name: 'CreateMenu',
+  components: {
+  }
+}
+</script>
diff --git a/ui/src/components/header/ProjectMenu.vue b/ui/src/components/header/ProjectMenu.vue
index 9182303..e2f3653 100644
--- a/ui/src/components/header/ProjectMenu.vue
+++ b/ui/src/components/header/ProjectMenu.vue
@@ -27,18 +27,6 @@
       @focus="fetchData"
       showSearch>
 
-      <template #suffixIcon>
-        <a-tooltip placement="bottom">
-          <template #title>
-            <span>{{ $t('label.projects') }}</span>
-          </template>
-          <span class="custom-suffix-icon">
-            <ProjectOutlined v-if="!loading" class="ant-select-suffix" />
-            <LoadingOutlined v-else />
-          </span>
-        </a-tooltip>
-      </template>
-
       <a-select-option
         v-for="(project, index) in projects"
         :key="index"
@@ -93,7 +81,7 @@
       const projects = []
       const getNextPage = () => {
         this.loading = true
-        api('listProjects', { listAll: true, details: 'min', page: page, pageSize: 500, showIcon: true }).then(json => {
+        api('listProjects', { listAll: true, page: page, pageSize: 500, showIcon: true }).then(json => {
           if (json?.listprojectsresponse?.project) {
             projects.push(...json.listprojectsresponse.project)
           }
@@ -142,7 +130,7 @@
 <style lang="less" scoped>
 .project {
   &-select {
-    width: 30vw;
+    width: 27vw;
   }
 
   &-icon {
diff --git a/ui/src/components/header/SamlDomainSwitcher.vue b/ui/src/components/header/SamlDomainSwitcher.vue
index fbb7557..1d820dc 100644
--- a/ui/src/components/header/SamlDomainSwitcher.vue
+++ b/ui/src/components/header/SamlDomainSwitcher.vue
@@ -25,7 +25,7 @@
       showSearch
       optionFilterProp="label"
       :filterOption="(input, option) => {
-        return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+        return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
       }"
       @change="changeAccount"
       @focus="fetchData" >
@@ -42,7 +42,7 @@
         </a-tooltip>
       </template>
 
-      <a-select-option v-for="(account, index) in samlAccounts" :key="index">
+      <a-select-option v-for="(account, index) in samlAccounts" :key="index" :label="`${account.accountName} (${account.domainName})`">
         {{ `${account.accountName} (${account.domainName})` }}
       </a-select-option>
     </a-select>
diff --git a/ui/src/components/header/UserMenu.vue b/ui/src/components/header/UserMenu.vue
index 0c74345..c1aa9d7 100644
--- a/ui/src/components/header/UserMenu.vue
+++ b/ui/src/components/header/UserMenu.vue
@@ -17,6 +17,9 @@
 
 <template>
   <div class="user-menu">
+    <span class="action">
+      <create-menu v-if="device === 'desktop'" />
+    </span>
     <external-link class="action"/>
     <translation-menu class="action"/>
     <header-notice class="action"/>
@@ -29,48 +32,38 @@
         <span v-if="image">
           <resource-icon :image="image" size="4x" style="margin-right: 5px; margin-top: -3px"/>
         </span>
-        <a-avatar v-else-if="userInitials" class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: '#1890ff', color: 'white' }">
+        <a-avatar v-else-if="userInitials" class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: $config.theme['@primary-color'], color: 'white' }">
           {{ userInitials }}
         </a-avatar>
-        <a-avatar v-else class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: '#1890ff', color: 'white' }">
+        <a-avatar v-else class="user-menu-avatar avatar" size="small" :style="{ backgroundColor: $config.theme['@primary-color'], color: 'white' }">
           <template #icon><user-outlined /></template>
         </a-avatar>
         <span>{{ nickname() }}</span>
       </span>
       <template #overlay>
-        <a-menu class="user-menu-wrapper">
-          <router-link :to="{ path: '/accountuser/' + $store.getters.userInfo.id }">
-            <a-menu-item class="user-menu-item" key="0">
-                <UserOutlined class="user-menu-item-icon" />
-                <span class="user-menu-item-name">{{ $t('label.profilename') }}</span>
-            </a-menu-item>
-          </router-link>
-          <router-link :to="{ path: '/account/' + $store.getters.userInfo.accountid, query: { tab: 'limits' } }">
-            <a-menu-item class="user-menu-item" key="0">
-                <ControlOutlined class="user-menu-item-icon" />
-                <span class="user-menu-item-name">{{ $t('label.limits') }}</span>
-            </a-menu-item>
-          </router-link>
-          <a @click="toggleUseBrowserTimezone">
-            <a-menu-item class="user-menu-item" key="1">
-                <ClockCircleOutlined class="user-menu-item-icon" />
-                <span class="user-menu-item-name" style="margin-right: 5px">{{ $t('label.use.local.timezone') }}</span>
-                <a-switch :checked="$store.getters.usebrowsertimezone" />
-            </a-menu-item>
-          </a>
-          <a :href="$config.docBase" target="_blank">
-            <a-menu-item class="user-menu-item" key="2">
-              <QuestionCircleOutlined class="user-menu-item-icon" />
-              <span class="user-menu-item-name">{{ $t('label.help') }}</span>
-            </a-menu-item>
-          </a>
+        <a-menu class="user-menu-wrapper" @click="handleClickMenu">
+          <a-menu-item class="user-menu-item" key="profile">
+            <UserOutlined class="user-menu-item-icon" />
+            <span class="user-menu-item-name">{{ $t('label.profilename') }}</span>
+          </a-menu-item>
+          <a-menu-item class="user-menu-item" key="limits">
+            <ControlOutlined class="user-menu-item-icon" />
+            <span class="user-menu-item-name">{{ $t('label.limits') }}</span>
+          </a-menu-item>
+          <a-menu-item class="user-menu-item" key="timezone">
+            <ClockCircleOutlined class="user-menu-item-icon" />
+            <span class="user-menu-item-name" style="margin-right: 5px">{{ $t('label.use.local.timezone') }}</span>
+            <a-switch :checked="$store.getters.usebrowsertimezone" />
+          </a-menu-item>
+          <a-menu-item class="user-menu-item" key="document">
+            <QuestionCircleOutlined class="user-menu-item-icon" />
+            <span class="user-menu-item-name">{{ $t('label.help') }}</span>
+          </a-menu-item>
           <a-menu-divider/>
-          <a href="javascript:;" @click="handleLogout">
-            <a-menu-item class="user-menu-item" key="3">
-              <LogoutOutlined class="user-menu-item-icon" />
-              <span class="user-menu-item-name">{{ $t('label.logout') }}</span>
-            </a-menu-item>
-          </a>
+          <a-menu-item class="user-menu-item" key="logout">
+            <LogoutOutlined class="user-menu-item-icon" />
+            <span class="user-menu-item-name">{{ $t('label.logout') }}</span>
+          </a-menu-item>
         </a-menu>
       </template>
     </a-dropdown>
@@ -79,6 +72,7 @@
 
 <script>
 import { api } from '@/api'
+import CreateMenu from './CreateMenu'
 import ExternalLink from './ExternalLink'
 import HeaderNotice from './HeaderNotice'
 import TranslationMenu from './TranslationMenu'
@@ -90,11 +84,19 @@
 export default {
   name: 'UserMenu',
   components: {
+    CreateMenu,
     ExternalLink,
     TranslationMenu,
     HeaderNotice,
     ResourceIcon
   },
+  props: {
+    device: {
+      type: String,
+      required: false,
+      default: 'desktop'
+    }
+  },
   data () {
     return {
       image: '',
@@ -151,6 +153,25 @@
         })
       })
     },
+    handleClickMenu (item) {
+      switch (item.key) {
+        case 'profile':
+          this.$router.push(`/accountuser/${this.$store.getters.userInfo.id}`)
+          break
+        case 'limits':
+          this.$router.push(`/account/${this.$store.getters.userInfo.accountid}?tab=limits`)
+          break
+        case 'timezone':
+          this.toggleUseBrowserTimezone()
+          break
+        case 'document':
+          window.open(this.$config.docBase, '_blank')
+          break
+        case 'logout':
+          this.handleLogout()
+          break
+      }
+    },
     handleLogout () {
       return this.Logout({}).then(() => {
         this.$router.push('/user/login')
diff --git a/ui/src/components/menu/SMenu.vue b/ui/src/components/menu/SMenu.vue
index 2c828a8..67603b7 100644
--- a/ui/src/components/menu/SMenu.vue
+++ b/ui/src/components/menu/SMenu.vue
@@ -41,6 +41,11 @@
               <render-icon
                 v-if="children.meta.icon && typeof (children.meta.icon) === 'string'"
                 :icon="children.meta.icon" />
+              <font-awesome-icon
+                v-else-if="children.meta.icon && Array.isArray(children.meta.icon)"
+                :icon="children.meta.icon"
+                class="anticon"
+                :style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
               <render-icon v-else :svgIcon="children.meta.icon" />
               <span>{{ $t(children.meta.title) }}</span>
             </router-link>
diff --git a/ui/src/components/multitab/index.less b/ui/src/components/multitab/index.less
index 917d40d..3ac1760 100644
--- a/ui/src/components/multitab/index.less
+++ b/ui/src/components/multitab/index.less
@@ -28,4 +28,4 @@
 .topmenu .@{multi-tab-wrapper-prefix-cls} {
   max-width: 1200px;
   margin: 0 auto;
-}
\ No newline at end of file
+}
diff --git a/ui/src/components/page/GlobalHeader.vue b/ui/src/components/page/GlobalHeader.vue
index 59c9184..dd51278 100644
--- a/ui/src/components/page/GlobalHeader.vue
+++ b/ui/src/components/page/GlobalHeader.vue
@@ -18,7 +18,7 @@
 <template>
   <a-layout-header v-if="!headerBarFixed" :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', theme ]" :style="{ padding: '0' }">
     <div v-if="mode === 'sidemenu'" class="header">
-      <template v-if="device==='mobile'">
+      <template v-if="device === 'mobile'">
         <menu-fold-outlined class="trigger" v-if="collapsed" @click="toggle" />
         <menu-unfold-outlined class="trigger" v-else @click="toggle" />
       </template>
@@ -28,7 +28,7 @@
       </template>
       <project-menu v-if="device !== 'mobile'" />
       <saml-domain-switcher style="margin-left: 20px" />
-      <user-menu></user-menu>
+      <user-menu :device="device"></user-menu>
     </div>
     <div v-else :class="['top-nav-header-index', theme]">
       <div class="header-index-wide">
diff --git a/ui/src/components/page/GlobalLayout.vue b/ui/src/components/page/GlobalLayout.vue
index 3f8779d..6dd5c53 100644
--- a/ui/src/components/page/GlobalLayout.vue
+++ b/ui/src/components/page/GlobalLayout.vue
@@ -16,98 +16,105 @@
 // under the License.
 
 <template>
-  <a-layout class="layout" :class="[device]">
-    <a-affix style="z-index: 200">
-      <template v-if="isSideMenu()">
-        <a-drawer
-          v-if="isMobile()"
-          :wrapClassName="'drawer-sider ' + navTheme"
-          :closable="false"
-          :visible="collapsed"
-          placement="left"
-          @close="() => this.collapsed = false"
-        >
-          <side-menu
-            :menus="menus"
-            :theme="navTheme"
-            :collapsed="false"
-            :collapsible="true"
-            mode="inline"
-            @menuSelect="menuSelect"></side-menu>
-        </a-drawer>
-
-        <side-menu
-          v-else
-          mode="inline"
-          :menus="menus"
-          :theme="navTheme"
-          :collapsed="collapsed"
-          :collapsible="true"></side-menu>
-      </template>
-      <template v-else>
-        <a-drawer
-          v-if="isMobile()"
-          :wrapClassName="'drawer-sider ' + navTheme"
-          placement="left"
-          @close="() => this.collapsed = false"
-          :closable="false"
-          :visible="collapsed"
-        >
-          <side-menu
-            :menus="menus"
-            :theme="navTheme"
-            :collapsed="false"
-            :collapsible="true"
-            mode="inline"
-            @menuSelect="menuSelect"></side-menu>
-        </a-drawer>
-      </template>
-
-      <drawer :visible="showSetting" placement="right" v-if="isAdmin && (isDevelopmentMode || allowSettingTheme)">
-        <template #handler>
-          <a-button type="primary" size="large">
-            <close-outlined v-if="showSetting" />
-            <setting-outlined v-else />
-          </a-button>
-        </template>
-        <template #drawer>
-          <setting :visible="showSetting" />
-        </template>
-      </drawer>
-
+  <div>
+    <a-affix v-if="this.$store.getters.shutdownTriggered" >
+      <a-alert :message="$t('message.shutdown.triggered')" type="error" banner :showIcon="false" class="shutdownHeader" />
     </a-affix>
+    <a-layout class="layout" :class="[device]">
+      <a-affix style="z-index: 200" :offsetTop="this.$store.getters.shutdownTriggered ? 25 : 0">
+        <template v-if="isSideMenu()">
+          <a-drawer
+            v-if="isMobile()"
+            :wrapClassName="'drawer-sider ' + navTheme"
+            :closable="false"
+            :visible="collapsed"
+            placement="left"
+            @close="() => this.collapsed = false"
+          >
+            <side-menu
+              :menus="menus"
+              :theme="navTheme"
+              :collapsed="false"
+              :collapsible="true"
+              mode="inline"
+              @menuSelect="menuSelect"></side-menu>
+          </a-drawer>
+          <side-menu
+            v-else
+            mode="inline"
+            :menus="menus"
+            :theme="navTheme"
+            :collapsed="collapsed"
+            :collapsible="true"></side-menu>
+        </template>
+        <template v-else>
+          <a-drawer
+            v-if="isMobile()"
+            :wrapClassName="'drawer-sider ' + navTheme"
+            placement="left"
+            @close="() => this.collapsed = false"
+            :closable="false"
+            :visible="collapsed"
+          >
+            <side-menu
+              :menus="menus"
+              :theme="navTheme"
+              :collapsed="false"
+              :collapsible="true"
+              mode="inline"
+              @menuSelect="menuSelect"></side-menu>
+          </a-drawer>
+        </template>
 
-    <a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
-      <!-- layout header -->
-      <a-affix style="z-index: 100">
-        <global-header
-          :mode="layoutMode"
-          :menus="menus"
-          :theme="navTheme"
-          :collapsed="collapsed"
-          :device="device"
-          @toggle="toggle"
-        />
+        <drawer :visible="showSetting" placement="right" v-if="isAdmin && (isDevelopmentMode || allowSettingTheme)">
+          <template #handler>
+            <a-button type="primary" size="large">
+              <close-outlined v-if="showSetting" />
+              <setting-outlined v-else />
+            </a-button>
+          </template>
+          <template #drawer>
+            <setting :visible="showSetting" />
+          </template>
+        </drawer>
+
       </a-affix>
 
-      <a-button
-        v-if="showClear"
-        type="default"
-        size="small"
-        class="button-clear-notification"
-        @click="onClearNotification">{{ $t('label.clear.notification') }}</a-button>
+      <a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }">
+        <!-- layout header -->
+        <a-affix style="z-index: 100">
+          <global-header
+            :style="this.$store.getters.shutdownTriggered ? 'margin-top: 25px;' : null"
+            :mode="layoutMode"
+            :menus="menus"
+            :theme="navTheme"
+            :collapsed="collapsed"
+            :device="device"
+            @toggle="toggle"
+          />
+        </a-affix>
 
-      <!-- layout content -->
-      <a-layout-content class="layout-content" :class="{'is-header-fixed': fixedHeader}">
-        <slot></slot>
-      </a-layout-content>
+        <a-button
+          v-if="showClear"
+          type="default"
+          size="small"
+          class="button-clear-notification"
+          @click="onClearNotification">{{ $t('label.clear.notification') }}</a-button>
 
-      <!-- layout footer -->
-      <a-layout-footer style="padding: 0">
-        <global-footer />
-      </a-layout-footer>
+        <!-- layout content -->
+        <a-layout-content
+        class="layout-content"
+        :class="{'is-header-fixed': fixedHeader}">
+          <slot></slot>
+        </a-layout-content>
+
+        <!-- layout footer -->
+        <a-layout-footer style="padding: 0">
+          <global-footer />
+        </a-layout-footer>
+      </a-layout>
     </a-layout>
-  </a-layout>
+  </div>
 </template>
 
 <script>
@@ -118,6 +125,7 @@
 import { mapState, mapActions } from 'vuex'
 import { mixin, mixinDevice } from '@/utils/mixin.js'
 import { isAdmin } from '@/role'
+import { api } from '@/api'
 import Drawer from '@/components/widgets/Drawer'
 import Setting from '@/components/view/Setting.vue'
 
@@ -191,6 +199,10 @@
   created () {
     this.menus = this.mainMenu.find((item) => item.path === '/').children
     this.collapsed = !this.sidebarOpened
+    if ('readyForShutdown' in this.$store.getters.apis) {
+      const readyForShutdownPollingJob = setInterval(this.checkShutdown, 5000)
+      this.$store.commit('SET_READY_FOR_SHUTDOWN_POLLING_JOB', readyForShutdownPollingJob)
+    }
   },
   mounted () {
     const layoutMode = this.$config.theme['@layout-mode'] || 'light'
@@ -243,6 +255,11 @@
     onClearNotification () {
       this.$notification.destroy()
       this.$store.commit('SET_COUNT_NOTIFY', 0)
+    },
+    checkShutdown () {
+      api('readyForShutdown', {}).then(json => {
+        this.$store.dispatch('SetShutdownTriggered', json.readyforshutdownresponse.readyforshutdown.shutdowntriggered || false)
+      })
     }
   }
 }
@@ -264,6 +281,11 @@
   &.dark {
     .ant-drawer-content {
       background-color: rgb(0, 21, 41);
+      max-width: 256px;
+    }
+
+    .ant-drawer-content-wrapper {
+      width: 256px !important;;
     }
   }
 
@@ -272,11 +294,27 @@
 
     .ant-drawer-content {
       background-color: #fff;
+      max-width: 256px;
+    }
+
+    .ant-drawer-content-wrapper {
+      width: 256px !important;
     }
   }
 
   .ant-drawer-body {
-    padding: 0
+    padding: 0;
   }
 }
+
+.shutdownHeader {
+  font-weight: bold;
+  height: 25px;
+  text-align: center;
+  padding: 0px;
+  margin: 0px;
+  width: 100vw;
+  position: absolute;
+}
+
 </style>
diff --git a/ui/src/components/view/ActionButton.vue b/ui/src/components/view/ActionButton.vue
index 95554f2..f15ec2f 100644
--- a/ui/src/components/view/ActionButton.vue
+++ b/ui/src/components/view/ActionButton.vue
@@ -21,14 +21,36 @@
       <template #title>
         {{ $t('label.view.console') }}
       </template>
-      <console :resource="resource" :size="size" />
+      <console
+        style="margin-top: -5px;"
+        :resource="resource"
+        size="default"
+        v-if="resource.id"
+        icon="code"
+      />
+    </a-tooltip>
+    <a-tooltip arrowPointAtCenter placement="bottomRight" v-if="resource && resource.id && dataView">
+      <template #title>
+        {{ $t('label.copy.consoleurl') }}
+      </template>
+      <console
+        copyUrlToClipboard
+        style="margin-top: -5px;"
+        :resource="resource"
+        size="default"
+        v-if="resource.id"
+        icon="copy"
+      />
     </a-tooltip>
     <a-tooltip
       v-for="(action, actionIndex) in actions"
       :key="actionIndex"
       arrowPointAtCenter
       placement="bottomRight">
-      <template #title>
+      <template v-if="action.hoverLabel" #title>
+        {{ $t(action.hoverLabel) }}
+      </template>
+      <template v-else #title>
         {{ $t(action.label) }}
       </template>
       <a-badge
diff --git a/ui/src/components/view/AnnotationsTab.vue b/ui/src/components/view/AnnotationsTab.vue
index 36539a2..5b42af8 100644
--- a/ui/src/components/view/AnnotationsTab.vue
+++ b/ui/src/components/view/AnnotationsTab.vue
@@ -96,7 +96,7 @@
         <template #content>
           <div v-ctrl-enter="saveNote">
             <a-textarea
-              rows="4"
+              :rows="4"
               @change="handleNoteChange"
               v-model:value="annotation"
               :placeholder="$t('label.add.note')" />
@@ -192,6 +192,8 @@
         case 'SystemVm': return 'SYSTEM_VM'
         case 'VirtualRouter': return 'VR'
         case 'AutoScaleVmGroup': return 'AUTOSCALE_VM_GROUP'
+        case 'ManagementServer': return 'MANAGEMENT_SERVER'
+        case 'ObjectStorage': return 'OBJECT_STORAGE'
         default: return ''
       }
     },
diff --git a/ui/src/components/view/BulkActionProgress.vue b/ui/src/components/view/BulkActionProgress.vue
index be21a05..c4068d0 100644
--- a/ui/src/components/view/BulkActionProgress.vue
+++ b/ui/src/components/view/BulkActionProgress.vue
@@ -52,36 +52,38 @@
         size="middle"
         :columns="selectedColumns"
         :dataSource="tableChanged ? filteredItems : selectedItems"
-        :rowKey="(record, idx) => ($route.path.includes('/template') || $route.path.includes('/iso')) ? record.zoneid: record.id"
+        :rowKey="record => ($route.path.includes('/template') || $route.path.includes('/iso')) ? record.zoneid: record.id"
         :pagination="true"
         @change="handleTableChange"
         style="overflow-y: auto">
-        <template #status="{text}">
-          <status :text=" text ? text : $t('state.inprogress') " displayText></status>
-        </template>
-        <template #algorithm="{record}">
-          {{ returnAlgorithmName(record.algorithm) }}
-        </template>
-        <template #privateport="{record}">
-          {{ record.privateport }} - {{ record.privateendport }}
-        </template>
-        <template #publicport="{record}">
-          {{ record.publicport }} - {{ record.publicendport }}
-        </template>
-        <template #protocol="{record}">
-          {{ capitalise(record.protocol) }}
-        </template>
-        <template #startport="{record}">
-          {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
-        </template>
-        <template #endport="{record}">
-          {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
-        </template>
-        <template #vm="{record}">
-          <div><desktop-outlined /> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
-        </template>
-        <template #cidrlist="{ record }">
-          <span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
+        <template #bodyCell="{ column, text, record }">
+          <template v-if="column.key === 'status'">
+            <status :text=" text ? text : $t('state.inprogress') " displayText></status>
+          </template>
+          <template v-if="column.key === 'algorithm'">
+            {{ returnAlgorithmName(record.algorithm) }}
+          </template>
+          <template v-if="column.key === 'privateport'">
+            {{ record.privateport }} - {{ record.privateendport }}
+          </template>
+          <template v-if="column.key === 'publicport'">
+            {{ record.publicport }} - {{ record.publicendport }}
+          </template>
+          <template v-if="column.key === 'protocol'">
+            {{ capitalise(record.protocol) }}
+          </template>
+          <template v-if="column.key === 'startport'">
+            {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
+          </template>
+          <template v-if="column.key === 'endport'">
+            {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
+          </template>
+          <template v-if="column.key === 'vm'">
+            <div><desktop-outlined /> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
+          </template>
+          <template v-if="column.key === 'cidrlist'">
+            <span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
+          </template>
         </template>
       </a-table>
       <br/>
diff --git a/ui/src/components/view/BulkActionView.vue b/ui/src/components/view/BulkActionView.vue
index a22ab5a..215abb6 100644
--- a/ui/src/components/view/BulkActionView.vue
+++ b/ui/src/components/view/BulkActionView.vue
@@ -55,35 +55,37 @@
           size="middle"
           :columns="selectedColumns"
           :dataSource="selectedItems"
-          :rowKey="(record, idx) => $route.path.includes('/iso/') ? record.zoneid : record.id"
+          :rowKey="record => $route.path.includes('/iso/') ? record.zoneid : record.id"
           :pagination="true"
           style="overflow-y: auto">
-          <template #algorithm="{record}">
-            {{ returnAlgorithmName(record.algorithm) }}
-          </template>
-          <template #column="{ text }">
-            <span v-for="(column, index) in selectedColumns" :key="index"> {{ text }} ==== {{ column }}</span>
-          </template>
-          <template #privateport="{record}">
-            {{ record.privateport }} - {{ record.privateendport }}
-          </template>
-          <template #publicport="{record}">
-            {{ record.publicport }} - {{ record.publicendport }}
-          </template>
-          <template #protocol="{record}">
-            {{ capitalise(record.protocol) }}
-          </template>
-          <template #vm="{record}">
-            <div><desktop-outlined /> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
-          </template>
-          <template #startport="{record}">
-            {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
-          </template>
-          <template #endport="{record}">
-            {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
-          </template>
-          <template #cidrlist="{record}">
-            <span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
+          <template #bodyCell="{ column, text, record }">
+            <template v-if="column.key === 'algorithm'">
+              {{ returnAlgorithmName(record.algorithm) }}
+            </template>
+            <template v-if="column.key === 'column'">
+              <span v-for="(column, index) in selectedColumns" :key="index"> {{ text }} ==== {{ column }}</span>
+            </template>
+            <template v-if="column.key === 'privateport'">
+              {{ record.privateport }} - {{ record.privateendport }}
+            </template>
+            <template v-if="column.key === 'publicport'">
+              {{ record.publicport }} - {{ record.publicendport }}
+            </template>
+            <template v-if="column.key === 'protocol'">
+              {{ capitalise(record.protocol) }}
+            </template>
+            <template v-if="column.key === 'vm'">
+              <div><desktop-outlined /> {{ record.virtualmachinename }} ({{ record.vmguestip }})</div>
+            </template>
+            <template v-if="column.key === 'startport'">
+              {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
+            </template>
+            <template v-if="column.key === 'endport'">
+              {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
+            </template>
+            <template v-if="column.key === 'cidrlist'">
+              <span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
+            </template>
           </template>
         </a-table>
         <a-divider />
diff --git a/ui/src/components/view/DedicateData.vue b/ui/src/components/view/DedicateData.vue
index 0ff01ac..d99b080 100644
--- a/ui/src/components/view/DedicateData.vue
+++ b/ui/src/components/view/DedicateData.vue
@@ -24,7 +24,7 @@
       </div>
       <p>
         <strong>{{ $t('label.domainid') }}</strong><br/>
-        <router-link :to="{ path: '/domain/' + dedicatedDomainId, query: { tab: 'details'} }">{{ dedicatedDomainId }}</router-link>
+        <router-link :to="{ path: '/domain/' + dedicatedDomainId, query: { tab: 'details'} }" target="_blank">{{ dedicatedDomainId }}</router-link>
       </p>
       <p v-if="dedicatedAccountId">
         <strong>{{ $t('label.account') }}</strong><br/>
diff --git a/ui/src/components/view/DedicateDomain.vue b/ui/src/components/view/DedicateDomain.vue
index 5f04083..0b3645c 100644
--- a/ui/src/components/view/DedicateDomain.vue
+++ b/ui/src/components/view/DedicateDomain.vue
@@ -26,12 +26,16 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }"
           @change="handleChangeDomain"
           v-focus="true"
           v-model:value="domainId">
-          <a-select-option v-for="(domain, index) in domainsList" :value="domain.id" :key="index">
+          <a-select-option
+            v-for="(domain, index) in domainsList"
+            :value="domain.id"
+            :key="index"
+            :label="domain.path || domain.name || domain.description">
             {{ domain.path || domain.name || domain.description }}
           </a-select-option>
         </a-select>
@@ -43,9 +47,9 @@
         style="width: 100%"
         @change="handleChangeAccount"
         showSearch
-        optionFilterProp="label"
+        optionFilterProp="value"
         :filterOption="(input, option) => {
-          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
         }" >
         <a-select-option v-for="(account, index) in accountsList" :value="account.name" :key="index">
           {{ account.name }}
diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue
index e359f14..00aa923 100644
--- a/ui/src/components/view/DetailsTab.vue
+++ b/ui/src/components/view/DetailsTab.vue
@@ -28,6 +28,11 @@
       <p v-html="ip6routes" />
     </template>
   </a-alert>
+  <a-alert v-if="vnfAccessMethods" type="info" :showIcon="true" :message="$t('label.vnf.appliance.access.methods')">
+    <template #description>
+      <p v-html="vnfAccessMethods" />
+    </template>
+  </a-alert>
   <a-list
     size="small"
     :dataSource="fetchDetails()">
@@ -41,6 +46,15 @@
               {{ service.name }} : {{ service.provider[0].name }}
             </div>
           </div>
+          <div v-else-if="$route.meta.name === 'backup' && (item === 'size' || item === 'virtualsize')">
+            {{ $bytesToHumanReadableSize(dataResource[item]) }}
+            <a-tooltip placement="right">
+              <template #title>
+                {{ dataResource[item] }} bytes
+              </template>
+              <QuestionCircleOutlined />
+            </a-tooltip>
+          </div>
           <div v-else-if="$route.meta.name === 'backup' && item === 'volumes'">
             <div v-for="(volume, idx) in JSON.parse(dataResource[item])" :key="idx">
               <router-link :to="{ path: '/volume/' + volume.uuid }">{{ volume.type }} - {{ volume.path }}</router-link> ({{ parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GB)
@@ -53,17 +67,17 @@
           </div>
           <div v-else-if="['template', 'iso'].includes($route.meta.name) && item === 'size'">
             <div>
-              {{ parseFloat(dataResource.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
+              {{ parseFloat(dataResource.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GiB
             </div>
           </div>
           <div v-else-if="['volume', 'snapshot', 'template', 'iso'].includes($route.meta.name) && item === 'physicalsize'">
             <div>
-              {{ parseFloat(dataResource.physicalsize / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
+              {{ parseFloat(dataResource.physicalsize / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GiB
             </div>
           </div>
           <div v-else-if="['volume', 'snapshot', 'template', 'iso'].includes($route.meta.name) && item === 'virtualsize'">
             <div>
-              {{ parseFloat(dataResource.virtualsize / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
+              {{ parseFloat(dataResource.virtualsize / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GiB
             </div>
           </div>
           <div v-else-if="['name', 'type'].includes(item)">
@@ -127,14 +141,14 @@
       type: Object,
       required: true
     },
-    loading: {
-      type: Boolean,
-      default: false
-    },
     items: {
       type: Object,
       default: () => {}
     },
+    loading: {
+      type: Boolean,
+      default: false
+    },
     bordered: {
       type: Boolean,
       default: false
@@ -157,13 +171,103 @@
   },
   computed: {
     customDisplayItems () {
-      return ['ip6routes', 'privatemtu', 'publicmtu']
+      return ['ip6routes', 'privatemtu', 'publicmtu', 'provider']
+    },
+    vnfAccessMethods () {
+      if (this.resource.templatetype === 'VNF' && ['vm', 'vnfapp'].includes(this.$route.meta.name)) {
+        const accessMethodsDescription = []
+        const accessMethods = this.resource.vnfdetails?.access_methods || null
+        const username = this.resource.vnfdetails?.username || null
+        const password = this.resource.vnfdetails?.password || null
+        const sshPort = this.resource.vnfdetails?.ssh_port || 22
+        const sshUsername = this.resource.vnfdetails?.ssh_user || null
+        const sshPassword = this.resource.vnfdetails?.ssh_password || null
+        let httpPath = this.resource.vnfdetails?.http_path || ''
+        if (!httpPath.startsWith('/')) {
+          httpPath = '/' + httpPath
+        }
+        const httpPort = this.resource.vnfdetails?.http_port || null
+        let httpsPath = this.resource.vnfdetails?.https_path || ''
+        if (!httpsPath.startsWith('/')) {
+          httpsPath = '/' + httpsPath
+        }
+        const httpsPort = this.resource.vnfdetails?.https_port || null
+        const webUsername = this.resource.vnfdetails?.web_user || null
+        const webPassword = this.resource.vnfdetails?.web_password || null
+
+        const credentials = []
+        if (username) {
+          credentials.push(this.$t('label.username') + ' : ' + username)
+        }
+        if (password) {
+          credentials.push(this.$t('label.password.default') + ' : ' + password)
+        }
+        if (webUsername) {
+          credentials.push('Web ' + this.$t('label.username') + ' : ' + webUsername)
+        }
+        if (webPassword) {
+          credentials.push('Web ' + this.$t('label.password.default') + ' : ' + webPassword)
+        }
+        if (sshUsername) {
+          credentials.push('SSH ' + this.$t('label.username') + ' : ' + sshUsername)
+        }
+        if (sshPassword) {
+          credentials.push('SSH ' + this.$t('label.password.default') + ' : ' + sshPassword)
+        }
+
+        const managementDeviceIds = []
+        if (this.resource.vnfnics) {
+          for (const vnfnic of this.resource.vnfnics) {
+            if (vnfnic.management) {
+              managementDeviceIds.push(vnfnic.deviceid)
+            }
+          }
+        }
+        const managementIps = []
+        for (const nic of this.resource.nic) {
+          if (managementDeviceIds.includes(parseInt(nic.deviceid)) && nic.ipaddress) {
+            managementIps.push(nic.ipaddress)
+            if (nic.publicip) {
+              managementIps.push(nic.publicip)
+            }
+          }
+        }
+
+        if (accessMethods) {
+          const accessMethodsArray = accessMethods.split(',')
+          for (const accessMethod of accessMethodsArray) {
+            if (accessMethod === 'console') {
+              accessMethodsDescription.push('- VM Console.')
+            } else if (accessMethod === 'ssh-password') {
+              accessMethodsDescription.push('- SSH with password' + (sshPort ? ' (SSH port is ' + sshPort + ').' : '.'))
+            } else if (accessMethod === 'ssh-key') {
+              accessMethodsDescription.push('- SSH with key' + (sshPort ? ' (SSH port is ' + sshPort + ').' : '.'))
+            } else if (accessMethod === 'http') {
+              for (const managementIp of managementIps) {
+                const url = 'http://' + managementIp + (httpPort ? ':' + httpPort : '') + httpPath
+                accessMethodsDescription.push('- Webpage: <a href="' + url + '" target="_blank>">' + url + '</a>')
+              }
+            } else if (accessMethod === 'https') {
+              for (const managementIp of managementIps) {
+                const url = 'https://' + managementIp + (httpsPort ? ':' + httpsPort : '') + httpsPath
+                accessMethodsDescription.push('- Webpage: <a href="' + url + '" target="_blank">' + url + '</a>')
+              }
+            }
+          }
+        } else {
+          accessMethodsDescription.push('- VM Console.')
+        }
+        if (credentials) {
+          accessMethodsDescription.push('<br>' + this.$t('message.vnf.credentials.in.template.vnf.details'))
+        }
+        return accessMethodsDescription.join('<br>')
+      }
+      return null
     },
     ipV6Address () {
       if (this.dataResource.nic && this.dataResource.nic.length > 0) {
         return this.dataResource.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ')
       }
-
       return null
     },
     ip6routes () {
diff --git a/ui/src/components/view/EventsTab.vue b/ui/src/components/view/EventsTab.vue
index 66f8ae7..bd01b3f 100644
--- a/ui/src/components/view/EventsTab.vue
+++ b/ui/src/components/view/EventsTab.vue
@@ -152,19 +152,15 @@
       for (var columnKey of this.columnKeys) {
         if (!this.selectedColumnKeys.includes(columnKey)) continue
         this.columns.push({
+          key: columnKey,
           title: this.$t('label.' + String(columnKey).toLowerCase()),
           dataIndex: columnKey,
-          slots: { customRender: columnKey },
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+          sorter: (a, b) => { return genericCompare(a[columnKey] || '', b[columnKey] || '') }
         })
       }
-      this.columns.push({
-        dataIndex: 'dropdownFilter',
-        slots: {
-          filterDropdown: 'filterDropdown',
-          filterIcon: 'filterIcon'
-        }
-      })
+      if (this.columns.length > 0) {
+        this.columns[this.columns.length - 1].customFilterDropdown = true
+      }
     }
   }
 }
diff --git a/ui/src/components/view/ImageStoreSelectView.vue b/ui/src/components/view/ImageStoreSelectView.vue
new file mode 100644
index 0000000..13c5a68
--- /dev/null
+++ b/ui/src/components/view/ImageStoreSelectView.vue
@@ -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.
+
+<template>
+  <div>
+    <a-input-search
+      class="top-spaced"
+      :placeholder="$t('label.search')"
+      v-model:value="searchQuery"
+      style="margin-bottom: 10px;"
+      @search="fetchImageStores"
+      v-focus="true" />
+    <a-table
+      size="small"
+      style="overflow-y: auto"
+      :loading="loading"
+      :columns="columns"
+      :dataSource="imageStores"
+      :pagination="false"
+      :rowKey="record => record.id">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          {{ record.name }}
+        </template>
+        <template v-if="column.key === 'disksizetotal'">
+          <span v-if="record.disksizetotal">{{ $bytesToHumanReadableSize(record.disksizetotal) }}</span>
+        </template>
+        <template v-if="column.key === 'disksizeused'">
+          <span v-if="record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizeused) }}</span>
+        </template>
+        <template v-if="column.key === 'disksizefree'">
+          <span v-if="record.disksizetotal && record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizetotal * 1 - record.disksizeused * 1) }}</span>
+        </template>
+        <template v-if="column.key === 'select' && record.id !== srcImageStoreId">
+          <a-tooltip placement="top" :title="record.readonly ? $t('message.secondary.storage.invalid.state') : ''">
+            <a-radio
+              :disabled="record.id === srcImageStoreId"
+              @click="updateSelection(record)"
+              :checked="selectedImageStore != null && record.id === selectedImageStore.id">
+            </a-radio>
+          </a-tooltip>
+        </template>
+      </template>
+    </a-table>
+    <a-pagination
+      class="top-spaced"
+      size="small"
+      :current="page"
+      :pageSize="pageSize"
+      :total="totalCount"
+      :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
+      :pageSizeOptions="['10', '20', '40', '80', '100']"
+      @change="handleChangePage"
+      @showSizeChange="handleChangePageSize"
+      showSizeChanger>
+      <template #buildOptionText="props">
+        <span>{{ props.value }} / {{ $t('label.page') }}</span>
+      </template>
+    </a-pagination>
+  </div>
+</template>
+
+<script>
+import { api } from '@/api'
+
+export default {
+  name: 'ImageStoreSelector',
+  props: {
+    zoneid: {
+      type: String,
+      required: true
+    },
+    srcImageStoreId: {
+      type: String,
+      required: false
+    }
+  },
+  data () {
+    return {
+      loading: false,
+      imageStores: [],
+      searchQuery: '',
+      totalCount: 0,
+      page: 1,
+      pageSize: 10,
+      selectedImageStore: null,
+      columns: [
+        {
+          key: 'name',
+          title: this.$t('label.storageid')
+        },
+        {
+          key: 'disksizetotal',
+          title: this.$t('label.disksizetotal')
+        },
+        {
+          key: 'disksizeused',
+          title: this.$t('label.disksizeused')
+        },
+        {
+          key: 'disksizefree',
+          title: this.$t('label.disksizefree')
+        },
+        {
+          key: 'select',
+          title: this.$t('label.select')
+        }
+      ]
+    }
+  },
+  created () {
+    this.fetchImageStores()
+  },
+  watch: {
+    searchQuery (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        this.page = 1
+      }
+    }
+  },
+  methods: {
+    fetchImageStores () {
+      this.loading = true
+      var params = {
+        zoneid: this.zoneid,
+        keyword: this.searchQuery,
+        page: this.page,
+        pagesize: this.pageSize
+      }
+      api('listImageStores', params).then(response => {
+        this.imageStores = response.listimagestoresresponse.imagestore || []
+        this.totalCount = response.listimagestoresresponse.count
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.handleImageStoresFetchComplete()
+      })
+    },
+    handleImageStoresFetchComplete () {
+      this.$emit('imageStoresUpdated', this.imageStores)
+      this.loading = false
+    },
+    handleChangePage (page, pageSize) {
+      this.page = page
+      this.pageSize = pageSize
+      this.fetchImageStores()
+    },
+    handleChangePageSize (currentPage, pageSize) {
+      this.page = currentPage
+      this.pageSize = pageSize
+      this.fetchImageStores()
+    },
+    clearView () {
+      this.imageStores = []
+      this.searchQuery = ''
+      this.totalCount = 0
+      this.page = 1
+      this.pageSize = 10
+    },
+    reset () {
+      this.clearView()
+      this.fetchImageStores()
+    },
+    updateSelection (imageStore) {
+      this.selectedImageStore = imageStore
+      this.$emit('select', this.selectedImageStore)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+  .top-spaced {
+    margin-top: 20px;
+  }
+  .table-tooltip-icon {
+    color: rgba(0,0,0,.45);
+  }
+</style>
diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue
index 72df182..66c878d 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -36,6 +36,12 @@
                 <span v-else>
                   <os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" @update-osname="setResourceOsType"/>
                   <render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :icon="$route.meta.icon" />
+                  <font-awesome-icon
+                    v-else-if="$route.meta.icon && Array.isArray($route.meta.icon)"
+                    :icon="$route.meta.icon"
+                    size="4x"
+                    class="anticon"
+                    :style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
                   <render-icon v-else style="font-size: 36px" :svgIcon="$route.meta.icon" />
                 </span>
               </slot>
@@ -84,11 +90,31 @@
               <a-tag v-if="resource.internetprotocol && ['IPv6', 'DualStack'].includes(resource.internetprotocol)">
                 {{ resource.internetprotocol ? $t('label.ip.v4.v6') : resource.internetprotocol }}
               </a-tag>
+              <a-tag v-if="resource.archived" :color="this.$config.theme['@warning-color']">
+                {{ $t('label.archived') }}
+              </a-tag>
               <a-tooltip placement="right" >
                 <template #title>
                   <span>{{ $t('label.view.console') }}</span>
                 </template>
-                <console style="margin-top: -5px;" :resource="resource" size="default" v-if="resource.id" />
+                <console
+                  style="margin-top: -5px;"
+                  :resource="resource"
+                  size="default"
+                  v-if="resource.id"
+                />
+              </a-tooltip>
+              <a-tooltip placement="right" >
+                <template #title>
+                  <span>{{ $t('label.copy.consoleurl') }}</span>
+                </template>
+                <console
+                  copyUrlToClipboard
+                  style="margin-top: -5px;"
+                  :resource="resource"
+                  size="default"
+                  v-if="resource.id"
+                />
               </a-tooltip>
             </div>
           </slot>
@@ -124,7 +150,7 @@
               icon="barcode-outlined"
               type="dashed"
               size="small"
-              :copyResource="resource.id"
+              :copyResource="String(resource.id)"
               @onClick="$message.success($t('label.copied.clipboard'))" />
             <span style="margin-left: 10px;"><copy-label :label="resource.id" /></span>
           </div>
@@ -264,10 +290,20 @@
           </div>
         </div>
         <div class="resource-detail-item" v-if="resource.volumes || resource.sizegb">
-          <div class="resource-detail-item__label">{{ $t('label.disksize') }}</div>
+          <div class="resource-detail-item__label" v-if="$route.meta.name === 'backup'">{{ $t('label.size') }}</div>
+          <div class="resource-detail-item__label" v-else>{{ $t('label.disksize') }}</div>
           <div class="resource-detail-item__details">
             <hdd-outlined />
             <span style="width: 100%;" v-if="$route.meta.name === 'vm' && resource.volumes">{{ (resource.volumes.reduce((total, item) => total += item.size, 0) / (1024 * 1024 * 1024.0)).toFixed(2) }} GB Storage</span>
+            <span style="width: 100%;" v-else-if="$route.meta.name === 'backup'">
+              {{ $bytesToHumanReadableSize(resource.size) }}
+              <a-tooltip placement="right">
+                <template #title>
+                  {{ resource.size }} bytes
+                </template>
+                <QuestionCircleOutlined />
+              </a-tooltip>
+            </span>
             <span style="width: 100%;" v-else-if="resource.sizegb || resource.size">{{ resource.sizegb || (resource.size/1024.0) }}</span>
           </div>
           <div style="margin-left: 25px; margin-top: 5px" v-if="resource.diskkbsread && resource.diskkbswrite && resource.diskioread && resource.diskiowrite">
@@ -503,7 +539,7 @@
           <div class="resource-detail-item__label">{{ $t('label.serviceofferingname') }}</div>
           <div class="resource-detail-item__details">
             <cloud-outlined />
-            <router-link v-if="!isStatic && $route.meta.name === 'router'" :to="{ path: '/computeoffering/' + resource.serviceofferingid, query: { issystem: true } }">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
+            <router-link v-if="!isStatic && ($route.meta.name === 'router' || $route.meta.name === 'systemvm')" :to="{ path: '/systemoffering/' + resource.serviceofferingid}">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
             <router-link v-else-if="$router.resolve('/computeoffering/' + resource.serviceofferingid).matched[0].redirect !== '/exception/404'" :to="{ path: '/computeoffering/' + resource.serviceofferingid }">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
             <span v-else>{{ resource.serviceofferingname || resource.serviceofferingid }}</span>
           </div>
@@ -867,7 +903,7 @@
     },
     name () {
       return this.resource.displayname || this.resource.name || this.resource.displaytext || this.resource.username ||
-        this.resource.ipaddress || this.resource.virtualmachinename || this.resource.templatetype
+        this.resource.ipaddress || this.resource.virtualmachinename || this.resource.osname || this.resource.osdisplayname || this.resource.templatetype
     },
     keypairs () {
       if (!this.resource.keypairs) {
@@ -882,8 +918,13 @@
       return this.resource.templateid
     },
     resourceIcon () {
-      if (this.$showIcon() && this.resource?.icon?.base64image) {
-        return this.resource.icon.base64image
+      if (this.$showIcon()) {
+        if (this.resource?.icon?.base64image) {
+          return this.resource.icon.base64image
+        }
+        if (this.resource?.resourceIcon?.base64image) {
+          return this.resource.resourceIcon.base64image
+        }
       }
       return null
     },
diff --git a/ui/src/components/view/InstanceNicsNetworkSelectListView.vue b/ui/src/components/view/InstanceNicsNetworkSelectListView.vue
index 8ec485c..b9fe55a 100644
--- a/ui/src/components/view/InstanceNicsNetworkSelectListView.vue
+++ b/ui/src/components/view/InstanceNicsNetworkSelectListView.vue
@@ -25,23 +25,25 @@
       :dataSource="nics"
       :pagination="false"
       :rowKey="record => record.InstanceID">
-      <template #displaytext="{record}">
-        <span>{{ record.elementName + ' - ' + record.name }}
-          <a-tooltip :title="record.nicDescription" placement="top">
-            <info-circle-outlined class="table-tooltip-icon" />
-          </a-tooltip>
-        </span>
-      </template>
-      <template #size="{record}">
-        <span v-if="record.size">
-          {{ $bytesToHumanReadableSize(record.size) }}
-        </span>
-      </template>
-      <template #selectednetwork="{record}">
-        <span>{{ record.selectednetworkname || '' }}</span>
-      </template>
-      <template #select="{record}">
-        <div style="display: flex; justify-content: flex-end;"><a-button @click="openNicNetworkSelector(record)">{{ record.selectednetworkid ? $t('label.change') : $t('label.select') }}</a-button></div>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'displaytext'">
+          <span>{{ record.elementName + ' - ' + record.name }}
+            <a-tooltip :title="record.nicDescription" placement="top">
+              <info-circle-outlined class="table-tooltip-icon" />
+            </a-tooltip>
+          </span>
+        </template>
+        <template v-if="column.key === 'size'">
+          <span v-if="record.size">
+            {{ $bytesToHumanReadableSize(record.size) }}
+          </span>
+        </template>
+        <template v-if="column.key === 'selectednetwork'">
+          <span>{{ record.selectednetworkname || '' }}</span>
+        </template>
+        <template v-if="column.key === 'select'">
+          <div style="display: flex; justify-content: flex-end;"><a-button @click="openNicNetworkSelector(record)">{{ record.selectednetworkid ? $t('label.change') : $t('label.select') }}</a-button></div>
+        </template>
       </template>
     </a-table>
 
@@ -87,16 +89,16 @@
     return {
       nicColumns: [
         {
-          title: this.$t('label.nic'),
-          slots: { customRender: 'displaytext' }
+          key: 'displaytext',
+          title: this.$t('label.nic')
         },
         {
-          title: this.$t('label.network'),
-          slots: { customRender: 'selectednetwork' }
+          key: 'selectednetwork',
+          title: this.$t('label.network')
         },
         {
-          title: '',
-          slots: { customRender: 'select' }
+          key: 'select',
+          title: ''
         }
       ],
       selectedNicForNetworkSelection: {}
diff --git a/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue b/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue
index 4489af6..77f1b49 100644
--- a/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue
+++ b/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue
@@ -26,16 +26,18 @@
       :dataSource="volumes"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #size="{ record }">
-        <span v-if="record.size">
-          {{ $bytesToHumanReadableSize(record.size) }}
-        </span>
-      </template>
-      <template #selectedstorage="{ record }">
-        <span>{{ record.selectedstoragename || '' }}</span>
-      </template>
-      <template #select="{ record }">
-        <div style="display: flex; justify-content: flex-end;"><a-button @click="openVolumeStoragePoolSelector(record)">{{ record.selectedstorageid ? $t('label.change') : $t('label.select') }}</a-button></div>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'size'">
+          <span v-if="record.size">
+            {{ $bytesToHumanReadableSize(record.size) }}
+          </span>
+        </template>
+        <template v-if="column.key === 'selectedstorage'">
+          <span>{{ record.selectedstoragename || '' }}</span>
+        </template>
+        <template v-if="column.key === 'select'">
+          <div style="display: flex; justify-content: flex-end;"><a-button @click="openVolumeStoragePoolSelector(record)">{{ record.selectedstorageid ? $t('label.change') : $t('label.select') }}</a-button></div>
+        </template>
       </template>
     </a-table>
 
@@ -86,24 +88,26 @@
       volumesLoading: false,
       volumeColumns: [
         {
+          key: 'name',
           title: this.$t('label.volumeid'),
           dataIndex: 'name'
         },
         {
+          key: 'type',
           title: this.$t('label.type'),
           dataIndex: 'type'
         },
         {
-          title: this.$t('label.size'),
-          slots: { customRender: 'size' }
+          key: 'size',
+          title: this.$t('label.size')
         },
         {
-          title: this.$t('label.storage'),
-          slots: { customRender: 'selectedstorage' }
+          key: 'selectedstorage',
+          title: this.$t('label.storage')
         },
         {
-          title: '',
-          slots: { customRender: 'select' }
+          key: 'select',
+          title: ''
         }
       ],
       selectedVolumeForStoragePoolSelection: {},
diff --git a/ui/src/components/view/ListResourceTable.vue b/ui/src/components/view/ListResourceTable.vue
index 398691a..16c1b38 100644
--- a/ui/src/components/view/ListResourceTable.vue
+++ b/ui/src/components/view/ListResourceTable.vue
@@ -34,20 +34,26 @@
       :pagination="defaultPagination"
       @change="handleTableChange"
       @handle-search-filter="handleTableChange" >
+      <template #bodyCell="{ column, text, record }">
+        <div
+          v-for="(col, index) in Object.keys(routerlinks({}))"
+          :key="index">
+          <template v-if="column.key === col">
+            <router-link :set="routerlink = routerlinks(record)" :to="{ path: routerlink[col] }" >{{ text }}</router-link>
+          </template>
+        </div>
 
-      <template
-        v-for="(column, index) in Object.keys(routerlinks({}))"
-        :key="index"
-        #[column]="{ text, record }" >
-        <router-link :set="routerlink = routerlinks(record)" :to="{ path: routerlink[column] }" >{{ text }}</router-link>
+        <template v-if="column.key === 'state'">
+          <status :text="text ? text : ''" />{{ text }}
+        </template>
+
+        <template v-if="column.key === 'status'">
+          <status :text="text ? text : ''" />{{ text }}
+        </template>
       </template>
 
-      <template #state="{text}">
-        <status :text="text ? text : ''" />{{ text }}
-      </template>
-
-      <template #status="{text}">
-        <status :text="text ? text : ''" />{{ text }}
+      <template v-slot:created="{ item }">
+        {{ $toLocaleDate(item) }}
       </template>
 
     </a-table>
@@ -197,9 +203,9 @@
       var columns = []
       for (const col of this.columns) {
         columns.push({
+          key: col,
           dataIndex: col,
-          title: this.$t('label.' + col),
-          slots: { customRender: col }
+          title: this.$t('label.' + col)
         })
       }
       return columns
diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue
index 1afeae9..cf3d936 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -23,11 +23,11 @@
     :dataSource="items"
     :rowKey="(record, idx) => record.id || record.name || record.usageType || idx + '-' + Math.random()"
     :pagination="false"
-    :rowSelection=" enableGroupAction() || $route.name === 'event' ? {selectedRowKeys: selectedRowKeys, onChange: onSelectChange} : null"
+    :rowSelection=" enableGroupAction() || $route.name === 'event' ? {selectedRowKeys: selectedRowKeys, onChange: onSelectChange, columnWidth: 30} : null"
     :rowClassName="getRowClassName"
     style="overflow-y: auto"
   >
-    <template #filterDropdown>
+    <template #customFilterDropdown>
       <div style="padding: 8px" class="filter-dropdown">
         <a-menu>
           <a-menu-item v-for="(column, idx) in columnKeys" :key="idx" @click="updateSelectedColumns(column)">
@@ -37,419 +37,443 @@
         </a-menu>
       </div>
     </template>
-    <template #footer>
-      <span v-if="hasSelected">
-        {{ `Selected ${selectedRowKeys.length} items` }}
-      </span>
-    </template>
-
-    <!--
-    <div #expandedRowRender="{ resource }">
-      <info-card :resource="resource style="margin-left: 0px; width: 50%">
-        <div #actions style="padding-top: 12px">
-          <a-tooltip
-            v-for="(action, actionIndex) in $route.meta.actions"
-            :key="actionIndex"
-            placement="bottom">
-            <template #title>
-              {{ $t(action.label) }}
-            </template>
-            <a-button
-              v-if="action.api in $store.getters.apis && action.dataView &&
-                ('show' in action ? action.show(resource, $store.getters.userInfo) : true)"
-              :icon="action.icon"
-              :type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
-              shape="circle"
-              style="margin-right: 5px; margin-top: 12px"
-              @click="$parent.execAction(action)"
-            >
-            </a-button>
-          </a-tooltip>
-        </div>
-      </info-card>
-    </div>
-    -->
-
-    <template #name="{text, record}">
-      <span v-if="['vm'].includes($route.path.split('/')[1])" style="margin-right: 5px">
-        <span v-if="record.icon && record.icon.base64image">
-          <resource-icon :image="record.icon.base64image" size="2x"/>
+    <template #bodyCell="{ column, text, record }">
+      <template v-if="['name', 'provider'].includes(column.key) ">
+        <span v-if="['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px">
+          <span v-if="record.icon && record.icon.base64image">
+            <resource-icon :image="record.icon.base64image" size="2x"/>
+          </span>
+          <os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="2x" />
         </span>
-        <os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="2x" />
-      </span>
-      <span style="min-width: 120px" >
+        <span style="min-width: 120px" >
+          <QuickView
+            style="margin-left: 5px"
+            :actions="actions"
+            :resource="record"
+            :enabled="quickViewEnabled() && actions.length > 0 && columns && ['name', 'provider'].includes(columns[0].dataIndex)"
+            @exec-action="$parent.execAction"/>
+          <span v-if="$route.path.startsWith('/project')" style="margin-right: 5px">
+            <tooltip-button type="dashed" size="small" icon="LoginOutlined" @onClick="changeProject(record)" />
+          </span>
+          <span v-if="$showIcon() && !['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px">
+            <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
+            <os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="2x" />
+            <render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px;" :icon="$route.meta.icon"/>
+            <render-icon v-else style="font-size: 16px;" :svgIcon="$route.meta.icon" />
+          </span>
+          <span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
+            <os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" />
+          </span>
+
+          <span v-if="record.hasannotations">
+            <span v-if="record.id">
+              <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+              <router-link :to="{ path: $route.path + '/' + record.id, query: { tab: 'comments' } }"><message-filled style="padding-left: 10px" size="small"/></router-link>
+            </span>
+            <router-link v-else :to="{ path: $route.path + '/' + record.name }" >{{ text }}</router-link>
+          </span>
+          <span v-else-if="$route.path.startsWith('/globalsetting')">{{ text }}</span>
+          <span v-else-if="$route.path.startsWith('/alert')">
+            <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ $t(text.toLowerCase()) }}</router-link>
+            <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
+          </span>
+          <span v-else-if="$route.path.startsWith('/tungstenfabric')">
+            <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ $t(text.toLowerCase()) }}</router-link>
+            <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
+          </span>
+          <span v-else-if="isTungstenPath()">
+            <router-link :to="{ path: $route.path + '/' + record.uuid, query: { zoneid: record.zoneid } }" v-if="record.uuid && record.zoneid">{{ $t(text.toLowerCase()) }}</router-link>
+            <router-link :to="{ path: $route.path + '/' + record.uuid, query: { zoneid: $route.query.zoneid } }" v-else-if="record.uuid && $route.query.zoneid">{{ $t(text.toLowerCase()) }}</router-link>
+            <router-link :to="{ path: $route.path }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
+          </span>
+          <span v-else>
+            <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ text }}</router-link>
+            <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ text }}</router-link>
+          </span>
+        </span>
+      </template>
+      <template v-if="record.clustertype === 'ExternalManaged' && $route.path.split('/')[1] === 'kubernetes' && ['cpunumber', 'memory', 'size'].includes(column.key)">
+        <span>{{ text <= 0 ? 'N/A' : text }}</span>
+      </template>
+      <template v-if="column.key === 'templatetype'">
+        <span>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'type'">
+        <span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(text)">{{ $t(text.toLowerCase()) }}</span>
+        <span v-else>{{ text }}</span>
+      </template>
+
+      <template v-if="column.key === 'schedule'">
+          {{ text }}
+          <br/>
+          ({{ generateHumanReadableSchedule(text) }})
+      </template>
+      <template v-if="column.key === 'displayname'">
         <QuickView
           style="margin-left: 5px"
           :actions="actions"
           :resource="record"
-          :enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'name' "
+          :enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'displayname' "
           @exec-action="$parent.execAction"/>
-        <span v-if="$route.path.startsWith('/project')" style="margin-right: 5px">
-          <tooltip-button type="dashed" size="small" icon="LoginOutlined" @onClick="changeProject(record)" />
-        </span>
-        <span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])" style="margin-right: 5px">
-          <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
-          <os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="2x" />
-          <render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px;" :icon="$route.meta.icon"/>
-          <render-icon v-else style="font-size: 16px;" :svgIcon="$route.meta.icon" />
-        </span>
-        <span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
-          <os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" />
-        </span>
-
-        <span v-if="record.hasannotations">
-          <span v-if="record.id">
-            <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
-            <router-link :to="{ path: $route.path + '/' + record.id, query: { tab: 'comments' } }"><message-filled style="padding-left: 10px" size="small"/></router-link>
-          </span>
-          <router-link v-else :to="{ path: $route.path + '/' + record.name }" >{{ text }}</router-link>
-        </span>
-        <span v-else-if="$route.path.startsWith('/globalsetting')">{{ text }}</span>
-        <span v-else-if="$route.path.startsWith('/preferences')">{{ text }}</span>
-        <span v-else-if="$route.path.startsWith('/alert')">
-          <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ $t(text.toLowerCase()) }}</router-link>
-          <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
-        </span>
-        <span v-else-if="$route.path.startsWith('/tungstenfabric')">
-          <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ $t(text.toLowerCase()) }}</router-link>
-          <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
-        </span>
-        <span v-else-if="isTungstenPath()">
-          <router-link :to="{ path: $route.path + '/' + record.uuid, query: { zoneid: record.zoneid } }" v-if="record.uuid && record.zoneid">{{ $t(text.toLowerCase()) }}</router-link>
-          <router-link :to="{ path: $route.path + '/' + record.uuid, query: { zoneid: $route.query.zoneid } }" v-else-if="record.uuid && $route.query.zoneid">{{ $t(text.toLowerCase()) }}</router-link>
-          <router-link :to="{ path: $route.path }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
-        </span>
-        <span v-else>
-          <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ text }}</router-link>
-          <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ text }}</router-link>
-        </span>
-      </span>
-    </template>
-    <template #templatetype="{ text, record }">
-      <router-link :to="{ path: $route.path + '/' + record.templatetype }">{{ text }}</router-link>
-    </template>
-    <template #type="{ text }">
-      <span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(text)">{{ $t(text.toLowerCase()) }}</span>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #displayname="{text, record}">
-      <QuickView
-        style="margin-left: 5px"
-        :actions="actions"
-        :resource="record"
-        :enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'displayname' "
-        @exec-action="$parent.execAction"/>
-      <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
-    </template>
-    <template #username="{text, record}">
-      <span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])" style="margin-right: 5px">
-        <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
-        <user-outlined v-else style="font-size: 16px;" />
-      </span>
-      <router-link :to="{ path: $route.path + '/' + record.id }" v-if="['/accountuser', '/vpnuser'].includes($route.path)">{{ text }}</router-link>
-      <router-link :to="{ path: '/accountuser', query: { username: record.username, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #entityid="{ record }" href="javascript:;">
-      <router-link :to="{ path: generateCommentsPath(record), query: { tab: 'comments' } }">{{ record.entityname }}</router-link>
-    </template>
-    <template #entitytype="{ record }" href="javascript:;">
-      {{ generateHumanReadableEntityType(record) }}
-    </template>
-    <template #adminsonly="{ record }" v-if="['Admin'].includes($store.getters.userInfo.roletype)" href="javascript:;">
-      <a-checkbox :checked="record.adminsonly" :value="record.id" v-if="record.userid === $store.getters.userInfo.id" @change="e => updateAdminsOnly(e)" />
-      <a-checkbox :checked="record.adminsonly" disabled v-else />
-    </template>
-    <template #ipaddress="{ text, record }" href="javascript:;">
-      <router-link v-if="['/publicip', '/privategw'].includes($route.path)" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
-      <span v-else>
-        <copy-label :label="text" />
-      </span>
-      <span v-if="record.issourcenat">
-        &nbsp;
-        <a-tag>source-nat</a-tag>
-      </span>
-      <span v-if="record.isstaticnat">
-        &nbsp;
-        <a-tag>static-nat</a-tag>
-      </span>
-    </template>
-    <template #ip6address="{ text, record }" href="javascript:;">
-      <span>{{ ipV6Address(text, record) }}</span>
-    </template>
-    <template #publicip="{ text, record }">
-      <router-link v-if="['/autoscalevmgroup'].includes($route.path)" :to="{ path: '/publicip' + '/' + record.publicipid }">{{ text }}</router-link>
-      <router-link v-else :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
-    </template>
-    <template #traffictype="{ text }" href="javascript:;">
-      {{ text }}
-    </template>
-    <template #vmname="{ text, record }">
-      <router-link :to="{ path: createPathBasedOnVmType(record.vmtype, record.virtualmachineid) }">{{ text }}</router-link>
-    </template>
-    <template #virtualmachinename="{ text, record }">
-      <router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
-    </template>
-    <template #volumename="{ text, record }">
-      <router-link :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link>
-    </template>
-    <template #size="{ text }">
-      <span v-if="text && $route.path === '/kubernetes'">
-        {{ text }}
-      </span>
-      <span v-else-if="text">
-        {{ parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) }} GiB
-      </span>
-    </template>
-    <template #physicalsize="{ text }">
-      <span v-if="text">
-        {{ isNaN(text) ? text : (parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) + ' GiB') }}
-      </span>
-    </template>
-    <template #physicalnetworkname="{ text, record }">
-      <router-link :to="{ path: '/physicalnetwork/' + record.physicalnetworkid }">{{ text }}</router-link>
-    </template>
-    <template #serviceofferingname="{ text, record }">
-      <router-link :to="{ path: '/computeoffering/' + record.serviceofferingid }">{{ text }}</router-link>
-    </template>
-    <template #hypervisor="{ text, record }">
-      <span v-if="$route.name === 'hypervisorcapability'">
         <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
-      </span>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #state="{ text, record }">
-      <status v-if="$route.path.startsWith('/host')" :text="getHostState(record)" displayText />
-      <status v-else :text="text ? text : ''" displayText :styles="{ 'min-width': '80px' }" />
-    </template>
-    <template #status="{ text }">
-      <status :text="text ? text : ''" displayText />
-    </template>
-    <template #allocationstate="{ text }">
-      <status :text="text ? text : ''" displayText />
-    </template>
-    <template #resourcestate="{ text }">
-      <status :text="text ? text : ''" displayText />
-    </template>
-    <template #powerstate="{ text }">
-      <status :text="text ? text : ''" displayText />
-    </template>
-    <template #agentstate="{ text }">
-      <status :text="text ? text : ''" displayText />
-    </template>
-    <template #quotastate="{ text }">
-      <status :text="text ? text : ''" displayText />
-    </template>
-    <template #vlan="{ text, record }">
-      <a href="javascript:;">
-        <router-link v-if="$route.path === '/guestvlans'" :to="{ path: '/guestvlans/' + record.id }">{{ text }}</router-link>
-      </a>
-    </template>
-    <template #guestnetworkname="{ text, record }">
-      <router-link :to="{ path: '/guestnetwork/' + record.guestnetworkid }">{{ text }}</router-link>
-    </template>
-    <template #associatednetworkname="{ text, record }">
-      <router-link :to="{ path: '/guestnetwork/' + record.associatednetworkid }">{{ text }}</router-link>
-    </template>
-    <template #vpcname="{ text, record }">
-      <router-link :to="{ path: '/vpc/' + record.vpcid }">{{ text }}</router-link>
-    </template>
-    <template #hostname="{ text, record }">
-      <router-link v-if="record.hostid" :to="{ path: '/host/' + record.hostid }">{{ text }}</router-link>
-      <router-link v-else-if="record.hostname" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #storage="{ text, record }">
-      <router-link v-if="record.storageid" :to="{ path: '/storagepool/' + record.storageid }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-
-    <template
-      v-for="(value, name) in thresholdMapping"
-      :key="name"
-      #[name]="{ text, record }"
-      href="javascript:;">
-      <span>
-        <span v-if="record[value.disable]" class="alert-disable-threshold">
+      </template>
+      <template v-if="column.key === 'username'">
+        <span v-if="$showIcon() && !['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px">
+          <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
+          <user-outlined v-else style="font-size: 16px;" />
+        </span>
+        <router-link :to="{ path: $route.path + '/' + record.id }" v-if="['/accountuser', '/vpnuser'].includes($route.path)">{{ text }}</router-link>
+        <router-link :to="{ path: '/accountuser', query: { username: record.username, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'entityid'">
+        <router-link :to="{ path: generateCommentsPath(record), query: { tab: 'comments' } }">{{ record.entityname }}</router-link>
+      </template>
+      <template v-if="column.key === 'entitytype'">
+        {{ generateHumanReadableEntityType(record) }}
+      </template>
+      <template v-if="column.key === 'adminsonly' && ['Admin'].includes($store.getters.userInfo.roletype)">
+        <a-checkbox :checked="record.adminsonly" :value="record.id" v-if="record.userid === $store.getters.userInfo.id" @change="e => updateAdminsOnly(e)" />
+        <a-checkbox :checked="record.adminsonly" disabled v-else />
+      </template>
+      <template v-if="column.key === 'ipaddress'" href="javascript:;">
+        <router-link v-if="['/publicip', '/privategw'].includes($route.path)" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+        <span v-else>
+          <copy-label :label="text" />
+        </span>
+        <span v-if="record.issourcenat">
+          &nbsp;
+          <a-tag>source-nat</a-tag>
+        </span>
+        <span v-if="record.isstaticnat">
+          &nbsp;
+          <a-tag>static-nat</a-tag>
+        </span>
+      </template>
+      <template v-if="column.key === 'ip6address'" href="javascript:;">
+        <span>{{ ipV6Address(text, record) }}</span>
+      </template>
+      <template v-if="column.key === 'publicip'">
+        <router-link v-if="['/autoscalevmgroup'].includes($route.path)" :to="{ path: '/publicip' + '/' + record.publicipid }">{{ text }}</router-link>
+        <router-link v-else :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'traffictype'">
+        {{ text }}
+      </template>
+      <template v-if="column.key === 'vmname'">
+        <router-link :to="{ path: createPathBasedOnVmType(record.vmtype, record.virtualmachineid) }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'virtualmachinename'">
+        <router-link :to="{ path: getVmRouteUsingType(record) + record.virtualmachineid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'volumename'">
+        <router-link :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'size'">
+        <span v-if="text && $route.path === '/kubernetes'">
           {{ text }}
         </span>
-        <span v-else-if="record[value.notification]" class="alert-notification-threshold">
-          {{ text }}
+        <span v-else-if="text">
+          {{ parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) }} GiB
         </span>
-        <span style="padding: 10%;" v-else>
-          {{ text }}
+      </template>
+      <template v-if="column.key === 'physicalsize'">
+        <span v-if="text">
+          {{ isNaN(text) ? text : (parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) + ' GiB') }}
         </span>
-      </span>
-    </template>
-
-    <template #level="{ text, record }">
-      <router-link :to="{ path: '/event/' + record.id }">{{ text }}</router-link>
-    </template>
-
-    <template #clustername="{ text, record }">
-      <router-link :to="{ path: '/cluster/' + record.clusterid }">{{ text }}</router-link>
-    </template>
-    <template #podname="{ text, record }">
-      <router-link :to="{ path: '/pod/' + record.podid }">{{ text }}</router-link>
-    </template>
-    <template #account="{ text, record }">
-      <template v-if="record.owner">
-        <template v-for="(item, idx) in record.owner" :key="idx">
-          <span style="margin-right:5px">
-            <span v-if="$store.getters.userInfo.roletype !== 'User'">
-              <router-link v-if="'user' in item" :to="{ path: '/accountuser', query: { username: item.user, domainid: record.domainid }}">{{ item.account + '(' + item.user + ')' }}</router-link>
-              <router-link v-else :to="{ path: '/account', query: { name: item.account, domainid: record.domainid, dataView: true } }">{{ item.account }}</router-link>
+      </template>
+      <template v-if="column.key === 'physicalnetworkname'">
+        <router-link :to="{ path: '/physicalnetwork/' + record.physicalnetworkid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'serviceofferingname'">
+        <router-link :to="{ path: '/computeoffering/' + record.serviceofferingid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'hypervisor'">
+        <span v-if="$route.name === 'hypervisorcapability'">
+          <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+        </span>
+        <span v-else-if="$route.name === 'guestoshypervisormapping'">
+          <QuickView
+            style="margin-left: 5px"
+            :actions="actions"
+            :resource="record"
+            :enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'hypervisor' "
+            @exec-action="$parent.execAction"/>
+          <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+        </span>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'osname'">
+        <span v-if="$route.name === 'guestos'">
+          <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+        </span>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'state'">
+        <status v-if="$route.path.startsWith('/host')" :text="getHostState(record)" displayText />
+        <status v-else :text="text ? text : ''" displayText :styles="{ 'min-width': '80px' }" />
+      </template>
+      <template v-if="column.key === 'status'">
+        <status :text="text ? text : ''" displayText />
+      </template>
+      <template v-if="column.key === 'allocationstate'">
+        <status :text="text ? text : ''" displayText />
+      </template>
+      <template v-if="column.key === 'resourcestate'">
+        <status :text="text ? text : ''" displayText />
+      </template>
+      <template v-if="column.key === 'powerstate'">
+        <status :text="text ? text : ''" displayText />
+      </template>
+      <template v-if="column.key === 'agentstate'">
+        <status :text="text ? text : ''" displayText />
+      </template>
+      <template v-if="column.key === 'quotastate'">
+        <status :text="text ? text : ''" displayText />
+      </template>
+      <template v-if="column.key === 'vlan'">
+        <a href="javascript:;">
+          <router-link v-if="$route.path === '/guestvlans'" :to="{ path: '/guestvlans/' + record.id }">{{ text }}</router-link>
+        </a>
+      </template>
+      <template v-if="column.key === 'guestnetworkname'">
+        <router-link :to="{ path: '/guestnetwork/' + record.guestnetworkid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'associatednetworkname'">
+        <router-link :to="{ path: '/guestnetwork/' + record.associatednetworkid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'vpcname'">
+        <a v-if="record.vpcid">
+          <router-link :to="{ path: '/vpc/' + record.vpcid }">{{ text }}</router-link>
+        </a>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'hostname'">
+        <router-link v-if="record.hostid" :to="{ path: '/host/' + record.hostid }">{{ text }}</router-link>
+        <router-link v-else-if="record.hostname" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'storage'">
+        <router-link v-if="record.storageid" :to="{ path: '/storagepool/' + record.storageid }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-for="(value, name) in thresholdMapping" :key="name">
+        <template v-if="column.key === name">
+          <span>
+            <span v-if="record[value.disable]" class="alert-disable-threshold">
+              {{ text }}
             </span>
-            <span v-else>{{ item.user ? item.account + '(' + item.user + ')' : item.account }}</span>
+            <span v-else-if="record[value.notification]" class="alert-notification-threshold">
+              {{ text }}
+            </span>
+            <span style="padding: 10%;" v-else>
+              {{ text }}
+            </span>
           </span>
         </template>
       </template>
-      <template v-if="text && !text.startsWith('PrjAcct-')">
-        <router-link
-          v-if="'quota' in record && $router.resolve(`${$route.path}/${record.account}`).matched[0].redirect !== '/exception/404'"
-          :to="{ path: `${$route.path}/${record.account}`, query: { account: record.account, domainid: record.domainid, quota: true } }">{{ text }}</router-link>
-        <router-link :to="{ path: '/account/' + record.accountid }" v-else-if="record.accountid">{{ text }}</router-link>
-        <router-link :to="{ path: '/account', query: { name: record.account, domainid: record.domainid, dataView: true } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
+      <template v-if="column.key === 'level'">
+        <router-link :to="{ path: '/event/' + record.id }">{{ text }}</router-link>
+      </template>
+
+      <template v-if="column.key === 'clustername'">
+        <router-link :to="{ path: '/cluster/' + record.clusterid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'objectstore'">
+        <router-link :to="{ path: '/objectstore/' + record.objectstorageid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'podname'">
+        <router-link :to="{ path: '/pod/' + record.podid }">{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'account'">
+        <template v-if="record.owner">
+          <template v-for="(item, idx) in record.owner" :key="idx">
+            <span style="margin-right:5px">
+              <span v-if="$store.getters.userInfo.roletype !== 'User'">
+                <router-link v-if="'user' in item" :to="{ path: '/accountuser', query: { username: item.user, domainid: record.domainid }}">{{ item.account + '(' + item.user + ')' }}</router-link>
+                <router-link v-else :to="{ path: '/account', query: { name: item.account, domainid: record.domainid, dataView: true } }">{{ item.account }}</router-link>
+              </span>
+              <span v-else>{{ item.user ? item.account + '(' + item.user + ')' : item.account }}</span>
+            </span>
+          </template>
+        </template>
+        <template v-if="text && !text.startsWith('PrjAcct-')">
+          <router-link
+            v-if="'quota' in record && $router.resolve(`${$route.path}/${record.account}`).matched[0].redirect !== '/exception/404'"
+            :to="{ path: `${$route.path}/${record.account}`, query: { account: record.account, domainid: record.domainid, quota: true } }">{{ text }}</router-link>
+          <router-link :to="{ path: '/account/' + record.accountid }" v-else-if="record.accountid">{{ text }}</router-link>
+          <router-link :to="{ path: '/account', query: { name: record.account, domainid: record.domainid, dataView: true } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
+          <span v-else>{{ text }}</span>
+        </template>
+      </template>
+      <template v-if="column.key === 'resource'">
+        <resource-label :resourceType="record.resourcetype" :resourceId="record.resourceid" :resourceName="record.resourcename" />
+      </template>
+      <template v-if="column.key === 'domain'">
+        <router-link v-if="record.domainid && !record.domainid.toString().includes(',') && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
         <span v-else>{{ text }}</span>
       </template>
-    </template>
-    <template #resource="{ record }">
-      <resource-label :resourceType="record.resourcetype" :resourceId="record.resourceid" :resourceName="record.resourcename" />
-    </template>
-    <template #domain="{ text, record }">
-      <router-link v-if="record.domainid && !record.domainid.toString().includes(',') && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #domainpath="{ text, record }">
-      <router-link v-if="record.domainid && !record.domainid.includes(',') && $router.resolve('/domain/' + record.domainid).matched[0].redirect !== '/exception/404'" :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #zone="{ text, record }">
-      <router-link v-if="record.zoneid && !record.zoneid.includes(',') && $router.resolve('/zone/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #zonename="{ text, record }">
-      <router-link v-if="$router.resolve('/zone/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
-      <router-link v-else-if="$router.resolve('/zones/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zones/' + record.zoneid }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #rolename="{ text, record }">
-      <router-link v-if="record.roleid && $router.resolve('/role/' + record.roleid).matched[0].redirect !== '/exception/404'" :to="{ path: '/role/' + record.roleid }">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #templateversion="{ record }">
-      <span>  {{ record.version }} </span>
-    </template>
-    <template #softwareversion="{ record }">
-      <span>  {{ record.softwareversion ? record.softwareversion : 'N/A' }} </span>
-    </template>
-    <template #access="{ record }">
-      <status :text="record.readonly ? 'ReadOnly' : 'ReadWrite'" displayText />
-    </template>
-    <template #requiresupgrade="{ record }">
-      <status :text="record.requiresupgrade ? 'warning' : ''" />
-      {{ record.requiresupgrade ? 'Yes' : 'No' }}
-    </template>
-    <template #loadbalancerrule="{ record }">
-      <span>  {{ record.loadbalancerrule }} </span>
-    </template>
-    <template #autoscalingenabled="{ record }">
-      <status :text="record.autoscalingenabled ? 'Enabled' : 'Disabled'" />
-      {{ record.autoscalingenabled ? 'Enabled' : 'Disabled' }}
-    </template>
-    <template #current="{record}">
-      <status :text="record.current ? record.current.toString() : 'false'" />
-    </template>
-    <template #created="{ text }">
-      {{ $toLocaleDate(text) }}
-    </template>
-    <template #sent="{ text }">
-      {{ $toLocaleDate(text) }}
-    </template>
-    <template #order="{ text, record }">
-      <div class="shift-btns">
-        <a-tooltip :name="text" placement="top">
-          <template #title>{{ $t('label.move.to.top') }}</template>
-          <a-button
-            shape="round"
-            @click="moveItemTop(record)"
-            class="shift-btn">
-            <DoubleLeftOutlined class="shift-btn shift-btn--rotated" />
-          </a-button>
-        </a-tooltip>
-        <a-tooltip placement="top">
-          <template #title>{{ $t('label.move.to.bottom') }}</template>
-          <a-button
-            shape="round"
-            @click="moveItemBottom(record)"
-            class="shift-btn">
-            <DoubleRightOutlined class="shift-btn shift-btn--rotated" />
-          </a-button>
-        </a-tooltip>
-        <a-tooltip placement="top">
-          <template #title>{{ $t('label.move.up.row') }}</template>
-          <a-button shape="round" @click="moveItemUp(record)" class="shift-btn">
-            <CaretUpOutlined class="shift-btn" />
-          </a-button>
-        </a-tooltip>
-        <a-tooltip placement="top">
-          <template #title>{{ $t('label.move.down.row') }}</template>
-          <a-button shape="round" @click="moveItemDown(record)" class="shift-btn">
-            <CaretDownOutlined class="shift-btn" />
-          </a-button>
-        </a-tooltip>
-      </div>
-    </template>
+      <template v-if="column.key === 'domainpath'">
+        <router-link v-if="record.domainid && !record.domainid.includes(',') && $router.resolve('/domain/' + record.domainid).matched[0].redirect !== '/exception/404'" :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'zone'">
+        <router-link v-if="record.zoneid && !record.zoneid.includes(',') && $router.resolve('/zone/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'zonename'">
+        <router-link v-if="$router.resolve('/zone/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
+        <router-link v-else-if="$router.resolve('/zones/' + record.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zones/' + record.zoneid }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'rolename'">
+        <router-link v-if="record.roleid && $router.resolve('/role/' + record.roleid).matched[0].redirect !== '/exception/404'" :to="{ path: '/role/' + record.roleid }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'project'">
+        <router-link v-if="$router.resolve('/project/' + record.projectid).matched[0].redirect !== '/exception/404'" :to="{ path: '/project/' + record.projectid }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'templateversion'">
+        <span>  {{ record.version }} </span>
+      </template>
+      <template v-if="column.key === 'drsimbalance'">
+        <span>  {{ record.drsimbalance }} </span>
+      </template>
+      <template v-if="column.key === 'softwareversion'">
+        <span>  {{ record.softwareversion ? record.softwareversion : 'N/A' }} </span>
+      </template>
+      <template v-if="column.key === 'readonly'">
+        <status :text="record.readonly ? 'ReadOnly' : 'ReadWrite'" displayText />
+      </template>
+      <template v-if="column.key === 'requiresupgrade'">
+        <status :text="record.requiresupgrade ? 'warning' : ''" />
+        {{ record.requiresupgrade ? 'Yes' : 'No' }}
+      </template>
+      <template v-if="column.key === 'loadbalancerrule'">
+        <span>  {{ record.loadbalancerrule }} </span>
+      </template>
+      <template v-if="column.key === 'autoscalingenabled'">
+        <status :text="record.autoscalingenabled ? 'Enabled' : 'Disabled'" />
+        {{ record.autoscalingenabled ? 'Enabled' : 'Disabled' }}
+      </template>
+      <template v-if="column.key === 'current'">
+        <status :text="record.current ? record.current.toString() : 'false'" />
+      </template>
+      <template v-if="column.key === 'enabled'">
+        <status :text="record.enabled ? record.enabled.toString() : 'false'" />
+        {{ record.enabled ? 'Enabled' : 'Disabled' }}
+      </template>
+      <template v-if="['created', 'sent'].includes(column.key)">
+        {{ $toLocaleDate(text) }}
+      </template>
+      <template v-if="['startdate', 'enddate'].includes(column.key) && ['vm', 'vnfapp'].includes($route.path.split('/')[1])">
+        {{ getDateAtTimeZone(text, record.timezone) }}
+      </template>
+      <template v-if="column.key === 'order'">
+        <div class="shift-btns">
+          <a-tooltip :name="text" placement="top">
+            <template #title>{{ $t('label.move.to.top') }}</template>
+            <a-button
+              shape="round"
+              @click="moveItemTop(record)"
+              class="shift-btn">
+              <DoubleLeftOutlined class="shift-btn shift-btn--rotated" />
+            </a-button>
+          </a-tooltip>
+          <a-tooltip placement="top">
+            <template #title>{{ $t('label.move.to.bottom') }}</template>
+            <a-button
+              shape="round"
+              @click="moveItemBottom(record)"
+              class="shift-btn">
+              <DoubleRightOutlined class="shift-btn shift-btn--rotated" />
+            </a-button>
+          </a-tooltip>
+          <a-tooltip placement="top">
+            <template #title>{{ $t('label.move.up.row') }}</template>
+            <a-button shape="round" @click="moveItemUp(record)" class="shift-btn">
+              <CaretUpOutlined class="shift-btn" />
+            </a-button>
+          </a-tooltip>
+          <a-tooltip placement="top">
+            <template #title>{{ $t('label.move.down.row') }}</template>
+            <a-button shape="round" @click="moveItemDown(record)" class="shift-btn">
+              <CaretDownOutlined class="shift-btn" />
+            </a-button>
+          </a-tooltip>
+        </div>
+      </template>
 
-    <template #value="{ text, record }">
-      <a-input
-        v-if="editableValueKey === record.key"
-        v-focus="true"
-        :defaultValue="record.value"
-        :disabled="!('updateConfiguration' in $store.getters.apis)"
-        v-model:value="editableValue"
-        @keydown.esc="editableValueKey = null"
-        @pressEnter="saveValue(record)">
-      </a-input>
-      <div v-else style="width: 200px; word-break: break-all">
-        {{ text }}
-      </div>
+      <template v-if="column.key === 'value'">
+        <a-input
+          v-if="editableValueKey === record.key"
+          v-focus="true"
+          :defaultValue="record.value"
+          :disabled="!('updateConfiguration' in $store.getters.apis)"
+          v-model:value="editableValue"
+          @keydown.esc="editableValueKey = null"
+          @pressEnter="saveValue(record)">
+        </a-input>
+        <div v-else style="width: 200px; word-break: break-all">
+          {{ text }}
+        </div>
+      </template>
+      <template v-if="column.key === 'actions'">
+        <tooltip-button
+          :tooltip="$t('label.edit')"
+          :disabled="!('updateConfiguration' in $store.getters.apis)"
+          v-if="editableValueKey !== record.key"
+          icon="edit-outlined"
+          @onClick="editValue(record)" />
+        <tooltip-button
+          :tooltip="$t('label.cancel')"
+          @onClick="editableValueKey = null"
+          v-if="editableValueKey === record.key"
+          iconType="CloseCircleTwoTone"
+          iconTwoToneColor="#f5222d" />
+        <tooltip-button
+          :tooltip="$t('label.ok')"
+          :disabled="!('updateConfiguration' in $store.getters.apis)"
+          @onClick="saveValue(record)"
+          v-if="editableValueKey === record.key"
+          iconType="CheckCircleTwoTone"
+          iconTwoToneColor="#52c41a" />
+        <tooltip-button
+          :tooltip="$t('label.reset.config.value')"
+          @onClick="resetConfig(record)"
+          v-if="editableValueKey !== record.key"
+          icon="reload-outlined"
+          :disabled="!('updateConfiguration' in $store.getters.apis)" />
+      </template>
+      <template v-if="column.key === 'tariffActions'">
+        <tooltip-button
+          :tooltip="$t('label.edit')"
+          v-if="editableValueKey !== record.key"
+          :disabled="!('quotaTariffUpdate' in $store.getters.apis)"
+          icon="edit-outlined"
+          @onClick="editTariffValue(record)" />
+        <slot></slot>
+      </template>
+      <template v-if="column.key === 'vmScheduleActions'">
+        <tooltip-button
+          :tooltip="$t('label.edit')"
+          :disabled="!('updateVMSchedule' in $store.getters.apis)"
+          icon="edit-outlined"
+          @onClick="updateVMSchedule(record)" />
+        <tooltip-button
+          :tooltip="$t('label.remove')"
+          :disabled="!('deleteVMSchedule' in $store.getters.apis)"
+          icon="delete-outlined"
+          :danger="true"
+          type="primary"
+          @onClick="removeVMSchedule(record)" />
+      </template>
     </template>
-    <template #actions="{ record }">
-      <tooltip-button
-        :tooltip="$t('label.edit')"
-        :disabled="!('updateConfiguration' in $store.getters.apis)"
-        v-if="editableValueKey !== record.key"
-        icon="edit-outlined"
-        @onClick="editValue(record)" />
-      <tooltip-button
-        :tooltip="$t('label.cancel')"
-        @onClick="editableValueKey = null"
-        v-if="editableValueKey === record.key"
-        iconType="CloseCircleTwoTone"
-        iconTwoToneColor="#f5222d" />
-      <tooltip-button
-        :tooltip="$t('label.ok')"
-        :disabled="!('updateConfiguration' in $store.getters.apis)"
-        @onClick="saveValue(record)"
-        v-if="editableValueKey === record.key"
-        iconType="CheckCircleTwoTone"
-        iconTwoToneColor="#52c41a" />
-      <tooltip-button
-        :tooltip="$t('label.reset.config.value')"
-        @onClick="resetConfig(record)"
-        v-if="editableValueKey !== record.key"
-        icon="reload-outlined"
-        :disabled="!('updateConfiguration' in $store.getters.apis)" />
-    </template>
-    <template #tariffActions="{ record }">
-      <tooltip-button
-        :tooltip="$t('label.edit')"
-        v-if="editableValueKey !== record.key"
-        :disabled="!('quotaTariffUpdate' in $store.getters.apis)"
-        icon="edit-outlined"
-        @onClick="editTariffValue(record)" />
-      <slot></slot>
+    <template #footer>
+      <span v-if="hasSelected">
+        {{ `Selected ${selectedRowKeys.length} items` }}
+      </span>
     </template>
   </a-table>
 </template>
@@ -464,6 +488,8 @@
 import ResourceIcon from '@/components/view/ResourceIcon'
 import ResourceLabel from '@/components/widgets/ResourceLabel'
 import { createPathBasedOnVmType } from '@/utils/plugins'
+import cronstrue from 'cronstrue/i18n'
+import moment from 'moment-timezone'
 
 export default {
   name: 'ListView',
@@ -567,21 +593,24 @@
     quickViewEnabled () {
       return new RegExp(['/vm', '/kubernetes', '/ssh', '/userdata', '/vmgroup', '/affinitygroup', '/autoscalevmgroup',
         '/volume', '/snapshot', '/vmsnapshot', '/backup',
-        '/guestnetwork', '/vpc', '/vpncustomergateway',
+        '/guestnetwork', '/vpc', '/vpncustomergateway', '/vnfapp',
         '/template', '/iso',
-        '/project', '/account',
+        '/project', '/account', 'buckets', 'objectstore',
         '/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', '/annotation',
         '/computeoffering', '/systemoffering', '/diskoffering', '/backupoffering', '/networkoffering', '/vpcoffering',
-        '/tungstenfabric'].join('|'))
+        '/tungstenfabric', '/oauthsetting', '/guestos', '/guestoshypervisormapping'].join('|'))
         .test(this.$route.path)
     },
     enableGroupAction () {
       return ['vm', 'alert', 'vmgroup', 'ssh', 'userdata', 'affinitygroup', 'autoscalevmgroup', 'volume', 'snapshot',
-        'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway',
+        'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp',
         'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering',
-        'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment'
+        'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets'
       ].includes(this.$route.name)
     },
+    getDateAtTimeZone (date, timezone) {
+      return date ? moment(date).tz(timezone).format('YYYY-MM-DD HH:mm:ss') : null
+    },
     fetchColumns () {
       if (this.isOrderUpdatable()) {
         return this.columns
@@ -746,6 +775,12 @@
     editTariffValue (record) {
       this.$emit('edit-tariff-action', true, record)
     },
+    updateVMSchedule (record) {
+      this.$emit('update-vm-schedule', record)
+    },
+    removeVMSchedule (record) {
+      this.$emit('remove-vm-schedule', record)
+    },
     ipV6Address (text, record) {
       if (!record || !record.nic || record.nic.length === 0) {
         return ''
@@ -790,6 +825,9 @@
         default: return record.entitytype.toLowerCase().replace('_', '')
       }
     },
+    generateHumanReadableSchedule (schedule) {
+      return cronstrue.toString(schedule, { locale: this.$i18n.locale })
+    },
     entityTypeToPath (entitytype) {
       switch (entitytype) {
         case 'VM' : return 'vm'
@@ -852,6 +890,14 @@
     },
     updateSelectedColumns (name) {
       this.$emit('update-selected-columns', name)
+    },
+    getVmRouteUsingType (record) {
+      switch (record.virtualmachinetype) {
+        case 'DomainRouter' : return '/router/'
+        case 'ConsoleProxy' :
+        case 'SecondaryStorageVm': return '/systemvm/'
+        default: return '/vm/'
+      }
     }
   }
 }
diff --git a/ui/src/components/view/NicNetworkSelectForm.vue b/ui/src/components/view/NicNetworkSelectForm.vue
index f19b151..d54f775 100644
--- a/ui/src/components/view/NicNetworkSelectForm.vue
+++ b/ui/src/components/view/NicNetworkSelectForm.vue
@@ -33,11 +33,13 @@
         :dataSource="networks"
         :pagination="false"
         :rowKey="record => record.id">
-        <template #select="{record}">
-          <a-radio
-            @click="updateSelection(record)"
-            :checked="selectedNetwork != null && record.id === selectedNetwork.id">
-          </a-radio>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'select'">
+            <a-radio
+              @click="updateSelection(record)"
+              :checked="selectedNetwork != null && record.id === selectedNetwork.id">
+            </a-radio>
+          </template>
         </template>
       </a-table>
       <a-pagination
@@ -97,24 +99,28 @@
       selectedNetwork: null,
       columns: [
         {
+          key: 'name',
           title: this.$t('label.networkid'),
           dataIndex: 'name'
         },
         {
+          key: 'type',
           title: this.$t('label.guestiptype'),
           dataIndex: 'type'
         },
         {
+          key: 'vpcName',
           title: this.$t('label.vpc'),
           dataIndex: 'vpcName'
         },
         {
+          key: 'cidr',
           title: this.$t('label.cidr'),
           dataIndex: 'cidr'
         },
         {
-          title: this.$t('label.select'),
-          slots: { customRender: 'select' }
+          key: 'select',
+          title: this.$t('label.select')
         }
       ]
     }
diff --git a/ui/src/components/view/ObjectStoreBrowser.vue b/ui/src/components/view/ObjectStoreBrowser.vue
new file mode 100644
index 0000000..531846a
--- /dev/null
+++ b/ui/src/components/view/ObjectStoreBrowser.vue
@@ -0,0 +1,541 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-modal
+    :visible="showUploadModal"
+    :closable="true"
+    :destroyOnClose="true"
+    :title="$t('label.upload')"
+    :maskClosable="false"
+    :cancelText="$t('label.cancel')"
+    @cancel="() => showUploadModal = false"
+    :okText="$t('label.upload')"
+    @ok="uploadFiles()"
+    centered
+    >
+    <a-upload-dragger
+      :multiple="true"
+      :v-model:file-list="uploadFileList"
+      listType="picture"
+      :beforeUpload="beforeUpload">
+      <p class="ant-upload-drag-icon">
+        <cloud-upload-outlined />
+      </p>
+      <p class="ant-upload-text">
+        {{ $t('label.volume.volumefileupload.description') }}
+      </p>
+    </a-upload-dragger>
+    <a-divider dashed/>
+    <tooltip-label bold :title="$t('label.upload.path')" :tooltip="$t('label.upload.description')"/>
+    <br/>
+    <a-input
+      v-model:value="uploadDirectory"
+      :placeholder="$t('label.upload.description')"
+      :loading="loading"
+      enter-button/>
+    <a-divider dashed/>
+    <tooltip-label bold :title="$t('label.metadata')" :tooltip="$t('label.metadata.upload.description')"/>
+    <KeyValuePairInput :pairs="uploadMetaData" @update-pairs="(pairs) => uploadMetaData = pairs" />
+  </a-modal>
+
+  <a-drawer
+    :visible="showObjectDetails"
+    :closable="true"
+    :maskClosable="true"
+    @close="() => showObjectDetails = false"
+    :title="record.name"
+    >
+    <div>
+      <a-row justify="space-between">
+        <a-col>
+          <tooltip-label :title="$t('label.name')" bold/>
+        </a-col>
+        <a-col>
+          {{ record.name.split('/').pop() }}
+        </a-col>
+      </a-row>
+      <a-row justify="space-between">
+        <a-col>
+          <tooltip-label :title="$t('label.size')" bold/>
+        </a-col>
+        <a-col>
+          {{ convertBytes(record.size) }}
+        </a-col>
+      </a-row>
+      <a-row justify="space-between">
+        <a-col>
+          <tooltip-label :title="$t('label.last.updated')" bold/>
+        </a-col>
+        <a-col>
+          {{ $toLocaleDate(record.lastModified) }}
+        </a-col>
+      </a-row>
+      <a-row justify="space-between">
+        <a-col>
+          <tooltip-label :title="$t('label.url')" :tooltip="$t('label.object.url.description')" bold/>
+        </a-col>
+        <a-col>
+          <a :href="record.url">{{ $t('label.link') }}</a>
+        </a-col>
+      </a-row>
+      <a-row justify="space-between">
+        <a-col>
+          <tooltip-label :title="$t('label.object.presigned.url')" :tooltip="$t('label.object.presigned.url.description')" bold />
+        </a-col>
+        <a-col>
+          <a :href="record.presignedUrl">{{ $t('label.link') }}</a>
+        </a-col>
+      </a-row>
+        <a-divider>
+          <tooltip-label :title="$t('label.metadata')" :tooltip="$t('label.metadata.description')"/>
+        </a-divider>
+        <template
+          v-for="(value,key) in record.metadata"
+          :key="key"
+          >
+          <a-row justify="space-between">
+            <a-col>
+              <tooltip-label :title="key" bold />
+            </a-col>
+            <a-col>
+              {{ value }}
+            </a-col>
+          </a-row>
+        </template>
+    </div>
+  </a-drawer>
+
+  <div>
+    <a-card class="breadcrumb-card">
+      <a-row>
+        <a-breadcrumb :routes="getRoutes()">
+          <template #itemRender="{ route }">
+            <span v-if="[''].includes(route.path) && route.breadcrumbName === 'root'">
+              <a @click="openDir('')">
+                <HomeOutlined/>
+              </a>
+            </span>
+            <span v-else>
+              <a @click="openDir(route.path)">
+              {{ route.breadcrumbName }}
+              </a>
+            </span>
+          </template>
+        </a-breadcrumb>
+      </a-row>
+      <a-divider/>
+      <a-row :gutter="[10,10]" :wrap="true">
+        <a-col flex="75%">
+          <a-input-search
+            allowClear
+            size="medium"
+            v-model:value="searchPrefix"
+            :placeholder="$t('label.objectstore.search')"
+            :loading="loading"
+            @search="listObjects()"
+            :enter-button="$t('label.search')"/>
+        </a-col>
+        <a-col flex="auto">
+          <a-button
+            :loading="loading"
+            style="margin-bottom: 5px"
+            shape="round"
+            size="medium"
+            @click="listObjects()">
+            <reload-outlined />
+            {{ $t('label.refresh') }}
+          </a-button>
+        </a-col>
+        <a-col flex="auto">
+          <a-button
+            :loading="loading"
+            style="margin-bottom: 5px"
+            shape="round"
+            size="medium"
+            type="primary"
+            @click="() => showUploadModal = true">
+            <upload-outlined />
+            {{ $t('label.upload') }}
+          </a-button>
+        </a-col>
+        <a-col flex="auto">
+          <tooltip-button
+            type="primary"
+            size="medium"
+            icon="delete-outlined"
+            :tooltip="$t('label.delete')"
+            v-if="selectedRows.length > 0"
+            :danger="true"
+            @onClick="removeObjects()"/>
+        </a-col>
+      </a-row>
+    </a-card>
+
+    <div>
+      <a-table
+        :columns="columns"
+        :scroll="{ y: 300 }"
+        :row-key="record => record"
+        :data-source="records"
+        :loading="loading"
+        :pagination="{ current: page, pageSize: 1000, total: total, showSizeChanger: false }"
+        :row-selection="{ selectedRowsKeys: selectedRows, onChange: onSelectChange }"
+        @change="handleTableChange">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key == 'name'">
+            <template v-if="record.name === undefined && record.prefix">
+              <a @click="openDir(record.prefix)">
+                <folder-outlined /> {{ record.prefix.replace(this.browserPath, '').replace('/', '') }}
+              </a>
+            </template>
+            <template v-else>
+              <a @click="showObjectDescription(record)">
+                {{ record.name.split('/').pop() }}
+              </a>
+            </template>
+          </template>
+          <template v-else-if="column.key == 'size'">
+            <template v-if="record.name !== undefined && !record.prefix">
+              {{ convertBytes(record.size) }}
+            </template>
+          </template>
+          <template v-else-if="column.key == 'lastModified' && record.lastModified">
+            {{ $toLocaleDate(record.lastModified) }}
+          </template>
+        </template>
+      </a-table>
+    </div>
+  </div>
+
+</template>
+
+<script>
+import { reactive } from 'vue'
+import * as Minio from 'minio'
+import { genericCompare } from '@/utils/sort.js'
+import InfoCard from '@/components/view/InfoCard'
+import TooltipButton from '@/components/widgets/TooltipButton'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import KeyValuePairInput from '@/components/KeyValuePairInput'
+
+export default {
+  name: 'ObjectStoreBrowser',
+  components: {
+    InfoCard,
+    TooltipButton,
+    TooltipLabel,
+    KeyValuePairInput
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    resourceType: {
+      type: String,
+      required: true
+    }
+  },
+  data () {
+    var columns = [
+      {
+        key: 'name',
+        title: this.$t('label.name'),
+        sorter: (a, b) => genericCompare(a?.name || '', b?.name || '')
+      },
+      {
+        key: 'size',
+        title: this.$t('label.size'),
+        sorter: (a, b) => genericCompare(a?.size || '', b?.size || '')
+      },
+      {
+        key: 'lastModified',
+        title: this.$t('label.last.updated'),
+        sorter: (a, b) => genericCompare(a?.lastModified || '', b?.lastModified || '')
+      }
+    ]
+    return {
+      client: null,
+      loading: false,
+      records: [],
+      browserPath: this.$route.query.browserPath || '',
+      page: 1,
+      pageStartAfterMap: { 1: '' },
+      total: 0,
+      columns: columns,
+      selectedRows: [],
+      searchPrefix: '',
+      showUploadModal: false,
+      uploadFileList: reactive([]),
+      uploadDirectory: this.$route.query.browserPath || '',
+      uploadMetaData: {},
+      record: {},
+      showObjectDetails: false,
+      fetching: false
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    handleTableChange (pagination, filters, sorter) {
+      if (this.page !== pagination.current) {
+        this.page = pagination.current
+        this.fetchData()
+      }
+    },
+    fetchData () {
+      this.loading = true
+      this.records = []
+      this.$router.replace(
+        {
+          query: {
+            ...this.$route.query,
+            browserPath: this.browserPath
+          }
+        }
+      )
+      if (!this.client) {
+        this.initMinioClient()
+      } else {
+        this.listObjects()
+      }
+    },
+    getRoutes () {
+      let path = ''
+      const routeList = [{
+        path: path,
+        breadcrumbName: 'root'
+      }]
+      for (const route of this.browserPath.split('/')) {
+        if (route) {
+          path = `${path}${route}/`
+          routeList.push({
+            path: path,
+            breadcrumbName: route
+          })
+        }
+      }
+      return routeList
+    },
+    convertBytes (val) {
+      if (val < 1024 * 1024) return `${(val / 1024).toFixed(2)} KB`
+      if (val < 1024 * 1024 * 1024) return `${(val / 1024 / 1024).toFixed(2)} MB`
+      if (val < 1024 * 1024 * 1024 * 1024) return `${(val / 1024 / 1024 / 1024).toFixed(2)} GB`
+      if (val < 1024 * 1024 * 1024 * 1024 * 1024) return `${(val / 1024 / 1024 / 1024 / 1024).toFixed(2)} TB`
+      return val
+    },
+    openDir (name) {
+      if (name === '/') {
+        name = ''
+      }
+      this.browserPath = name
+      this.uploadDirectory = name
+      this.page = 1
+      this.fetchData()
+    },
+    listObjects () {
+      while (this.fetching) {
+        // sleep for 500ms
+        setTimeout(() => {
+          console.log('waiting for previous request to complete')
+        }, 500)
+      }
+      this.fetching = true
+      this.records = []
+      var stream = this.client.extensions.listObjectsV2WithMetadata(this.resource.name, this.browserPath + this.searchPrefix, false, this.pageStartAfterMap[this.page])
+      stream.on('data', obj => {
+        this.records.push(obj)
+        if (this.records.length >= 1000) {
+          stream.destroy()
+        }
+      })
+      stream.on('end', obj => {
+        var total = 0
+        if (this.records.length > 0) {
+          if (this.records.length >= 1000) {
+            total = (this.page + 1) * 1000
+            if (total > this.total) {
+              this.total = total
+            }
+          } else {
+            total = (this.page - 1) * 1000 + this.records.length
+          }
+          this.pageStartAfterMap[this.page + 1] = this.records[this.records.length - 1].name
+        }
+        if (total > this.total) {
+          this.total = total
+        }
+        this.loading = false
+        this.fetching = false
+      })
+      stream.on('error', err => {
+        console.log(err)
+        this.loading = false
+        this.fetching = false
+      })
+    },
+    removeObjects () {
+      this.loading = true
+      this.page = 1
+      this.pageStartAfterMap = { 1: '' }
+      const objectsToDelete = this.selectedRows.filter((row) => row.name).map((row) => row.name)
+      const directoriesToDelete = this.selectedRows.filter((row) => row.prefix).map((row) => row.prefix)
+      this.selectedRows = []
+      this.removeDirectories(directoriesToDelete)
+      if (objectsToDelete.length > 0) {
+        this.client.removeObjects(this.resource.name, objectsToDelete, err => {
+          if (err) {
+            return this.$notification.error({
+              message: this.$t('error.execute.api.failed'),
+              description: err.message
+            })
+          }
+          this.$notification.success({
+            message: this.$t('label.delete'),
+            description: this.$t('message.success.remove.objectstore.objects') + ' ' + objectsToDelete.length
+          })
+          this.listObjects()
+        })
+      }
+    },
+    removeDirectories (directoriesToDelete) {
+      for (const directory of directoriesToDelete) {
+        var objectsList = []
+        const stream = this.client.listObjectsV2(this.resource.name, directory, true, '')
+        stream.on('data', (obj) => {
+          objectsList.push(obj.name)
+        })
+
+        stream.on('error', (err) => {
+          console.log(err)
+        })
+        stream.on('end', (err) => {
+          if (err) {
+            return console.log(err)
+          }
+          this.client.removeObjects(this.resource.name, objectsList, err => {
+            if (err) {
+              return this.$notification.error({
+                message: this.$t('error.execute.api.failed'),
+                description: err.message
+              })
+            }
+            this.$notification.success({
+              message: this.$t('label.delete'),
+              description: this.$t('message.success.remove.objectstore.directory') + ' ' + directory
+            })
+            console.log('Removed the objects successfully')
+            this.listObjects()
+          })
+        })
+      }
+    },
+    initMinioClient () {
+      if (!this.client) {
+        const url = /https?:\/\/([^/]+)\/?/.exec(this.resource.url.split(this.resource.name)[0])[1]
+        const isHttps = /^https/.test(this.resource.url)
+        this.client = new Minio.Client({
+          endPoint: url.split(':')[0],
+          port: url.split(':').length > 1 ? parseInt(url.split(':')[1]) : isHttps ? 443 : 80,
+          useSSL: isHttps,
+          accessKey: this.resource.accesskey,
+          secretKey: this.resource.usersecretkey
+        })
+        this.listObjects()
+      }
+    },
+    onSelectChange (selectedRow) {
+      this.selectedRows = selectedRow
+    },
+    beforeUpload (file) {
+      this.uploadFileList.push(file)
+      return false
+    },
+    uploadFiles () {
+      if (!this.uploadDirectory.endsWith('/')) {
+        this.uploadDirectory = this.uploadDirectory + '/'
+      }
+      var promises = []
+      while (this.uploadFileList.length > 0) {
+        const file = this.uploadFileList.pop()
+        const objectName = this.uploadDirectory + file.name
+        promises.push(this.asyncUploadFile(file, objectName))
+      }
+      Promise.allSettled(promises).then(() => {
+        this.uploadDirectory = this.browserPath
+        this.uploadMetaData = {}
+        this.uploadFileList = []
+        this.listObjects()
+      })
+      this.showUploadModal = false
+    },
+    asyncUploadFile (file, objectName) {
+      return new Promise((resolve, reject) => {
+        file.arrayBuffer().then((buffer) => {
+          this.client.putObject(this.resource.name, objectName, Buffer.from(buffer), file.size, this.uploadMetaData, err => {
+            if (err) {
+              return reject(this.$notification.error({
+                message: this.$t('message.upload.failed'),
+                description: err.message
+              }))
+            }
+            return resolve(this.$notification.success({
+              message: this.$t('message.success.upload'),
+              description: objectName.split('/').pop()
+            }))
+          })
+        })
+      })
+    },
+    showObjectDescription (record) {
+      this.record = { ...record }
+      this.record.url = this.resource.url + '/' + record.name
+      this.client.presignedGetObject(this.resource.name, record.name, 24 * 60 * 60, (err, presignedUrl) => {
+        if (err) {
+          return this.$notification.error({
+            message: this.$t('error.execute.api.failed'),
+            description: err.message
+          })
+        } else {
+          this.record.presignedUrl = presignedUrl
+        }
+        this.showObjectDetails = true
+      })
+    },
+    updateMetadata () {
+      this.client.copyObject(
+        new Minio.CopySourceOptions({ Bucket: this.resource.name, Object: this.record.name }),
+        new Minio.CopyDestinationOptions({ Bucket: this.resource.name, Object: this.record.name, MetadataDirective: 'REPLACE', UserMetadata: this.record.metadata }),
+        err => {
+          if (err) {
+            this.$notification.error({
+              message: this.$t('error.execute.api.failed'),
+              description: err.message
+            })
+          }
+          this.$notification.success({
+            message: this.$t('label.metadata'),
+            description: this.$t('message.update.success')
+          })
+          this.listObjects()
+        })
+    }
+  }
+}
+</script>
diff --git a/ui/src/components/view/ResourceView.vue b/ui/src/components/view/ResourceView.vue
index a919005..367c589 100644
--- a/ui/src/components/view/ResourceView.vue
+++ b/ui/src/components/view/ResourceView.vue
@@ -37,7 +37,7 @@
         </keep-alive>
         <a-tabs
           v-else
-          style="width: 100%"
+          style="width: 100%; margin-top: -12px"
           :animated="false"
           :activeKey="activeTab || tabs[0].name"
           @change="onTabChange" >
diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue
index 1a3642d..7a2154a 100644
--- a/ui/src/components/view/SearchView.vue
+++ b/ui/src/components/view/SearchView.vue
@@ -71,7 +71,7 @@
                       v-for="(opt, idx) in field.opts"
                       :key="idx"
                       :value="opt.id"
-                      :label="$t(opt.name)">
+                      :label="$t(opt.path || opt.name)">
                       <div>
                         <span v-if="(field.name.startsWith('zone'))">
                           <span v-if="opt.icon">
@@ -186,7 +186,8 @@
       inputKey: null,
       inputValue: null,
       fieldValues: {},
-      isFiltered: false
+      isFiltered: false,
+      alertTypes: []
     }
   },
   created () {
@@ -279,7 +280,7 @@
         if (item === 'groupid' && !('listInstanceGroups' in this.$store.getters.apis)) {
           return true
         }
-        if (['zoneid', 'domainid', 'state', 'level', 'clusterid', 'podid', 'groupid', 'entitytype', 'type'].includes(item)) {
+        if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'level', 'clusterid', 'podid', 'groupid', 'entitytype', 'type'].includes(item)) {
           type = 'list'
         } else if (item === 'tags') {
           type = 'tag'
@@ -347,12 +348,23 @@
     },
     async fetchDynamicFieldData (arrayField, searchKeyword) {
       const promises = []
+      let typeIndex = -1
       let zoneIndex = -1
       let domainIndex = -1
+      let imageStoreIndex = -1
+      let storageIndex = -1
       let podIndex = -1
       let clusterIndex = -1
       let groupIndex = -1
 
+      if (arrayField.includes('type')) {
+        if (this.$route.path === '/alert') {
+          typeIndex = this.fields.findIndex(item => item.name === 'type')
+          this.fields[typeIndex].loading = true
+          promises.push(await this.fetchAlertTypes())
+        }
+      }
+
       if (arrayField.includes('zoneid')) {
         zoneIndex = this.fields.findIndex(item => item.name === 'zoneid')
         this.fields[zoneIndex].loading = true
@@ -365,6 +377,18 @@
         promises.push(await this.fetchDomains(searchKeyword))
       }
 
+      if (arrayField.includes('imagestoreid')) {
+        imageStoreIndex = this.fields.findIndex(item => item.name === 'imagestoreid')
+        this.fields[imageStoreIndex].loading = true
+        promises.push(await this.fetchImageStores(searchKeyword))
+      }
+
+      if (arrayField.includes('storageid')) {
+        storageIndex = this.fields.findIndex(item => item.name === 'storageid')
+        this.fields[storageIndex].loading = true
+        promises.push(await this.fetchStoragePools(searchKeyword))
+      }
+
       if (arrayField.includes('podid')) {
         podIndex = this.fields.findIndex(item => item.name === 'podid')
         this.fields[podIndex].loading = true
@@ -384,6 +408,12 @@
       }
 
       Promise.all(promises).then(response => {
+        if (typeIndex > -1) {
+          const types = response.filter(item => item.type === 'type')
+          if (types && types.length > 0) {
+            this.fields[typeIndex].opts = this.sortArray(types[0].data)
+          }
+        }
         if (zoneIndex > -1) {
           const zones = response.filter(item => item.type === 'zoneid')
           if (zones && zones.length > 0) {
@@ -396,6 +426,18 @@
             this.fields[domainIndex].opts = this.sortArray(domain[0].data, 'path')
           }
         }
+        if (imageStoreIndex > -1) {
+          const imageStore = response.filter(item => item.type === 'imagestoreid')
+          if (imageStore && imageStore.length > 0) {
+            this.fields[imageStoreIndex].opts = this.sortArray(imageStore[0].data, 'name')
+          }
+        }
+        if (storageIndex > -1) {
+          const storagePool = response.filter(item => item.type === 'storageid')
+          if (storagePool && storagePool.length > 0) {
+            this.fields[storageIndex].opts = this.sortArray(storagePool[0].data, 'name')
+          }
+        }
         if (podIndex > -1) {
           const pod = response.filter(item => item.type === 'podid')
           if (pod && pod.length > 0) {
@@ -415,12 +457,21 @@
           }
         }
       }).finally(() => {
+        if (typeIndex > -1) {
+          this.fields[typeIndex].loading = false
+        }
         if (zoneIndex > -1) {
           this.fields[zoneIndex].loading = false
         }
         if (domainIndex > -1) {
           this.fields[domainIndex].loading = false
         }
+        if (imageStoreIndex > -1) {
+          this.fields[imageStoreIndex].loading = false
+        }
+        if (storageIndex > -1) {
+          this.fields[storageIndex].loading = false
+        }
         if (podIndex > -1) {
           this.fields[podIndex].loading = false
         }
@@ -488,6 +539,32 @@
         })
       })
     },
+    fetchImageStores (searchKeyword) {
+      return new Promise((resolve, reject) => {
+        api('listImageStores', { listAll: true, showicon: true, keyword: searchKeyword }).then(json => {
+          const imageStore = json.listimagestoresresponse.imagestore
+          resolve({
+            type: 'imagestoreid',
+            data: imageStore
+          })
+        }).catch(error => {
+          reject(error.response.headers['x-description'])
+        })
+      })
+    },
+    fetchStoragePools (searchKeyword) {
+      return new Promise((resolve, reject) => {
+        api('listStoragePools', { listAll: true, showicon: true, keyword: searchKeyword }).then(json => {
+          const storagePool = json.liststoragepoolsresponse.storagepool
+          resolve({
+            type: 'storageid',
+            data: storagePool
+          })
+        }).catch(error => {
+          reject(error.response.headers['x-description'])
+        })
+      })
+    },
     fetchPods (searchKeyword) {
       return new Promise((resolve, reject) => {
         api('listPods', { keyword: searchKeyword }).then(json => {
@@ -527,6 +604,29 @@
         })
       })
     },
+    fetchAlertTypes () {
+      if (this.alertTypes.length > 0) {
+        return new Promise((resolve, reject) => {
+          resolve({
+            type: 'type',
+            data: this.alertTypes
+          })
+        })
+      } else {
+        return new Promise((resolve, reject) => {
+          api('listAlertTypes').then(json => {
+            const alerttypes = json.listalerttypesresponse.alerttype.map(a => { return { id: a.alerttypeid, name: a.name } })
+            this.alertTypes = alerttypes
+            resolve({
+              type: 'type',
+              data: alerttypes
+            })
+          }).catch(error => {
+            reject(error.response.headers['x-description'])
+          })
+        })
+      }
+    },
     fetchGuestNetworkTypes () {
       const types = []
       if (this.apiName.indexOf('listNetworks') > -1) {
@@ -546,30 +646,35 @@
       return types
     },
     fetchState () {
-      const state = []
-      if (this.apiName.indexOf('listVolumes') > -1) {
-        state.push({
-          id: 'Allocated',
-          name: 'label.allocated'
-        })
-        state.push({
-          id: 'Ready',
-          name: 'label.isready'
-        })
-        state.push({
-          id: 'Destroy',
-          name: 'label.destroy'
-        })
-        state.push({
-          id: 'Expunging',
-          name: 'label.expunging'
-        })
-        state.push({
-          id: 'Expunged',
-          name: 'label.expunged'
-        })
+      if (this.apiName.includes('listVolumes')) {
+        return [
+          {
+            id: 'Allocated',
+            name: 'label.allocated'
+          },
+          {
+            id: 'Ready',
+            name: 'label.isready'
+          },
+          {
+            id: 'Destroy',
+            name: 'label.destroy'
+          },
+          {
+            id: 'Expunging',
+            name: 'label.expunging'
+          },
+          {
+            id: 'Expunged',
+            name: 'label.expunged'
+          },
+          {
+            id: 'Migrating',
+            name: 'label.migrating'
+          }
+        ]
       }
-      return state
+      return []
     },
     fetchEntityType () {
       const entityType = []
@@ -633,6 +738,7 @@
     },
     onClear () {
       this.formRef.value.resetFields()
+      this.form = reactive({})
       this.isFiltered = false
       this.inputKey = null
       this.inputValue = null
@@ -645,7 +751,6 @@
       this.formRef.value.validate().then(() => {
         const values = toRaw(this.form)
         this.isFiltered = true
-        console.log(values)
         for (const key in values) {
           const input = values[key]
           if (input === '' || input === null || input === undefined) {
@@ -700,12 +805,6 @@
       right: 0;
     }
   }
-
-  :deep(.ant-input-group) {
-    .ant-input-affix-wrapper {
-      width: calc(100% - 10px);
-    }
-  }
 }
 
 .filter-button {
diff --git a/ui/src/components/view/StoragePoolSelectView.vue b/ui/src/components/view/StoragePoolSelectView.vue
index 1422cea..8f006ed 100644
--- a/ui/src/components/view/StoragePoolSelectView.vue
+++ b/ui/src/components/view/StoragePoolSelectView.vue
@@ -32,45 +32,49 @@
       :dataSource="storagePools"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #suitabilityCustomTitle>
-        {{ $t('label.suitability') }}
-        <a-tooltip :title="$t('message.volume.state.primary.storage.suitability')" placement="top">
-          <info-circle-outlined class="table-tooltip-icon" />
-        </a-tooltip>
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'suitability'">
+          {{ $t('label.suitability') }}
+          <a-tooltip :title="$t('message.volume.state.primary.storage.suitability')" placement="top">
+            <info-circle-outlined class="table-tooltip-icon" />
+          </a-tooltip>
+        </template>
       </template>
-      <template #name="{ record }">
-        {{ record.name }}
-        <a-tooltip v-if="record.name === $t('label.auto.assign')" :title="$t('message.migrate.volume.pool.auto.assign')" placement="top">
-          <info-circle-outlined class="table-tooltip-icon" />
-        </a-tooltip>
-      </template>
-      <template #suitability="{ record }">
-        <check-circle-two-tone
-          class="host-item__suitability-icon"
-          twoToneColor="#52c41a"
-          v-if="record.suitableformigration" />
-        <close-circle-two-tone
-          class="host-item__suitability-icon"
-          twoToneColor="#f5222d"
-          v-else />
-      </template>
-      <template #disksizetotal="{ record }">
-        <span v-if="record.disksizetotal">{{ $bytesToHumanReadableSize(record.disksizetotal) }}</span>
-      </template>
-      <template #disksizeused="{ record }">
-        <span v-if="record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizeused) }}</span>
-      </template>
-      <template #disksizefree="{ record }">
-        <span v-if="record.disksizetotal && record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizetotal * 1 - record.disksizeused * 1) }}</span>
-      </template>
-      <template #select="{ record }">
-        <a-tooltip placement="top" :title="record.state !== 'Up' ? $t('message.primary.storage.invalid.state') : ''">
-          <a-radio
-            :disabled="record.id !== -1 && record.state !== 'Up'"
-            @click="updateSelection(record)"
-            :checked="selectedStoragePool != null && record.id === selectedStoragePool.id">
-          </a-radio>
-        </a-tooltip>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          {{ record.name }}
+          <a-tooltip v-if="record.name === $t('label.auto.assign')" :title="$t('message.migrate.volume.pool.auto.assign')" placement="top">
+            <info-circle-outlined class="table-tooltip-icon" />
+          </a-tooltip>
+        </template>
+        <template v-if="column.key === 'suitability'">
+          <check-circle-two-tone
+            class="host-item__suitability-icon"
+            twoToneColor="#52c41a"
+            v-if="record.suitableformigration" />
+          <close-circle-two-tone
+            class="host-item__suitability-icon"
+            twoToneColor="#f5222d"
+            v-else />
+        </template>
+        <template v-if="column.key === 'disksizetotal'">
+          <span v-if="record.disksizetotal">{{ $bytesToHumanReadableSize(record.disksizetotal) }}</span>
+        </template>
+        <template v-if="column.key === 'disksizeused'">
+          <span v-if="record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizeused) }}</span>
+        </template>
+        <template v-if="column.key === 'disksizefree'">
+          <span v-if="record.disksizetotal && record.disksizeused">{{ $bytesToHumanReadableSize(record.disksizetotal * 1 - record.disksizeused * 1) }}</span>
+        </template>
+        <template v-if="column.key === 'select'">
+          <a-tooltip placement="top" :title="record.state !== 'Up' ? $t('message.primary.storage.invalid.state') : ''">
+            <a-radio
+              :disabled="record.id !== -1 && record.state !== 'Up'"
+              @click="updateSelection(record)"
+              :checked="selectedStoragePool != null && record.id === selectedStoragePool.id">
+            </a-radio>
+          </a-tooltip>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -132,8 +136,8 @@
       selectedStoragePool: null,
       columns: [
         {
-          title: this.$t('label.storageid'),
-          slots: { customRender: 'name' }
+          key: 'name',
+          title: this.$t('label.storageid')
         },
         {
           title: this.$t('label.clusterid'),
@@ -144,27 +148,27 @@
           dataIndex: 'podname'
         },
         {
-          title: this.$t('label.disksizetotal'),
-          slots: { customRender: 'disksizetotal' }
+          key: 'disksizetotal',
+          title: this.$t('label.disksizetotal')
         },
         {
-          title: this.$t('label.disksizeused'),
-          slots: { customRender: 'disksizeused' }
+          key: 'disksizeused',
+          title: this.$t('label.disksizeused')
         },
         {
-          title: this.$t('label.disksizefree'),
-          slots: { customRender: 'disksizefree' }
+          key: 'disksizefree',
+          title: this.$t('label.disksizefree')
         },
         {
-          title: this.$t('label.select'),
-          slots: { customRender: 'select' }
+          key: 'select',
+          title: this.$t('label.select')
         }
       ]
     }
   },
   created () {
     if (this.suitabilityEnabled) {
-      this.columns.splice(1, 0, { title: 'suitabilityCustomTitle', slots: 'suitability' })
+      this.columns.splice(1, 0, { key: 'suitability' })
     }
     this.preselectStoragePool()
     this.fetchStoragePools()
diff --git a/ui/src/components/view/TreeView.vue b/ui/src/components/view/TreeView.vue
index 47f199b..40cd6f0 100644
--- a/ui/src/components/view/TreeView.vue
+++ b/ui/src/components/view/TreeView.vue
@@ -42,8 +42,9 @@
               @select="onSelect"
               @expand="onExpand"
               :expandedKeys="arrExpand">
-              <template #parent><folder-outlined /></template>
-              <template #leaf><block-outlined /></template>
+              <template #icon="{data}">
+                <block-outlined v-if="data.isLeaf" />
+              </template>
             </a-tree>
           </a-spin>
         </a-card>
@@ -136,6 +137,10 @@
       default () {
         return {}
       }
+    },
+    treeDeletedKey: {
+      type: String,
+      default: null
     }
   },
   provide: function () {
@@ -183,11 +188,11 @@
       this.treeViewData = []
       this.arrExpand = []
       if (!this.loading) {
-        this.treeViewData = this.treeData
-        this.treeVerticalData = this.treeData
+        this.treeViewData = this.treeData.slice()
+        this.treeVerticalData = this.treeData.slice()
 
         if (this.treeViewData.length > 0) {
-          this.oldTreeViewData = this.treeViewData
+          this.oldTreeViewData = this.treeViewData.slice()
           this.rootKey = this.treeViewData[0].key
         }
       }
@@ -288,39 +293,47 @@
             }
           }
 
-          this.onSelectResource()
+          this.handleSelectResource(treeNode.eventKey)
           resolve()
         })
       })
     },
-    onSelectResource () {
-      if (this.treeStore.selected) {
-        this.selectedTreeKey = this.treeStore.selected
-        this.defaultSelected = [this.selectedTreeKey]
+    async handleSelectResource (treeKey) {
+      if (this.treeDeletedKey && this.treeDeletedKey === this.treeStore?.treeSelected?.key) {
+        const treeSelectedKey = this.treeStore.treeSelected.parentdomainid
+        if (treeSelectedKey === this.rootKey) {
+          this.resource = this.treeVerticalData[0]
 
-        const resource = this.treeVerticalData.filter(item => item.id === this.selectedTreeKey)
-        if (resource.length > 0) {
-          this.resource = resource[0]
+          this.selectedTreeKey = treeSelectedKey
+          this.defaultSelected = [treeSelectedKey]
           this.$emit('change-resource', this.resource)
-        } else {
-          const resourceIdx = this.treeVerticalData.findIndex(item => item.id === this.resource.id)
-          const parentIndex = this.treeVerticalData.findIndex(item => item.id === this.resource.parentdomainid)
-          if (resourceIdx !== -1) {
-            this.resource = this.treeVerticalData[resourceIdx]
-          } else if (parentIndex !== 1) {
-            this.resource = this.treeVerticalData[parentIndex]
-          } else {
-            this.resource = this.treeVerticalData[0]
-          }
-          this.selectedTreeKey = this.resource.key
-          this.defaultSelected = [this.selectedTreeKey]
+          await this.setTreeStore(false, false, this.resource)
+          return
+        }
+        const resourceIndex = await this.treeVerticalData.findIndex(item => item.id === treeSelectedKey)
+        if (resourceIndex > -1) {
+          const resource = await this.getDetailResource(treeSelectedKey)
+          this.resource = await this.createResourceData(resource)
+
+          this.selectedTreeKey = treeSelectedKey
+          this.defaultSelected = [treeSelectedKey]
           this.$emit('change-resource', this.resource)
+          await this.setTreeStore(false, false, this.resource)
+          return
         }
       }
+      const treeSelectedKey = this.treeStore.treeSelected.key
+      const resourceIndex = await this.treeVerticalData.findIndex(item => item.id === treeSelectedKey)
+      if (resourceIndex > -1) {
+        this.selectedTreeKey = treeSelectedKey
+        this.defaultSelected = [treeSelectedKey]
+
+        this.resource = this.treeStore.treeSelected
+        this.$emit('change-resource', this.resource)
+      }
     },
-    onSelect (selectedKeys, event) {
+    async onSelect (selectedKeys, event) {
       if (!event.selected) {
-        setTimeout(() => { event.node.$refs.selectHandle.click() })
         return
       }
 
@@ -331,20 +344,20 @@
 
       this.defaultSelected = []
       this.defaultSelected.push(this.selectedTreeKey)
+      const resource = await this.getDetailResource(this.selectedTreeKey)
+      this.resource = await this.createResourceData(resource)
+      const index = this.treeVerticalData.findIndex(item => item.key === this.selectedTreeKey)
+      this.treeVerticalData[index] = this.resource
 
-      const treeStore = this.treeStore
-      treeStore.expands = this.arrExpand
-      treeStore.selected = this.selectedTreeKey
-      this.$emit('change-tree-store', this.treeStore)
-
-      this.getDetailResource(this.selectedTreeKey)
+      this.$emit('change-resource', this.resource)
+      await this.setTreeStore(false, false, this.resource)
     },
     onExpand (treeExpand) {
       const treeStore = this.treeStore
       this.arrExpand = treeExpand
       treeStore.isExpand = true
       treeStore.expands = this.arrExpand
-      treeStore.selected = this.selectedTreeKey
+      treeStore.treeSelected = this.resource
       this.$emit('change-tree-store', treeStore)
     },
     onSearch (value) {
@@ -416,40 +429,35 @@
     onTabChange (key) {
       this.tabActive = key
     },
+    setTreeStore (arrExpand, isExpand, resource) {
+      const treeStore = this.treeStore
+      if (arrExpand) treeStore.expands = arrExpand
+      if (isExpand) treeStore.isExpand = true
+      if (resource) treeStore.treeSelected = resource
+      this.$emit('change-tree-store', treeStore)
+    },
     getDetailResource (selectedKey) {
-      // set api name and parameter
-      const apiName = this.$route.meta.permission[0]
-      const params = {}
+      return new Promise(resolve => {
+        // set api name and parameter
+        const apiName = this.$route.meta.permission[0]
+        const params = {}
 
-      // set id to parameter
-      params.id = selectedKey
-      params.listAll = true
-      params.showicon = true
-      params.page = 1
-      params.pageSize = 1
+        // set id to parameter
+        params.id = selectedKey
+        params.listAll = true
+        params.showicon = true
+        params.page = 1
+        params.pageSize = 1
 
-      this.detailLoading = true
-      api(apiName, params).then(json => {
-        const jsonResponse = this.getResponseJsonData(json)
-
-        // check json response is empty
-        if (!jsonResponse || jsonResponse.length === 0) {
-          this.resource = []
-        } else {
-          this.resource = jsonResponse[0]
-          this.resource = this.createResourceData(this.resource)
-          // set all value of resource tree data
-          this.treeVerticalData.filter((item, index) => {
-            if (item.id === this.resource.id) {
-              this.treeVerticalData[index] = this.resource
-            }
-          })
-        }
-
-        // emit change resource to parent
-        this.$emit('change-resource', this.resource)
-      }).finally(() => {
-        this.detailLoading = false
+        this.detailLoading = true
+        api(apiName, params).then(json => {
+          const jsonResponse = this.getResponseJsonData(json)
+          resolve(jsonResponse[0])
+        }).catch(() => {
+          resolve()
+        }).finally(() => {
+          this.detailLoading = false
+        })
       })
     },
     getResponseJsonData (json) {
@@ -524,15 +532,13 @@
       })
       resource.title = resource.name
       resource.key = resource.id
-      resource.slots = {
-        icon: 'parent'
+      if (resource?.icon) {
+        resource.resourceIcon = resource.icon
+        delete resource.icon
       }
 
       if (!resource.haschild) {
         resource.isLeaf = true
-        resource.slots = {
-          icon: 'leaf'
-        }
       }
 
       return resource
@@ -586,8 +592,9 @@
 </script>
 
 <style lang="less" scoped>
-.list-tree-view {
+:deep(.ant-tree).list-tree-view {
   overflow-y: hidden;
+  margin-top: 10px;
 }
 :deep(.ant-tree).ant-tree-directory {
   li.ant-tree-treenode-selected {
@@ -615,7 +622,7 @@
   }
 }
 
-:deep(.ant-tree) li span.ant-tree-switcher.ant-tree-switcher-noop {
+:deep(.ant-tree-show-line) .ant-tree-switcher.ant-tree-switcher-noop {
   display: none
 }
 
@@ -626,10 +633,38 @@
 
 :deep(.ant-tree-icon__customize) {
   padding-right: 5px;
+  background: transparent;
 }
 
-:deep(.ant-tree) li .ant-tree-node-content-wrapper {
-  padding-left: 0;
-  margin-left: 3px;
+:deep(.ant-tree) {
+  .ant-tree-treenode {
+    .ant-tree-node-content-wrapper {
+      margin-left: 0;
+      margin-bottom: 2px;
+
+      &:before {
+        border-left: 1px solid #d9d9d9;
+        content: " ";
+        height: 100%;
+        height: calc(100% - 15px);
+        left: 12px;
+        margin: 22px 0 0;
+        position: absolute;
+        width: 1px;
+        top: 0;
+      }
+    }
+
+    &.ant-tree-treenode-switcher-close, &.ant-tree-treenode-switcher-open, &.ant-tree-treenode-leaf-last {
+      .ant-tree-node-content-wrapper:before {
+        display: none;
+      }
+    }
+  }
+}
+
+:deep(.ant-tree-show-line) .ant-tree-indent-unit:before {
+  bottom: -2px;
+  top: -5px;
 }
 </style>
diff --git a/ui/src/components/view/VolumesTab.vue b/ui/src/components/view/VolumesTab.vue
index 939f708..e9165b1 100644
--- a/ui/src/components/view/VolumesTab.vue
+++ b/ui/src/components/view/VolumesTab.vue
@@ -24,20 +24,22 @@
     :rowKey="item => item.id"
     :pagination="false"
   >
-    <template #name="{ text, record }">
-      <hdd-outlined style="margin-right: 5px"/>
-      <router-link :to="{ path: '/volume/' + record.id }" style="margin-right: 5px">
-        {{ text }}
-      </router-link>
-      <a-tag v-if="record.provisioningtype">
-        {{  record.provisioningtype  }}
-      </a-tag>
-    </template>
-    <template #state="{ text }">
-      <status :text="text ? text : ''" />{{ text }}
-    </template>
-    <template #size="{ record }">
-      {{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
+    <template #bodyCell="{ column, text, record }">
+      <template v-if="column.key === 'name'">
+        <hdd-outlined style="margin-right: 5px"/>
+        <router-link :to="{ path: '/volume/' + record.id }" style="margin-right: 5px">
+          {{ text }}
+        </router-link>
+        <a-tag v-if="record.provisioningtype">
+          {{  record.provisioningtype  }}
+        </a-tag>
+      </template>
+      <template v-if="column.key === 'state'">
+        <status :text="text ? text : ''" />{{ text }}
+      </template>
+      <template v-if="column.key === 'size'">
+        {{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
+      </template>
     </template>
   </a-table>
 </template>
@@ -68,23 +70,23 @@
       volumes: [],
       volumeColumns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.type'),
           dataIndex: 'type'
         },
         {
+          key: 'size',
           title: this.$t('label.size'),
-          dataIndex: 'size',
-          slots: { customRender: 'size' }
+          dataIndex: 'size'
         }
       ]
     }
@@ -92,7 +94,6 @@
   created () {
     this.vm = this.resource
     this.fetchData()
-    console.log(this.resource.volumes)
   },
   watch: {
     resource: function (newItem) {
diff --git a/ui/src/components/widgets/Breadcrumb.vue b/ui/src/components/widgets/Breadcrumb.vue
index 097a922..147e779 100644
--- a/ui/src/components/widgets/Breadcrumb.vue
+++ b/ui/src/components/widgets/Breadcrumb.vue
@@ -40,7 +40,7 @@
           </span>
         </label>
         <label v-else>
-          {{ resource.displayname || resource.name || resource.displaytext || resource.hostname || resource.username || resource.ipaddress || $route.params.id }}
+          {{ resource.displayname || resource.name || resource.displaytext || resource.hostname || resource.username || resource.ipaddress || resource.osname || resource.osdisplayname || $route.params.id }}
         </label>
       </span>
       <span v-else>
diff --git a/ui/src/components/widgets/Console.vue b/ui/src/components/widgets/Console.vue
index 03a37a2..ae0a034 100644
--- a/ui/src/components/widgets/Console.vue
+++ b/ui/src/components/widgets/Console.vue
@@ -17,10 +17,11 @@
 
 <template>
   <a
-    v-if="['vm', 'systemvm', 'router', 'ilbvm'].includes($route.meta.name) && 'listVirtualMachines' in $store.getters.apis && 'createConsoleEndpoint' in $store.getters.apis"
+    v-if="['vm', 'systemvm', 'router', 'ilbvm', 'vnfapp'].includes($route.meta.name) && 'listVirtualMachines' in $store.getters.apis && 'createConsoleEndpoint' in $store.getters.apis"
     @click="consoleUrl">
     <a-button style="margin-left: 5px" shape="circle" type="dashed" :size="size" :disabled="['Stopped', 'Error', 'Destroyed'].includes(resource.state) || resource.hostcontrolstate === 'Offline'" >
-      <code-outlined />
+      <code-outlined v-if="!copyUrlToClipboard"/>
+      <copy-outlined v-else />
     </a-button>
   </a>
 </template>
@@ -39,7 +40,8 @@
     size: {
       type: String,
       default: 'small'
-    }
+    },
+    copyUrlToClipboard: Boolean
   },
   data () {
     return {
@@ -53,7 +55,21 @@
       api('createConsoleEndpoint', params).then(json => {
         this.url = (json && json.createconsoleendpointresponse) ? json.createconsoleendpointresponse.consoleendpoint.url : '#/exception/404'
         if (json.createconsoleendpointresponse.consoleendpoint.success) {
-          window.open(this.url, '_blank')
+          if (this.copyUrlToClipboard) {
+            this.$message.success({
+              content: this.$t('label.copied.clipboard')
+            })
+            const hiddenElement = document.createElement('textarea')
+            hiddenElement.value = this.url
+            document.body.appendChild(hiddenElement)
+            hiddenElement.focus()
+            hiddenElement.select()
+
+            document.execCommand('copy')
+            document.body.removeChild(hiddenElement)
+          } else {
+            window.open(this.url, '_blank')
+          }
         } else {
           this.$notification.error({
             message: this.$t('error.execute.api.failed') + ' ' + 'createConsoleEndpoint',
diff --git a/ui/src/components/widgets/OsLogo.vue b/ui/src/components/widgets/OsLogo.vue
index b15aba9..570cb40 100644
--- a/ui/src/components/widgets/OsLogo.vue
+++ b/ui/src/components/widgets/OsLogo.vue
@@ -24,29 +24,15 @@
       :icon="['fab', logo]"
       :size="size"
       :style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#666' }]"
-      v-if="logo !== 'debian'" />
-    <debian-icon
-      v-else-if="logo === 'debian'"
-      :width="size === '4x' ? 56 : 16"
-      :height="size === '4x' ? 56 : 16"
-      :style="{
-        height: size === '4x' ? '56px' : '16px',
-        width: size === '4x' ? '56px' : '16px',
-        marginBottom: '-4px',
-        background: $store.getters.darkMode ? 'rgba(255, 255, 255, 0.65)' : ''
-      }" />
+      />
   </a-tooltip>
 </template>
 
 <script>
 import { api } from '@/api'
-import DebianIcon from '@/assets/icons/debian.svg?inline'
 
 export default {
   name: 'OsLogo',
-  components: {
-    DebianIcon
-  },
   props: {
     osId: {
       type: String,
diff --git a/ui/src/components/widgets/Status.vue b/ui/src/components/widgets/Status.vue
index 6243882..6575d40 100644
--- a/ui/src/components/widgets/Status.vue
+++ b/ui/src/components/widgets/Status.vue
@@ -16,14 +16,16 @@
 // under the License.
 
 <template>
-  <a-tooltip placement="bottom" :title="getTooltip(text)">
-    <a-badge
-      :style="getStyle()"
-      :title="text"
-      :color="getStatusColor(text)"
-      :status="getBadgeStatus(text)"
-      :text="getText()" />
-  </a-tooltip>
+  <div style="display: inline-flex;">
+    <a-tooltip placement="bottom" :title="getTooltip(text)">
+      <a-badge
+        :style="getStyle()"
+        :title="text"
+        :color="getStatusColor(text)"
+        :status="getBadgeStatus(text)"
+        :text="getText()" />
+    </a-tooltip>
+  </div>
 </template>
 
 <script>
diff --git a/ui/src/components/widgets/TooltipLabel.vue b/ui/src/components/widgets/TooltipLabel.vue
index 2874b1e..c596bbf 100644
--- a/ui/src/components/widgets/TooltipLabel.vue
+++ b/ui/src/components/widgets/TooltipLabel.vue
@@ -54,5 +54,6 @@
 <style scoped lang="scss">
   .tooltip-icon {
     color: rgba(0,0,0,.45);
+    margin-left: 2px;
   }
 </style>
diff --git a/ui/src/config/router.js b/ui/src/config/router.js
index 502a0246..c3b69bd 100644
--- a/ui/src/config/router.js
+++ b/ui/src/config/router.js
@@ -302,6 +302,15 @@
     component: () => import('@/views/dashboard/VerifyTwoFa')
   },
   {
+    path: '/verifyOauth',
+    name: 'VerifyOauth',
+    meta: {
+      title: 'label.oauth.verification',
+      hidden: true
+    },
+    component: () => import('@/views/dashboard/VerifyOauth')
+  },
+  {
     path: '/setup2FA',
     name: 'SetupTwoFaAtLogin',
     meta: {
diff --git a/ui/src/config/section/account.js b/ui/src/config/section/account.js
index f7af6f0..7164175 100644
--- a/ui/src/config/section/account.js
+++ b/ui/src/config/section/account.js
@@ -199,9 +199,8 @@
       label: 'label.action.delete.account',
       message: 'message.delete.account',
       dataView: true,
-      show: (record, store) => {
-        return ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) && !record.isdefault &&
-          !(record.domain === 'ROOT' && record.name === 'admin' && record.accounttype === 1)
+      disabled: (record, store) => {
+        return record.id !== 'undefined' && store.userInfo.accountid === record.id
       },
       groupAction: true,
       popup: true,
diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js
index 9bf8c94..db17b20 100644
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@ -16,7 +16,6 @@
 // under the License.
 
 import { shallowRef, defineAsyncComponent } from 'vue'
-import kubernetes from '@/assets/icons/kubernetes.svg?inline'
 import store from '@/store'
 
 export default {
@@ -27,7 +26,7 @@
     {
       name: 'vm',
       title: 'label.instances',
-      icon: 'desktop-outlined',
+      icon: 'cloud-server-outlined',
       docHelp: 'adminguide/virtual_machines.html',
       permission: ['listVirtualMachinesMetrics'],
       resourceType: 'UserVm',
@@ -36,6 +35,7 @@
         if (store.getters.metrics) {
           params = { details: 'servoff,tmpl,iso,nics,backoff,stats' }
         }
+        params.isvnf = false
         return params
       },
       filters: () => {
@@ -61,16 +61,18 @@
         if (store.getters.metrics) {
           fields.push(...metricsFields)
         }
-
         if (store.getters.userInfo.roletype === 'Admin') {
           fields.splice(2, 0, 'instancename')
-          fields.push('account')
           fields.push('hostname')
+          fields.push('account')
         } else if (store.getters.userInfo.roletype === 'DomainAdmin') {
           fields.push('account')
         } else {
           fields.push('serviceofferingname')
         }
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
         fields.push('zonename')
         return fields
       },
@@ -162,33 +164,10 @@
           label: 'label.reinstall.vm',
           message: 'message.reinstall.vm',
           dataView: true,
-          args: ['virtualmachineid', 'templateid'],
-          filters: (record) => {
-            var filters = {}
-            var filterParams = {}
-            filterParams.hypervisortype = record.hypervisor
-            filterParams.zoneid = record.zoneid
-            filters.templateid = filterParams
-            return filters
-          },
+          popup: true,
           show: (record) => { return ['Running', 'Stopped'].includes(record.state) },
-          mapping: {
-            virtualmachineid: {
-              value: (record) => { return record.id }
-            }
-          },
           disabled: (record) => { return record.hostcontrolstate === 'Offline' },
-          successMethod: (obj, result) => {
-            const vm = result.jobresult.virtualmachine || {}
-            if (result.jobstatus === 1 && vm.password) {
-              const name = vm.displayname || vm.name || vm.id
-              obj.$notification.success({
-                message: `${obj.$t('label.reinstall.vm')}: ` + name,
-                description: `${obj.$t('label.password.reset.confirm')}: ` + vm.password,
-                duration: 0
-              })
-            }
-          }
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ReinstallVm.vue')))
         },
         {
           api: 'createVMSnapshot',
@@ -256,7 +235,7 @@
         {
           api: 'createBackupSchedule',
           icon: 'schedule-outlined',
-          label: 'Configure Backup Schedule',
+          label: 'label.backup.configure.schedule',
           docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
           dataView: true,
           popup: true,
@@ -369,8 +348,15 @@
           label: 'label.action.reset.password',
           message: 'message.action.instance.reset.password',
           dataView: true,
+          args: ['password'],
           show: (record) => { return ['Stopped'].includes(record.state) && record.passwordenabled },
-          response: (result) => { return result.virtualmachine && result.virtualmachine.password ? `The password of VM <b>${result.virtualmachine.displayname}</b> is <b>${result.virtualmachine.password}</b>` : null }
+          response: (result) => {
+            return {
+              message: result.virtualmachine && result.virtualmachine.password ? `The password of VM <b>${result.virtualmachine.displayname}</b> is <b>${result.virtualmachine.password}</b>` : null,
+              copybuttontext: result.virtualmachine.password ? 'label.copy.password' : null,
+              copytext: result.virtualmachine.password ? result.virtualmachine.password : null
+            }
+          }
         },
         {
           api: 'resetSSHKeyForVirtualMachine',
@@ -417,7 +403,7 @@
           label: 'label.action.unmanage.virtualmachine',
           message: 'message.action.unmanage.virtualmachine',
           dataView: true,
-          show: (record) => { return ['Running', 'Stopped'].includes(record.state) && record.hypervisor === 'VMware' }
+          show: (record) => { return ['Running', 'Stopped'].includes(record.state) && ['VMware', 'KVM'].includes(record.hypervisor) }
         },
         {
           api: 'expungeVirtualMachine',
@@ -448,23 +434,112 @@
       ]
     },
     {
+      name: 'vmsnapshot',
+      title: 'label.vm.snapshots',
+      icon: 'camera-outlined',
+      docHelp: 'adminguide/storage.html#working-with-volume-snapshots',
+      permission: ['listVMSnapshot'],
+      resourceType: 'VMSnapshot',
+      columns: () => {
+        const fields = ['displayname', 'state', 'name', 'type', 'current', 'parentName', 'created']
+        if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
+          fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
+          fields.push('domain')
+        } else if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
+        return fields
+      },
+      details: ['name', 'id', 'displayname', 'description', 'type', 'current', 'parentName', 'virtualmachineid', 'account', 'domain', 'created'],
+      searchFilters: ['name', 'domainid', 'account', 'tags'],
+      tabs: [
+        {
+          name: 'details',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+        },
+        {
+          name: 'events',
+          resourceType: 'VmSnapshot',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
+          name: 'comments',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
+        }
+      ],
+      actions: [
+        {
+          api: 'createSnapshotFromVMSnapshot',
+          icon: 'camera-outlined',
+          label: 'label.action.create.snapshot.from.vmsnapshot',
+          message: 'message.action.create.snapshot.from.vmsnapshot',
+          dataView: true,
+          popup: true,
+          show: (record) => { return (record.state === 'Ready' && record.hypervisor === 'KVM') },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateSnapshotFromVMSnapshot.vue')))
+        },
+        {
+          api: 'revertToVMSnapshot',
+          icon: 'sync-outlined',
+          label: 'label.action.vmsnapshot.revert',
+          message: 'label.action.vmsnapshot.revert',
+          dataView: true,
+          show: (record) => { return record.state === 'Ready' },
+          args: ['vmsnapshotid'],
+          mapping: {
+            vmsnapshotid: {
+              value: (record) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'deleteVMSnapshot',
+          icon: 'delete-outlined',
+          label: 'label.action.vmsnapshot.delete',
+          message: 'message.action.vmsnapshot.delete',
+          dataView: true,
+          show: (record) => { return ['Ready', 'Expunging', 'Error'].includes(record.state) },
+          args: ['vmsnapshotid'],
+          mapping: {
+            vmsnapshotid: {
+              value: (record) => { return record.id }
+            }
+          },
+          groupAction: true,
+          popup: true,
+          groupMap: (selection) => { return selection.map(x => { return { vmsnapshotid: x } }) }
+        }
+      ]
+    },
+    {
       name: 'kubernetes',
       title: 'label.kubernetes',
-      icon: shallowRef(kubernetes),
+      icon: ['fa-solid', 'fa-dharmachakra'],
       docHelp: 'plugins/cloudstack-kubernetes-service.html',
       permission: ['listKubernetesClusters'],
       columns: (store) => {
-        var fields = ['name', 'state', 'size', 'cpunumber', 'memory', 'kubernetesversionname']
+        var fields = ['name', 'state', 'clustertype', 'size', 'cpunumber', 'memory', 'kubernetesversionname']
         if (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) {
           fields.push('account')
         }
+        if (store.listAllProjects) {
+          fields.push('project')
+        }
         if (store.apis.scaleKubernetesCluster.params.filter(x => x.name === 'autoscalingenabled').length > 0) {
           fields.splice(2, 0, 'autoscalingenabled')
         }
         fields.push('zonename')
         return fields
       },
-      details: ['name', 'description', 'zonename', 'kubernetesversionname', 'autoscalingenabled', 'minsize', 'maxsize', 'size', 'controlnodes', 'cpunumber', 'memory', 'keypair', 'associatednetworkname', 'account', 'domain', 'zonename', 'created'],
+      filters: () => {
+        const filters = ['cloud.managed', 'external.managed']
+        return filters
+      },
+      details: ['name', 'description', 'zonename', 'kubernetesversionname', 'autoscalingenabled', 'minsize', 'maxsize', 'size', 'controlnodes', 'cpunumber', 'memory', 'keypair', 'associatednetworkname', 'account', 'domain', 'zonename', 'clustertype', 'created'],
       tabs: [{
         name: 'k8s',
         component: shallowRef(defineAsyncComponent(() => import('@/views/compute/KubernetesServiceTab.vue')))
@@ -487,7 +562,7 @@
           message: 'message.kubernetes.cluster.start',
           docHelp: 'plugins/cloudstack-kubernetes-service.html#starting-a-stopped-kubernetes-cluster',
           dataView: true,
-          show: (record) => { return ['Stopped'].includes(record.state) },
+          show: (record) => { return ['Stopped'].includes(record.state) && record.clustertype === 'CloudManaged' },
           groupAction: true,
           popup: true,
           groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
@@ -499,7 +574,7 @@
           message: 'message.kubernetes.cluster.stop',
           docHelp: 'plugins/cloudstack-kubernetes-service.html#stopping-kubernetes-cluster',
           dataView: true,
-          show: (record) => { return !['Stopped', 'Destroyed', 'Destroying'].includes(record.state) },
+          show: (record) => { return !['Stopped', 'Destroyed', 'Destroying'].includes(record.state) && record.clustertype === 'CloudManaged' },
           groupAction: true,
           popup: true,
           groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
@@ -511,7 +586,7 @@
           message: 'message.kubernetes.cluster.scale',
           docHelp: 'plugins/cloudstack-kubernetes-service.html#scaling-kubernetes-cluster',
           dataView: true,
-          show: (record) => { return ['Created', 'Running', 'Stopped'].includes(record.state) },
+          show: (record) => { return ['Created', 'Running', 'Stopped'].includes(record.state) && record.clustertype === 'CloudManaged' },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ScaleKubernetesCluster.vue')))
         },
@@ -522,7 +597,7 @@
           message: 'message.kubernetes.cluster.upgrade',
           docHelp: 'plugins/cloudstack-kubernetes-service.html#upgrading-kubernetes-cluster',
           dataView: true,
-          show: (record) => { return ['Created', 'Running'].includes(record.state) },
+          show: (record) => { return ['Created', 'Running'].includes(record.state) && record.clustertype === 'CloudManaged' },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => import('@/views/compute/UpgradeKubernetesCluster.vue')))
         },
@@ -536,18 +611,28 @@
           show: (record) => { return !['Destroyed', 'Destroying'].includes(record.state) },
           groupAction: true,
           popup: true,
-          groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
+          args: (record, store, group) => {
+            return (['Admin'].includes(store.userInfo.roletype) || store.features.allowuserexpungerecovervm)
+              ? ['cleanup', 'expunge'] : ['cleanup']
+          },
+          groupMap: (selection, values) => { return selection.map(x => { return { id: x, expunge: values.expunge, cleanup: values.cleanup } }) }
         }
       ]
     },
     {
       name: 'autoscalevmgroup',
       title: 'label.autoscale.vm.groups',
-      icon: 'ordered-list-outlined',
+      icon: 'fullscreen-outlined',
       docHelp: 'adminguide/autoscale_without_netscaler.html',
       resourceType: 'AutoScaleVmGroup',
       permission: ['listAutoScaleVmGroups'],
-      columns: ['name', 'state', 'associatednetworkname', 'publicip', 'publicport', 'privateport', 'minmembers', 'maxmembers', 'availablevirtualmachinecount', 'account'],
+      columns: (store) => {
+        var fields = ['name', 'state', 'associatednetworkname', 'publicip', 'publicport', 'privateport', 'minmembers', 'maxmembers', 'availablevirtualmachinecount', 'account']
+        if (store.listAllProjects) {
+          fields.push('project')
+        }
+        return fields
+      },
       details: ['name', 'id', 'account', 'domain', 'associatednetworkname', 'associatednetworkid', 'lbruleid', 'lbprovider', 'publicip', 'publicipid', 'publicport', 'privateport', 'minmembers', 'maxmembers', 'availablevirtualmachinecount', 'interval', 'state', 'created'],
       related: [{
         name: 'vm',
@@ -651,7 +736,15 @@
       docHelp: 'adminguide/virtual_machines.html#changing-the-vm-name-os-or-group',
       resourceType: 'VMInstanceGroup',
       permission: ['listInstanceGroups'],
-      columns: ['name', 'account', 'domain'],
+
+      columns: (store) => {
+        var fields = ['name', 'account']
+        if (store.listAllProjects) {
+          fields.push('project')
+        }
+        fields.push('domain')
+        return fields
+      },
       details: ['name', 'id', 'account', 'domain', 'created'],
       related: [{
         name: 'vm',
@@ -705,7 +798,12 @@
         var fields = ['name', 'fingerprint']
         if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
           fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
           fields.push('domain')
+        } else if (store.getters.listAllProjects) {
+          fields.push('project')
         }
         return fields
       },
@@ -783,7 +881,12 @@
         var fields = ['name', 'id']
         if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
           fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
           fields.push('domain')
+        } else if (store.getters.listAllProjects) {
+          fields.push('project')
         }
         return fields
       },
@@ -855,7 +958,12 @@
         var fields = ['name', 'type', 'description']
         if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
           fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
           fields.push('domain')
+        } else if (store.getters.listAllProjects) {
+          fields.push('project')
         }
         return fields
       },
@@ -865,6 +973,18 @@
         title: 'label.instances',
         param: 'affinitygroupid'
       }],
+      tabs: [
+        {
+          name: 'details',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+        },
+        {
+          name: 'events',
+          resourceType: 'AffinityGroup',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        }
+      ],
       actions: [
         {
           api: 'createAffinityGroup',
diff --git a/ui/src/config/section/config.js b/ui/src/config/section/config.js
index c7e0972..6fa33e3 100644
--- a/ui/src/config/section/config.js
+++ b/ui/src/config/section/config.js
@@ -25,6 +25,7 @@
       name: 'globalsetting',
       title: 'label.global.settings',
       icon: 'setting-outlined',
+      docHelp: 'adminguide/index.html#tuning',
       permission: ['listConfigurations'],
       listView: true,
       popup: true,
@@ -34,6 +35,7 @@
       name: 'ldapsetting',
       title: 'label.ldap.configuration',
       icon: 'team-outlined',
+      docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication',
       permission: ['listLdapConfigurations'],
       columns: ['hostname', 'port', 'domainid'],
       details: ['hostname', 'port', 'domainid'],
@@ -69,9 +71,52 @@
       ]
     },
     {
+      name: 'oauthsetting',
+      title: 'label.oauth.configuration',
+      icon: 'login-outlined',
+      docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication',
+      permission: ['listOauthProvider'],
+      columns: ['provider', 'enabled', 'description', 'clientid', 'secretkey', 'redirecturi'],
+      details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi'],
+      actions: [
+        {
+          api: 'registerOauthProvider',
+          icon: 'plus-outlined',
+          label: 'label.register.oauth',
+          listView: true,
+          dataView: false,
+          args: [
+            'provider', 'description', 'clientid', 'redirecturi', 'secretkey'
+          ],
+          mapping: {
+            provider: {
+              options: ['google', 'github']
+            }
+          }
+        },
+        {
+          api: 'updateOauthProvider',
+          icon: 'edit-outlined',
+          label: 'label.edit',
+          dataView: true,
+          popup: true,
+          args: ['description', 'clientid', 'redirecturi', 'secretkey', 'enabled']
+        },
+        {
+          api: 'deleteOauthProvider',
+          icon: 'delete-outlined',
+          label: 'label.action.delete.oauth.provider',
+          message: 'message.action.delete.guest.os',
+          dataView: true,
+          popup: true
+        }
+      ]
+    },
+    {
       name: 'hypervisorcapability',
       title: 'label.hypervisor.capabilities',
       icon: 'database-outlined',
+      docHelp: 'adminguide/hosts.html?highlight=Hypervisor%20capabilities#hypervisor-capabilities',
       permission: ['listHypervisorCapabilities'],
       columns: ['hypervisor', 'hypervisorversion', 'maxguestslimit', 'maxhostspercluster'],
       details: ['hypervisor', 'hypervisorversion', 'maxguestslimit', 'maxdatavolumeslimit', 'maxhostspercluster', 'securitygroupenabled', 'storagemotionenabled'],
@@ -84,6 +129,102 @@
           args: ['maxguestslimit']
         }
       ]
+    },
+    {
+      name: 'guestos',
+      title: 'label.guest.os',
+      docHelp: 'adminguide/guest_os.html#guest-os',
+      icon: 'laptop-outlined',
+      permission: ['listOsTypes', 'listOsCategories'],
+      columns: ['name', 'oscategoryname', 'isuserdefined'],
+      details: ['name', 'oscategoryname', 'isuserdefined'],
+      related: [{
+        name: 'guestoshypervisormapping',
+        title: 'label.guest.os.hypervisor.mappings',
+        param: 'ostypeid'
+      }],
+      actions: [
+        {
+          api: 'addGuestOs',
+          icon: 'plus-outlined',
+          label: 'label.add.guest.os',
+          listView: true,
+          dataView: false,
+          args: ['osdisplayname', 'oscategoryid'],
+          mapping: {
+            oscategoryid: {
+              api: 'listOsCategories',
+              params: (record) => { return { oscategoryid: record.id } }
+            }
+          }
+        },
+        {
+          api: 'updateGuestOs',
+          icon: 'edit-outlined',
+          label: 'label.edit',
+          dataView: true,
+          popup: true,
+          args: ['osdisplayname']
+        },
+        {
+          api: 'addGuestOsMapping',
+          icon: 'link-outlined',
+          label: 'label.add.guest.os.hypervisor.mapping',
+          dataView: true,
+          popup: true,
+          args: ['ostypeid', 'hypervisor', 'hypervisorversion', 'osnameforhypervisor', 'osmappingcheckenabled', 'forced'],
+          mapping: {
+            ostypeid: {
+              value: (record) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'removeGuestOs',
+          icon: 'delete-outlined',
+          label: 'label.action.delete.guest.os',
+          message: 'message.action.delete.guest.os',
+          dataView: true,
+          popup: true
+        }
+      ]
+    },
+    {
+      name: 'guestoshypervisormapping',
+      title: 'label.guest.os.hypervisor.mappings',
+      docHelp: 'adminguide/guest_os.html#guest-os-hypervisor-mapping',
+      icon: 'api-outlined',
+      permission: ['listGuestOsMapping'],
+      columns: ['hypervisor', 'hypervisorversion', 'osdisplayname', 'osnameforhypervisor'],
+      details: ['hypervisor', 'hypervisorversion', 'osdisplayname', 'osnameforhypervisor', 'isuserdefined'],
+      filters: ['all', 'kvm', 'vmware', 'xenserver', 'lxc', 'ovm3'],
+      searchFilters: ['osdisplayname', 'osnameforhypervisor', 'hypervisor', 'hypervisorversion'],
+      actions: [
+        {
+          api: 'addGuestOsMapping',
+          icon: 'plus-outlined',
+          label: 'label.add.guest.os.hypervisor.mapping',
+          listView: true,
+          dataView: false,
+          args: ['ostypeid', 'hypervisor', 'hypervisorversion', 'osnameforhypervisor', 'osmappingcheckenabled', 'forced']
+        },
+        {
+          api: 'updateGuestOsMapping',
+          icon: 'edit-outlined',
+          label: 'label.edit',
+          dataView: true,
+          popup: true,
+          args: ['osnameforhypervisor', 'osmappingcheckenabled']
+        },
+        {
+          api: 'removeGuestOsMapping',
+          icon: 'delete-outlined',
+          label: 'label.action.delete.guest.os.hypervisor.mapping',
+          message: 'message.action.delete.guest.os.hypervisor.mapping',
+          dataView: true,
+          popup: true
+        }
+      ]
     }
   ]
 }
diff --git a/ui/src/config/section/event.js b/ui/src/config/section/event.js
index 4673a64..5ab87e8 100644
--- a/ui/src/config/section/event.js
+++ b/ui/src/config/section/event.js
@@ -15,13 +15,22 @@
 // specific language governing permissions and limitations
 // under the License.
 
+import store from '@/store'
+
 export default {
   name: 'event',
   title: 'label.events',
   icon: 'ScheduleOutlined',
   docHelp: 'adminguide/events.html',
   permission: ['listEvents'],
-  columns: ['level', 'type', 'state', 'description', 'resource', 'username', 'account', 'domain', 'created'],
+  columns: () => {
+    var fields = ['level', 'type', 'state', 'description', 'resource', 'username', 'account']
+    if (store.getters.listAllProjects) {
+      fields.push('project')
+    }
+    fields.push(...['domain', 'created'])
+    return fields
+  },
   details: ['username', 'id', 'description', 'resourcetype', 'resourceid', 'state', 'level', 'type', 'account', 'domain', 'created'],
   searchFilters: ['level', 'domainid', 'account', 'keyword', 'resourcetype'],
   related: [{
@@ -29,6 +38,9 @@
     title: 'label.event.timeline',
     param: 'startid'
   }],
+  filters: () => {
+    return ['active', 'archived']
+  },
   actions: [
     {
       api: 'archiveEvents',
@@ -45,6 +57,12 @@
         ids: {
           value: (record) => { return record.id }
         }
+      },
+      show: (record) => {
+        return !(record.archived)
+      },
+      groupShow: (selectedItems) => {
+        return selectedItems.filter(x => { return !(x.archived) }).length > 0
       }
     },
     {
diff --git a/ui/src/config/section/image.js b/ui/src/config/section/image.js
index c41ab93..7a5d52d 100644
--- a/ui/src/config/section/image.js
+++ b/ui/src/config/section/image.js
@@ -16,7 +16,6 @@
 // under the License.
 
 import { shallowRef, defineAsyncComponent } from 'vue'
-import kubernetes from '@/assets/icons/kubernetes.svg?inline'
 import store from '@/store'
 
 export default {
@@ -48,21 +47,32 @@
           fields.push('size')
           fields.push('account')
         }
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
         if (['Admin'].includes(store.getters.userInfo.roletype)) {
+          fields.push('templatetype')
           fields.push('order')
         }
         return fields
       },
       details: () => {
         var fields = ['name', 'id', 'displaytext', 'checksum', 'hypervisor', 'format', 'ostypename', 'size', 'physicalsize', 'isready', 'passwordenabled',
-          'crossZones', 'directdownload', 'deployasis', 'ispublic', 'isfeatured', 'isextractable', 'isdynamicallyscalable', 'crosszones', 'type',
+          'crossZones', 'templatetype', 'directdownload', 'deployasis', 'ispublic', 'isfeatured', 'isextractable', 'isdynamicallyscalable', 'crosszones', 'type',
           'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy']
         if (['Admin'].includes(store.getters.userInfo.roletype)) {
-          fields.push('templatetype', 'url')
+          fields.push('url')
         }
         return fields
       },
-      searchFilters: ['name', 'zoneid', 'tags'],
+      searchFilters: () => {
+        var filters = ['name', 'zoneid', 'tags']
+        if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
+          filters.push('storageid')
+          filters.push('imagestoreid')
+        }
+        return filters
+      },
       related: [{
         name: 'vm',
         title: 'label.instances',
@@ -77,6 +87,10 @@
       }, {
         name: 'settings',
         component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailSettings')))
+      }, {
+        name: 'vnf.settings',
+        component: shallowRef(defineAsyncComponent(() => import('@/views/image/TemplateVnfSettings.vue'))),
+        show: (record) => { return record.templatetype === 'VNF' }
       },
       {
         name: 'events',
@@ -102,6 +116,7 @@
           api: 'registerTemplate',
           icon: 'cloud-upload-outlined',
           label: 'label.upload.template.from.local',
+          show: () => { return 'getUploadParamsForTemplate' in store.getters.apis },
           docHelp: 'adminguide/templates.html#uploading-templates-and-isos-from-a-local-computer',
           listView: true,
           popup: true,
@@ -208,13 +223,23 @@
           fields.push('size')
           fields.push('account')
         }
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
         if (['Admin'].includes(store.getters.userInfo.roletype)) {
           fields.push('order')
         }
         return fields
       },
       details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'bootable', 'isready', 'directdownload', 'isextractable', 'ispublic', 'isfeatured', 'crosszones', 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url'],
-      searchFilters: ['name', 'zoneid', 'tags'],
+      searchFilters: () => {
+        var filters = ['name', 'zoneid', 'tags']
+        if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
+          filters.push('storageid')
+          filters.push('imagestoreid')
+        }
+        return filters
+      },
       related: [{
         name: 'vm',
         title: 'label.instances',
@@ -251,6 +276,7 @@
           api: 'registerIso',
           icon: 'cloud-upload-outlined',
           label: 'label.upload.iso.from.local',
+          show: () => { return 'getUploadParamsForIso' in store.getters.apis },
           docHelp: 'adminguide/templates.html#id10',
           listView: true,
           popup: true,
@@ -338,7 +364,7 @@
     {
       name: 'kubernetesiso',
       title: 'label.kubernetes.isos',
-      icon: shallowRef(kubernetes),
+      icon: ['fa-solid', 'fa-dharmachakra'],
       docHelp: 'plugins/cloudstack-kubernetes-service.html#kubernetes-supported-versions',
       permission: ['listKubernetesSupportedVersions'],
       columns: ['name', 'state', 'semanticversion', 'isostate', 'mincpunumber', 'minmemory', 'zonename'],
diff --git a/ui/src/config/section/infra.js b/ui/src/config/section/infra.js
index 41b08fc..5b3b38a 100644
--- a/ui/src/config/section/infra.js
+++ b/ui/src/config/section/infra.js
@@ -23,6 +23,7 @@
 import hosts from '@/config/section/infra/hosts'
 import primaryStorages from '@/config/section/infra/primaryStorages'
 import secondaryStorages from '@/config/section/infra/secondaryStorages'
+import objectStorages from '@/config/section/infra/objectStorages'
 import systemVms from '@/config/section/infra/systemVms'
 import routers from '@/config/section/infra/routers'
 import ilbvms from '@/config/section/infra/ilbvms'
@@ -49,6 +50,7 @@
     hosts,
     primaryStorages,
     secondaryStorages,
+    objectStorages,
     systemVms,
     routers,
     ilbvms,
@@ -77,6 +79,7 @@
       permission: ['listAlerts'],
       columns: ['name', 'description', 'type', 'sent'],
       details: ['name', 'id', 'type', 'sent', 'description'],
+      searchFilters: ['type'],
       actions: [
         {
           api: 'archiveAlerts',
diff --git a/ui/src/config/section/infra/clusters.js b/ui/src/config/section/infra/clusters.js
index 904938c..ab8bea6 100644
--- a/ui/src/config/section/infra/clusters.js
+++ b/ui/src/config/section/infra/clusters.js
@@ -22,10 +22,11 @@
   name: 'cluster',
   title: 'label.clusters',
   icon: 'cluster-outlined',
+  docHelp: 'conceptsandterminology/concepts.html#about-clusters',
   permission: ['listClustersMetrics'],
   columns: () => {
     const fields = ['name', 'state', 'allocationstate', 'clustertype', 'hypervisortype', 'hosts']
-    const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal']
+    const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal', 'drsimbalance']
     if (store.getters.metrics) {
       fields.push(...metricsFields)
     }
@@ -33,7 +34,7 @@
     fields.push('zonename')
     return fields
   },
-  details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 'hypervisortype', 'podname', 'zonename'],
+  details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 'hypervisortype', 'podname', 'zonename', 'drsimbalance'],
   related: [{
     name: 'host',
     title: 'label.hosts',
@@ -54,8 +55,17 @@
     name: 'settings',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
   }, {
+    name: 'drs',
+    component: shallowRef(defineAsyncComponent(() => import('@/views/infra/ClusterDRSTab.vue')))
+  }, {
     name: 'comments',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
+  },
+  {
+    name: 'events',
+    resourceType: 'Cluster',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+    show: () => { return 'listEvents' in store.getters.apis }
   }],
   actions: [
     {
@@ -113,6 +123,16 @@
       show: (record) => { return record.managedstate === 'Managed' }
     },
     {
+      api: 'executeDRS',
+      icon: 'gold-outlined',
+      label: 'label.action.drs.cluster',
+      message: 'message.action.drs.cluster',
+      dataView: true,
+      defaultArgs: { iterations: null },
+      args: ['iterations'],
+      show: (record) => { return record.managedstate === 'Managed' }
+    },
+    {
       api: 'enableOutOfBandManagementForCluster',
       icon: 'plus-circle-outlined',
       label: 'label.outofbandmanagement.enable',
diff --git a/ui/src/config/section/infra/hosts.js b/ui/src/config/section/infra/hosts.js
index 62fd6ea..d92f4af 100644
--- a/ui/src/config/section/infra/hosts.js
+++ b/ui/src/config/section/infra/hosts.js
@@ -21,7 +21,8 @@
 export default {
   name: 'host',
   title: 'label.hosts',
-  icon: 'desktop-outlined',
+  icon: 'database-outlined',
+  docHelp: 'conceptsandterminology/concepts.html#about-hosts',
   permission: ['listHostsMetrics'],
   resourceType: 'Host',
   filters: () => {
@@ -44,6 +45,11 @@
     name: 'details',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
   }, {
+    name: 'events',
+    resourceType: 'Host',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+    show: () => { return 'listEvents' in store.getters.apis }
+  }, {
     name: 'comments',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
   }],
@@ -67,7 +73,7 @@
       icon: 'edit-outlined',
       label: 'label.edit',
       dataView: true,
-      args: ['name', 'hosttags', 'oscategoryid'],
+      args: ['name', 'hosttags', 'istagarule', 'oscategoryid'],
       mapping: {
         oscategoryid: {
           api: 'listOsCategories'
@@ -80,7 +86,9 @@
       label: 'label.action.secure.host',
       message: 'message.action.secure.host',
       dataView: true,
-      show: (record) => { return record.hypervisor === 'KVM' },
+      show: (record) => {
+        return record.hypervisor === 'KVM' || record.hypervisor === store.getters.customHypervisorName
+      },
       args: ['hostid'],
       mapping: {
         hostid: {
@@ -102,8 +110,9 @@
       label: 'label.disable.host',
       message: 'message.confirm.disable.host',
       dataView: true,
-      defaultArgs: { allocationstate: 'Disable' },
-      show: (record) => { return record.resourcestate === 'Enabled' }
+      show: (record) => { return record.resourcestate === 'Enabled' },
+      popup: true,
+      component: shallowRef(defineAsyncComponent(() => import('@/views/infra/HostEnableDisable')))
     },
     {
       api: 'updateHost',
@@ -111,8 +120,9 @@
       label: 'label.enable.host',
       message: 'message.confirm.enable.host',
       dataView: true,
-      defaultArgs: { allocationstate: 'Enable' },
-      show: (record) => { return record.resourcestate === 'Disabled' }
+      show: (record) => { return record.resourcestate === 'Disabled' },
+      popup: true,
+      component: shallowRef(defineAsyncComponent(() => import('@/views/infra/HostEnableDisable')))
     },
     {
       api: 'prepareHostForMaintenance',
diff --git a/ui/src/config/section/infra/ilbvms.js b/ui/src/config/section/infra/ilbvms.js
index beba33e..5ab9b3e 100644
--- a/ui/src/config/section/infra/ilbvms.js
+++ b/ui/src/config/section/infra/ilbvms.js
@@ -16,14 +16,26 @@
 // under the License.
 
 import { shallowRef, defineAsyncComponent } from 'vue'
+import store from '@/store'
+
 export default {
   name: 'ilbvm',
   title: 'label.internal.lb',
   icon: 'share-alt-outlined',
+  docHelp: 'adminguide/networking_and_traffic.html#creating-an-internal-lb-rule',
   permission: ['listInternalLoadBalancerVMs'],
   params: { projectid: '-1' },
   columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'version', 'softwareversion', 'hostname', 'account', 'zonename', 'requiresupgrade'],
   details: ['name', 'id', 'version', 'softwareversion', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created', 'hostcontrolstate'],
+  tabs: [{
+    name: 'details',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+  }, {
+    name: 'events',
+    resourceType: 'InternalLbVm',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+    show: () => { return 'listEvents' in store.getters.apis }
+  }],
   actions: [
     {
       api: 'startInternalLoadBalancerVM',
diff --git a/ui/src/config/section/infra/managementServers.js b/ui/src/config/section/infra/managementServers.js
index 6059af9..d1dfa6d 100644
--- a/ui/src/config/section/infra/managementServers.js
+++ b/ui/src/config/section/infra/managementServers.js
@@ -22,7 +22,9 @@
   name: 'managementserver',
   title: 'label.management.servers',
   icon: 'CloudServerOutlined',
+  docHelp: 'conceptsandterminology/concepts.html#management-server-overview',
   permission: ['listManagementServersMetrics'],
+  resourceType: 'ManagementServer',
   columns: () => {
     const fields = ['name', 'state', 'version']
     const metricsFields = ['collectiontime', 'availableprocessors', 'cpuload', 'heapmemoryused', 'agentcount']
@@ -36,6 +38,53 @@
     {
       name: 'details',
       component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+    },
+    {
+      name: 'pending.jobs',
+      component: shallowRef(defineAsyncComponent(() => import('@/views/infra/AsyncJobsTab.vue')))
+    },
+    {
+      name: 'comments',
+      component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
+    }
+  ],
+  actions: [
+    {
+      api: 'prepareForShutdown',
+      icon: 'exclamation-circle-outlined',
+      label: 'label.prepare.for.shutdown',
+      message: 'message.prepare.for.shutdown',
+      dataView: true,
+      popup: true,
+      confirmationText: 'SHUTDOWN',
+      show: (record, store) => { return record.state === 'Up' },
+      component: shallowRef(defineAsyncComponent(() => import('@/views/infra/Confirmation.vue')))
+    },
+    {
+      api: 'triggerShutdown',
+      icon: 'poweroff-outlined',
+      label: 'label.trigger.shutdown',
+      message: 'message.trigger.shutdown',
+      dataView: true,
+      popup: true,
+      confirmationText: 'SHUTDOWN',
+      show: (record, store) => { return ['Up', 'PreparingToShutDown', 'ReadyToShutDown'].includes(record.state) },
+      component: shallowRef(defineAsyncComponent(() => import('@/views/infra/Confirmation.vue')))
+    },
+    {
+      api: 'cancelShutdown',
+      icon: 'close-circle-outlined',
+      label: 'label.cancel.shutdown',
+      message: 'message.cancel.shutdown',
+      docHelp: 'installguide/configuration.html#adding-a-zone',
+      dataView: true,
+      popup: true,
+      show: (record, store) => { return ['PreparingToShutDown', 'ReadyToShutDown', 'ShuttingDown'].includes(record.state) },
+      mapping: {
+        managementserverid: {
+          value: (record, params) => { return record.id }
+        }
+      }
     }
   ]
 }
diff --git a/ui/src/config/section/infra/objectStorages.js b/ui/src/config/section/infra/objectStorages.js
new file mode 100644
index 0000000..821c1b2
--- /dev/null
+++ b/ui/src/config/section/infra/objectStorages.js
@@ -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.
+
+import { shallowRef, defineAsyncComponent } from 'vue'
+import store from '@/store'
+
+export default {
+  name: 'objectstore',
+  title: 'label.object.storage',
+  icon: 'gold-outlined',
+  docHelp: 'adminguide/storage.html#object-storage',
+  permission: ['listObjectStoragePools'],
+  columns: () => {
+    var fields = ['name', 'url', 'providername']
+    return fields
+  },
+  details: () => {
+    var fields = ['name', 'id', 'url', 'providername']
+    return fields
+  },
+  resourceType: 'ObjectStorage',
+  tabs: [{
+    name: 'details',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+  }, {
+    name: 'events',
+    resourceType: 'ObjectStore',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+    show: () => { return 'listEvents' in store.getters.apis }
+  }, {
+    name: 'comments',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
+  }],
+  actions: [
+    {
+      api: 'addObjectStoragePool',
+      icon: 'plus-outlined',
+      docHelp: 'installguide/configuration.html#add-object-storage',
+      label: 'label.add.object.storage',
+      listView: true,
+      popup: true,
+      component: shallowRef(defineAsyncComponent(() => import('@/views/infra/AddObjectStorage.vue')))
+    },
+    {
+      api: 'updateObjectStoragePool',
+      icon: 'edit-outlined',
+      label: 'label.action.update.object.storage',
+      args: ['name', 'url'],
+      dataView: true
+    },
+    {
+      api: 'deleteObjectStoragePool',
+      icon: 'delete-outlined',
+      label: 'label.action.delete.object.storage',
+      message: 'message.action.delete.object.storage',
+      dataView: true,
+      displayName: (record) => { return record.name || record.displayName || record.id }
+    }
+  ]
+}
diff --git a/ui/src/config/section/infra/pods.js b/ui/src/config/section/infra/pods.js
index a462478..eff62e0 100644
--- a/ui/src/config/section/infra/pods.js
+++ b/ui/src/config/section/infra/pods.js
@@ -22,6 +22,7 @@
   name: 'pod',
   title: 'label.pods',
   icon: 'appstore-outlined',
+  docHelp: 'conceptsandterminology/concepts.html#about-pods',
   permission: ['listPods'],
   columns: ['name', 'allocationstate', 'gateway', 'netmask', 'zonename'],
   details: ['name', 'id', 'allocationstate', 'netmask', 'gateway', 'zonename'],
diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js
index 4537b99..f222ede 100644
--- a/ui/src/config/section/infra/primaryStorages.js
+++ b/ui/src/config/section/infra/primaryStorages.js
@@ -21,7 +21,7 @@
 export default {
   name: 'storagepool',
   title: 'label.primary.storage',
-  icon: 'database-outlined',
+  icon: 'hdd-outlined',
   docHelp: 'adminguide/storage.html#primary-storage',
   permission: ['listStoragePoolsMetrics'],
   columns: () => {
@@ -39,6 +39,16 @@
     name: 'volume',
     title: 'label.volumes',
     param: 'storageid'
+  },
+  {
+    name: 'template',
+    title: 'label.templates',
+    param: 'storageid'
+  },
+  {
+    name: 'iso',
+    title: 'label.isos',
+    param: 'storageid'
   }],
   resourceType: 'PrimaryStorage',
   filters: () => {
@@ -52,6 +62,10 @@
     name: 'settings',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
   }, {
+    name: 'browser',
+    resourceType: 'PrimaryStorage',
+    component: shallowRef(defineAsyncComponent(() => import('@/views/infra/StorageBrowser.vue')))
+  }, {
     name: 'events',
     resourceType: 'StoragePool',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
@@ -75,7 +89,7 @@
       icon: 'edit-outlined',
       label: 'label.edit',
       dataView: true,
-      args: ['name', 'tags', 'capacitybytes', 'capacityiops']
+      args: ['name', 'tags', 'istagarule', 'capacitybytes', 'capacityiops']
     },
     {
       api: 'updateStoragePool',
diff --git a/ui/src/config/section/infra/secondaryStorages.js b/ui/src/config/section/infra/secondaryStorages.js
index 93c54dc..774c233 100644
--- a/ui/src/config/section/infra/secondaryStorages.js
+++ b/ui/src/config/section/infra/secondaryStorages.js
@@ -42,6 +42,21 @@
     return fields
   },
   resourceType: 'SecondaryStorage',
+  related: [{
+    name: 'template',
+    title: 'label.templates',
+    param: 'imagestoreid'
+  },
+  {
+    name: 'iso',
+    title: 'label.isos',
+    param: 'imagestoreid'
+  },
+  {
+    name: 'snapshot',
+    title: 'label.snapshots',
+    param: 'imagestoreid'
+  }],
   tabs: [{
     name: 'details',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
@@ -49,6 +64,10 @@
     name: 'settings',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/SettingsTab.vue')))
   }, {
+    name: 'browser',
+    resourceType: 'ImageStore',
+    component: shallowRef(defineAsyncComponent(() => import('@/views/infra/StorageBrowser.vue')))
+  }, {
     name: 'events',
     resourceType: 'ImageStore',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
diff --git a/ui/src/config/section/infra/zones.js b/ui/src/config/section/infra/zones.js
index cc9fa16..b2768f7 100644
--- a/ui/src/config/section/infra/zones.js
+++ b/ui/src/config/section/infra/zones.js
@@ -22,6 +22,7 @@
   name: 'zone',
   title: 'label.zones',
   icon: 'global-outlined',
+  docHelp: 'conceptsandterminology/concepts.html#about-zones',
   permission: ['listZonesMetrics'],
   columns: () => {
     const fields = ['name', 'allocationstate', 'type', 'networktype', 'clusters']
diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js
index f923271..3d5241f 100644
--- a/ui/src/config/section/network.js
+++ b/ui/src/config/section/network.js
@@ -30,23 +30,30 @@
       name: 'guestnetwork',
       title: 'label.guest.networks',
       icon: 'apartment-outlined',
+      docHelp: 'adminguide/networking_and_traffic.html#adding-an-additional-guest-network',
       permission: ['listNetworks'],
       resourceType: 'Network',
       columns: () => {
-        var fields = ['name', 'state', 'type', 'vpcname', 'cidr', 'ip6cidr', 'broadcasturi', 'account', 'domain', 'zonename']
+        var fields = ['name', 'state', 'type', 'vpcname', 'cidr', 'ip6cidr', 'broadcasturi', 'domainpath']
         if (!isAdmin()) {
           fields = fields.filter(function (e) { return e !== 'broadcasturi' })
         }
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        } else {
+          fields.push('account')
+        }
+        fields.push('zonename')
         return fields
       },
       details: () => {
-        var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domain', 'associatednetwork', 'associatednetworkid', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu']
+        var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu']
         if (!isAdmin()) {
           fields = fields.filter(function (e) { return e !== 'broadcasturi' })
         }
         return fields
       },
-      filters: ['all', 'account', 'domain', 'shared'],
+      filters: ['all', 'account', 'domainpath', 'shared'],
       searchFilters: ['keyword', 'zoneid', 'domainid', 'account', 'type', 'tags'],
       related: [{
         name: 'vm',
@@ -59,7 +66,7 @@
       }, {
         name: 'egress.rules',
         component: shallowRef(defineAsyncComponent(() => import('@/views/network/EgressRulesTab.vue'))),
-        show: (record, route, user) => { return record.type === 'Isolated' && !('vpcid' in record) && 'listEgressFirewallRules' in store.getters.apis && (['Admin', 'DomainAdmin'].includes(user.roletype) || record.account === user.account || record.projectid) }
+        show: (record, route, user) => { return record.type === 'Isolated' && !('vpcname' in record) && 'listEgressFirewallRules' in store.getters.apis && (['Admin', 'DomainAdmin'].includes(user.roletype) || record.account === user.account || record.projectid) }
       }, {
         name: 'ip.v6.firewall',
         component: shallowRef(defineAsyncComponent(() => import('@/views/network/Ipv6FirewallRulesTab.vue'))),
@@ -67,12 +74,16 @@
       }, {
         name: (record) => { return record.type === 'Shared' ? 'ip.addresses' : 'public.ip.addresses' },
         component: shallowRef(defineAsyncComponent(() => import('@/views/network/IpAddressesTab.vue'))),
-        show: (record, route, user) => { return 'listPublicIpAddresses' in store.getters.apis && (record.type === 'Shared' || (record.type === 'Isolated' && !('vpcid' in record) && (['Admin', 'DomainAdmin'].includes(user.roletype) || record.account === user.account || record.projectid))) }
+        show: (record, route, user) => { return 'listPublicIpAddresses' in store.getters.apis && (record.type === 'Shared' || (record.type === 'Isolated' && !('vpcname' in record) && (['Admin', 'DomainAdmin'].includes(user.roletype) || record.account === user.account || record.projectid))) }
       }, {
         name: 'virtual.routers',
         component: shallowRef(defineAsyncComponent(() => import('@/views/network/RoutersTab.vue'))),
         show: (record) => { return (record.type === 'Isolated' || record.type === 'Shared') && 'listRouters' in store.getters.apis && isAdmin() }
       }, {
+        name: 'vnf.appliances',
+        component: shallowRef(defineAsyncComponent(() => import('@/views/network/VnfAppliancesTab.vue'))),
+        show: () => { return 'deployVnfAppliance' in store.getters.apis }
+      }, {
         name: 'guest.ip.range',
         component: shallowRef(defineAsyncComponent(() => import('@/views/network/GuestIpRanges.vue'))),
         show: (record) => { return 'listVlanIpRanges' in store.getters.apis && (record.type === 'Shared' || (record.service && record.service.filter(x => x.name === 'SourceNat').count === 0)) }
@@ -129,6 +140,7 @@
           icon: 'edit-outlined',
           label: 'label.update.network',
           dataView: true,
+          disabled: (record, user) => { return (record.account !== user.userInfo.account && !['Admin', 'DomainAdmin'].includes(user.userInfo.roletype)) },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => import('@/views/network/UpdateNetwork.vue')))
         },
@@ -138,6 +150,7 @@
           label: 'label.restart.network',
           message: 'message.restart.network',
           dataView: true,
+          disabled: (record, user) => { return (record.account !== user.userInfo.account && !['Admin', 'DomainAdmin'].includes(user.userInfo.roletype)) },
           args: (record, store, isGroupAction) => {
             var fields = []
             if (isGroupAction || record.vpcid == null) {
@@ -176,6 +189,7 @@
           label: 'label.action.delete.network',
           message: 'message.action.delete.network',
           dataView: true,
+          disabled: (record, user) => { return (record.account !== user.userInfo.account && !['Admin', 'DomainAdmin'].includes(user.userInfo.roletype)) },
           groupAction: true,
           popup: true,
           groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
@@ -189,7 +203,14 @@
       docHelp: 'adminguide/networking_and_traffic.html#configuring-a-virtual-private-cloud',
       permission: ['listVPCs'],
       resourceType: 'Vpc',
-      columns: ['name', 'state', 'displaytext', 'cidr', 'account', 'domain', 'zonename'],
+      columns: () => {
+        var fields = ['name', 'state', 'displaytext', 'cidr', 'account']
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
+        fields.push(...['domain', 'zonename'])
+        return fields
+      },
       details: ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu'],
       searchFilters: ['name', 'zoneid', 'domainid', 'account', 'tags'],
       related: [{
@@ -224,7 +245,7 @@
           icon: 'edit-outlined',
           label: 'label.edit',
           dataView: true,
-          args: ['name', 'displaytext', 'publicmtu']
+          args: ['name', 'displaytext', 'publicmtu', 'sourcenatipaddress']
         },
         {
           api: 'restartVPC',
@@ -313,13 +334,429 @@
       ]
     },
     {
+      name: 'vnfapp',
+      title: 'label.vnf.appliances',
+      icon: 'gateway-outlined',
+      permission: ['listVnfTemplates'],
+      resourceType: 'UserVm',
+      params: () => {
+        return { details: 'servoff,tmpl,nics', isvnf: true }
+      },
+      columns: () => {
+        const fields = ['name', 'state', 'ipaddress']
+        if (store.getters.userInfo.roletype === 'Admin') {
+          fields.splice(2, 0, 'instancename')
+          fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
+          fields.push('domain')
+          fields.push('hostname')
+        } else if (store.getters.userInfo.roletype === 'DomainAdmin') {
+          fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
+        } else {
+          fields.push('serviceofferingname')
+        }
+        fields.push('zonename')
+        return fields
+      },
+      searchFilters: ['name', 'zoneid', 'domainid', 'account', 'groupid', 'tags'],
+      details: () => {
+        var fields = ['name', 'displayname', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename',
+          'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'boottype', 'bootmode', 'account',
+          'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy', 'hostcontrolstate']
+        const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true)
+        if (!listZoneHaveSGEnabled || listZoneHaveSGEnabled.length === 0) {
+          return fields
+        }
+        fields.push('securitygroup')
+        return fields
+      },
+      tabs: [{
+        component: shallowRef(defineAsyncComponent(() => import('@/views/compute/InstanceTab.vue')))
+      }],
+      actions: [
+        {
+          api: 'deployVnfAppliance',
+          icon: 'plus-outlined',
+          label: 'label.vnf.appliance.add',
+          docHelp: 'adminguide/networking/vnf_templates_appliances.html#deploying-vnf-appliances',
+          listView: true,
+          component: () => import('@/views/compute/DeployVnfAppliance.vue')
+        },
+        {
+          api: 'updateVirtualMachine',
+          icon: 'edit-outlined',
+          label: 'label.vnf.app.action.edit',
+          docHelp: 'adminguide/virtual_machines.html#changing-the-vm-name-os-or-group',
+          dataView: true,
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/EditVM.vue')))
+        },
+        {
+          api: 'startVirtualMachine',
+          icon: 'caret-right-outlined',
+          label: 'label.vnf.app.action.start',
+          message: 'message.action.start.instance',
+          docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
+          dataView: true,
+          groupAction: true,
+          popup: true,
+          groupMap: (selection, values) => { return selection.map(x => { return { id: x, considerlasthost: values.considerlasthost } }) },
+          args: ['considerlasthost'],
+          show: (record) => { return ['Stopped'].includes(record.state) },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/StartVirtualMachine.vue')))
+        },
+        {
+          api: 'stopVirtualMachine',
+          icon: 'poweroff-outlined',
+          label: 'label.vnf.app.action.stop',
+          message: 'message.action.stop.instance',
+          docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
+          dataView: true,
+          groupAction: true,
+          groupMap: (selection, values) => { return selection.map(x => { return { id: x, forced: values.forced } }) },
+          args: ['forced'],
+          show: (record) => { return ['Running'].includes(record.state) }
+        },
+        {
+          api: 'rebootVirtualMachine',
+          icon: 'reload-outlined',
+          label: 'label.vnf.app.action.reboot',
+          message: 'message.action.reboot.instance',
+          docHelp: 'adminguide/virtual_machines.html#stopping-and-starting-vms',
+          dataView: true,
+          show: (record) => { return ['Running'].includes(record.state) },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' },
+          args: (record, store) => {
+            var fields = []
+            fields.push('forced')
+            if (record.hypervisor === 'VMware') {
+              if (store.apis.rebootVirtualMachine.params.filter(x => x.name === 'bootintosetup').length > 0) {
+                fields.push('bootintosetup')
+              }
+            }
+            return fields
+          },
+          groupAction: true,
+          popup: true,
+          groupMap: (selection, values) => { return selection.map(x => { return { id: x, forced: values.forced } }) }
+        },
+        {
+          api: 'restoreVirtualMachine',
+          icon: 'sync-outlined',
+          label: 'label.vnf.app.action.reinstall',
+          message: 'message.reinstall.vm',
+          dataView: true,
+          args: ['virtualmachineid', 'templateid'],
+          filters: (record) => {
+            var filters = {}
+            var filterParams = {}
+            filterParams.hypervisortype = record.hypervisor
+            filterParams.zoneid = record.zoneid
+            filters.templateid = filterParams
+            return filters
+          },
+          show: (record) => { return ['Running', 'Stopped'].includes(record.state) },
+          mapping: {
+            virtualmachineid: {
+              value: (record) => { return record.id }
+            }
+          },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' },
+          successMethod: (obj, result) => {
+            const vm = result.jobresult.virtualmachine || {}
+            if (result.jobstatus === 1 && vm.password) {
+              const name = vm.displayname || vm.name || vm.id
+              obj.$notification.success({
+                message: `${obj.$t('label.reinstall.vm')}: ` + name,
+                description: `${obj.$t('label.password.reset.confirm')}: ` + vm.password,
+                duration: 0
+              })
+            }
+          }
+        },
+        {
+          api: 'createVMSnapshot',
+          icon: 'camera-outlined',
+          label: 'label.action.vmsnapshot.create',
+          docHelp: 'adminguide/virtual_machines.html#virtual-machine-snapshots',
+          dataView: true,
+          args: ['virtualmachineid', 'name', 'description', 'snapshotmemory', 'quiescevm'],
+          show: (record) => {
+            return ((['Running'].includes(record.state) && record.hypervisor !== 'LXC') ||
+              (['Stopped'].includes(record.state) && ((record.hypervisor !== 'KVM' && record.hypervisor !== 'LXC') ||
+                (record.hypervisor === 'KVM' && record.pooltype === 'PowerFlex'))))
+          },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' && record.hypervisor === 'KVM' },
+          mapping: {
+            virtualmachineid: {
+              value: (record, params) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'createSnapshot',
+          icon: ['fas', 'camera-retro'],
+          label: 'label.action.vmstoragesnapshot.create',
+          docHelp: 'adminguide/virtual_machines.html#virtual-machine-snapshots',
+          dataView: true,
+          popup: true,
+          show: (record) => {
+            return ((['Running'].includes(record.state) && record.hypervisor !== 'LXC') ||
+              (['Stopped'].includes(record.state) && !['KVM', 'LXC'].includes(record.hypervisor)))
+          },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' && record.hypervisor === 'KVM' },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/CreateSnapshotWizard.vue')))
+        },
+        {
+          api: 'assignVirtualMachineToBackupOffering',
+          icon: 'folder-add-outlined',
+          label: 'label.backup.offering.assign',
+          message: 'label.backup.offering.assign',
+          docHelp: 'adminguide/virtual_machines.html#backup-offerings',
+          dataView: true,
+          args: ['virtualmachineid', 'backupofferingid'],
+          show: (record) => { return !record.backupofferingid },
+          mapping: {
+            virtualmachineid: {
+              value: (record, params) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'createBackup',
+          icon: 'cloud-upload-outlined',
+          label: 'label.create.backup',
+          message: 'message.backup.create',
+          docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
+          dataView: true,
+          args: ['virtualmachineid'],
+          show: (record) => { return record.backupofferingid },
+          mapping: {
+            virtualmachineid: {
+              value: (record, params) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'createBackupSchedule',
+          icon: 'schedule-outlined',
+          label: 'label.backup.configure.schedule',
+          docHelp: 'adminguide/virtual_machines.html#creating-vm-backups',
+          dataView: true,
+          popup: true,
+          show: (record) => { return record.backupofferingid },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/BackupScheduleWizard.vue'))),
+          mapping: {
+            virtualmachineid: {
+              value: (record, params) => { return record.id }
+            },
+            intervaltype: {
+              options: ['HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY']
+            }
+          }
+        },
+        {
+          api: 'removeVirtualMachineFromBackupOffering',
+          icon: 'scissor-outlined',
+          label: 'label.backup.offering.remove',
+          message: 'label.backup.offering.remove',
+          docHelp: 'adminguide/virtual_machines.html#restoring-vm-backups',
+          dataView: true,
+          args: ['virtualmachineid', 'forced'],
+          show: (record) => { return record.backupofferingid },
+          mapping: {
+            virtualmachineid: {
+              value: (record, params) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'attachIso',
+          icon: 'paper-clip-outlined',
+          label: 'label.action.attach.iso',
+          docHelp: 'adminguide/templates.html#attaching-an-iso-to-a-vm',
+          dataView: true,
+          popup: true,
+          show: (record) => { return ['Running', 'Stopped'].includes(record.state) && !record.isoid },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' || record.hostcontrolstate === 'Maintenance' },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AttachIso.vue')))
+        },
+        {
+          api: 'detachIso',
+          icon: 'link-outlined',
+          label: 'label.action.detach.iso',
+          message: 'message.detach.iso.confirm',
+          dataView: true,
+          args: (record, store) => {
+            var args = ['virtualmachineid']
+            if (record && record.hypervisor && record.hypervisor === 'VMware') {
+              args.push('forced')
+            }
+            return args
+          },
+          show: (record) => { return ['Running', 'Stopped'].includes(record.state) && 'isoid' in record && record.isoid },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' || record.hostcontrolstate === 'Maintenance' },
+          mapping: {
+            virtualmachineid: {
+              value: (record, params) => { return record.id }
+            }
+          }
+        },
+        {
+          api: 'updateVMAffinityGroup',
+          icon: 'swap-outlined',
+          label: 'label.change.affinity',
+          docHelp: 'adminguide/virtual_machines.html#change-affinity-group-for-an-existing-vm',
+          dataView: true,
+          args: ['affinitygroupids'],
+          show: (record) => { return ['Stopped'].includes(record.state) },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ChangeAffinity'))),
+          popup: true
+        },
+        {
+          api: 'scaleVirtualMachine',
+          icon: 'arrows-alt-outlined',
+          label: 'label.vnf.app.action.scale',
+          docHelp: 'adminguide/virtual_machines.html#how-to-dynamically-scale-cpu-and-ram',
+          dataView: true,
+          show: (record) => { return ['Stopped'].includes(record.state) || (['Running'].includes(record.state) && record.hypervisor !== 'LXC') },
+          disabled: (record) => { return record.state === 'Running' && !record.isdynamicallyscalable },
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ScaleVM.vue')))
+        },
+        {
+          api: 'migrateVirtualMachine',
+          icon: 'drag-outlined',
+          label: 'label.vnf.app.action.migrate.to.host',
+          docHelp: 'adminguide/virtual_machines.html#moving-vms-between-hosts-manual-live-migration',
+          dataView: true,
+          show: (record, store) => { return ['Running'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' },
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateWizard.vue')))
+        },
+        {
+          api: 'migrateVirtualMachine',
+          icon: 'drag-outlined',
+          label: 'label.vnf.app.action.migrate.to.ps',
+          message: 'message.migrate.instance.to.ps',
+          docHelp: 'adminguide/virtual_machines.html#moving-vms-between-hosts-manual-live-migration',
+          dataView: true,
+          show: (record, store) => { return ['Stopped'].includes(record.state) && ['Admin'].includes(store.userInfo.roletype) },
+          disabled: (record) => { return record.hostcontrolstate === 'Offline' },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/MigrateVMStorage'))),
+          popup: true
+        },
+        {
+          api: 'resetPasswordForVirtualMachine',
+          icon: 'key-outlined',
+          label: 'label.action.reset.password',
+          message: 'message.action.instance.reset.password',
+          dataView: true,
+          show: (record) => { return ['Stopped'].includes(record.state) && record.passwordenabled },
+          response: (result) => {
+            return {
+              message: result.virtualmachine && result.virtualmachine.password ? `The password of VM <b>${result.virtualmachine.displayname}</b> is <b>${result.virtualmachine.password}</b>` : null,
+              copybuttontext: result.virtualmachine.password ? 'label.copy.password' : null,
+              copytext: result.virtualmachine.password ? result.virtualmachine.password : null
+            }
+          }
+        },
+        {
+          api: 'resetSSHKeyForVirtualMachine',
+          icon: 'lock-outlined',
+          label: 'label.reset.ssh.key.pair',
+          message: 'message.desc.reset.ssh.key.pair',
+          docHelp: 'adminguide/virtual_machines.html#resetting-ssh-keys',
+          dataView: true,
+          show: (record) => { return ['Stopped'].includes(record.state) },
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ResetSshKeyPair')))
+        },
+        {
+          api: 'resetUserDataForVirtualMachine',
+          icon: 'solution-outlined',
+          label: 'label.reset.userdata.on.vm',
+          message: 'message.desc.reset.userdata',
+          docHelp: 'adminguide/virtual_machines.html#resetting-userdata',
+          dataView: true,
+          show: (record) => { return ['Stopped'].includes(record.state) },
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ResetUserData')))
+        },
+        {
+          api: 'assignVirtualMachine',
+          icon: 'user-add-outlined',
+          label: 'label.assign.instance.another',
+          dataView: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AssignInstance'))),
+          popup: true,
+          show: (record) => { return ['Stopped'].includes(record.state) }
+        },
+        {
+          api: 'recoverVirtualMachine',
+          icon: 'medicine-box-outlined',
+          label: 'label.vnf.app.action.recover',
+          message: 'message.recover.vm',
+          dataView: true,
+          show: (record, store) => { return ['Destroyed'].includes(record.state) && store.features.allowuserexpungerecovervm }
+        },
+        {
+          api: 'unmanageVirtualMachine',
+          icon: 'disconnect-outlined',
+          label: 'label.action.unmanage.virtualmachine',
+          message: 'message.action.unmanage.virtualmachine',
+          dataView: true,
+          show: (record) => { return ['Running', 'Stopped'].includes(record.state) && record.hypervisor === 'VMware' }
+        },
+        {
+          api: 'expungeVirtualMachine',
+          icon: 'delete-outlined',
+          label: 'label.vnf.app.action.expunge',
+          message: (record) => { return record.backupofferingid ? 'message.action.expunge.instance.with.backups' : 'message.action.expunge.instance' },
+          docHelp: 'adminguide/virtual_machines.html#deleting-vms',
+          dataView: true,
+          show: (record, store) => { return ['Destroyed', 'Expunging'].includes(record.state) && store.features.allowuserexpungerecovervm }
+        },
+        {
+          api: 'destroyVirtualMachine',
+          icon: 'delete-outlined',
+          label: 'label.vnf.app.action.destroy',
+          message: 'message.action.destroy.instance',
+          docHelp: 'adminguide/virtual_machines.html#deleting-vms',
+          dataView: true,
+          groupAction: true,
+          args: (record, store, group) => {
+            return (['Admin'].includes(store.userInfo.roletype) || store.features.allowuserexpungerecovervm)
+              ? ['expunge'] : []
+          },
+          popup: true,
+          groupMap: (selection, values) => { return selection.map(x => { return { id: x, expunge: values.expunge } }) },
+          show: (record) => { return ['Running', 'Stopped', 'Error'].includes(record.state) },
+          component: shallowRef(defineAsyncComponent(() => import('@/views/compute/DestroyVM.vue')))
+        }
+      ]
+    },
+    {
       name: 'publicip',
       title: 'label.public.ip.addresses',
       icon: 'environment-outlined',
       docHelp: 'adminguide/networking_and_traffic.html#reserving-public-ip-addresses-and-vlans-for-accounts',
       permission: ['listPublicIpAddresses'],
       resourceType: 'PublicIpAddress',
-      columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'domain', 'zonename'],
+      columns: () => {
+        var fields = ['ipaddress', 'state', 'associatednetworkname', 'vpcname', 'virtualmachinename', 'allocated', 'account']
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
+        fields.push(...['domain', 'zonename'])
+        return fields
+      },
       details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'domain', 'zonename'],
       filters: ['allocated', 'reserved', 'free'],
       component: shallowRef(() => import('@/views/network/PublicIpResource.vue')),
@@ -609,6 +1046,11 @@
         name: 'loadbalancerinstance',
         component: shallowRef(defineAsyncComponent(() => import('@/views/network/InternalLBAssignedVmTab.vue'))),
         show: () => true
+      }, {
+        name: 'events',
+        resourceType: 'LoadBalancerRule',
+        component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+        show: () => { return 'listEvents' in store.getters.apis }
       }],
       actions: [
         {
@@ -709,7 +1151,14 @@
       title: 'label.vpncustomergatewayid',
       icon: 'lock-outlined',
       permission: ['listVpnCustomerGateways'],
-      columns: ['name', 'gateway', 'cidrlist', 'ipsecpsk', 'account', 'domain'],
+      columns: () => {
+        var fields = ['name', 'gateway', 'cidrlist', 'ipsecpsk', 'account']
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
+        fields.push('domain')
+        return fields
+      },
       details: ['name', 'id', 'gateway', 'cidrlist', 'ipsecpsk', 'ikepolicy', 'ikelifetime', 'ikeversion', 'esppolicy', 'esplifetime', 'dpd', 'splitconnections', 'forceencap', 'account', 'domain'],
       searchFilters: ['keyword', 'domainid', 'account'],
       resourceType: 'VPNCustomerGateway',
@@ -719,6 +1168,12 @@
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
         },
         {
+          name: 'events',
+          resourceType: 'VpnCustomerGateway',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
           name: 'comments',
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
         }
@@ -905,6 +1360,7 @@
       name: 'guestvlans',
       title: 'label.guest.vlan',
       icon: 'folder-outlined',
+      docHelp: 'conceptsandterminology/network_setup.html#vlan-allocation-example',
       permission: ['listGuestVlans'],
       resourceType: 'GuestVlan',
       filters: ['allocatedonly', 'all'],
diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js
index 35acfbb..3e99f60 100644
--- a/ui/src/config/section/offering.js
+++ b/ui/src/config/section/offering.js
@@ -36,7 +36,8 @@
         }
         return params
       },
-      columns: ['name', 'displaytext', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
+      filters: ['active', 'inactive'],
+      columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
       details: () => {
         var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot']
         if (store.getters.apis.createServiceOffering &&
@@ -56,6 +57,12 @@
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
         },
         {
+          name: 'events',
+          resourceType: 'ServiceOffering',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
           name: 'comments',
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue'))),
           show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) }
@@ -89,15 +96,33 @@
         dataView: true,
         popup: true,
         component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
+      },
+      {
+        api: 'updateServiceOffering',
+        icon: 'play-circle-outlined',
+        label: 'label.action.enable.service.offering',
+        message: 'message.action.enable.service.offering',
+        dataView: true,
+        args: ['state'],
+        mapping: {
+          state: {
+            value: (record) => { return 'Active' }
+          }
+        },
+        groupAction: true,
+        popup: true,
+        show: (record) => { return record.state !== 'Active' },
+        groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Active' } }) }
       }, {
         api: 'deleteServiceOffering',
-        icon: 'delete-outlined',
-        label: 'label.action.delete.service.offering',
-        message: 'message.action.delete.service.offering',
+        icon: 'pause-circle-outlined',
+        label: 'label.action.disable.service.offering',
+        message: 'message.action.disable.service.offering',
         docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
         dataView: true,
         groupAction: true,
         popup: true,
+        show: (record) => { return record.state === 'Active' },
         groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
       }]
     },
@@ -108,8 +133,27 @@
       docHelp: 'adminguide/service_offerings.html#system-service-offerings',
       permission: ['listServiceOfferings', 'listInfrastructure'],
       params: { issystem: 'true', isrecursive: 'true' },
-      columns: ['name', 'systemvmtype', 'cpunumber', 'cpuspeed', 'memory', 'storagetype', 'order'],
-      details: ['name', 'id', 'displaytext', 'systemvmtype', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness'],
+      columns: ['name', 'state', 'systemvmtype', 'cpunumber', 'cpuspeed', 'memory', 'storagetype', 'order'],
+      filters: ['active', 'inactive'],
+      details: ['name', 'id', 'displaytext', 'systemvmtype', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'storagetags', 'hosttags', 'tags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness'],
+      resourceType: 'ServiceOffering',
+      tabs: [
+        {
+          name: 'details',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+        },
+        {
+          name: 'events',
+          resourceType: 'ServiceOffering',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
+          name: 'comments',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue'))),
+          show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) }
+        }
+      ],
       actions: [{
         api: 'createServiceOffering',
         icon: 'plus-outlined',
@@ -126,17 +170,35 @@
         dataView: true,
         params: { issystem: 'true' },
         docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
-        args: ['name', 'displaytext']
+        args: ['name', 'displaytext', 'storagetags', 'hosttags']
+      }, {
+        api: 'updateServiceOffering',
+        icon: 'play-circle-outlined',
+        label: 'label.action.enable.system.service.offering',
+        message: 'message.action.enable.system.service.offering',
+        dataView: true,
+        params: { issystem: 'true' },
+        args: ['state'],
+        mapping: {
+          state: {
+            value: (record) => { return 'Active' }
+          }
+        },
+        groupAction: true,
+        popup: true,
+        show: (record) => { return record.state !== 'Active' },
+        groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Active' } }) }
       }, {
         api: 'deleteServiceOffering',
-        icon: 'delete-outlined',
-        label: 'label.action.delete.system.service.offering',
-        message: 'message.action.delete.system.service.offering',
+        icon: 'pause-circle-outlined',
+        label: 'label.action.disable.system.service.offering',
+        message: 'message.action.disable.system.service.offering',
         docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
         dataView: true,
         params: { issystem: 'true' },
         groupAction: true,
         popup: true,
+        show: (record) => { return record.state === 'Active' },
         groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
       }]
     },
@@ -153,9 +215,10 @@
         }
         return params
       },
-      columns: ['name', 'displaytext', 'disksize', 'domain', 'zone', 'order'],
+      columns: ['name', 'displaytext', 'state', 'disksize', 'domain', 'zone', 'order'],
+      filters: ['active', 'inactive'],
       details: () => {
-        var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'disksizestrictness', 'iscustomizediops', 'tags', 'domain', 'zone', 'created', 'encrypt']
+        var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'disksizestrictness', 'iscustomizediops', 'diskIopsReadRate', 'diskIopsWriteRate', 'diskBytesReadRate', 'diskBytesWriteRate', 'miniops', 'maxiops', 'tags', 'domain', 'zone', 'created', 'encrypt']
         if (store.getters.apis.createDiskOffering &&
           store.getters.apis.createDiskOffering.params.filter(x => x.name === 'storagepolicy').length > 0) {
           fields.splice(6, 0, 'vspherestoragepolicy')
@@ -169,6 +232,12 @@
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
         },
         {
+          name: 'events',
+          resourceType: 'DiskOffering',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
           name: 'comments',
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue'))),
           show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) }
@@ -203,14 +272,32 @@
         popup: true,
         component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
       }, {
+        api: 'updateDiskOffering',
+        icon: 'play-circle-outlined',
+        label: 'label.action.enable.disk.offering',
+        message: 'message.action.enable.disk.offering',
+        dataView: true,
+        params: { issystem: 'true' },
+        args: ['state'],
+        mapping: {
+          state: {
+            value: (record) => { return 'Active' }
+          }
+        },
+        groupAction: true,
+        popup: true,
+        show: (record) => { return record.state !== 'Active' },
+        groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Active' } }) }
+      }, {
         api: 'deleteDiskOffering',
-        icon: 'delete-outlined',
-        label: 'label.action.delete.disk.offering',
-        message: 'message.action.delete.disk.offering',
+        icon: 'pause-circle-outlined',
+        label: 'label.action.disable.disk.offering',
+        message: 'message.action.disable.disk.offering',
         docHelp: 'adminguide/service_offerings.html#modifying-or-deleting-a-service-offering',
         dataView: true,
         groupAction: true,
         popup: true,
+        show: (record) => { return record.state === 'Active' },
         groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
       }]
     },
@@ -227,6 +314,18 @@
         title: 'label.instances',
         param: 'backupofferingid'
       }],
+      tabs: [
+        {
+          name: 'details',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+        },
+        {
+          name: 'events',
+          resourceType: 'BackupOffering',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        }
+      ],
       actions: [{
         api: 'importBackupOffering',
         icon: 'plus-outlined',
@@ -270,6 +369,12 @@
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
         },
         {
+          name: 'events',
+          resourceType: 'NetworkOffering',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
           name: 'comments',
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue'))),
           show: (record, route, user) => { return ['Admin', 'DomainAdmin'].includes(user.roletype) }
@@ -361,6 +466,18 @@
         title: 'label.vpc',
         param: 'vpcofferingid'
       }],
+      tabs: [
+        {
+          name: 'details',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+        },
+        {
+          name: 'events',
+          resourceType: 'VpcOffering',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        }
+      ],
       actions: [{
         api: 'createVPCOffering',
         icon: 'plus-outlined',
diff --git a/ui/src/config/section/project.js b/ui/src/config/section/project.js
index a5370e2..18354c3 100644
--- a/ui/src/config/section/project.js
+++ b/ui/src/config/section/project.js
@@ -101,7 +101,7 @@
       icon: 'edit-outlined',
       label: 'label.edit.project.details',
       dataView: true,
-      args: ['displaytext'],
+      args: ['name', 'displaytext'],
       show: (record, store) => {
         return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin
       }
diff --git a/ui/src/config/section/role.js b/ui/src/config/section/role.js
index a040615..3823d63 100644
--- a/ui/src/config/section/role.js
+++ b/ui/src/config/section/role.js
@@ -16,6 +16,8 @@
 // under the License.
 
 import { shallowRef, defineAsyncComponent } from 'vue'
+import store from '@/store'
+
 export default {
   name: 'role',
   title: 'label.roles',
@@ -23,13 +25,18 @@
   docHelp: 'adminguide/accounts.html#roles',
   permission: ['listRoles', 'listRolePermissions'],
   columns: ['name', 'type', 'description'],
-  details: ['name', 'id', 'type', 'description'],
+  details: ['name', 'id', 'type', 'description', 'ispublic'],
   tabs: [{
     name: 'details',
     component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
   }, {
     name: 'rules',
     component: shallowRef(defineAsyncComponent(() => import('@/views/iam/RolePermissionTab.vue')))
+  }, {
+    name: 'events',
+    resourceType: 'Role',
+    component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+    show: () => { return 'listEvents' in store.getters.apis }
   }],
   actions: [
     {
@@ -53,7 +60,7 @@
       icon: 'edit-outlined',
       label: 'label.edit.role',
       dataView: true,
-      args: ['name', 'description', 'type'],
+      args: (record) => record.isdefault ? ['ispublic'] : ['name', 'description', 'type', 'ispublic'],
       mapping: {
         type: {
           options: ['Admin', 'DomainAdmin', 'User']
diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js
index d73b989..ff0fd99 100644
--- a/ui/src/config/section/storage.js
+++ b/ui/src/config/section/storage.js
@@ -21,7 +21,7 @@
 export default {
   name: 'storage',
   title: 'label.storage',
-  icon: 'database-outlined',
+  icon: 'hdd-outlined',
   children: [
     {
       name: 'volume',
@@ -49,13 +49,15 @@
         if (store.getters.metrics) {
           fields.push(...metricsFields)
         }
-
         if (store.getters.userInfo.roletype === 'Admin') {
-          fields.push('account')
           fields.push('storage')
+          fields.push('account')
         } else if (store.getters.userInfo.roletype === 'DomainAdmin') {
           fields.push('account')
         }
+        if (store.getters.listAllProjects) {
+          fields.push('project')
+        }
         fields.push('zonename')
 
         return fields
@@ -88,7 +90,13 @@
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
         }
       ],
-      searchFilters: ['name', 'zoneid', 'domainid', 'account', 'state', 'tags'],
+      searchFilters: () => {
+        var filters = ['name', 'zoneid', 'domainid', 'account', 'state', 'tags']
+        if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
+          filters.push('storageid')
+        }
+        return filters
+      },
       actions: [
         {
           api: 'createVolume',
@@ -104,6 +112,7 @@
           icon: 'cloud-upload-outlined',
           docHelp: 'adminguide/storage.html#uploading-an-existing-volume-to-a-virtual-machine',
           label: 'label.upload.volume.from.local',
+          show: () => { return 'getUploadParamsForVolume' in store.getters.apis },
           listView: true,
           popup: true,
           component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UploadLocalVolume.vue')))
@@ -157,8 +166,8 @@
           dataView: true,
           show: (record, store) => {
             return record.state === 'Ready' && (record.hypervisor !== 'KVM' ||
-              record.hypervisor === 'KVM' && record.vmstate === 'Running' && store.features.kvmsnapshotenabled ||
-              record.hypervisor === 'KVM' && record.vmstate !== 'Running')
+                record.hypervisor === 'KVM' && record.vmstate === 'Running' && store.features.kvmsnapshotenabled ||
+                record.hypervisor === 'KVM' && record.vmstate !== 'Running')
           },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => import('@/views/storage/TakeSnapshot.vue')))
@@ -171,8 +180,8 @@
           dataView: true,
           show: (record, store) => {
             return record.state === 'Ready' && (record.hypervisor !== 'KVM' ||
-              record.hypervisor === 'KVM' && record.vmstate === 'Running' && store.features.kvmsnapshotenabled ||
-              record.hypervisor === 'KVM' && record.vmstate !== 'Running')
+                record.hypervisor === 'KVM' && record.vmstate === 'Running' && store.features.kvmsnapshotenabled ||
+                record.hypervisor === 'KVM' && record.vmstate !== 'Running')
           },
           popup: true,
           component: shallowRef(defineAsyncComponent(() => import('@/views/storage/RecurringSnapshotVolume.vue'))),
@@ -243,10 +252,17 @@
           dataView: true,
           show: (record) => {
             return !['Destroy', 'Destroyed', 'Expunging', 'Expunged', 'Migrating', 'Uploading', 'UploadError', 'Creating'].includes(record.state) &&
-            ((record.type === 'ROOT' && record.vmstate === 'Stopped') ||
-            (record.type !== 'ROOT' && !record.virtualmachineid && !['Allocated', 'Uploaded'].includes(record.state)))
+                ((record.type === 'ROOT' && record.vmstate === 'Stopped') ||
+                    (record.type !== 'ROOT' && !record.virtualmachineid && !['Allocated', 'Uploaded'].includes(record.state)))
           },
-          args: ['volumeid', 'name', 'displaytext', 'ostypeid', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled'],
+          args: (record, store) => {
+            var fields = ['volumeid', 'name', 'displaytext', 'ostypeid', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled']
+            if (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) {
+              fields.push('domainid')
+              fields.push('account')
+            }
+            return fields
+          },
           mapping: {
             volumeid: {
               value: (record) => { return record.id }
@@ -271,8 +287,8 @@
           dataView: true,
           show: (record, store) => {
             return ['Expunging', 'Expunged', 'UploadError'].includes(record.state) ||
-              ['Allocated', 'Uploaded'].includes(record.state) && record.type !== 'ROOT' && !record.virtualmachineid ||
-              ((['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) || store.features.allowuserexpungerecovervolume) && record.state === 'Destroy')
+                ['Allocated', 'Uploaded'].includes(record.state) && record.type !== 'ROOT' && !record.virtualmachineid ||
+                ((['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) || store.features.allowuserexpungerecovervolume) && record.state === 'Destroy')
           },
           groupAction: true,
           popup: true,
@@ -306,7 +322,12 @@
         var fields = ['name', 'state', 'volumename', 'intervaltype', 'physicalsize', 'created']
         if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
           fields.push('account')
+          if (store.getters.listAllProjects) {
+            fields.push('project')
+          }
           fields.push('domain')
+        } else if (store.getters.listAllProjects) {
+          fields.push('project')
         }
         fields.push('zonename')
         return fields
@@ -318,11 +339,28 @@
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
         },
         {
+          name: 'zones',
+          component: shallowRef(defineAsyncComponent(() => import('@/views/storage/SnapshotZones.vue')))
+        },
+        {
+          name: 'events',
+          resourceType: 'Snapshot',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        },
+        {
           name: 'comments',
           component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
         }
       ],
-      searchFilters: ['name', 'domainid', 'account', 'tags'],
+      searchFilters: () => {
+        var filters = ['name', 'domainid', 'account', 'tags', 'zoneid']
+        if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
+          filters.push('storageid')
+          filters.push('imagestoreid')
+        }
+        return filters
+      },
       actions: [
         {
           api: 'createTemplate',
@@ -330,12 +368,8 @@
           label: 'label.create.template',
           dataView: true,
           show: (record) => { return record.state === 'BackedUp' },
-          args: ['snapshotid', 'name', 'displaytext', 'ostypeid', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled'],
-          mapping: {
-            snapshotid: {
-              value: (record) => { return record.id }
-            }
-          }
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateTemplate.vue')))
         },
         {
           api: 'createVolume',
@@ -368,77 +402,6 @@
       ]
     },
     {
-      name: 'vmsnapshot',
-      title: 'label.vm.snapshots',
-      icon: 'camera-outlined',
-      docHelp: 'adminguide/storage.html#working-with-volume-snapshots',
-      permission: ['listVMSnapshot'],
-      resourceType: 'VMSnapshot',
-      columns: () => {
-        const fields = ['displayname', 'state', 'name', 'type', 'current', 'parentName', 'created']
-        if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
-          fields.push('account')
-          fields.push('domain')
-        }
-        return fields
-      },
-      details: ['name', 'id', 'displayname', 'description', 'type', 'current', 'parentName', 'virtualmachineid', 'account', 'domain', 'created'],
-      searchFilters: ['name', 'domainid', 'account', 'tags'],
-      tabs: [
-        {
-          name: 'details',
-          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
-        },
-        {
-          name: 'comments',
-          component: shallowRef(defineAsyncComponent(() => import('@/components/view/AnnotationsTab.vue')))
-        }
-      ],
-      actions: [
-        {
-          api: 'createSnapshotFromVMSnapshot',
-          icon: 'camera-outlined',
-          label: 'label.action.create.snapshot.from.vmsnapshot',
-          message: 'message.action.create.snapshot.from.vmsnapshot',
-          dataView: true,
-          popup: true,
-          show: (record) => { return (record.state === 'Ready' && record.hypervisor === 'KVM') },
-          component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateSnapshotFromVMSnapshot.vue')))
-        },
-        {
-          api: 'revertToVMSnapshot',
-          icon: 'sync-outlined',
-          label: 'label.action.vmsnapshot.revert',
-          message: 'label.action.vmsnapshot.revert',
-          dataView: true,
-          show: (record) => { return record.state === 'Ready' },
-          args: ['vmsnapshotid'],
-          mapping: {
-            vmsnapshotid: {
-              value: (record) => { return record.id }
-            }
-          }
-        },
-        {
-          api: 'deleteVMSnapshot',
-          icon: 'delete-outlined',
-          label: 'label.action.vmsnapshot.delete',
-          message: 'message.action.vmsnapshot.delete',
-          dataView: true,
-          show: (record) => { return ['Ready', 'Expunging', 'Error'].includes(record.state) },
-          args: ['vmsnapshotid'],
-          mapping: {
-            vmsnapshotid: {
-              value: (record) => { return record.id }
-            }
-          },
-          groupAction: true,
-          popup: true,
-          groupMap: (selection) => { return selection.map(x => { return { vmsnapshotid: x } }) }
-        }
-      ]
-    },
-    {
       name: 'backup',
       title: 'label.backup',
       icon: 'cloud-upload-outlined',
@@ -495,6 +458,65 @@
           args: ['forced']
         }
       ]
+    },
+    {
+      name: 'buckets',
+      title: 'label.buckets',
+      icon: 'funnel-plot-outlined',
+      permission: ['listBuckets'],
+      columns: ['name', 'state', 'objectstore', 'size', 'account'],
+      details: ['id', 'name', 'state', 'objectstore', 'size', 'url', 'accesskey', 'usersecretkey', 'account', 'domain', 'created', 'quota', 'encryption', 'versioning', 'objectlocking', 'policy'],
+      tabs: [
+        {
+          name: 'details',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+        },
+        {
+          name: 'browser',
+          resourceType: 'Bucket',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/ObjectStoreBrowser.vue'))),
+          show: (record) => { return record.provider !== 'Simulator' }
+
+        },
+        {
+          name: 'events',
+          resourceType: 'Bucket',
+          component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+          show: () => { return 'listEvents' in store.getters.apis }
+        }
+      ],
+      actions: [
+        {
+          api: 'createBucket',
+          icon: 'plus-outlined',
+          docHelp: 'installguide/configuration.html#create-bucket',
+          label: 'label.create.bucket',
+          listView: true,
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateBucket.vue')))
+        },
+        {
+          api: 'updateBucket',
+          icon: 'edit-outlined',
+          docHelp: 'adminguide/object_storage.html#update-bucket',
+          label: 'label.bucket.update',
+          dataView: true,
+          popup: true,
+          component: shallowRef(defineAsyncComponent(() => import('@/views/storage/UpdateBucket.vue'))),
+          show: (record) => { return record.state !== 'Destroyed' }
+        },
+        {
+          api: 'deleteBucket',
+          icon: 'delete-outlined',
+          label: 'label.bucket.delete',
+          message: 'message.bucket.delete',
+          dataView: true,
+          show: (record) => { return record.state !== 'Destroyed' },
+          groupAction: true,
+          popup: true,
+          groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
+        }
+      ]
     }
   ]
 }
diff --git a/ui/src/config/section/user.js b/ui/src/config/section/user.js
index 894e4a6..d4f4d70 100644
--- a/ui/src/config/section/user.js
+++ b/ui/src/config/section/user.js
@@ -25,7 +25,7 @@
   docHelp: 'adminguide/accounts.html#users',
   hidden: true,
   permission: ['listUsers'],
-  columns: ['username', 'state', 'firstname', 'lastname', 'email', 'account'],
+  columns: ['username', 'state', 'firstname', 'lastname', 'email', 'account', 'domain'],
   details: ['username', 'id', 'firstname', 'lastname', 'email', 'usersource', 'timezone', 'rolename', 'roletype', 'is2faenabled', 'account', 'domain', 'created'],
   tabs: [
     {
@@ -68,6 +68,7 @@
       api: 'registerUserKeys',
       icon: 'file-protect-outlined',
       label: 'label.action.generate.keys',
+      hoverLabel: 'label.action.generate.api.secret.keys',
       message: 'message.generate.keys',
       dataView: true
     },
@@ -144,9 +145,8 @@
       label: 'label.action.delete.user',
       message: 'message.delete.user',
       dataView: true,
-      show: (record, store) => {
-        return ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) && !record.isdefault &&
-          !(record.domain === 'ROOT' && record.account === 'admin' && record.accounttype === 1)
+      disabled: (record, store) => {
+        return record.id !== 'undefined' && store.userInfo.id === record.id
       }
     }
   ]
diff --git a/ui/src/core/ext.js b/ui/src/core/ext.js
index 3cea518..f6cfb35 100644
--- a/ui/src/core/ext.js
+++ b/ui/src/core/ext.js
@@ -22,11 +22,11 @@
 // import { fas } from '@fortawesome/free-solid-svg-icons'
 // import { far } from '@fortawesome/free-regular-svg-icons'
 
-import { faCentos, faUbuntu, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava } from '@fortawesome/free-brands-svg-icons'
-import { faCompactDisc, faCameraRetro } from '@fortawesome/free-solid-svg-icons'
+import { faCentos, faUbuntu, faDebian, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava } from '@fortawesome/free-brands-svg-icons'
+import { faCompactDisc, faCameraRetro, faDharmachakra } from '@fortawesome/free-solid-svg-icons'
 
-library.add(faCentos, faUbuntu, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava)
-library.add(faCompactDisc, faCameraRetro)
+library.add(faCentos, faUbuntu, faDebian, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava)
+library.add(faCompactDisc, faCameraRetro, faDharmachakra)
 
 export default {
   install: (app) => {
diff --git a/ui/src/core/lazy_lib/components_use.js b/ui/src/core/lazy_lib/components_use.js
index 3c61c02..98fc9e0 100644
--- a/ui/src/core/lazy_lib/components_use.js
+++ b/ui/src/core/lazy_lib/components_use.js
@@ -69,6 +69,9 @@
 import VueClipboard from 'vue3-clipboard'
 import VueCropper from 'vue-cropper'
 
+import cronAnt from '@vue-js-cron/ant'
+import '@vue-js-cron/ant/dist/ant.css'
+
 export default {
   install: (app) => {
     app.config.globalProperties.$confirm = Modal.confirm
@@ -78,6 +81,7 @@
     app.config.globalProperties.$error = Modal.error
     app.config.globalProperties.$warning = Modal.warning
 
+    app.use(cronAnt)
     app.use(VueClipboard, { autoSetContainer: true })
     app.use(VueCropper)
     app.use(ConfigProvider)
diff --git a/ui/src/core/lazy_lib/icons_use.js b/ui/src/core/lazy_lib/icons_use.js
index bbda90e..ea0abdc 100644
--- a/ui/src/core/lazy_lib/icons_use.js
+++ b/ui/src/core/lazy_lib/icons_use.js
@@ -16,6 +16,7 @@
 // under the License.
 
 import {
+  AimOutlined,
   ApartmentOutlined,
   ApiOutlined,
   AppstoreOutlined,
@@ -89,6 +90,7 @@
   FormOutlined,
   ForwardOutlined,
   FullscreenOutlined,
+  FunnelPlotOutlined,
   GatewayOutlined,
   GithubOutlined,
   GlobalOutlined,
@@ -120,6 +122,7 @@
   MoreOutlined,
   NotificationOutlined,
   NumberOutlined,
+  LaptopOutlined,
   OrderedListOutlined,
   PaperClipOutlined,
   PauseCircleOutlined,
@@ -172,6 +175,7 @@
 
 export default {
   install: (app) => {
+    app.component('AimOutlined', AimOutlined)
     app.component('ApartmentOutlined', ApartmentOutlined)
     app.component('ApiOutlined', ApiOutlined)
     app.component('AppstoreOutlined', AppstoreOutlined)
@@ -245,6 +249,7 @@
     app.component('FormOutlined', FormOutlined)
     app.component('ForwardOutlined', ForwardOutlined)
     app.component('FullscreenOutlined', FullscreenOutlined)
+    app.component('FunnelPlotOutlined', FunnelPlotOutlined)
     app.component('GatewayOutlined', GatewayOutlined)
     app.component('GithubOutlined', GithubOutlined)
     app.component('GlobalOutlined', GlobalOutlined)
@@ -276,6 +281,7 @@
     app.component('MoreOutlined', MoreOutlined)
     app.component('NotificationOutlined', NotificationOutlined)
     app.component('NumberOutlined', NumberOutlined)
+    app.component('LaptopOutlined', LaptopOutlined)
     app.component('OrderedListOutlined', OrderedListOutlined)
     app.component('PaperClipOutlined', PaperClipOutlined)
     app.component('PauseCircleOutlined', PauseCircleOutlined)
diff --git a/ui/src/layouts/UserLayout.vue b/ui/src/layouts/UserLayout.vue
index 24ed6b4..6c81c85 100644
--- a/ui/src/layouts/UserLayout.vue
+++ b/ui/src/layouts/UserLayout.vue
@@ -46,6 +46,7 @@
 <script>
 import RouteView from '@/layouts/RouteView'
 import { mixinDevice } from '@/utils/mixin.js'
+import notification from 'ant-design-vue/es/notification'
 
 export default {
   name: 'UserLayout',
@@ -90,7 +91,7 @@
   },
   methods: {
     onClearNotification () {
-      this.$notification.destroy()
+      notification.destroy()
       this.$store.commit('SET_COUNT_NOTIFY', 0)
     }
   }
diff --git a/ui/src/permission.js b/ui/src/permission.js
index ec6a816..6db690f 100644
--- a/ui/src/permission.js
+++ b/ui/src/permission.js
@@ -26,11 +26,11 @@
 import message from 'ant-design-vue/es/message'
 import notification from 'ant-design-vue/es/notification'
 import { setDocumentTitle } from '@/utils/domUtil'
-import { ACCESS_TOKEN, APIS, SERVER_MANAGER } from '@/store/mutation-types'
+import { ACCESS_TOKEN, APIS, SERVER_MANAGER, CURRENT_PROJECT } from '@/store/mutation-types'
 
 NProgress.configure({ showSpinner: false }) // NProgress Configuration
 
-const allowList = ['login'] // no redirect allowlist
+const allowList = ['login', 'VerifyOauth'] // no redirect allowlist
 
 router.beforeEach((to, from, next) => {
   // start progress bar
@@ -56,6 +56,14 @@
 
   const validLogin = vueProps.$localStorage.get(ACCESS_TOKEN) || Cookies.get('userid') || Cookies.get('userid', { path: '/client' })
   if (validLogin) {
+    var currentURL = new URL(window.location.href)
+    var urlParams = new URLSearchParams(currentURL.search)
+    var code = urlParams.get('code')
+    if (code != null) {
+      urlParams.delete('code')
+    }
+    currentURL.search = ''
+    window.history.replaceState(null, null, currentURL.toString())
     if (to.path === '/user/login') {
       next({ path: '/dashboard' })
       NProgress.done()
@@ -104,7 +112,8 @@
               } else {
                 next({ path: redirect })
               }
-              store.dispatch('ToggleTheme', 'light')
+              const project = vueProps.$localStorage.get(CURRENT_PROJECT)
+              store.dispatch('ToggleTheme', project.id === undefined ? 'light' : 'dark')
             })
           })
           .catch(() => {
@@ -133,7 +142,16 @@
       }
     }
   } else {
-    if (allowList.includes(to.name)) {
+    if (window.location.href.includes('verifyOauth') && to.name === undefined) {
+      currentURL = new URL(window.location.href)
+      urlParams = new URLSearchParams(currentURL.search)
+      code = urlParams.get('code')
+      urlParams.delete('verifyOauth')
+      urlParams.delete('state')
+      currentURL.search = '?code=' + code
+      window.history.replaceState(null, null, currentURL.toString())
+      next({ path: '/verifyOauth', query: { redirect: to.fullPath } })
+    } else if (allowList.includes(to.name)) {
       next()
     } else {
       next({ path: '/user/login', query: { redirect: to.fullPath } })
diff --git a/ui/src/store/getters.js b/ui/src/store/getters.js
index 0273fd0..67b168b 100644
--- a/ui/src/store/getters.js
+++ b/ui/src/store/getters.js
@@ -44,11 +44,14 @@
   countNotify: state => state.user.countNotify,
   customColumns: state => state.user.customColumns,
   logoutFlag: state => state.user.logoutFlag,
+  shutdownTriggered: state => state.user.shutdownTriggered,
   twoFaEnabled: state => state.user.twoFaEnabled,
   twoFaProvider: state => state.user.twoFaProvider,
   twoFaIssuer: state => state.user.twoFaIssuer,
   loginFlag: state => state.user.loginFlag,
-  allProjects: (state) => state.app.allProjects
+  allProjects: (state) => state.app.allProjects,
+  customHypervisorName: state => state.user.customHypervisorName,
+  readyForShutdownPollingJob: state => state.user.readyForShutdownPollingJob
 }
 
 export default getters
diff --git a/ui/src/store/modules/app.js b/ui/src/store/modules/app.js
index 806e11b..cf2b34e 100644
--- a/ui/src/store/modules/app.js
+++ b/ui/src/store/modules/app.js
@@ -127,6 +127,12 @@
     RELOAD_ALL_PROJECTS: (state, allProjects = []) => {
       vueProps.$localStorage.set(RELOAD_ALL_PROJECTS, allProjects)
       state.allProjects = allProjects
+    },
+    SET_SHUTDOWN_TRIGGERED: (state, shutdownTriggered) => {
+      state.shutdownTriggered = shutdownTriggered
+    },
+    SET_READY_FOR_SHUTDOWN_POLLING_JOB: (state, readyForShutdownPollingJob) => {
+      state.readyForShutdownPollingJob = readyForShutdownPollingJob
     }
   },
   actions: {
@@ -186,6 +192,12 @@
     },
     ReloadAllProjects ({ commit, allProjects }) {
       commit('RELOAD_ALL_PROJECTS', allProjects)
+    },
+    SetShutdownTriggered ({ commit }, bool) {
+      commit('SET_SHUTDOWN_TRIGGERED', bool)
+    },
+    SetReadyForShutdownPollingJob ({ commit }, job) {
+      commit('SET_READY_FOR_SHUTDOWN_POLLING_JOB', job)
     }
   }
 }
diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js
index 3994a3f..fb5b6ff 100644
--- a/ui/src/store/modules/user.js
+++ b/ui/src/store/modules/user.js
@@ -22,7 +22,7 @@
 import { vueProps } from '@/vue-app'
 import router from '@/router'
 import store from '@/store'
-import { login, logout, api } from '@/api'
+import { oauthlogin, login, logout, api } from '@/api'
 import { i18n } from '@/locales'
 
 import {
@@ -36,7 +36,9 @@
   HEADER_NOTICES,
   DOMAIN_STORE,
   DARK_MODE,
-  CUSTOM_COLUMNS
+  CUSTOM_COLUMNS,
+  OAUTH_DOMAIN,
+  OAUTH_PROVIDER
 } from '@/store/mutation-types'
 
 const user = {
@@ -61,9 +63,12 @@
     loginFlag: false,
     logoutFlag: false,
     customColumns: {},
+    shutdownTriggered: false,
     twoFaEnabled: false,
     twoFaProvider: '',
-    twoFaIssuer: ''
+    twoFaIssuer: '',
+    customHypervisorName: 'Custom',
+    readyForShutdownPollingJob: ''
   },
 
   mutations: {
@@ -133,6 +138,9 @@
       vueProps.$localStorage.set(CUSTOM_COLUMNS, customColumns)
       state.customColumns = customColumns
     },
+    SET_SHUTDOWN_TRIGGERED: (state, shutdownTriggered) => {
+      state.shutdownTriggered = shutdownTriggered
+    },
     SET_LOGOUT_FLAG: (state, flag) => {
       state.logoutFlag = flag
     },
@@ -147,6 +155,18 @@
     },
     SET_LOGIN_FLAG: (state, flag) => {
       state.loginFlag = flag
+    },
+    SET_CUSTOM_HYPERVISOR_NAME (state, name) {
+      state.customHypervisorName = name
+    },
+    SET_READY_FOR_SHUTDOWN_POLLING_JOB: (state, job) => {
+      state.readyForShutdownPollingJob = job
+    },
+    SET_DOMAIN_USED_TO_LOGIN: (state, domain) => {
+      vueProps.$localStorage.set(OAUTH_DOMAIN, domain)
+    },
+    SET_OAUTH_PROVIDER_USED_TO_LOGIN: (state, provider) => {
+      vueProps.$localStorage.set(OAUTH_PROVIDER, provider)
     }
   },
 
@@ -201,6 +221,53 @@
       })
     },
 
+    OauthLogin ({ commit }, userInfo) {
+      return new Promise((resolve, reject) => {
+        oauthlogin(userInfo).then(response => {
+          const result = response.loginresponse || {}
+          Cookies.set('account', result.account, { expires: 1 })
+          Cookies.set('domainid', result.domainid, { expires: 1 })
+          Cookies.set('role', result.type, { expires: 1 })
+          Cookies.set('timezone', result.timezone, { expires: 1 })
+          Cookies.set('timezoneoffset', result.timezoneoffset, { expires: 1 })
+          Cookies.set('userfullname', result.firstname + ' ' + result.lastname, { expires: 1 })
+          Cookies.set('userid', result.userid, { expires: 1 })
+          Cookies.set('username', result.username, { expires: 1 })
+          vueProps.$localStorage.set(ACCESS_TOKEN, result.sessionkey, 24 * 60 * 60 * 1000)
+          commit('SET_TOKEN', result.sessionkey)
+          commit('SET_TIMEZONE_OFFSET', result.timezoneoffset)
+
+          const cachedUseBrowserTimezone = vueProps.$localStorage.get(USE_BROWSER_TIMEZONE, false)
+          commit('SET_USE_BROWSER_TIMEZONE', cachedUseBrowserTimezone)
+          const darkMode = vueProps.$localStorage.get(DARK_MODE, false)
+          commit('SET_DARK_MODE', darkMode)
+          const cachedCustomColumns = vueProps.$localStorage.get(CUSTOM_COLUMNS, {})
+          commit('SET_CUSTOM_COLUMNS', cachedCustomColumns)
+
+          commit('SET_APIS', {})
+          commit('SET_NAME', '')
+          commit('SET_AVATAR', '')
+          commit('SET_INFO', {})
+          commit('SET_PROJECT', {})
+          commit('SET_HEADER_NOTICES', [])
+          commit('SET_FEATURES', {})
+          commit('SET_LDAP', {})
+          commit('SET_CLOUDIAN', {})
+          commit('SET_DOMAIN_STORE', {})
+          commit('SET_LOGOUT_FLAG', false)
+          commit('SET_2FA_ENABLED', (result.is2faenabled === 'true'))
+          commit('SET_2FA_PROVIDER', result.providerfor2fa)
+          commit('SET_2FA_ISSUER', result.issuerfor2fa)
+          commit('SET_LOGIN_FLAG', false)
+          notification.destroy()
+
+          resolve()
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+
     GetInfo ({ commit }, switchDomain) {
       return new Promise((resolve, reject) => {
         const cachedApis = switchDomain ? {} : vueProps.$localStorage.get(APIS, {})
@@ -278,6 +345,9 @@
           if (result && result.defaultuipagesize) {
             commit('SET_DEFAULT_LISTVIEW_PAGE_SIZE', result.defaultuipagesize)
           }
+          if (result && result.customhypervisordisplayname) {
+            commit('SET_CUSTOM_HYPERVISOR_NAME', result.customhypervisordisplayname)
+          }
         }).catch(error => {
           reject(error)
         })
@@ -391,6 +461,15 @@
         }).catch(error => {
           reject(error)
         })
+
+        api('listConfigurations', { name: 'hypervisor.custom.display.name' }).then(json => {
+          if (json.listconfigurationsresponse.configuration !== null) {
+            const config = json.listconfigurationsresponse.configuration[0]
+            commit('SET_CUSTOM_HYPERVISOR_NAME', config.value)
+          }
+        }).catch(error => {
+          reject(error)
+        })
       })
     },
     UpdateConfiguration ({ commit }) {
@@ -411,6 +490,9 @@
     },
     SetLoginFlag ({ commit }, loggedIn) {
       commit('SET_LOGIN_FLAG', loggedIn)
+    },
+    SetCustomHypervisorName ({ commit }, name) {
+      commit('SET_CUSTOM_HYPERVISOR_NAME', name)
     }
   }
 }
diff --git a/ui/src/store/mutation-types.js b/ui/src/store/mutation-types.js
index 0fe4a10..77aeb8f 100644
--- a/ui/src/store/mutation-types.js
+++ b/ui/src/store/mutation-types.js
@@ -38,6 +38,8 @@
 export const VUE_VERSION = 'VUE_VERSION'
 export const CUSTOM_COLUMNS = 'CUSTOM_COLUMNS'
 export const RELOAD_ALL_PROJECTS = 'RELOAD_ALL_PROJECTS'
+export const OAUTH_DOMAIN = 'OAUTH_DOMAIN'
+export const OAUTH_PROVIDER = 'OAUTH_PROVIDER'
 
 export const CONTENT_WIDTH_TYPE = {
   Fluid: 'Fluid',
diff --git a/ui/src/style/common/function.less b/ui/src/style/common/function.less
index 76d3fe9..b6ab109 100644
--- a/ui/src/style/common/function.less
+++ b/ui/src/style/common/function.less
@@ -1380,4 +1380,4 @@
 // It is hacky way to make this function will be compiled preferentially by less
 // resolve error: `ReferenceError: colorPalette is not defined`
 // https://github.com/ant-design/ant-motion/issues/44
-.colorPaletteMixin();
\ No newline at end of file
+.colorPaletteMixin();
diff --git a/ui/src/style/dark-mode.less b/ui/src/style/dark-mode.less
index 4d8ae25..608e147 100644
--- a/ui/src/style/dark-mode.less
+++ b/ui/src/style/dark-mode.less
@@ -83,6 +83,7 @@
 
   .sider.light {
     background: @dark-secondary-bgColor;
+    padding-top: 15px;
 
     .logo {
       background-color: @dark-secondary-bgColor;
@@ -139,6 +140,10 @@
         color: @dark-text-color-3;
       }
     }
+
+    &-inline, &-vertical, &-vertical-left {
+      border-color: transparent;
+    }
   }
 
   .kubernet-icon path
@@ -289,6 +294,10 @@
       & > tr > th.ant-table-column-sort {
         background-color: @dark-bgColor;
       }
+
+      & > tr.ant-table-row:hover > td, & > tr > td.ant-table-cell-row-hover {
+        background: transparent;
+      }
     }
 
     &-footer {
@@ -336,6 +345,12 @@
         border-color: @dark-border-color;
       }
     }
+
+    &-small {
+      .ant-table-thead > tr > th {
+        background-color: transparent;
+      }
+    }
   }
 
   .light-row, .dark-row {
@@ -524,13 +539,17 @@
       background-color: #1890ff;
     }
 
-    &-loading-icon, &:after {
+    &-loading-icon, &::before {
       background-color: @dark-secondary-bgColor;
     }
 
     &:disabled {
       background-color: @disabled-bgColor;
     }
+
+    &-handle::before {
+      background-color: @dark-secondary-bgColor;
+    }
   }
 
   .ant-select-dropdown {
@@ -783,7 +802,7 @@
     background-color: @dark-secondary-bgColor;
   }
 
-  .ant-upload.ant-upload-drag {
+  .ant-form-item .ant-upload.ant-upload-drag {
     background-color: transparent;
     border-color: @dark-border-color;
   }
@@ -792,12 +811,43 @@
     color: @dark-text-color-3;
   }
 
-  .ant-tree.ant-tree-show-line li span.ant-tree-switcher {
+  .ant-upload-list {
+    color: @dark-text-color-3;
+
+    &-item {
+      &:hover {
+        .ant-upload-list-item-info {
+          background: @dark-bgColor;
+        }
+
+        .ant-upload-list-item-card-actions-btn {
+          border-color: transparent;
+        }
+
+        .ant-upload-list-item-card-actions .anticon {
+          color: @dark-text-color-3;
+        }
+      }
+    }
+  }
+
+  .ant-upload-list-item-info {
+    .anticon-loading .anticon,
+    .ant-upload-text-icon .anticon {
+      color: rgba(255, 255, 255, 0.65);
+    }
+  }
+
+  .ant-tree {
+    background: transparent;
+  }
+
+  .ant-tree-show-line .ant-tree-switcher {
     background-color: transparent;
     color: @dark-text-color-3;
   }
 
-  .ant-tree li .ant-tree-node-content-wrapper {
+  .ant-tree .ant-tree-node-content-wrapper {
     color: @dark-text-color-3;
   }
 
@@ -806,7 +856,7 @@
     background-color: transparent;
   }
 
-  .ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected {
+  .ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
     .ant-tree-icon__customize {
       color: #000;
     }
@@ -814,7 +864,7 @@
     color: #000;
   }
 
-  .ant-tree li .ant-tree-node-content-wrapper:hover {
+  .ant-tree .ant-tree-node-content-wrapper:hover {
     .ant-tree-icon__customize {
       color: #000;
     }
diff --git a/ui/src/style/frame/content.less b/ui/src/style/frame/content.less
index aa11574..bc47d35 100644
--- a/ui/src/style/frame/content.less
+++ b/ui/src/style/frame/content.less
@@ -24,4 +24,4 @@
       margin-right: 8px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/style/frame/search.less b/ui/src/style/frame/search.less
index 3e0f952..3c98626 100644
--- a/ui/src/style/frame/search.less
+++ b/ui/src/style/frame/search.less
@@ -49,4 +49,4 @@
     white-space: nowrap;
   }
 
-}
\ No newline at end of file
+}
diff --git a/ui/src/style/frame/sider.less b/ui/src/style/frame/sider.less
index c021946..80304dd 100644
--- a/ui/src/style/frame/sider.less
+++ b/ui/src/style/frame/sider.less
@@ -84,4 +84,4 @@
     }
   }
 
-}
\ No newline at end of file
+}
diff --git a/ui/src/style/frame/top-menu.less b/ui/src/style/frame/top-menu.less
index 4998735..3712902 100644
--- a/ui/src/style/frame/top-menu.less
+++ b/ui/src/style/frame/top-menu.less
@@ -20,4 +20,4 @@
     max-width: 1200px;
     margin: 0 auto;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/style/index.less b/ui/src/style/index.less
index 38b4c6b..c0f1866 100644
--- a/ui/src/style/index.less
+++ b/ui/src/style/index.less
@@ -16,8 +16,8 @@
 // under the License.
 
 //* import all  ## official ant ##  variables; mixins and styles
-@import "~ant-design-vue/lib/style/themes/default";
-@import "~ant-design-vue/lib/style/core/index";
+@import "~ant-design-vue/es/style/themes/default.less";
+@import "~ant-design-vue/es/style/core/index.less";
 
 //* import all  ## custom ##  variables, mixins and styles
 
diff --git a/ui/src/style/objects/table.less b/ui/src/style/objects/table.less
index 8b51257..6992543 100644
--- a/ui/src/style/objects/table.less
+++ b/ui/src/style/objects/table.less
@@ -17,4 +17,4 @@
 
 .table-alert {
   margin-bottom: 16px;
-}
\ No newline at end of file
+}
diff --git a/ui/src/style/vars.less b/ui/src/style/vars.less
index 6d219a1..fc6fdf7 100644
--- a/ui/src/style/vars.less
+++ b/ui/src/style/vars.less
@@ -505,4 +505,38 @@
 .ant-dropdown-menu-item:hover,
 .ant-dropdown-menu-submenu-title:hover {
   background-color: color(~`colorPalette("@{primary-color}", 1)`) !important;
-}
\ No newline at end of file
+}
+
+.ant-tabs.tab-center {
+  > .ant-tabs-nav, > div > .ant-tabs-nav,
+  > .ant-tabs-nav .ant-tabs-nav-wrap,
+  > div > .ant-tabs-nav .ant-tabs-nav-wrap {
+    justify-content: center;
+  }
+}
+
+.ant-tabs-large>.ant-tabs-nav .ant-tabs-tab {
+  padding: 16px;
+}
+
+.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab {
+  padding: 8px 24px 8px 8px;
+  text-align: center;
+}
+
+.ant-steps {
+  &-item-container {
+    &:hover {
+      .ant-steps-item-content .ant-steps-item-description {
+        color: inherit !important;
+      }
+    }
+  }
+
+  &-item-process {
+    .ant-steps-item-content .ant-steps-item-title {
+      color: inherit !important;
+      font-weight: 600;
+    }
+  }
+}
diff --git a/ui/src/utils/request.js b/ui/src/utils/request.js
index 2c51ede..c2fe04a 100644
--- a/ui/src/utils/request.js
+++ b/ui/src/utils/request.js
@@ -109,8 +109,6 @@
         if (originalPath !== '/user/login') {
           router.push({ path: '/user/login', query: { redirect: originalPath } })
         }
-        store.commit('SET_COUNT_NOTIFY', 0)
-        notification.destroy()
       })
     }
     if (response.status === 404) {
@@ -196,6 +194,7 @@
     if (!source) sourceToken.init()
     if (source) {
       source.cancel()
+      source = null
     } else {
       console.log('Source token failed to be cancelled')
     }
diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue
index acb6fd6..be6c0f2 100644
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@ -17,10 +17,10 @@
 
 <template>
   <div>
-    <a-affix :offsetTop="78">
+    <a-affix :offsetTop="this.$store.getters.shutdownTriggered ? 103 : 78">
       <a-card class="breadcrumb-card" style="z-index: 10">
         <a-row>
-          <a-col :span="device === 'mobile' ? 24 : 12" style="padding-left: 12px">
+          <a-col :span="device === 'mobile' ? 24 : 12" style="padding-left: 12px; margin-top: 10px">
             <breadcrumb :resource="resource">
               <template #end>
                 <a-button
@@ -32,20 +32,6 @@
                   <template #icon><reload-outlined /></template>
                   {{ $t('label.refresh') }}
                 </a-button>
-                <a-switch
-                  v-if="!dataView && ['vm', 'volume', 'zone', 'cluster', 'host', 'storagepool', 'managementserver'].includes($route.name)"
-                  style="margin-left: 8px"
-                  :checked-children="$t('label.metrics')"
-                  :un-checked-children="$t('label.metrics')"
-                  :checked="$store.getters.metrics"
-                  @change="(checked, event) => { $store.dispatch('SetMetrics', checked) }"/>
-                <a-switch
-                  v-if="!projectView && hasProjectId"
-                  style="margin-left: 8px"
-                  :checked-children="$t('label.projects')"
-                  :un-checked-children="$t('label.projects')"
-                  :checked="$store.getters.listAllProjects"
-                  @change="(checked, event) => { $store.dispatch('SetListAllProjects', checked) }"/>
                 <a-tooltip placement="right">
                   <template #title>
                     {{ $t('label.filterby') }}
@@ -53,14 +39,9 @@
                   <a-select
                     v-if="!dataView && filters && filters.length > 0"
                     :placeholder="$t('label.filterby')"
-                    :value="$route.query.filter || (projectView && $route.name === 'vm' ||
-                      ['Admin', 'DomainAdmin'].includes($store.getters.userInfo.roletype) &&
-                      ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool'].includes($route.name)
-                        ? 'all' : ['publicip'].includes($route.name)
-                        ? 'allocated' : ['account', 'guestnetwork', 'guestvlans'].includes($route.name)
-                        ? 'all' : ['volume'].includes($route.name)
-                        ? 'user' : 'self')"
-                    style="min-width: 120px; margin-left: 10px"
+                    :value="filterValue"
+                    style="min-width: 100px; margin-left: 10px; margin-bottom: 5px"
+                    size=small
                     @change="changeFilter"
                     showSearch
                     optionFilterProp="label"
@@ -70,7 +51,7 @@
                     <template #suffixIcon><filter-outlined class="ant-select-suffix" /></template>
                     <a-select-option
                       v-if="['Admin', 'DomainAdmin'].includes($store.getters.userInfo.roletype) &&
-                      ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool'].includes($route.name) ||
+                      ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool', 'kubernetes', 'computeoffering', 'systemoffering', 'diskoffering'].includes($route.name) ||
                       ['account'].includes($route.name)"
                       key="all"
                       :label="$t('label.all')">
@@ -85,16 +66,30 @@
                     </a-select-option>
                   </a-select>
                 </a-tooltip>
+                <a-switch
+                  v-if="!dataView && ['vm', 'volume', 'zone', 'cluster', 'host', 'storagepool', 'managementserver'].includes($route.name)"
+                  style="margin-left: 8px; min-height: 23px; margin-bottom: 4px"
+                  :checked-children="$t('label.metrics')"
+                  :un-checked-children="$t('label.metrics')"
+                  :checked="$store.getters.metrics"
+                  @change="(checked, event) => { $store.dispatch('SetMetrics', checked) }"/>
+                <a-switch
+                  v-if="!projectView && hasProjectId"
+                  style="margin-left: 8px; min-height: 23px; margin-bottom: 4px"
+                  :checked-children="$t('label.projects')"
+                  :un-checked-children="$t('label.projects')"
+                  :checked="$store.getters.listAllProjects"
+                  @change="(checked, event) => { $store.dispatch('SetListAllProjects', checked) }"/>
               </template>
             </breadcrumb>
           </a-col>
           <a-col
             :span="device === 'mobile' ? 24 : 12"
-            :style="device === 'mobile' ? { float: 'right', 'margin-top': '12px', 'margin-bottom': '-6px', display: 'table' } : { float: 'right', display: 'table', 'margin-bottom': '-6px' }" >
+            :style="device === 'mobile' ? { float: 'right', 'margin-top': '12px', 'margin-bottom': '-6px', display: 'table' } : { float: 'right', display: 'table', 'margin-top': '6px' }" >
             <slot name="action" v-if="dataView && $route.path.startsWith('/publicip')"></slot>
             <action-button
               v-else
-              :style="dataView ? { float: device === 'mobile' ? 'left' : 'right' } : { 'margin-right': '10px', display: getStyle(), padding: '5px' }"
+              :style="dataView ? { float: device === 'mobile' ? 'left' : 'right' } : { 'margin-right': '10px', display: getStyle() }"
               :loading="loading"
               :actions="actions"
               :selectedRowKeys="selectedRowKeys"
@@ -252,11 +247,14 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }"
                 >
-                  <a-select-option key="" >{{ }}</a-select-option>
-                  <a-select-option v-for="(opt, optIndex) in currentAction.mapping[field.name].options" :key="optIndex">
+                  <a-select-option key="" label="">{{ }}</a-select-option>
+                  <a-select-option
+                    v-for="(opt, optIndex) in currentAction.mapping[field.name].options"
+                    :key="optIndex"
+                    :label="opt">
                     {{ opt }}
                   </a-select-option>
                 </a-select>
@@ -269,12 +267,15 @@
                   :loading="field.loading"
                   :placeholder="field.description"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }"
                   v-focus="fieldIndex === firstIndex"
                 >
-                  <a-select-option key="">{{ }}</a-select-option>
-                  <a-select-option v-for="(opt, optIndex) in field.opts" :key="optIndex">
+                  <a-select-option key="" label="">{{ }}</a-select-option>
+                  <a-select-option
+                    v-for="(opt, optIndex) in field.opts"
+                    :key="optIndex"
+                    :label="opt.name || opt.description || opt.traffictype || opt.publicip">
                     {{ opt.name || opt.description || opt.traffictype || opt.publicip }}
                   </a-select-option>
                 </a-select>
@@ -343,10 +344,13 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }"
                 >
-                  <a-select-option v-for="(opt, optIndex) in field.opts" :key="optIndex">
+                  <a-select-option
+                    v-for="(opt, optIndex) in field.opts"
+                    :key="optIndex"
+                    :label="opt.name && opt.type ? opt.name + ' (' + opt.type + ')' : opt.name || opt.description">
                     {{ opt.name && opt.type ? opt.name + ' (' + opt.type + ')' : opt.name || opt.description }}
                   </a-select-option>
                 </a-select>
@@ -389,44 +393,46 @@
       </a-modal>
     </div>
 
-    <div v-if="dataView" style="margin-top: -10px">
-      <slot name="resource" v-if="$route.path.startsWith('/quotasummary') || $route.path.startsWith('/publicip')"></slot>
-      <resource-view
-        v-else
-        :resource="resource"
-        :loading="loading"
-        :tabs="$route.meta.tabs" />
-    </div>
-    <div class="row-element" v-else>
-      <list-view
-        :loading="loading"
-        :columns="columns"
-        :items="items"
-        :actions="actions"
-        :columnKeys="columnKeys"
-        :selectedColumns="selectedColumns"
-        ref="listview"
-        @update-selected-columns="updateSelectedColumns"
-        @selection-change="onRowSelectionChange"
-        @refresh="this.fetchData"
-        @edit-tariff-action="(showAction, record) => $emit('edit-tariff-action', showAction, record)"/>
-      <a-pagination
-        class="row-element"
-        style="margin-top: 10px"
-        size="small"
-        :current="page"
-        :pageSize="pageSize"
-        :total="itemCount"
-        :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page-1)*pageSize))}-${Math.min(page*pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
-        :pageSizeOptions="pageSizeOptions"
-        @change="changePage"
-        @showSizeChange="changePageSize"
-        showSizeChanger
-        showQuickJumper>
-        <template #buildOptionText="props">
-          <span>{{ props.value }} / {{ $t('label.page') }}</span>
-        </template>
-      </a-pagination>
+    <div :style="this.$store.getters.shutdownTriggered ? 'margin-top: 24px; margin-bottom: 12px' : null">
+      <div v-if="dataView">
+        <slot name="resource" v-if="$route.path.startsWith('/quotasummary') || $route.path.startsWith('/publicip')"></slot>
+        <resource-view
+          v-else
+          :resource="resource"
+          :loading="loading"
+          :tabs="$route.meta.tabs" />
+      </div>
+      <div class="row-element" v-else>
+        <list-view
+          :loading="loading"
+          :columns="columns"
+          :items="items"
+          :actions="actions"
+          :columnKeys="columnKeys"
+          :selectedColumns="selectedColumns"
+          ref="listview"
+          @update-selected-columns="updateSelectedColumns"
+          @selection-change="onRowSelectionChange"
+          @refresh="fetchData"
+          @edit-tariff-action="(showAction, record) => $emit('edit-tariff-action', showAction, record)"/>
+        <a-pagination
+          class="row-element"
+          style="margin-top: 10px"
+          size="small"
+          :current="page"
+          :pageSize="pageSize"
+          :total="itemCount"
+          :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page-1)*pageSize))}-${Math.min(page*pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
+          :pageSizeOptions="pageSizeOptions"
+          @change="changePage"
+          @showSizeChange="changePageSize"
+          showSizeChanger
+          showQuickJumper>
+          <template #buildOptionText="props">
+            <span>{{ props.value }} / {{ $t('label.page') }}</span>
+          </template>
+        </a-pagination>
+      </div>
     </div>
     <bulk-action-progress
       :showGroupActionModal="showGroupActionModal"
@@ -438,7 +444,8 @@
 </template>
 
 <script>
-import { ref, reactive, toRaw } from 'vue'
+import { ref, reactive, toRaw, h } from 'vue'
+import { Button } from 'ant-design-vue'
 import { api } from '@/api'
 import { mixinDevice } from '@/utils/mixin.js'
 import { genericCompare } from '@/utils/sort.js'
@@ -630,7 +637,7 @@
   },
   watch: {
     '$route' (to, from) {
-      if (to.fullPath !== from.fullPath && !to.fullPath.includes('action/')) {
+      if (to.fullPath !== from.fullPath && !to.fullPath.includes('action/') && to?.query?.tab !== 'browser') {
         if ('page' in to.query) {
           this.page = Number(to.query.page)
           this.pageSize = Number(to.query.pagesize)
@@ -668,6 +675,25 @@
       return [...new Set(sizes)].sort(function (a, b) {
         return a - b
       }).map(String)
+    },
+    filterValue () {
+      if (this.$route.query.filter) {
+        return this.$route.query.filter
+      }
+      const routeName = this.$route.name
+      if ((this.projectView && routeName === 'vm') || (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) && ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool'].includes(routeName)) || ['account', 'guestnetwork', 'guestvlans', 'oauthsetting', 'guestos', 'guestoshypervisormapping', 'kubernetes'].includes(routeName)) {
+        return 'all'
+      }
+      if (['publicip'].includes(routeName)) {
+        return 'allocated'
+      }
+      if (['volume'].includes(routeName)) {
+        return 'user'
+      }
+      if (['event', 'computeoffering', 'systemoffering', 'diskoffering'].includes(routeName)) {
+        return 'active'
+      }
+      return 'self'
     }
   },
   methods: {
@@ -736,13 +762,16 @@
         }
       }
       if (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) &&
-        'templatefilter' in params && this.routeName === 'template') {
+        'templatefilter' in params && (['template'].includes(this.routeName))) {
         params.templatefilter = 'all'
       }
       if (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) &&
         'isofilter' in params && this.routeName === 'iso') {
         params.isofilter = 'all'
       }
+      if (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) && ['computeoffering', 'systemoffering', 'diskoffering'].includes(this.routeName) && this.$route.params.id) {
+        params.state = 'all'
+      }
       if (Object.keys(this.$route.query).length > 0) {
         if ('page' in this.$route.query) {
           this.page = Number(this.$route.query.page)
@@ -762,8 +791,14 @@
         this.filters = this.filters()
       }
 
+      if (typeof this.searchFilters === 'function') {
+        this.searchFilters = this.searchFilters()
+      }
+
       this.projectView = Boolean(store.getters.project && store.getters.project.id)
-      this.hasProjectId = ['vm', 'vmgroup', 'ssh', 'affinitygroup', 'volume', 'snapshot', 'vmsnapshot', 'guestnetwork', 'vpc', 'securitygroups', 'publicip', 'vpncustomergateway', 'template', 'iso', 'event', 'kubernetes', 'autoscalevmgroup'].includes(this.$route.name)
+      this.hasProjectId = ['vm', 'vmgroup', 'ssh', 'affinitygroup', 'userdata', 'volume', 'snapshot', 'vmsnapshot', 'guestnetwork',
+        'vpc', 'securitygroups', 'publicip', 'vpncustomergateway', 'template', 'iso', 'event', 'kubernetes',
+        'autoscalevmgroup', 'vnfapp'].includes(this.$route.name)
 
       if ((this.$route && this.$route.params && this.$route.params.id) || this.$route.query.dataView) {
         this.dataView = true
@@ -832,10 +867,10 @@
           }
         }
         this.columns.push({
+          key: key,
           title: this.$t('label.' + String(title).toLowerCase()),
           dataIndex: key,
-          slots: { customRender: key },
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+          sorter: (a, b) => genericCompare(a[key] || '', b[key] || '')
         })
         this.selectedColumns.push(key)
       }
@@ -847,6 +882,9 @@
           this.$store.getters.customColumns[this.$store.getters.userInfo.id][this.$route.path] = this.selectedColumns
         } else {
           this.selectedColumns = this.$store.getters.customColumns[this.$store.getters.userInfo.id][this.$route.path] || this.selectedColumns
+          if (this.$store.getters.listAllProjects && !this.projectView) {
+            this.selectedColumns.push('project')
+          }
           this.updateSelectedColumns()
         }
       }
@@ -857,6 +895,7 @@
           this.$t('label.linklocalip'), this.$t('label.size'), this.$t('label.sizegb'), this.$t('label.current'),
           this.$t('label.created'), this.$t('label.order')].includes(column.title)
       })
+      this.chosenColumns.splice(this.chosenColumns.length - 1, 1)
 
       if (['listTemplates', 'listIsos'].includes(this.apiName) && this.dataView) {
         delete params.showunique
@@ -864,6 +903,7 @@
 
       if (['listVirtualMachinesMetrics'].includes(this.apiName) && this.dataView) {
         delete params.details
+        delete params.isvnf
       }
 
       this.loading = true
@@ -1032,6 +1072,19 @@
         this.loading = false
         this.searchParams = params
       })
+
+      if ('action' in this.$route.query) {
+        const actionName = this.$route.query.action
+        for (const action of this.actions) {
+          if (action.listView && action.api === actionName) {
+            this.execAction(action, false)
+            const query = Object.assign({}, this.$route.query)
+            delete query.action
+            this.$router.replace({ query })
+            break
+          }
+        }
+      }
     },
     closeAction () {
       this.actionLoading = false
@@ -1061,7 +1114,7 @@
       this.rules = reactive({})
       if (action.component && action.api && !action.popup) {
         const query = {}
-        if (this.$route.path.startsWith('/vm')) {
+        if (this.$route.path.startsWith('/vm') || this.$route.path.startsWith('/vnfapp')) {
           switch (true) {
             case ('templateid' in this.$route.query):
               query.templateid = this.$route.query.templateid
@@ -1139,6 +1192,14 @@
                 description: self.$t('label.confirmpassword.description')
               }
             }
+            if (arg === 'ostypeid') {
+              return {
+                type: 'uuid',
+                name: 'ostypeid',
+                required: true,
+                description: self.$t('label.select.guest.os.type')
+              }
+            }
             return paramFields.filter(function (param) {
               return param.name.toLowerCase() === arg.toLowerCase()
             })[0]
@@ -1267,13 +1328,30 @@
               eventBus.emit('update-resource-state', { selectedItems: this.selectedItems, resource, state: 'success' })
             }
             if (action.response) {
-              const description = action.response(result.jobresult)
-              if (description) {
-                this.$notification.info({
-                  message: this.$t(action.label),
-                  description: (<span v-html={description}></span>),
-                  duration: 0
-                })
+              const response = action.response(result.jobresult)
+              if (response) {
+                if (typeof response === 'object') {
+                  this.$notification.info({
+                    message: this.$t(action.label),
+                    description: (<span v-html={response.message}></span>),
+                    btn: () => h(
+                      Button,
+                      {
+                        type: 'primary',
+                        size: 'small',
+                        onClick: () => this.copyToClipboard(response.copytext)
+                      },
+                      () => [this.$t(response.copybuttontext)]
+                    ),
+                    duration: 0
+                  })
+                } else {
+                  this.$notification.info({
+                    message: this.$t(action.label),
+                    description: (<span v-html={response}></span>),
+                    duration: 0
+                  })
+                }
               }
             }
             if ('successMethod' in action) {
@@ -1327,9 +1405,9 @@
           this.bulkColumns = this.chosenColumns
           this.selectedItems = this.selectedItems.map(v => ({ ...v, status: 'InProgress' }))
           this.bulkColumns.splice(0, 0, {
+            key: 'status',
             dataIndex: 'status',
             title: this.$t('label.operation.status'),
-            slots: { customRender: 'status' },
             filters: [
               { text: 'In Progress', value: 'InProgress' },
               { text: 'Success', value: 'success' },
@@ -1581,13 +1659,17 @@
       }
 
       this.columns = this.allColumns.filter(x => this.selectedColumns.includes(x.dataIndex))
-      this.columns.push({
-        dataIndex: 'dropdownFilter',
-        slots: {
-          filterDropdown: 'filterDropdown',
-          filterIcon: 'filterIcon'
-        }
-      })
+      const filterColumn = {
+        key: 'filtercolumn',
+        dataIndex: 'filtercolumn',
+        title: '',
+        customFilterDropdown: true,
+        width: 5
+      }
+      if (this.columns.length === 0) {
+        filterColumn.width = 'auto'
+      }
+      this.columns.push(filterColumn)
       if (!this.$store.getters.customColumns[this.$store.getters.userInfo.id]) {
         this.$store.getters.customColumns[this.$store.getters.userInfo.id] = {}
       }
@@ -1660,6 +1742,26 @@
         } else if (filter === 'allocatedonly') {
           query.allocatedonly = 'true'
         }
+      } else if (this.$route.name === 'event') {
+        if (filter === 'archived') {
+          query.archived = true
+        } else {
+          delete query.archived
+        }
+      } else if (this.$route.name === 'guestoshypervisormapping') {
+        if (filter === 'all') {
+          delete query.hypervisor
+        } else {
+          query.hypervisor = filter
+        }
+      } else if (this.$route.name === 'kubernetes') {
+        if (filter === 'all') {
+          delete query.clustertype
+        } else {
+          query.clustertype = filter === 'cloud.managed' ? 'CloudManaged' : 'ExternalManaged'
+        }
+      } else if (['computeoffering', 'systemoffering', 'diskoffering'].includes(this.$route.name)) {
+        query.state = filter
       }
       query.filter = filter
       query.page = '1'
@@ -1685,6 +1787,12 @@
               query.templatetype = value
             } else if (this.$route.name === 'globalsetting') {
               query.name = value
+            } else if (this.$route.name === 'guestoshypervisormapping') {
+              query.hypervisor = value
+            } else if (this.$route.name === 'guestos') {
+              query.description = value
+            } else if (this.$route.name === 'oauthsetting') {
+              query.provider = value
             } else {
               query.keyword = value
             }
@@ -1843,6 +1951,14 @@
       if (screenWidth <= 768) {
         this.modalWidth = '450px'
       }
+    },
+    copyToClipboard (txt) {
+      const parent = this
+      this.$copyText(txt, document.body, function (err) {
+        if (!err) {
+          parent.$message.success(parent.$t('label.copied.clipboard'))
+        }
+      })
     }
   }
 }
@@ -1864,6 +1980,12 @@
   vertical-align: text-bottom;
 }
 
+:deep(.ant-switch-inner) {
+  display: block;
+  font-size: 14px;
+  margin: 0px 14px 0px 28px;
+}
+
 :deep(.ant-alert-message) {
   display: flex;
   align-items: center;
diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue
index 34f5a70..8503f71 100644
--- a/ui/src/views/auth/Login.vue
+++ b/ui/src/views/auth/Login.vue
@@ -26,6 +26,7 @@
     v-ctrl-enter="handleSubmit"
   >
     <a-tabs
+      class="tab-center"
       :activeKey="customActiveKey"
       size="large"
       :tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }"
@@ -48,9 +49,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
-            <a-select-option v-for="item in $config.servers" :key="(item.apiHost || '') + item.apiBase">
+            <a-select-option v-for="item in $config.servers" :key="(item.apiHost || '') + item.apiBase" :label="item.name">
               <template #prefix>
                 <database-outlined />
               </template>
@@ -113,9 +114,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="item in $config.servers" :key="(item.apiHost || '') + item.apiBase">
+            <a-select-option v-for="item in $config.servers" :key="(item.apiHost || '') + item.apiBase" :label="item.name">
               <template #prefix>
                 <database-outlined />
               </template>
@@ -129,9 +130,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="(idp, idx) in idps" :key="idx" :value="idp.id">
+            <a-select-option v-for="(idp, idx) in idps" :key="idx" :value="idp.id" :label="idp.orgName">
               {{ idp.orgName }}
             </a-select-option>
           </a-select>
@@ -152,6 +153,35 @@
       >{{ $t('label.login') }}</a-button>
     </a-form-item>
     <translation-menu/>
+    <div class="content" v-if="socialLogin">
+      <p class="or">or</p>
+    </div>
+    <div class="center">
+      <div class="social-auth" v-if="githubprovider">
+        <a-button
+          @click="handleGithubProviderAndDomain"
+          tag="a"
+          color="primary"
+          :href="getGitHubUrl(from)"
+          class="auth-btn github-auth"
+          style="height: 38px; width: 185px; padding: 0; margin-bottom: 5px;" >
+          <img src="/assets/github.svg" style="width: 32px; padding: 5px" />
+          <a-text>Sign in with Github</a-text>
+        </a-button>
+      </div>
+      <div class="social-auth" v-if="googleprovider">
+        <a-button
+          @click="handleGoogleProviderAndDomain"
+          tag="a"
+          color="primary"
+          :href="getGoogleUrl(from)"
+          class="auth-btn google-auth"
+          style="height: 38px; width: 185px; padding: 0" >
+          <img src="/assets/google.svg" style="width: 32px; padding: 5px" />
+          <a-text>Sign in with Google</a-text>
+        </a-button>
+      </div>
+    </div>
   </a-form>
 </template>
 
@@ -172,7 +202,18 @@
     return {
       idps: [],
       customActiveKey: 'cs',
+      customActiveKeyOauth: false,
       loginBtn: false,
+      email: '',
+      secretcode: '',
+      oauthexclude: '',
+      socialLogin: false,
+      googleprovider: false,
+      githubprovider: false,
+      googleredirecturi: '',
+      githubredirecturi: '',
+      googleclientid: '',
+      githubclientid: '',
       loginType: 0,
       state: {
         time: 60,
@@ -188,6 +229,9 @@
     }
     this.initForm()
     if (store.getters.logoutFlag) {
+      if (store.getters.readyForShutdownPollingJob !== '' || store.getters.readyForShutdownPollingJob !== undefined) {
+        clearInterval(store.getters.readyForShutdownPollingJob)
+      }
       sourceToken.init()
       this.fetchData()
     } else {
@@ -195,7 +239,7 @@
     }
   },
   methods: {
-    ...mapActions(['Login', 'Logout']),
+    ...mapActions(['Login', 'Logout', 'OauthLogin']),
     initForm () {
       this.formRef = ref()
       this.form = reactive({
@@ -205,7 +249,7 @@
       this.setRules()
     },
     setRules () {
-      if (this.customActiveKey === 'cs') {
+      if (this.customActiveKey === 'cs' && this.customActiveKeyOauth === false) {
         this.rules.username = [
           {
             required: true,
@@ -241,6 +285,24 @@
           this.form.idp = this.idps[0].id || ''
         }
       })
+      api('listOauthProvider', {}).then(response => {
+        if (response) {
+          const oauthproviders = response.listoauthproviderresponse.oauthprovider || []
+          oauthproviders.forEach(item => {
+            this.socialLogin = true
+            if (item.provider === 'google') {
+              this.googleprovider = item.enabled
+              this.googleclientid = item.clientid
+              this.googleredirecturi = item.redirecturi
+            }
+            if (item.provider === 'github') {
+              this.githubprovider = item.enabled
+              this.githubclientid = item.clientid
+              this.githubredirecturi = item.redirecturi
+            }
+          })
+        }
+      })
     },
     // handler
     async handleUsernameOrEmail (rule, value) {
@@ -257,6 +319,53 @@
       this.customActiveKey = key
       this.setRules()
     },
+    handleGithubProviderAndDomain () {
+      this.handleDomain()
+      this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'github')
+    },
+    handleGoogleProviderAndDomain () {
+      this.handleDomain()
+      this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'google')
+    },
+    handleDomain () {
+      const values = toRaw(this.form)
+      if (!values.domain) {
+        this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', '/')
+      } else {
+        this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', values.domain)
+      }
+    },
+    getGitHubUrl (from) {
+      const rootURl = 'https://github.com/login/oauth/authorize'
+      const options = {
+        client_id: this.githubclientid,
+        scope: 'user:email',
+        state: 'cloudstack'
+      }
+
+      const qs = new URLSearchParams(options)
+
+      return `${rootURl}?${qs.toString()}`
+    },
+    getGoogleUrl (from) {
+      const rootUrl = 'https://accounts.google.com/o/oauth2/v2/auth'
+      const options = {
+        redirect_uri: this.googleredirecturi,
+        client_id: this.googleclientid,
+        access_type: 'offline',
+        response_type: 'code',
+        prompt: 'consent',
+        scope: [
+          'https://www.googleapis.com/auth/userinfo.profile',
+          'https://www.googleapis.com/auth/userinfo.email'
+        ].join(' '),
+        state: from
+      }
+
+      const qs = new URLSearchParams(options)
+
+      return `${rootUrl}?${qs.toString()}`
+    },
     handleSubmit (e) {
       e.preventDefault()
       if (this.state.loginBtn) return
@@ -295,6 +404,28 @@
         this.formRef.value.scrollToField(error.errorFields[0].name)
       })
     },
+    handleSubmitOauth (provider) {
+      this.customActiveKeyOauth = true
+      this.setRules()
+      this.formRef.value.validate().then(() => {
+        const values = toRaw(this.form)
+        const loginParams = { ...values }
+        delete loginParams.username
+        loginParams.email = this.email
+        loginParams.provider = provider
+        loginParams.secretcode = this.secretcode
+        loginParams.domain = values.domain
+        if (!loginParams.domain) {
+          loginParams.domain = '/'
+        }
+        this.OauthLogin(loginParams)
+          .then((res) => this.loginSuccess(res))
+          .catch(err => {
+            this.requestFailed(err)
+            this.state.loginBtn = false
+          })
+      })
+    },
     loginSuccess (res) {
       this.$notification.destroy()
       this.$store.commit('SET_COUNT_NOTIFY', 0)
@@ -311,6 +442,9 @@
       if (err && err.response && err.response.data && err.response.data.loginresponse) {
         const error = err.response.data.loginresponse.errorcode + ': ' + err.response.data.loginresponse.errortext
         this.$message.error(`${this.$t('label.error')} ${error}`)
+      } else if (err && err.response && err.response.data && err.response.data.oauthloginresponse) {
+        const error = err.response.data.oauthloginresponse.errorcode + ': ' + err.response.data.oauthloginresponse.errortext
+        this.$message.error(`${this.$t('label.error')} ${error}`)
       } else {
         this.$message.error(this.$t('message.login.failed'))
       }
@@ -368,6 +502,34 @@
     .register {
       float: right;
     }
+
+    .g-btn-wrapper {
+      background-color: rgb(221, 75, 57);
+      height: 40px;
+      width: 80px;
+    }
   }
+    .center {
+     display: flex;
+     flex-direction: column;
+     justify-content: center;
+     align-items: center;
+     height: 100px;
+    }
+
+    .content {
+      margin: 10px auto;
+      width: 300px;
+    }
+
+    .or {
+      text-align: center;
+      font-size: 16px;
+      background:
+        linear-gradient(#CCC 0 0) left,
+        linear-gradient(#CCC 0 0) right;
+      background-size: 40% 1px;
+      background-repeat: no-repeat;
+    }
 }
 </style>
diff --git a/ui/src/views/compute/AssignInstance.vue b/ui/src/views/compute/AssignInstance.vue
index 9acc821..9726dc2 100644
--- a/ui/src/views/compute/AssignInstance.vue
+++ b/ui/src/views/compute/AssignInstance.vue
@@ -35,9 +35,9 @@
           v-model:value="selectedAccountType"
           v-focus="true"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }">
           <a-select-option :value="$t('label.account')">{{ $t('label.account') }}</a-select-option>
           <a-select-option :value="$t('label.project')">{{ $t('label.project') }}</a-select-option>
@@ -71,7 +71,7 @@
             @change="changeAccount"
             v-model:value="selectedAccount"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
               return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
@@ -306,7 +306,11 @@
           message: this.$t('label.loadbalancerinstance')
         })
         this.$emit('close-action')
-        this.parentFetchData()
+        if (this.$store.getters.project?.id) {
+          this.$router.push({ path: '/vm' })
+        } else {
+          this.parentFetchData()
+        }
       }).catch(error => {
         this.$notifyError(error)
       }).finally(() => {
diff --git a/ui/src/views/compute/AttachIso.vue b/ui/src/views/compute/AttachIso.vue
index 20b9e4e..ba54219 100644
--- a/ui/src/views/compute/AttachIso.vue
+++ b/ui/src/views/compute/AttachIso.vue
@@ -31,9 +31,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
-            <a-select-option v-for="iso in isos" :key="iso.id">
+            <a-select-option v-for="iso in isos" :key="iso.id" :label="iso.displaytext || iso.name">
               {{ iso.displaytext || iso.name }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/compute/AutoScaleDownPolicyTab.vue b/ui/src/views/compute/AutoScaleDownPolicyTab.vue
index ecbc584..422d1ab 100644
--- a/ui/src/views/compute/AutoScaleDownPolicyTab.vue
+++ b/ui/src/views/compute/AutoScaleDownPolicyTab.vue
@@ -36,12 +36,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-                      return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }" >
           <a-select-option
             v-for="(scalepolicy, index) in this.policies"
             :value="scalepolicy.id"
-            :key="index">
+            :key="index"
+            :label="scalepolicy.displaytext || scalepolicy.name" >
             {{ scalepolicy.name || scalepolicy.id }}
           </a-select-option>
         </a-select>
@@ -109,28 +110,24 @@
       :dataSource="policy.conditions"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #name="{ record }">
-        {{ record.name }}
-      </template>
-      <template #relationaloperator="{ record }">
-        {{ getOperator(record.relationaloperator) }}
-      </template>
-      <template #threshold="{ record }">
-        {{ record.threshold }}
-      </template>
-      <template #actions="{ record }">
-        <tooltip-button
-          :tooltip="$t('label.edit')"
-          :disabled="!('updateCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
-          icon="edit-outlined"
-          @onClick="() => openUpdateConditionModal(record)" />
-        <tooltip-button
-          :tooltip="$t('label.delete')"
-          :disabled="!('deleteCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
-          type="primary"
-          :danger="true"
-          icon="delete-outlined"
-          @onClick="deleteConditionFromAutoScalePolicy(record.id)" />
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'relationaloperator'">
+          {{ getOperator(record.relationaloperator) }}
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            :tooltip="$t('label.edit')"
+            :disabled="!('updateCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
+            icon="edit-outlined"
+            @onClick="() => openUpdateConditionModal(record)" />
+          <tooltip-button
+            :tooltip="$t('label.delete')"
+            :disabled="!('deleteCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="deleteConditionFromAutoScalePolicy(record.id)" />
+        </template>
       </template>
     </a-table>
 
@@ -147,11 +144,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-focus="true"
             v-model:value="newCondition.counterid">
-            <a-select-option v-for="(counter, index) in countersList" :value="counter.id" :key="index">
+            <a-select-option
+              v-for="(counter, index) in countersList"
+              :value="counter.id"
+              :key="index"
+              :label="counter.name" >
               {{ counter.name }}
             </a-select-option>
           </a-select>
@@ -165,11 +166,11 @@
           <a-select
             v-model:value="newCondition.relationaloperator"
             style="width: 100%;"
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="LT">{{ getOperator('LT') }}</a-select-option>
+            <a-select-option value="LT" >{{ getOperator('LT') }}</a-select-option>
           </a-select>
           <span class="error-text">{{ $t('label.required') }}</span>
         </div>
@@ -216,11 +217,11 @@
           <a-select
             v-model:value="updateConditionDetails.relationaloperator"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="LT">{{ getOperator('LT') }}</a-select-option>
+            <a-select-option value="LT" >{{ getOperator('LT') }}</a-select-option>
           </a-select>
         </div>
         <div class="update-condition__item">
@@ -277,10 +278,14 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-model:value="newPolicy.counterid">
-            <a-select-option v-for="(counter, index) in countersList" :value="counter.id" :key="index">
+            <a-select-option
+              v-for="(counter, index) in countersList"
+              :value="counter.id"
+              :key="index"
+              :label="counter.name">
               {{ counter.name }}
             </a-select-option>
           </a-select>
@@ -293,11 +298,11 @@
           <a-select
             v-model:value="newPolicy.relationaloperator"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="LT">{{ getOperator('LT') }}</a-select-option>
+            <a-select-option value="LT" >{{ getOperator('LT') }}</a-select-option>
           </a-select>
         </div>
         <div class="update-condition__item">
@@ -338,7 +343,7 @@
   },
   data () {
     return {
-      filterColumns: ['Action'],
+      filterColumns: ['Actions'],
       loading: true,
       policies: [],
       isEditable: false,
@@ -377,15 +382,15 @@
         },
         {
           title: this.$t('label.relationaloperator'),
-          slots: { customRender: 'relationaloperator' }
+          key: 'relationaloperator'
         },
         {
           title: this.$t('label.threshold'),
-          slots: { customRender: 'threshold' }
+          dataIndex: 'threshold'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          title: this.$t('label.actions'),
+          key: 'actions'
         }
       ]
     }
diff --git a/ui/src/views/compute/AutoScaleLoadBalancing.vue b/ui/src/views/compute/AutoScaleLoadBalancing.vue
index f6e29b3..6091689 100644
--- a/ui/src/views/compute/AutoScaleLoadBalancing.vue
+++ b/ui/src/views/compute/AutoScaleLoadBalancing.vue
@@ -33,16 +33,37 @@
       :dataSource="lbRules"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #algorithm="{ record }">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'algorithm'">
         {{ returnAlgorithmName(record.algorithm) }}
-      </template>
-      <template #protocol="{record}">
-        {{ getCapitalise(record.protocol) }}
-      </template>
-      <template #stickiness="{record}">
-        <a-button @click="() => openStickinessModal(record.id)">
-          {{ returnStickinessLabel(record.id) }}
-        </a-button>
+        </template>
+        <template v-if="column.key === 'protocol'">
+          {{ getCapitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'stickiness'">
+          <a-button @click="() => openStickinessModal(record.id)">
+            {{ returnStickinessLabel(record.id) }}
+          </a-button>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="actions">
+            <tooltip-button :tooltip="$t('label.edit')" icon="edit-outlined" @onClick="() => openEditRuleModal(record)" />
+            <tooltip-button :tooltip="$t('label.edit.tags')" :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" icon="tag-outlined" @onClick="() => openTagsModal(record.id)" />
+            <a-popconfirm
+              :title="$t('label.delete') + '?'"
+              @confirm="handleDeleteRule(record)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')"
+            >
+              <tooltip-button
+                :tooltip="$t('label.delete')"
+                :disabled="!('deleteLoadBalancerRule' in $store.getters.apis) || record.autoscalevmgroup"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined" />
+            </a-popconfirm>
+          </div>
+        </template>
       </template>
       <template #expandedRowRender="{ record }">
         <div class="rule-instance-list">
@@ -67,25 +88,6 @@
           </div>
         </div>
       </template>
-      <template #actions="{record}">
-        <div class="actions">
-          <tooltip-button :tooltip="$t('label.edit')" icon="edit-outlined" @onClick="() => openEditRuleModal(record)" />
-          <tooltip-button :tooltip="$t('label.edit.tags')" :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" icon="tag-outlined" @onClick="() => openTagsModal(record.id)" />
-          <a-popconfirm
-            :title="$t('label.delete') + '?'"
-            @confirm="handleDeleteRule(record)"
-            :okText="$t('label.yes')"
-            :cancelText="$t('label.no')"
-          >
-            <tooltip-button
-              :tooltip="$t('label.delete')"
-              :disabled="!('deleteLoadBalancerRule' in $store.getters.apis) || record.autoscalevmgroup"
-              type="primary"
-              :danger="true"
-              icon="delete-outlined" />
-          </a-popconfirm>
-        </div>
-      </template>
     </a-table>
     <a-modal
       v-if="tagsModalVisible"
@@ -169,12 +171,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="LbCookie">{{ $t('label.lb.cookie') }}</a-select-option>
-            <a-select-option value="AppCookie">{{ $t('label.app.cookie') }}</a-select-option>
-            <a-select-option value="SourceBased">{{ $t('label.source.based') }}</a-select-option>
-            <a-select-option value="none">{{ $t('label.none') }}</a-select-option>
+            <a-select-option value="LbCookie" :label="$t('label.lb.cookie')">{{ $t('label.lb.cookie') }}</a-select-option>
+            <a-select-option value="AppCookie" :label="$t('label.lb.cookie')">{{ $t('label.app.cookie') }}</a-select-option>
+            <a-select-option value="SourceBased" :label="$t('label.lb.cookie')">{{ $t('label.source.based') }}</a-select-option>
+            <a-select-option value="none" :label="$t('label.lb.cookie')">{{ $t('label.none') }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item
@@ -261,9 +263,9 @@
           <a-select
             v-model:value="editRuleDetails.algorithm"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option value="roundrobin">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
             <a-select-option value="leastconn">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
@@ -275,9 +277,9 @@
           <a-select
             v-model:value="editRuleDetails.protocol"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option value="tcp-proxy">{{ $t('label.tcp.proxy') }}</a-select-option>
             <a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
@@ -355,11 +357,11 @@
         },
         {
           title: this.$t('label.algorithm'),
-          slots: { customRender: 'algorithm' }
+          key: 'algorithm'
         },
         {
           title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol'
         },
         {
           title: this.$t('label.state'),
@@ -367,11 +369,11 @@
         },
         {
           title: this.$t('label.action.configure.stickiness'),
-          slots: { customRender: 'stickiness' }
+          key: 'stickiness'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          title: this.$t('label.actions'),
+          key: 'actions'
         }
       ],
       tiers: {
@@ -382,13 +384,13 @@
         {
           title: this.$t('label.name'),
           dataIndex: 'name',
-          slots: { customRender: 'name' },
+          key: 'name',
           width: 220
         },
         {
           title: this.$t('label.state'),
           dataIndex: 'state',
-          slots: { customRender: 'state' }
+          key: 'state'
         },
         {
           title: this.$t('label.displayname'),
@@ -404,8 +406,8 @@
         },
         {
           title: this.$t('label.select'),
-          dataIndex: 'action',
-          slots: { customRender: 'action' },
+          dataIndex: 'actions',
+          key: 'actions',
           width: 80
         }
       ],
diff --git a/ui/src/views/compute/AutoScaleUpPolicyTab.vue b/ui/src/views/compute/AutoScaleUpPolicyTab.vue
index 450b954..4ddd67a 100644
--- a/ui/src/views/compute/AutoScaleUpPolicyTab.vue
+++ b/ui/src/views/compute/AutoScaleUpPolicyTab.vue
@@ -36,12 +36,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-                      return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }" >
           <a-select-option
             v-for="(scalepolicy, index) in this.policies"
             :value="scalepolicy.id"
-            :key="index">
+            :key="index"
+            :label="scalepolicy.displaytext || scalepolicy.name">
             {{ scalepolicy.name || scalepolicy.id }}
           </a-select-option>
         </a-select>
@@ -109,28 +110,24 @@
       :dataSource="policy.conditions"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #name="{ record }">
-        {{ record.name }}
-      </template>
-      <template #relationaloperator="{ record }">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'relationaloperator'">
         {{ getOperator(record.relationaloperator) }}
-      </template>
-      <template #threshold="{ record }">
-        {{ record.threshold }}
-      </template>
-      <template #actions="{ record }">
-        <tooltip-button
-          :tooltip="$t('label.edit')"
-          :disabled="!('updateCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
-          icon="edit-outlined"
-          @onClick="() => openUpdateConditionModal(record)" />
-        <tooltip-button
-          :tooltip="$t('label.delete')"
-          :disabled="!('deleteCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
-          type="primary"
-          :danger="true"
-          icon="delete-outlined"
-          @onClick="deleteConditionFromAutoScalePolicy(record.id)" />
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            :tooltip="$t('label.edit')"
+            :disabled="!('updateCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
+            icon="edit-outlined"
+            @onClick="() => openUpdateConditionModal(record)" />
+          <tooltip-button
+            :tooltip="$t('label.delete')"
+            :disabled="!('deleteCondition' in $store.getters.apis) || resource.state !== 'DISABLED'"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="deleteConditionFromAutoScalePolicy(record.id)" />
+        </template>
       </template>
     </a-table>
 
@@ -147,11 +144,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-focus="true"
             v-model:value="newCondition.counterid">
-            <a-select-option v-for="(counter, index) in countersList" :value="counter.id" :key="index">
+            <a-select-option
+              v-for="(counter, index) in countersList"
+              :value="counter.id"
+              :key="index"
+              :label="counter.name">>
               {{ counter.name }}
             </a-select-option>
           </a-select>
@@ -165,9 +166,9 @@
           <a-select
             v-model:value="newCondition.relationaloperator"
             style="width: 100%;"
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option value="GT">{{ getOperator('GT') }}</a-select-option>
           </a-select>
@@ -216,9 +217,9 @@
           <a-select
             v-model:value="updateConditionDetails.relationaloperator"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option value="GT">{{ getOperator('GT') }}</a-select-option>
           </a-select>
@@ -277,10 +278,14 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-model:value="newPolicy.counterid">
-            <a-select-option v-for="(counter, index) in countersList" :value="counter.id" :key="index">
+            <a-select-option
+              v-for="(counter, index) in countersList"
+              :value="counter.id"
+              :key="index"
+              :label="counter.name">
               {{ counter.name }}
             </a-select-option>
           </a-select>
@@ -293,9 +298,9 @@
           <a-select
             v-model:value="newPolicy.relationaloperator"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option value="GT">{{ getOperator('GT') }}</a-select-option>
           </a-select>
@@ -338,7 +343,7 @@
   },
   data () {
     return {
-      filterColumns: ['Action'],
+      filterColumns: ['Actions'],
       loading: true,
       policies: [],
       isEditable: false,
@@ -377,15 +382,15 @@
         },
         {
           title: this.$t('label.relationaloperator'),
-          slots: { customRender: 'relationaloperator' }
+          key: 'relationaloperator'
         },
         {
           title: this.$t('label.threshold'),
-          slots: { customRender: 'threshold' }
+          dataIndex: 'threshold'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          title: this.$t('label.actions'),
+          key: 'actions'
         }
       ]
     }
diff --git a/ui/src/views/compute/AutoScaleVmProfile.vue b/ui/src/views/compute/AutoScaleVmProfile.vue
index 12081df..208fa6a 100644
--- a/ui/src/views/compute/AutoScaleVmProfile.vue
+++ b/ui/src/views/compute/AutoScaleVmProfile.vue
@@ -35,11 +35,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-focus="true"
             v-model:value="autoscaleuserid">
-            <a-select-option v-for="(user, index) in usersList" :value="user.id" :key="index">
+            <a-select-option
+              v-for="(user, index) in usersList"
+              :value="user.id"
+              :key="index"
+              :label="user.username">
               {{ user.username }}
             </a-select-option>
           </a-select>
@@ -137,9 +141,9 @@
           <a-select
             style="width: 100%"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-focus="true"
             v-model:value="newParam.name">
@@ -173,13 +177,8 @@
       :dataSource="allParams"
       :pagination="false"
       :rowKey="record => record.name">
-      <template #name="{ record }">
-        {{ record.name }}
-      </template>
-      <template #threshold="{ record }">
-        {{ record.threshold }}
-      </template>
-      <template #actions="{ record }">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'actions'">
         <a-popconfirm
           :title="$t('label.delete') + '?'"
           @confirm="deleteParam(record.name)"
@@ -193,6 +192,7 @@
             :danger="true"
             icon="delete-outlined" />
         </a-popconfirm>
+        </template>
       </template>
     </a-table>
 
@@ -213,11 +213,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
             v-focus="true"
             v-model:value="autoscaleuserid">
-            <a-select-option v-for="(user, index) in usersList" :value="user.id" :key="index">
+            <a-select-option
+              v-for="(user, index) in usersList"
+              :value="user.id"
+              :key="index"
+              :label="user.username">
               {{ user.username }}
             </a-select-option>
           </a-select>
@@ -241,11 +245,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
             v-focus="true"
             v-model:value="templateid">
-            <a-select-option v-for="(template, index) in templatesList" :value="template.id" :key="index">
+            <a-select-option
+              v-for="(template, index) in templatesList"
+              :value="template.id"
+              :key="index"
+              :label="template.name">
               {{ template.name }}
             </a-select-option>
           </a-select>
@@ -261,11 +269,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
             v-focus="true"
             v-model:value="serviceofferingid">
-            <a-select-option v-for="(offering, index) in serviceOfferingsList" :value="offering.id" :key="index">
+            <a-select-option
+              v-for="(offering, index) in serviceOfferingsList"
+              :value="offering.id"
+              :key="index"
+              :label="offering.name">
               {{ offering.name }}
             </a-select-option>
           </a-select>
@@ -318,7 +330,7 @@
   },
   data () {
     return {
-      filterColumns: ['Action'],
+      filterColumns: ['Actions'],
       loading: true,
       editProfileModalVisible: false,
       showUpdateUserDataForm: false,
@@ -352,8 +364,8 @@
           dataIndex: 'value'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          title: this.$t('label.actions'),
+          key: 'actions'
         }
       ]
     }
diff --git a/ui/src/views/compute/ChangeAffinity.vue b/ui/src/views/compute/ChangeAffinity.vue
index 9175217..9230db9 100644
--- a/ui/src/views/compute/ChangeAffinity.vue
+++ b/ui/src/views/compute/ChangeAffinity.vue
@@ -76,17 +76,17 @@
         {
           dataIndex: 'name',
           title: this.$t('label.name'),
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+          sorter: (a, b) => genericCompare(a?.name || '', b?.name || '')
         },
         {
           dataIndex: 'type',
           title: this.$t('label.type'),
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+          sorter: (a, b) => genericCompare(a?.type || '', b?.type || '')
         },
         {
           dataIndex: 'description',
           title: this.$t('label.description'),
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+          sorter: (a, b) => genericCompare(a?.description || '', b?.description || '')
         }
       ],
       selectedRowKeys: [],
diff --git a/ui/src/views/compute/CreateAutoScaleVmGroup.vue b/ui/src/views/compute/CreateAutoScaleVmGroup.vue
index b9dce0e..981c2b0 100644
--- a/ui/src/views/compute/CreateAutoScaleVmGroup.vue
+++ b/ui/src/views/compute/CreateAutoScaleVmGroup.vue
@@ -153,7 +153,7 @@
                         }"
                         @change="onSelectTemplateConfigurationId"
                       >
-                        <a-select-option v-for="opt in templateConfigurations" :key="opt.id">
+                        <a-select-option v-for="opt in templateConfigurations" :key="opt.id" :label="opt.name || opt.description">
                           {{ opt.name || opt.description }}
                         </a-select-option>
                       </a-select>
@@ -421,11 +421,11 @@
                         <span v-else-if="property.type && property.type==='string' && property.qualifiers && property.qualifiers.startsWith('ValueMap')">
                           <a-select
                             showSearch
-                            optionFilterProp="label"
+                            optionFilterProp="value"
                             v-model:value="form['properties.' + escapePropertyKey(property.key)]"
                             :placeholder="property.description"
                             :filterOption="(input, option) => {
-                              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                             }"
                           >
                             <a-select-option v-for="opt in getPropertyQualifiers(property.qualifiers, 'select')" :key="opt">
@@ -464,11 +464,12 @@
                       showSearch
                       optionFilterProp="label"
                       :filterOption="(input, option) => {
-                        return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                       }" >
                       <a-select-option
                         v-for="policy in this.scaleUpPolicies"
-                        :key="policy.id">
+                        :key="policy.id"
+                        :label="policy.name">
                         {{ policy.name }}
                       </a-select-option>
                     </a-select>
@@ -517,11 +518,15 @@
                         showSearch
                         optionFilterProp="label"
                         :filterOption="(input, option) => {
-                          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                         }"
                         v-focus="true"
                         v-model:value="newScaleUpCondition.counterid">
-                        <a-select-option v-for="(counter, index) in countersList" :value="counter.id" :key="index">
+                        <a-select-option
+                          v-for="(counter, index) in countersList"
+                          :value="counter.id"
+                          :key="index"
+                          :label="counter.name">
                           {{ counter.name }}
                         </a-select-option>
                       </a-select>
@@ -535,9 +540,9 @@
                       <a-select
                         v-model:value="newScaleUpCondition.relationaloperator"
                         style="width: 100%;"
-                        optionFilterProp="label"
+                        optionFilterProp="value"
                         :filterOption="(input, option) => {
-                          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                          return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                         }" >
                         <a-select-option value="GT">{{ getOperator('GT') }}</a-select-option>
                       </a-select>
@@ -569,20 +574,16 @@
                       :dataSource="scaleUpConditions"
                       :pagination="false"
                       :rowKey="record => record.counterid">
-                      <template #countername="{ record }">
-                        {{ record.countername }}
-                      </template>
-                      <template #relationaloperator="{ record }">
-                        {{ getOperator(record.relationaloperator) }}
-                      </template>
-                      <template #threshold="{ record }">
-                        {{ record.threshold }}
-                      </template>
-                      <template #actions="{ record }">
+                      <template #bodyCell="{ column, record }">
+                        <template v-if="column.key === 'relationaloperator'">
+                          {{ getOperator(record.relationaloperator) }}
+                        </template>
+                        <template v-if="column.key === 'actions'">
                           <a-button ref="submit" type="primary" :danger="true" @click="deleteScaleUpCondition(record.counterid)">
                             <template #icon><delete-outlined /></template>
                             {{ $t('label.delete') }}
                           </a-button>
+                        </template>
                       </template>
                     </a-table>
                   </div>
@@ -604,11 +605,12 @@
                       showSearch
                       optionFilterProp="label"
                       :filterOption="(input, option) => {
-                        return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                       }" >
                       <a-select-option
                         v-for="policy in this.scaleDownPolicies"
-                        :key="policy.id">
+                        :key="policy.id"
+                        :label="policy.name">
                         {{ policy.name }}
                       </a-select-option>
                     </a-select>
@@ -657,11 +659,15 @@
                         showSearch
                         optionFilterProp="label"
                         :filterOption="(input, option) => {
-                          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                         }"
                         v-focus="true"
                         v-model:value="newScaleDownCondition.counterid">
-                        <a-select-option v-for="(counter, index) in countersList" :value="counter.id" :key="index">
+                        <a-select-option
+                          v-for="(counter, index) in countersList"
+                          :value="counter.id"
+                          :key="index"
+                          :label="counter.name">
                           {{ counter.name }}
                         </a-select-option>
                       </a-select>
@@ -675,9 +681,9 @@
                       <a-select
                         v-model:value="newScaleDownCondition.relationaloperator"
                         style="width: 100%;"
-                        optionFilterProp="label"
+                        optionFilterProp="value"
                         :filterOption="(input, option) => {
-                          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                          return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                         }" >
                         <a-select-option value="LT">{{ getOperator('LT') }}</a-select-option>
                       </a-select>
@@ -700,7 +706,7 @@
                     </div>
                   </div>
                   <a-divider/>
-                  <div>
+                  <div style="display: block">
                     <a-table
                       size="small"
                       style="overflow-y: auto"
@@ -709,20 +715,16 @@
                       :dataSource="scaleDownConditions"
                       :pagination="false"
                       :rowKey="record => record.counterid">
-                      <template #countername="{ record }">
-                        {{ record.countername }}
-                      </template>
-                      <template #relationaloperator="{ record }">
-                        {{ getOperator(record.relationaloperator) }}
-                      </template>
-                      <template #threshold="{ record }">
-                        {{ record.threshold }}
-                      </template>
-                      <template #actions="{ record }">
-                        <a-button ref="submit" type="primary" :danger="true" @click="deleteScaleDownCondition(record.counterid)">
-                          <template #icon><delete-outlined /></template>
-                          {{ $t('label.delete') }}
-                        </a-button>
+                      <template #bodyCell="{ column, record }">
+                        <template v-if="column.key === 'relationaloperator'">
+                          {{ getOperator(record.relationaloperator) }}
+                        </template>
+                        <template v-if="column.key === 'actions'">
+                          <a-button ref="submit" type="primary" :danger="true" @click="deleteScaleDownCondition(record.counterid)">
+                            <template #icon><delete-outlined /></template>
+                            {{ $t('label.delete') }}
+                          </a-button>
+                        </template>
                       </template>
                     </a-table>
                   </div>
@@ -878,11 +880,15 @@
                         showSearch
                         optionFilterProp="label"
                         :filterOption="(input, option) => {
-                          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                         }"
                         v-focus="true"
                         v-model:value="form.autoscaleuserid">
-                        <a-select-option v-for="(user, index) in usersList" :value="user.id" :key="index">
+                        <a-select-option
+                          v-for="(user, index) in usersList"
+                          :value="user.id"
+                          :key="index"
+                          :label="user.username">
                           {{ user.username }}
                         </a-select-option>
                       </a-select>
@@ -1175,15 +1181,15 @@
         },
         {
           title: this.$t('label.relationaloperator'),
-          slots: { customRender: 'relationaloperator' }
+          key: 'relationaloperator'
         },
         {
           title: this.$t('label.threshold'),
-          slots: { customRender: 'threshold' }
+          dataIndex: 'threshold'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          title: this.$t('label.actions'),
+          key: 'actions'
         }
       ],
       scaleDownPolicies: [],
@@ -1203,15 +1209,15 @@
         },
         {
           title: this.$t('label.relationaloperator'),
-          slots: { customRender: 'relationaloperator' }
+          key: 'relationaloperator'
         },
         {
           title: this.$t('label.threshold'),
-          slots: { customRender: 'threshold' }
+          dataIndex: 'threshold'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          title: this.$t('label.actions'),
+          key: 'actions'
         }
       ],
       usersList: [],
diff --git a/ui/src/views/compute/CreateKubernetesCluster.vue b/ui/src/views/compute/CreateKubernetesCluster.vue
index 908fc58..ca1e424 100644
--- a/ui/src/views/compute/CreateKubernetesCluster.vue
+++ b/ui/src/views/compute/CreateKubernetesCluster.vue
@@ -75,12 +75,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="kubernetesVersionLoading"
             :placeholder="apiParams.kubernetesversionid.description"
             @change="val => { handleKubernetesVersionChange(kubernetesVersions[val]) }">
-            <a-select-option v-for="(opt, optIndex) in kubernetesVersions" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in kubernetesVersions" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -95,11 +95,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="serviceOfferingLoading"
             :placeholder="apiParams.serviceofferingid.description">
-            <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -122,11 +122,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="networkLoading"
             :placeholder="apiParams.networkid.description">
-            <a-select-option v-for="(opt, optIndex) in networks" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in networks" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -171,11 +171,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="keyPairLoading"
             :placeholder="apiParams.keypair.description">
-            <a-select-option v-for="(opt, optIndex) in keyPairs" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in keyPairs" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -259,18 +259,12 @@
     this.apiParams = this.$getApiParams('createKubernetesCluster')
   },
   created () {
-    this.networks = [
-      {
-        id: null,
-        name: ''
-      }
-    ]
-    this.keyPairs = [
-      {
-        id: null,
-        name: ''
-      }
-    ]
+    this.emptyEntry = {
+      id: null,
+      name: ''
+    }
+    this.networks = [this.emptyEntry]
+    this.keyPairs = [this.emptyEntry]
     this.initForm()
     this.fetchData()
   },
@@ -284,7 +278,6 @@
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.kubecluster.name') }],
-        description: [{ required: true, message: this.$t('message.error.cluster.description') }],
         zoneid: [{ required: true, message: this.$t('message.error.zone.for.cluster') }],
         kubernetesversionid: [{ required: true, message: this.$t('message.error.version.for.cluster') }],
         serviceofferingid: [{ required: true, message: this.$t('message.error.serviceoffering.for.cluster') }],
@@ -323,7 +316,6 @@
     },
     fetchData () {
       this.fetchZoneData()
-      this.fetchNetworkData()
       this.fetchKeyPairData()
     },
     isValidValueForKey (obj, key) {
@@ -418,14 +410,16 @@
         params.zoneid = this.selectedZone.id
       }
       this.networkLoading = true
+      this.networks = []
       api('listNetworks', params).then(json => {
         var listNetworks = json.listnetworksresponse.network
         if (this.arrayHasItems(listNetworks)) {
           listNetworks = listNetworks.filter(n => n.type !== 'L2')
-          this.networks = this.networks.concat(listNetworks)
+          this.networks = listNetworks
         }
       }).finally(() => {
         this.networkLoading = false
+        this.networks = [this.emptyEntry].concat(this.networks)
         if (this.arrayHasItems(this.networks)) {
           this.form.networkid = 0
         }
@@ -464,7 +458,8 @@
           zoneid: this.zones[values.zoneid].id,
           kubernetesversionid: this.kubernetesVersions[values.kubernetesversionid].id,
           serviceofferingid: this.serviceOfferings[values.serviceofferingid].id,
-          size: values.size
+          size: values.size,
+          clustertype: 'CloudManaged'
         }
         if (this.isValidValueForKey(values, 'noderootdisksize') && values.noderootdisksize > 0) {
           params.noderootdisksize = values.noderootdisksize
diff --git a/ui/src/views/compute/CreateSSHKeyPair.vue b/ui/src/views/compute/CreateSSHKeyPair.vue
index 164fbe1..0f879cb 100644
--- a/ui/src/views/compute/CreateSSHKeyPair.vue
+++ b/ui/src/views/compute/CreateSSHKeyPair.vue
@@ -62,12 +62,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="domainLoading"
             :placeholder="apiParams.domainid.description"
             @change="val => { handleDomainChanged(domains[val]) }">
-            <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label=" opt.path || opt.name || opt.description || ''">
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/compute/CreateSnapshotWizard.vue b/ui/src/views/compute/CreateSnapshotWizard.vue
index bcd7d0f..0da4299 100644
--- a/ui/src/views/compute/CreateSnapshotWizard.vue
+++ b/ui/src/views/compute/CreateSnapshotWizard.vue
@@ -37,11 +37,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
             <a-select-option
               v-for="volume in listVolumes"
-              :key="volume.id">
+              :key="volume.id"
+              :label="volume.name">
               {{ volume.name }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue
index 2de28c9..ef41189 100644
--- a/ui/src/views/compute/DeployVM.vue
+++ b/ui/src/views/compute/DeployVM.vue
@@ -222,7 +222,7 @@
                         }"
                         @change="onSelectTemplateConfigurationId"
                       >
-                        <a-select-option v-for="opt in templateConfigurations" :key="opt.id">
+                        <a-select-option v-for="opt in templateConfigurations" :key="opt.id" :label="opt.name || opt.description">
                           {{ opt.name || opt.description }}
                         </a-select-option>
                       </a-select>
@@ -390,7 +390,10 @@
                 :status="zoneSelected ? 'process' : 'wait'"
                 v-if="zone && zone.networktype !== 'Basic'">
                 <template #description>
-                  <div v-if="zoneSelected">
+                  <div v-if="zoneSelected" style="margin-top: 5px">
+                    <div style="margin-bottom: 10px">
+                      {{ $t('message.network.selection') }}
+                    </div>
                     <div v-if="vm.templateid && templateNics && templateNics.length > 0">
                       <instance-nics-network-select-list-view
                         :nics="templateNics"
@@ -519,7 +522,7 @@
                     {{ $t('label.isadvanced') }}
                     <a-switch v-model:checked="showDetails" style="margin-left: 10px"/>
                   </span>
-                  <div style="margin-top: 15px" v-show="showDetails">
+                  <div style="margin-top: 15px" v-if="showDetails">
                     <div
                       v-if="vm.templateid && ['KVM', 'VMware', 'XenServer'].includes(hypervisor) && !template.deployasis">
                       <a-form-item :label="$t('label.boottype')" name="boottype" ref="boottype">
@@ -529,7 +532,7 @@
                           showSearch
                           optionFilterProp="label"
                           :filterOption="filterOption">
-                          <a-select-option v-for="bootType in options.bootTypes" :key="bootType.id">
+                          <a-select-option v-for="bootType in options.bootTypes" :key="bootType.id" :label="bootType.description">
                             {{ bootType.description }}
                           </a-select-option>
                         </a-select>
@@ -540,7 +543,7 @@
                           showSearch
                           optionFilterProp="label"
                           :filterOption="filterOption">
-                          <a-select-option v-for="bootMode in options.bootModes" :key="bootMode.id">
+                          <a-select-option v-for="bootMode in options.bootModes" :key="bootMode.id" :label="bootMode.description">
                             {{ bootMode.description }}
                           </a-select-option>
                         </a-select>
@@ -583,8 +586,10 @@
                                 :dataSource="templateUserDataParams"
                                 :pagination="false"
                                 :rowKey="record => record.key">
-                                <template #value="{ record }">
-                                  <a-input v-model:value="templateUserDataValues[record.key]" />
+                                <template #bodyCell="{ column, record }">
+                                  <template v-if="column.key === 'value'">
+                                    <a-input v-model:value="templateUserDataValues[record.key]" />
+                                  </template>
                                 </template>
                               </a-table>
                             </a-input-group>
@@ -606,8 +611,10 @@
                                 :dataSource="templateUserDataParams"
                                 :pagination="false"
                                 :rowKey="record => record.key">
-                                <template #value="{ record }">
-                                  <a-input v-model:value="templateUserDataValues[record.key]" />
+                                <template #bodyCell="{ column, record }">
+                                  <template v-if="column.key === 'value'">
+                                    <a-input v-model:value="templateUserDataValues[record.key]" />
+                                  </template>
                                 </template>
                               </a-table>
                             </a-input-group>
@@ -655,8 +662,10 @@
                                                 :dataSource="userDataParams"
                                                 :pagination="false"
                                                 :rowKey="record => record.key">
-                                                <template #value="{ record }">
-                                                  <a-input v-model:value="userDataValues[record.key]" />
+                                                <template #bodyCell="{ column, record }">
+                                                  <template v-if="column.key === 'value'">
+                                                    <a-input v-model:value="userDataValues[record.key]" />
+                                                  </template>
                                                 </template>
                                               </a-table>
                                             </a-input-group>
@@ -691,6 +700,23 @@
                         @select-affinity-group-item="($event) => updateAffinityGroups($event)"
                         @handle-search-filter="($event) => handleSearchFilter('affinityGroups', $event)"/>
                     </a-form-item>
+                    <a-form-item name="nicmultiqueuenumber" ref="nicmultiqueuenumber" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
+                      <template #label>
+                        <tooltip-label :title="$t('label.nicmultiqueuenumber')" :tooltip="$t('label.nicmultiqueuenumber.tooltip')"/>
+                      </template>
+                      <a-input-number
+                        style="width: 100%;"
+                        v-model:value="form.nicmultiqueuenumber" />
+                    </a-form-item>
+                    <a-form-item name="nicpackedvirtqueuesenabled" ref="nicpackedvirtqueuesenabled" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
+                      <template #label>
+                        <tooltip-label :title="$t('label.nicpackedvirtqueuesenabled')" :tooltip="$t('label.nicpackedvirtqueuesenabled.tooltip')"/>
+                      </template>
+                      <a-switch
+                        v-model:checked="form.nicpackedvirtqueuesenabled"
+                        :checked="nicpackedvirtqueuesenabled"
+                        @change="val => { nicpackedvirtqueuesenabled = val }"/>
+                    </a-form-item>
                     <a-form-item name="iothreadsenabled" ref="iothreadsenabled" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
                       <template #label>
                         <tooltip-label :title="$t('label.iothreadsenabled')" :tooltip="$t('label.iothreadsenabled.tooltip')"/>
@@ -710,7 +736,7 @@
                         v-model:value="form.iodriverpolicy"
                         optionFilterProp="label"
                         :filterOption="filterOption">
-                        <a-select-option v-for="iodriverpolicy in options.ioPolicyTypes" :key="iodriverpolicy.id">
+                        <a-select-option v-for="iodriverpolicy in options.ioPolicyTypes" :key="iodriverpolicy.id" :label="iodriverpolicy.description">
                           {{ iodriverpolicy.description }}
                         </a-select-option>
                       </a-select>
@@ -814,7 +840,8 @@
 </template>
 
 <script>
-import { ref, reactive, toRaw, nextTick } from 'vue'
+import { ref, reactive, toRaw, nextTick, h } from 'vue'
+import { Button } from 'ant-design-vue'
 import { api } from '@/api'
 import _ from 'lodash'
 import { mixin, mixinDevice } from '@/utils/mixin.js'
@@ -967,7 +994,7 @@
         {
           title: this.$t('label.value'),
           dataIndex: 'value',
-          slots: { customRender: 'value' }
+          key: 'value'
         }
       ],
       userDataValues: {},
@@ -1602,7 +1629,7 @@
       this.fetchInstaceGroups()
       this.fetchIoPolicyTypes()
       nextTick().then(() => {
-        ['name', 'keyboard', 'boottype', 'bootmode', 'userdata', 'iothreadsenabled', 'iodriverpolicy'].forEach(this.fillValue)
+        ['name', 'keyboard', 'boottype', 'bootmode', 'userdata', 'iothreadsenabled', 'iodriverpolicy', 'nicmultiqueuenumber', 'nicpackedvirtqueues'].forEach(this.fillValue)
         this.form.boottype = this.defaultBootType ? this.defaultBootType : this.options.bootTypes && this.options.bootTypes.length > 0 ? this.options.bootTypes[0].id : undefined
         this.form.bootmode = this.defaultBootMode ? this.defaultBootMode : this.options.bootModes && this.options.bootModes.length > 0 ? this.options.bootModes[0].id : undefined
         this.instanceConfig = toRaw(this.form)
@@ -1731,6 +1758,9 @@
           this.form.iothreadsenabled = template.details && Object.prototype.hasOwnProperty.call(template.details, 'iothreads')
           this.form.iodriverpolicy = template.details?.['io.policy']
           this.form.keyboard = template.details?.keyboard
+          if (template.details['vmware-to-kvm-mac-addresses']) {
+            this.dataPreFill.macAddressArray = JSON.parse(template.details['vmware-to-kvm-mac-addresses'])
+          }
         }
       } else if (name === 'isoid') {
         this.templateConfigurations = []
@@ -1822,8 +1852,8 @@
       this.templateUserDataParams = []
 
       api('listUserData', { id: id }).then(json => {
-        const resp = json?.listuserdataresponse?.userdata || []
-        if (resp) {
+        const resp = json.listuserdataresponse.userdata || []
+        if (resp.length > 0) {
           var params = resp[0].params
           if (params) {
             var dataParams = params.split(',')
@@ -1906,6 +1936,8 @@
         deployVmData.dynamicscalingenabled = values.dynamicscalingenabled
         deployVmData.iothreadsenabled = values.iothreadsenabled
         deployVmData.iodriverpolicy = values.iodriverpolicy
+        deployVmData.nicmultiqueuenumber = values.nicmultiqueuenumber
+        deployVmData.nicpackedvirtqueuesenabled = values.nicpackedvirtqueuesenabled
         const isUserdataAllowed = !this.userdataDefaultOverridePolicy || (this.userdataDefaultOverridePolicy === 'ALLOWOVERRIDE' && this.doUserdataOverride) || (this.userdataDefaultOverridePolicy === 'APPEND' && this.doUserdataAppend)
         if (isUserdataAllowed && values.userdata && values.userdata.length > 0) {
           deployVmData.userdata = this.$toBase64AndURIEncoded(values.userdata)
@@ -2093,6 +2125,15 @@
                   this.$notification.success({
                     message: password + ` ${this.$t('label.for')} ` + name,
                     description: vm.password,
+                    btn: () => h(
+                      Button,
+                      {
+                        type: 'primary',
+                        size: 'small',
+                        onClick: () => this.copyToClipboard(vm.password)
+                      },
+                      () => [this.$t('label.copy.password')]
+                    ),
                     duration: 0
                   })
                 }
@@ -2238,6 +2279,7 @@
       args.details = 'all'
       args.showicon = 'true'
       args.id = this.templateId
+      args.isvnf = false
 
       return new Promise((resolve, reject) => {
         api('listTemplates', args).then((response) => {
@@ -2651,6 +2693,14 @@
         }
       }
       return networks
+    },
+    copyToClipboard (txt) {
+      const parent = this
+      this.$copyText(txt, document.body, function (err) {
+        if (!err) {
+          parent.$message.success(parent.$t('label.copied.clipboard'))
+        }
+      })
     }
   }
 }
diff --git a/ui/src/views/compute/DeployVnfAppliance.vue b/ui/src/views/compute/DeployVnfAppliance.vue
new file mode 100644
index 0000000..5e1baac
--- /dev/null
+++ b/ui/src/views/compute/DeployVnfAppliance.vue
@@ -0,0 +1,2919 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div>
+    <a-row :gutter="12">
+      <a-col :md="24" :lg="17">
+        <a-card :bordered="true" :title="$t('label.vnf.appliance.add')">
+          <a-form
+            v-ctrl-enter="handleSubmit"
+            :ref="formRef"
+            :model="form"
+            :rules="rules"
+            @finish="handleSubmit"
+            layout="vertical"
+          >
+            <a-steps direction="vertical" size="small">
+              <a-step :title="$t('label.select.deployment.infrastructure')" status="process">
+                <template #description>
+                  <div style="margin-top: 15px">
+                    <span>{{ $t('message.select.a.zone') }}</span><br/>
+                    <a-form-item :label="$t('label.zoneid')" name="zoneid" ref="zoneid">
+                      <div v-if="zones.length <= 8">
+                        <a-row type="flex" :gutter="[16, 18]" justify="start">
+                          <div v-for="(zoneItem, idx) in zones" :key="idx">
+                            <a-radio-group
+                              :key="idx"
+                              :size="large"
+                              v-model:value="form.zoneid"
+                              @change="onSelectZoneId(zoneItem.id)">
+                              <a-col :span="6">
+                                <a-radio-button
+                                  :value="zoneItem.id"
+                                  style="border-width: 2px"
+                                  class="zone-radio-button">
+                                  <span>
+                                    <resource-icon
+                                      v-if="zoneItem && zoneItem.icon && zoneItem.icon.base64image"
+                                      :image="zoneItem.icon.base64image"
+                                      size="2x" />
+                                    <global-outlined size="2x" v-else />
+                                    {{ zoneItem.name }}
+                                    </span>
+                                </a-radio-button>
+                              </a-col>
+                            </a-radio-group>
+                          </div>
+                        </a-row>
+                      </div>
+                      <a-select
+                        v-else
+                        v-model:value="form.zoneid"
+                        showSearch
+                        optionFilterProp="label"
+                        :filterOption="(input, option) => {
+                          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }"
+                        @change="onSelectZoneId"
+                        :loading="loading.zones"
+                        v-focus="true"
+                      >
+                        <a-select-option v-for="zone1 in zones" :key="zone1.id" :label="zone1.name">
+                          <span>
+                            <resource-icon v-if="zone1.icon && zone1.icon.base64image" :image="zone1.icon.base64image" size="2x" style="margin-right: 5px"/>
+                            <global-outlined v-else style="margin-right: 5px" />
+                            {{ zone1.name }}
+                          </span>
+                        </a-select-option>
+                      </a-select>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.template')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template #description>
+                  <div v-if="zoneSelected" style="margin-top: 15px">
+                    <a-card
+                      :tabList="tabList"
+                      :activeTabKey="tabKey"
+                      @tabChange="key => onTabChange(key, 'tabKey')">
+                      <div v-if="tabKey === 'templateid'">
+                        {{ $t('message.template.desc') }}
+                        <template-iso-selection
+                          input-decorator="templateid"
+                          :items="options.templates"
+                          :selected="tabKey"
+                          :loading="loading.templates"
+                          :preFillContent="dataPreFill"
+                          :key="templateKey"
+                          @handle-search-filter="($event) => fetchAllTemplates($event)"
+                          @update-template-iso="updateFieldValue" />
+                         <div>
+                          {{ $t('label.override.rootdisk.size') }}
+                          <a-switch
+                            v-model:checked="form.rootdisksizeitem"
+                            :disabled="rootDiskSizeFixed > 0 || template.deployasis || showOverrideDiskOfferingOption"
+                            @change="val => { showRootDiskSizeChanger = val }"
+                            style="margin-left: 10px;"/>
+                          <div v-if="template.deployasis">  {{ $t('message.deployasis') }} </div>
+                        </div>
+                        <disk-size-selection
+                          v-if="showRootDiskSizeChanger"
+                          input-decorator="rootdisksize"
+                          :preFillContent="dataPreFill"
+                          :isCustomized="true"
+                          :minDiskSize="dataPreFill.minrootdisksize"
+                          @update-disk-size="updateFieldValue"
+                          style="margin-top: 10px;"/>
+                      </div>
+                    </a-card>
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-model:value="form.templateid" />
+                    </a-form-item>
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-model:value="form.rootdisksize" />
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.serviceofferingid')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template #description>
+                  <div v-if="zoneSelected">
+                    <a-form-item v-if="zoneSelected && templateConfigurationExists" name="templateConfiguration" ref="templateConfiguration">
+                      <template #label>
+                        <tooltip-label :title="$t('label.configuration')" :tooltip="$t('message.ovf.configurations')"/>
+                      </template>
+                      <a-select
+                        showSearch
+                        optionFilterProp="label"
+                        v-model:value="form.templateConfiguration"
+                        defaultActiveFirstOption
+                        :placeholder="$t('message.ovf.configurations')"
+                        :filterOption="(input, option) => {
+                          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }"
+                        @change="onSelectTemplateConfigurationId"
+                      >
+                        <a-select-option v-for="opt in templateConfigurations" :key="opt.id" :label="opt.name || opt.description">
+                          {{ opt.name || opt.description }}
+                        </a-select-option>
+                      </a-select>
+                      <span v-if="selectedTemplateConfiguration && selectedTemplateConfiguration.description">{{ selectedTemplateConfiguration.description }}</span>
+                    </a-form-item>
+                    <compute-offering-selection
+                      :compute-items="options.serviceOfferings"
+                      :selected-template="template ? template : {}"
+                      :row-count="rowCount.serviceOfferings"
+                      :zoneId="zoneId"
+                      :value="serviceOffering ? serviceOffering.id : ''"
+                      :loading="loading.serviceOfferings"
+                      :preFillContent="dataPreFill"
+                      :minimum-cpunumber="templateConfigurationExists && selectedTemplateConfiguration && selectedTemplateConfiguration.cpunumber ? selectedTemplateConfiguration.cpunumber : 0"
+                      :minimum-cpuspeed="templateConfigurationExists && selectedTemplateConfiguration && selectedTemplateConfiguration.cpuspeed ? selectedTemplateConfiguration.cpuspeed : 0"
+                      :minimum-memory="templateConfigurationExists && selectedTemplateConfiguration && selectedTemplateConfiguration.memory ? selectedTemplateConfiguration.memory : 0"
+                      @select-compute-item="($event) => updateComputeOffering($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('serviceOfferings', $event)"
+                    ></compute-offering-selection>
+                    <compute-selection
+                      v-if="serviceOffering && (serviceOffering.iscustomized || serviceOffering.iscustomizediops)"
+                      cpuNumberInputDecorator="cpunumber"
+                      cpuSpeedInputDecorator="cpuspeed"
+                      memoryInputDecorator="memory"
+                      :preFillContent="dataPreFill"
+                      :computeOfferingId="vnfAppConfig.computeofferingid"
+                      :isConstrained="isOfferingConstrained(serviceOffering)"
+                      :minCpu="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.mincpunumber*1 : 0"
+                      :maxCpu="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.maxcpunumber*1 : Number.MAX_SAFE_INTEGER"
+                      :minMemory="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.minmemory*1 : 0"
+                      :maxMemory="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.maxmemory*1 : Number.MAX_SAFE_INTEGER"
+                      :isCustomized="serviceOffering.iscustomized"
+                      :isCustomizedIOps="'iscustomizediops' in serviceOffering && serviceOffering.iscustomizediops"
+                      @handler-error="handlerError"
+                      @update-iops-value="updateIOPSValue"
+                      @update-compute-cpunumber="updateFieldValue"
+                      @update-compute-cpuspeed="updateFieldValue"
+                      @update-compute-memory="updateFieldValue" />
+                    <span v-if="serviceOffering && serviceOffering.iscustomized">
+                      <a-form-item name="cpunumber" ref="cpunumber" class="form-item-hidden">
+                        <a-input v-model:value="form.cpunumber"/>
+                      </a-form-item>
+                      <a-form-item
+                        class="form-item-hidden"
+                        v-if="(serviceOffering && !(serviceOffering.cpuspeed > 0))"
+                        name="cpuspeed"
+                        ref="cpuspeed">
+                        <a-input v-model:value="form.cpuspeed"/>
+                      </a-form-item>
+                      <a-form-item class="form-item-hidden" name="memory" ref="memory">
+                        <a-input v-model:value="form.memory"/>
+                      </a-form-item>
+                    </span>
+                    <span v-if="tabKey!=='isoid'">
+                      {{ $t('label.override.root.diskoffering') }}
+                      <a-switch
+                        v-model:checked="showOverrideDiskOfferingOption"
+                        :checked="serviceOffering && !serviceOffering.diskofferingstrictness && showOverrideDiskOfferingOption"
+                        :disabled="(serviceOffering && serviceOffering.diskofferingstrictness)"
+                        @change="val => { updateOverrideRootDiskShowParam(val) }"
+                        style="margin-left: 10px;"/>
+                    </span>
+                    <span v-if="tabKey!=='isoid' && serviceOffering && !serviceOffering.diskofferingstrictness">
+                      <a-step
+                        :status="zoneSelected ? 'process' : 'wait'"
+                        v-if="!template.deployasis && template.childtemplates && template.childtemplates.length > 0" >
+                        <template #description>
+                          <div v-if="zoneSelected">
+                            <multi-disk-selection
+                              :items="template.childtemplates"
+                              :diskOfferings="options.diskOfferings"
+                              :zoneId="zoneId"
+                              @select-multi-disk-offering="updateMultiDiskOffering($event)" />
+                          </div>
+                        </template>
+                      </a-step>
+                      <a-step
+                        v-else
+                        :status="zoneSelected ? 'process' : 'wait'">
+                        <template #description>
+                          <div v-if="zoneSelected">
+                            <disk-offering-selection
+                              v-if="showOverrideDiskOfferingOption"
+                              :items="options.diskOfferings"
+                              :row-count="rowCount.diskOfferings"
+                              :zoneId="zoneId"
+                              :value="overrideDiskOffering ? overrideDiskOffering.id : ''"
+                              :loading="loading.diskOfferings"
+                              :preFillContent="dataPreFill"
+                              :isIsoSelected="tabKey==='isoid'"
+                              :isRootDiskOffering="true"
+                              @on-selected-root-disk-size="onSelectRootDiskSize"
+                              @select-disk-offering-item="($event) => updateOverrideDiskOffering($event)"
+                              @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
+                            ></disk-offering-selection>
+                            <disk-size-selection
+                              v-if="overrideDiskOffering && (overrideDiskOffering.iscustomized || overrideDiskOffering.iscustomizediops)"
+                              input-decorator="rootdisksize"
+                              :preFillContent="dataPreFill"
+                              :minDiskSize="dataPreFill.minrootdisksize"
+                              :rootDiskSelected="overrideDiskOffering"
+                              :isCustomized="overrideDiskOffering.iscustomized"
+                              @handler-error="handlerError"
+                              @update-disk-size="updateFieldValue"
+                              @update-root-disk-iops-value="updateIOPSValue"/>
+                            <a-form-item class="form-item-hidden">
+                              <a-input v-model:value="form.rootdisksize"/>
+                            </a-form-item>
+                          </div>
+                        </template>
+                      </a-step>
+                    </span>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.data.disk')"
+                :status="zoneSelected ? 'process' : 'wait'"
+                v-if="!template.deployasis && template.childtemplates && template.childtemplates.length > 0" >
+                <template #description>
+                  <div v-if="zoneSelected">
+                    <multi-disk-selection
+                      :items="template.childtemplates"
+                      :diskOfferings="options.diskOfferings"
+                      :zoneId="zoneId"
+                      @select-multi-disk-offering="updateMultiDiskOffering($event)" />
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                v-else-if="vm.templateid && template.templatetype !== 'VNF'"
+                :title="tabKey === 'templateid' ? $t('label.data.disk') : $t('label.disk.size')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template #description>
+                  <div v-if="zoneSelected">
+                    <span>
+                      {{ $t('label.data.disk') }}
+                      <a-switch
+                        v-model:checked="showDiskOfferingOption"
+                        @change="updateDiskOffering(0)"
+                        style="margin-left: 10px;"/>
+                    </span>
+                    <disk-offering-selection
+                      v-if="showDiskOfferingOption"
+                      :items="options.diskOfferings"
+                      :row-count="rowCount.diskOfferings"
+                      :zoneId="zoneId"
+                      :value="diskOffering ? diskOffering.id : ''"
+                      :loading="loading.diskOfferings"
+                      :preFillContent="dataPreFill"
+                      :isIsoSelected="tabKey==='isoid'"
+                      @on-selected-disk-size="onSelectDiskSize"
+                      @select-disk-offering-item="($event) => updateDiskOffering($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
+                    ></disk-offering-selection>
+                    <disk-size-selection
+                      v-if="diskOffering && (diskOffering.iscustomized || diskOffering.iscustomizediops)"
+                      input-decorator="size"
+                      :preFillContent="dataPreFill"
+                      :diskSelected="diskSelected"
+                      :isCustomized="diskOffering.iscustomized"
+                      @handler-error="handlerError"
+                      @update-disk-size="updateFieldValue"
+                      @update-iops-value="updateIOPSValue"/>
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-model:value="form.size"/>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.networks')"
+                :status="zoneSelected ? 'process' : 'wait'"
+                v-if="zone && zone.networktype !== 'Basic'">
+                <template #description>
+                  <div v-if="zoneSelected" style="margin-top: 5px">
+                    <div style="margin-bottom: 10px">
+                      {{ $t('message.vnf.appliance.networks') }}
+                    </div>
+                    <div v-if="vm.templateid && templateNics && templateNics.length > 0">
+                      <instance-nics-network-select-list-view
+                        :nics="templateNics"
+                        :zoneid="selectedZone"
+                        @select="handleNicsNetworkSelection" />
+                    </div>
+                    <div v-else>
+                      <network-selection
+                        :items="options.networks"
+                        :row-count="rowCount.networks"
+                        :value="networkOfferingIds"
+                        :loading="loading.networks"
+                        :zoneId="zoneId"
+                        :vnf="true"
+                        :preFillContent="dataPreFill"
+                        @select-network-item="($event) => updateNetworks($event)"
+                        @handle-search-filter="($event) => handleSearchFilter('networks', $event)"
+                      ></network-selection>
+                      <network-configuration
+                        v-if="networks.length > 0"
+                        :items="networks"
+                        :vnf="true"
+                        :preFillContent="dataPreFill"
+                        @update-network-config="($event) => updateNetworkConfig($event)"
+                        @handler-error="($event) => hasError = $event"
+                        @select-default-network-item="($event) => updateDefaultNetworks($event)"
+                      ></network-configuration>
+                    </div>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.vnf.nic.mappings')"
+                :status="zoneSelected ? 'process' : 'wait'"
+                v-if="showVnfNicsSection">
+                <template #description>
+                  <div>
+                    <vnf-nics-selection
+                      :items="templateVnfNics"
+                      :networks="networks"
+                      @update-vnf-nic-networks="($event) => updateVnfNicNetworks($event)" />
+                  </div>
+                  <div style="margin-top: 15px" v-if="showVnfConfigureManagement">
+                    <a-form-item name="vnfconfiguremanagement" ref="vnfconfiguremanagement">
+                      <template #label>
+                        <tooltip-label :title="$t('label.vnf.configure.management')" :tooltip="$t('label.vnf.configure.management.tooltip')"/>
+                      </template>
+                      <a-switch v-model:checked="form.vnfconfiguremanagement" />
+                    </a-form-item>
+                    <a-form-item name="vnfcidrlist" ref="vnfcidrlist" v-if="form.vnfconfiguremanagement === true">
+                      <template #label>
+                        <tooltip-label :title="$t('label.vnf.cidr.list')" :tooltip="$t('label.vnf.cidr.list.tooltip')"/>
+                      </template>
+                      <a-input v-model:value="form.vnfcidrlist" />
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                v-if="showSecurityGroupSection"
+                :title="$t('label.security.groups')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template #description>
+                  <security-group-selection
+                    :zoneId="zoneId"
+                    :value="securitygroupids"
+                    :loading="loading.networks"
+                    :preFillContent="dataPreFill"
+                    @select-security-group-item="($event) => updateSecurityGroups($event)"></security-group-selection>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.ovf.properties')"
+                :status="zoneSelected ? 'process' : 'wait'"
+                v-if="vm.templateid && templateProperties && Object.keys(templateProperties).length > 0">
+                <template #description>
+                  <div v-for="(props, category) in templateProperties" :key="category">
+                    <a-alert :message="'Category: ' + category + ' (' + props.length + ' properties)'" type="info" />
+                    <div style="margin-left: 15px; margin-top: 10px">
+                      <a-form-item
+                        v-for="(property, propertyIndex) in props"
+                        :key="propertyIndex"
+                        :v-bind="property.key"
+                        :name="'properties.' + escapePropertyKey(property.key)"
+                        :ref="'properties.' + escapePropertyKey(property.key)">
+                        <tooltip-label style="text-transform: capitalize" :title="property.label" :tooltip="property.description"/>
+
+                        <span v-if="property.type && property.type==='boolean'">
+                          <a-switch
+                            v-model:checked="form['properties.' + escapePropertyKey(property.key)]"
+                            :placeholder="property.description"
+                          />
+                        </span>
+                        <span v-else-if="property.type && (property.type==='int' || property.type==='real')">
+                          <a-input-number
+                            v-model:value="form['properties.'+ escapePropertyKey(property.key)]"
+                            :placeholder="property.description"
+                            :min="getPropertyQualifiers(property.qualifiers, 'number-select').min"
+                            :max="getPropertyQualifiers(property.qualifiers, 'number-select').max" />
+                        </span>
+                        <span v-else-if="property.type && property.type==='string' && property.qualifiers && property.qualifiers.startsWith('ValueMap')">
+                          <a-select
+                            showSearch
+                            optionFilterProp="value"
+                            v-model:value="form['properties.' + escapePropertyKey(property.key)]"
+                            :placeholder="property.description"
+                            :filterOption="(input, option) => {
+                              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                            }"
+                          >
+                            <a-select-option v-for="opt in getPropertyQualifiers(property.qualifiers, 'select')" :key="opt">
+                              {{ opt }}
+                            </a-select-option>
+                          </a-select>
+                        </span>
+                        <span v-else-if="property.type && property.type==='string' && property.password">
+                          <a-input-password
+                            v-model:value="form['properties.' + escapePropertyKey(property.key)]"
+                            :placeholder="property.description" />
+                        </span>
+                        <span v-else>
+                          <a-input
+                            v-model:value="form['properties.' + escapePropertyKey(property.key)]"
+                            :placeholder="property.description" />
+                        </span>
+                      </a-form-item>
+                    </div>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.advanced.mode')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template #description v-if="zoneSelected">
+                  <span>
+                    {{ $t('label.isadvanced') }}
+                    <a-switch v-model:checked="showDetails" style="margin-left: 10px"/>
+                  </span>
+                  <div style="margin-top: 15px" v-if="showDetails">
+                    <div>
+                      <a-form-item
+                        v-if="!isNormalAndDomainUser"
+                        :label="$t('label.podid')"
+                        name="podid"
+                        ref="podid">
+                        <a-select
+                          v-model:value="form.podid"
+                          showSearch
+                          optionFilterProp="label"
+                          :filterOption="filterOption"
+                          :options="podSelectOptions"
+                          :loading="loading.pods"
+                          @change="onSelectPodId"
+                        ></a-select>
+                      </a-form-item>
+                      <a-form-item
+                        v-if="!isNormalAndDomainUser"
+                        :label="$t('label.clusterid')"
+                        name="clusterid"
+                        ref="clusterid">
+                        <a-select
+                          v-model:value="form.clusterid"
+                          showSearch
+                          optionFilterProp="label"
+                          :filterOption="filterOption"
+                          :options="clusterSelectOptions"
+                          :loading="loading.clusters"
+                          @change="onSelectClusterId"
+                        ></a-select>
+                      </a-form-item>
+                      <a-form-item
+                        v-if="!isNormalAndDomainUser"
+                        :label="$t('label.hostid')"
+                        name="hostid"
+                        ref="hostid">
+                        <a-select
+                          v-model:value="form.hostid"
+                          showSearch
+                          optionFilterProp="label"
+                          :filterOption="filterOption"
+                          :options="hostSelectOptions"
+                          :loading="loading.hosts"
+                          @change="onSelectHostId"
+                        ></a-select>
+                      </a-form-item>
+                    </div>
+                    <div
+                      v-if="vm.templateid && ['KVM', 'VMware', 'XenServer'].includes(hypervisor) && !template.deployasis">
+                      <a-form-item :label="$t('label.boottype')" name="boottype" ref="boottype">
+                        <a-select
+                          v-model:value="form.boottype"
+                          @change="onBootTypeChange"
+                          showSearch
+                          optionFilterProp="label"
+                          :filterOption="filterOption">
+                          <a-select-option v-for="bootType in options.bootTypes" :key="bootType.id" :label="bootType.description">
+                            {{ bootType.description }}
+                          </a-select-option>
+                        </a-select>
+                      </a-form-item>
+                      <a-form-item :label="$t('label.bootmode')" name="bootmode" ref="bootmode">
+                        <a-select
+                          v-model:value="form.bootmode"
+                          showSearch
+                          optionFilterProp="label"
+                          :filterOption="filterOption">
+                          <a-select-option v-for="bootMode in options.bootModes" :key="bootMode.id" :label="bootMode.description">
+                            {{ bootMode.description }}
+                          </a-select-option>
+                        </a-select>
+                      </a-form-item>
+                    </div>
+                    <a-form-item
+                      :label="$t('label.bootintosetup')"
+                      v-if="zoneSelected && ((tabKey === 'isoid' && hypervisor === 'VMware') || (tabKey === 'templateid' && template && template.hypervisor === 'VMware'))"
+                      name="bootintosetup"
+                      ref="bootintosetup">
+                      <a-switch v-model:checked="form.bootintosetup" />
+                    </a-form-item>
+                    <a-form-item name="dynamicscalingenabled" ref="dynamicscalingenabled">
+                      <template #label>
+                        <tooltip-label :title="$t('label.dynamicscalingenabled')" :tooltip="$t('label.dynamicscalingenabled.tooltip')"/>
+                      </template>
+                      <a-form-item name="dynamicscalingenabled" ref="dynamicscalingenabled">
+                        <a-switch
+                          v-model:checked="form.dynamicscalingenabled"
+                          :checked="isDynamicallyScalable() && dynamicscalingenabled"
+                          :disabled="!isDynamicallyScalable()"
+                          @change="val => { dynamicscalingenabled = val }"/>
+                      </a-form-item>
+                    </a-form-item>
+                    <a-form-item :label="$t('label.userdata')">
+                      <a-card>
+                        <div v-if="this.template && this.template.userdataid">
+                          <a-text type="primary">
+                              Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}"
+                          </a-text><br/><br/>
+                          <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride">
+                            <a-text type="primary" v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0">
+                                Enter the values for the variables in userdata
+                            </a-text>
+                            <a-input-group>
+                              <a-table
+                                size="small"
+                                style="overflow-y: auto"
+                                :columns="userDataParamCols"
+                                :dataSource="templateUserDataParams"
+                                :pagination="false"
+                                :rowKey="record => record.key">
+                                <template #bodyCell="{ column, record }">
+                                  <template v-if="column.key === 'value'">
+                                    <a-input v-model:value="templateUserDataValues[record.key]" />
+                                  </template>
+                                </template>
+                              </a-table>
+                            </a-input-group>
+                          </div>
+                        </div>
+                        <div v-if="this.iso && this.iso.userdataid">
+                          <a-text type="primary">
+                              Userdata "{{ $t(this.iso.userdataname) }}" is linked with ISO "{{ $t(this.iso.name) }}" with override policy "{{ $t(this.iso.userdatapolicy) }}"
+                          </a-text><br/><br/>
+                          <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride">
+                            <a-text type="primary" v-if="this.iso && this.iso.userdataid && templateUserDataParams.length > 0">
+                                Enter the values for the variables in userdata
+                            </a-text>
+                            <a-input-group>
+                              <a-table
+                                size="small"
+                                style="overflow-y: auto"
+                                :columns="userDataParamCols"
+                                :dataSource="templateUserDataParams"
+                                :pagination="false"
+                                :rowKey="record => record.key">
+                                <template #bodyCell="{ column, record }">
+                                  <template v-if="column.key === 'value'">
+                                    <a-input v-model:value="templateUserDataValues[record.key]" />
+                                  </template>
+                                </template>
+                              </a-table>
+                            </a-input-group>
+                          </div>
+                        </div><br/><br/>
+                        <div v-if="userdataDefaultOverridePolicy === 'ALLOWOVERRIDE' || userdataDefaultOverridePolicy === 'APPEND' || !userdataDefaultOverridePolicy">
+                          <span v-if="userdataDefaultOverridePolicy === 'ALLOWOVERRIDE'" >
+                            {{ $t('label.userdata.do.override') }}
+                            <a-switch v-model:checked="doUserdataOverride" style="margin-left: 10px"/>
+                          </span>
+                          <span v-if="userdataDefaultOverridePolicy === 'APPEND'">
+                            {{ $t('label.userdata.do.append') }}
+                            <a-switch v-model:checked="doUserdataAppend" style="margin-left: 10px"/>
+                          </span>
+                          <a-step
+                            :status="zoneSelected ? 'process' : 'wait'">
+                            <template #description>
+                              <div v-if="doUserdataOverride || doUserdataAppend || !userdataDefaultOverridePolicy" style="margin-top: 15px">
+                                <a-card
+                                  :tabList="userdataTabList"
+                                  :activeTabKey="userdataTabKey"
+                                  @tabChange="key => onUserdataTabChange(key, 'userdataTabKey')">
+                                  <div v-if="userdataTabKey === 'userdataregistered'">
+                                    <a-step
+                                      v-if="isUserAllowedToListUserDatas"
+                                      :status="zoneSelected ? 'process' : 'wait'">
+                                      <template #description>
+                                        <div v-if="zoneSelected">
+                                          <user-data-selection
+                                            :items="options.userDatas"
+                                            :row-count="rowCount.userDatas"
+                                            :zoneId="zoneId"
+                                            :disabled="template.userdatapolicy === 'DENYOVERRIDE'"
+                                            :loading="loading.userDatas"
+                                            :preFillContent="dataPreFill"
+                                            @select-user-data-item="($event) => updateUserData($event)"
+                                            @handle-search-filter="($event) => handleSearchFilter('userData', $event)"
+                                          />
+                                          <div v-if="userDataParams.length > 0">
+                                            <a-input-group>
+                                              <a-table
+                                                size="small"
+                                                style="overflow-y: auto"
+                                                :columns="userDataParamCols"
+                                                :dataSource="userDataParams"
+                                                :pagination="false"
+                                                :rowKey="record => record.key">
+                                                <template #bodyCell="{ column, record }">
+                                                  <template v-if="column.key === 'value'">
+                                                    <a-input v-model:value="userDataValues[record.key]" />
+                                                  </template>
+                                                </template>
+                                              </a-table>
+                                            </a-input-group>
+                                          </div>
+                                        </div>
+                                      </template>
+                                    </a-step>
+                                  </div>
+                                  <div v-else>
+                                    <a-form-item name="userdata" ref="userdata" >
+                                      <a-textarea
+                                        placeholder="Userdata"
+                                        v-model:value="form.userdata">
+                                      </a-textarea>
+                                    </a-form-item>
+                                  </div>
+                                </a-card>
+                              </div>
+                            </template>
+                          </a-step>
+                        </div>
+                      </a-card>
+                    </a-form-item>
+                    <a-step
+                      v-if="isUserAllowedToListSshKeys"
+                      :title="$t('label.sshkeypairs')"
+                      :status="zoneSelected ? 'process' : 'wait'">
+                      <template #description>
+                        <div v-if="zoneSelected">
+                          <ssh-key-pair-selection
+                            :items="options.sshKeyPairs"
+                            :row-count="rowCount.sshKeyPairs"
+                            :zoneId="zoneId"
+                            :value="sshKeyPairs"
+                            :loading="loading.sshKeyPairs"
+                            :preFillContent="dataPreFill"
+                            @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)"
+                            @handle-search-filter="($event) => handleSearchFilter('sshKeyPairs', $event)"
+                          />
+                        </div>
+                      </template>
+                    </a-step>
+                    <a-form-item :label="$t('label.affinity.groups')">
+                      <affinity-group-selection
+                        :items="options.affinityGroups"
+                        :row-count="rowCount.affinityGroups"
+                        :zoneId="zoneId"
+                        :value="affinityGroupIds"
+                        :loading="loading.affinityGroups"
+                        :preFillContent="dataPreFill"
+                        @select-affinity-group-item="($event) => updateAffinityGroups($event)"
+                        @handle-search-filter="($event) => handleSearchFilter('affinityGroups', $event)"/>
+                    </a-form-item>
+                    <a-form-item name="nicmultiqueuenumber" ref="nicmultiqueuenumber" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
+                      <template #label>
+                        <tooltip-label :title="$t('label.nicmultiqueuenumber')" :tooltip="$t('label.nicmultiqueuenumber.tooltip')"/>
+                      </template>
+                      <a-input-number
+                        style="width: 100%;"
+                        v-model:value="form.nicmultiqueuenumber" />
+                    </a-form-item>
+                    <a-form-item name="nicpackedvirtqueuesenabled" ref="nicpackedvirtqueuesenabled" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
+                      <template #label>
+                        <tooltip-label :title="$t('label.nicpackedvirtqueuesenabled')" :tooltip="$t('label.nicpackedvirtqueuesenabled.tooltip')"/>
+                      </template>
+                      <a-switch
+                        v-model:checked="form.nicpackedvirtqueuesenabled"
+                        :checked="nicpackedvirtqueuesenabled"
+                        @change="val => { nicpackedvirtqueuesenabled = val }"/>
+                    </a-form-item>
+                    <a-form-item name="iothreadsenabled" ref="iothreadsenabled" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
+                      <template #label>
+                        <tooltip-label :title="$t('label.iothreadsenabled')" :tooltip="$t('label.iothreadsenabled.tooltip')"/>
+                      </template>
+                      <a-form-item name="iothreadsenabled" ref="iothreadsenabled">
+                        <a-switch
+                          v-model:checked="form.iothreadsenabled"
+                          :checked="iothreadsenabled"
+                          @change="val => { iothreadsenabled = val }"/>
+                      </a-form-item>
+                    </a-form-item>
+                    <a-form-item name="iodriverpolicy" ref="iodriverpolicy" v-if="vm.templateid && ['KVM'].includes(hypervisor)">
+                      <template #label>
+                        <tooltip-label :title="$t('label.iodriverpolicy')" :tooltip="$t('label.iodriverpolicy.tooltip')"/>
+                      </template>
+                      <a-select
+                        v-model:value="form.iodriverpolicy"
+                        optionFilterProp="label"
+                        :filterOption="filterOption">
+                        <a-select-option v-for="iodriverpolicy in options.ioPolicyTypes" :key="iodriverpolicy.id" :label="iodriverpolicy.description">
+                          {{ iodriverpolicy.description }}
+                        </a-select-option>
+                      </a-select>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.details')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template #description v-if="zoneSelected">
+                  <div style="margin-top: 15px">
+                    {{ $t('message.vm.review.launch') }}
+                    <a-form-item :label="$t('label.name.optional')" name="name" ref="name">
+                      <a-input v-model:value="form.name" />
+                    </a-form-item>
+                    <a-form-item :label="$t('label.group.optional')" name="group" ref="group">
+                      <a-auto-complete
+                        v-model:value="form.group"
+                        :filterOption="filterOption"
+                        :options="options.instanceGroups" />
+                    </a-form-item>
+                    <a-form-item :label="$t('label.keyboard')" name="keyboard" ref="keyboard">
+                      <a-select
+                        v-model:value="form.keyboard"
+                        :options="keyboardSelectOptions"
+                        showSearch
+                        optionFilterProp="label"
+                        :filterOption="filterOption"
+                      ></a-select>
+                    </a-form-item>
+                    <a-form-item :label="$t('label.action.start.instance')" name="startvm" ref="startvm">
+                      <a-switch v-model:checked="form.startvm" />
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="$t('label.license.agreements')"
+                :status="zoneSelected ? 'process' : 'wait'"
+                v-if="vm.templateid && templateLicenses && templateLicenses.length > 0">
+                <template #description>
+                  <div style="margin-top: 10px">
+                    {{ $t('message.read.accept.license.agreements') }}
+                    <a-form-item
+                      style="margin-top: 10px"
+                      v-for="(license, licenseIndex) in templateLicenses"
+                      :key="licenseIndex"
+                      :v-bind="license.id">
+                      <template #label>
+                        <tooltip-label style="text-transform: capitalize" :title="$t('label.agreement' + ' ' + (licenseIndex+1) + ': ' + license.name)"/>
+                      </template>
+                      <a-textarea
+                        v-model:value="license.text"
+                        :auto-size="{ minRows: 3, maxRows: 8 }"
+                        readOnly />
+                      <a-checkbox
+                        style="margin-top: 10px"
+                        v-model:checked="form.licensesaccepted">
+                        {{ $t('label.i.accept.all.license.agreements') }}
+                      </a-checkbox>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+            </a-steps>
+            <div class="card-footer">
+              <a-form-item name="stayonpage" ref="stayonpage">
+                <a-switch
+                  class="form-item-hidden"
+                  v-model:checked="form.stayonpage" />
+              </a-form-item>
+              <!-- ToDo extract as component -->
+              <a-button @click="() => $router.back()" :disabled="loading.deploy">
+                {{ $t('label.cancel') }}
+              </a-button>
+              <a-dropdown-button style="margin-left: 10px" type="primary" ref="submit" @click="handleSubmit" :loading="loading.deploy">
+                <rocket-outlined />
+                {{ $t('label.launch.vnf.appliance') }}
+                <template #icon><down-outlined /></template>
+                <template #overlay>
+                  <a-menu type="primary" @click="handleSubmitAndStay" theme="dark" class="btn-stay-on-page">
+                    <a-menu-item type="primary" key="1">
+                      <rocket-outlined />
+                      {{ $t('label.launch.vnf.appliance.and.stay') }}
+                    </a-menu-item>
+                  </a-menu>
+                </template>
+              </a-dropdown-button>
+            </div>
+          </a-form>
+        </a-card>
+      </a-col>
+      <a-col :md="24" :lg="7" v-if="!isMobile()">
+        <a-affix :offsetTop="75" class="vm-info-card">
+          <info-card :resource="vm" :title="$t('label.vnf.appliance')" @change-resource="(data) => resource = data" />
+        </a-affix>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script>
+import { ref, reactive, toRaw, nextTick } from 'vue'
+import { api } from '@/api'
+import _ from 'lodash'
+import { mixin, mixinDevice } from '@/utils/mixin.js'
+import store from '@/store'
+import eventBus from '@/config/eventBus'
+
+import InfoCard from '@/components/view/InfoCard'
+import ResourceIcon from '@/components/view/ResourceIcon'
+import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection'
+import ComputeSelection from '@views/compute/wizard/ComputeSelection'
+import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
+import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
+import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection'
+import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
+import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection'
+import NetworkSelection from '@views/compute/wizard/NetworkSelection'
+import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration'
+import SshKeyPairSelection from '@views/compute/wizard/SshKeyPairSelection'
+import UserDataSelection from '@views/compute/wizard/UserDataSelection'
+import SecurityGroupSelection from '@views/compute/wizard/SecurityGroupSelection'
+import VnfNicsSelection from '@views/compute/wizard/VnfNicsSelection.vue'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import InstanceNicsNetworkSelectListView from '@/components/view/InstanceNicsNetworkSelectListView.vue'
+
+export default {
+  name: 'Wizard',
+  components: {
+    SshKeyPairSelection,
+    UserDataSelection,
+    NetworkConfiguration,
+    NetworkSelection,
+    AffinityGroupSelection,
+    TemplateIsoSelection,
+    DiskSizeSelection,
+    MultiDiskSelection,
+    DiskOfferingSelection,
+    InfoCard,
+    ComputeOfferingSelection,
+    ComputeSelection,
+    SecurityGroupSelection,
+    VnfNicsSelection,
+    ResourceIcon,
+    TooltipLabel,
+    InstanceNicsNetworkSelectListView
+  },
+  props: {
+    visible: {
+      type: Boolean
+    },
+    preFillContent: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  mixins: [mixin, mixinDevice],
+  data () {
+    return {
+      zoneId: '',
+      podId: null,
+      clusterId: null,
+      zoneSelected: false,
+      dynamicscalingenabled: true,
+      templateKey: 0,
+      showRegisteredUserdata: true,
+      doUserdataOverride: false,
+      doUserdataAppend: false,
+      userdataDefaultOverridePolicy: 'ALLOWOVERRIDE',
+      vm: {
+        name: null,
+        zoneid: null,
+        zonename: null,
+        hypervisor: null,
+        templateid: null,
+        templatename: null,
+        keyboard: null,
+        keypairs: [],
+        group: null,
+        affinitygroupids: [],
+        affinitygroup: [],
+        serviceofferingid: null,
+        serviceofferingname: null,
+        ostypeid: null,
+        ostypename: null,
+        rootdisksize: null,
+        disksize: null
+      },
+      options: {
+        templates: {},
+        isos: {},
+        hypervisors: [],
+        serviceOfferings: [],
+        diskOfferings: [],
+        zones: [],
+        affinityGroups: [],
+        networks: [],
+        sshKeyPairs: [],
+        UserDatas: [],
+        pods: [],
+        clusters: [],
+        hosts: [],
+        groups: [],
+        keyboards: [],
+        bootTypes: [],
+        bootModes: [],
+        ioPolicyTypes: [],
+        dynamicScalingVmConfig: false
+      },
+      rowCount: {},
+      loading: {
+        deploy: false,
+        templates: false,
+        isos: false,
+        hypervisors: false,
+        serviceOfferings: false,
+        diskOfferings: false,
+        affinityGroups: false,
+        networks: false,
+        sshKeyPairs: false,
+        userDatas: false,
+        zones: false,
+        pods: false,
+        clusters: false,
+        hosts: false,
+        groups: false
+      },
+      vnfAppConfig: {},
+      template: {},
+      defaultBootType: '',
+      defaultBootMode: '',
+      templateConfigurations: [],
+      templateNics: [],
+      templateLicenses: [],
+      templateProperties: {},
+      templateVnfNics: [],
+      vnfNicNetworks: {},
+      selectedTemplateConfiguration: {},
+      iso: {},
+      hypervisor: '',
+      serviceOffering: {},
+      diskOffering: {},
+      affinityGroups: [],
+      networks: [],
+      networksAdd: [],
+      zone: {},
+      sshKeyPairs: [],
+      sshKeyPair: {},
+      userData: {},
+      userDataParams: [],
+      userDataParamCols: [
+        {
+          title: this.$t('label.key'),
+          dataIndex: 'key'
+        },
+        {
+          title: this.$t('label.value'),
+          dataIndex: 'value',
+          key: 'value'
+        }
+      ],
+      userDataValues: {},
+      templateUserDataCols: [
+        {
+          title: this.$t('label.userdata'),
+          dataIndex: 'userdata'
+        },
+        {
+          title: this.$t('label.userdatapolicy'),
+          dataIndex: 'userdataoverridepolicy'
+        }
+      ],
+      templateUserDataParams: [],
+      templateUserDataValues: {},
+      overrideDiskOffering: {},
+      templateFilter: [
+        'featured',
+        'community',
+        'selfexecutable',
+        'sharedexecutable'
+      ],
+      isoFilter: [
+        'featured',
+        'community',
+        'selfexecutable',
+        'sharedexecutable'
+      ],
+      initDataConfig: {},
+      defaultnetworkid: '',
+      networkConfig: [],
+      dataNetworkCreated: [],
+      tabKey: 'templateid',
+      userdataTabKey: 'userdataregistered',
+      dataPreFill: {},
+      showDetails: false,
+      showRootDiskSizeChanger: false,
+      showOverrideDiskOfferingOption: false,
+      showDiskOfferingOption: false,
+      securitygroupids: [],
+      rootDiskSizeFixed: 0,
+      hasError: false,
+      error: false,
+      diskSelected: {},
+      rootDiskSelected: {},
+      diskIOpsMin: 0,
+      diskIOpsMax: 0,
+      minIops: 0,
+      maxIops: 0,
+      zones: [],
+      selectedZone: '',
+      formModel: {},
+      nicToNetworkSelection: []
+    }
+  },
+  computed: {
+    rootDiskSize () {
+      return this.showRootDiskSizeChanger && this.rootDiskSizeFixed > 0
+    },
+    isNormalAndDomainUser () {
+      return ['DomainAdmin', 'User'].includes(this.$store.getters.userInfo.roletype)
+    },
+    diskSize () {
+      const rootDiskSize = _.get(this.vnfAppConfig, 'rootdisksize', 0)
+      const customDiskSize = _.get(this.vnfAppConfig, 'size', 0)
+      const diskOfferingDiskSize = _.get(this.diskOffering, 'disksize', 0)
+      const dataDiskSize = diskOfferingDiskSize > 0 ? diskOfferingDiskSize : customDiskSize
+      const size = []
+      if (rootDiskSize > 0) {
+        size.push(`${rootDiskSize} GB (Root)`)
+      }
+      if (dataDiskSize > 0) {
+        size.push(`${dataDiskSize} GB (Data)`)
+      }
+      return size.join(' | ')
+    },
+    affinityGroupIds () {
+      return _.map(this.affinityGroups, 'id')
+    },
+    params () {
+      return {
+        serviceOfferings: {
+          list: 'listServiceOfferings',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            issystem: false,
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
+          }
+        },
+        diskOfferings: {
+          list: 'listDiskOfferings',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
+          }
+        },
+        zones: {
+          list: 'listZones',
+          isLoad: true,
+          field: 'zoneid'
+        },
+        hypervisors: {
+          list: 'listHypervisors',
+          options: {
+            zoneid: _.get(this.zone, 'id')
+          },
+          field: 'hypervisor'
+        },
+        affinityGroups: {
+          list: 'listAffinityGroups',
+          options: {
+            page: 1,
+            pageSize: 10,
+            keyword: undefined,
+            listall: false
+          }
+        },
+        sshKeyPairs: {
+          list: 'listSSHKeyPairs',
+          options: {
+            page: 1,
+            pageSize: 10,
+            keyword: undefined,
+            listall: false
+          }
+        },
+        userDatas: {
+          list: 'listUserData',
+          options: {
+            page: 1,
+            pageSize: 10,
+            keyword: undefined,
+            listall: false
+          }
+        },
+        networks: {
+          list: 'listNetworks',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            canusefordeploy: true,
+            projectid: store.getters.project ? store.getters.project.id : null,
+            domainid: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.domainid,
+            account: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.account,
+            page: 1,
+            pageSize: 10,
+            keyword: undefined,
+            showIcon: true
+          }
+        },
+        pods: {
+          list: 'listPods',
+          isLoad: !this.isNormalAndDomainUser,
+          options: {
+            zoneid: _.get(this.zone, 'id')
+          },
+          field: 'podid'
+        },
+        clusters: {
+          list: 'listClusters',
+          isLoad: !this.isNormalAndDomainUser,
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            podid: this.podId
+          },
+          field: 'clusterid'
+        },
+        hosts: {
+          list: 'listHosts',
+          isLoad: !this.isNormalAndDomainUser,
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            podid: this.podId,
+            clusterid: this.clusterId,
+            state: 'Up',
+            type: 'Routing'
+          },
+          field: 'hostid'
+        },
+        dynamicScalingVmConfig: {
+          list: 'listConfigurations',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            name: 'enable.dynamic.scale.vm'
+          }
+        }
+      }
+    },
+    networkOfferingIds () {
+      return _.map(this.networks, 'id')
+    },
+    zoneSelectOptions () {
+      return this.options.zones.map((zone) => {
+        return {
+          label: zone.name,
+          value: zone.id
+        }
+      })
+    },
+    hypervisorSelectOptions () {
+      return this.options.hypervisors.map((hypervisor) => {
+        return {
+          label: hypervisor.name,
+          value: hypervisor.name
+        }
+      })
+    },
+    podSelectOptions () {
+      const options = this.options.pods.map((pod) => {
+        return {
+          label: pod.name,
+          value: pod.id
+        }
+      })
+      options.unshift({
+        label: this.$t('label.default'),
+        value: null
+      })
+      return options
+    },
+    clusterSelectOptions () {
+      const options = this.options.clusters.map((cluster) => {
+        return {
+          label: cluster.name,
+          value: cluster.id
+        }
+      })
+      options.unshift({
+        label: this.$t('label.default'),
+        value: null
+      })
+      return options
+    },
+    hostSelectOptions () {
+      const options = this.options.hosts.map((host) => {
+        return {
+          label: host.name,
+          value: host.id
+        }
+      })
+      options.unshift({
+        label: this.$t('label.default'),
+        value: null
+      })
+      return options
+    },
+    keyboardSelectOptions () {
+      const keyboardOpts = this.$config.keyboardOptions || {}
+      return Object.keys(keyboardOpts).map((keyboard) => {
+        return {
+          label: this.$t(keyboardOpts[keyboard]),
+          value: keyboard
+        }
+      })
+    },
+    templateConfigurationExists () {
+      return this.vm.templateid && this.templateConfigurations && this.templateConfigurations.length > 0
+    },
+    templateId () {
+      return this.$route.query.templateid || null
+    },
+    isoId () {
+      return this.$route.query.isoid || null
+    },
+    networkId () {
+      return this.$route.query.networkid || null
+    },
+    tabList () {
+      const tabList = [{
+        key: 'templateid',
+        tab: this.$t('label.templates')
+      }]
+      return tabList
+    },
+    userdataTabList () {
+      let tabList = []
+      tabList = [{
+        key: 'userdataregistered',
+        tab: this.$t('label.userdata.registered')
+      },
+      {
+        key: 'userdatatext',
+        tab: this.$t('label.userdata.text')
+      }]
+
+      return tabList
+    },
+    showVnfNicsSection () {
+      return this.networks && this.networks.length > 0 && this.vm.templateid && this.templateVnfNics && this.templateVnfNics.length > 0
+    },
+    showVnfConfigureManagement () {
+      const managementDeviceIds = []
+      for (const templateVnfNic of this.templateVnfNics) {
+        if (templateVnfNic.management) {
+          managementDeviceIds.push(templateVnfNic.deviceid)
+        }
+      }
+      for (const deviceId of managementDeviceIds) {
+        if (this.vnfNicNetworks && this.vnfNicNetworks[deviceId] &&
+          ((this.vnfNicNetworks[deviceId].type === 'Isolated' && this.vnfNicNetworks[deviceId].vpcid === undefined) ||
+            (this.vnfNicNetworks[deviceId].type === 'Shared' && this.zone.securitygroupsenabled))) {
+          return true
+        }
+      }
+      return false
+    },
+    showSecurityGroupSection () {
+      return (this.networks.length > 0 && this.zone.securitygroupsenabled) || (this.zone && this.zone.networktype === 'Basic')
+    },
+    isUserAllowedToListSshKeys () {
+      return Boolean('listSSHKeyPairs' in this.$store.getters.apis)
+    },
+    isUserAllowedToListUserDatas () {
+      return Boolean('listUserData' in this.$store.getters.apis)
+    },
+    dynamicScalingVmConfigValue () {
+      return this.options.dynamicScalingVmConfig?.[0]?.value === 'true'
+    },
+    isCustomizedDiskIOPS () {
+      return this.diskSelected?.iscustomizediops || false
+    },
+    isCustomizedIOPS () {
+      return this.rootDiskSelected?.iscustomizediops || this.serviceOffering?.iscustomizediops || false
+    }
+  },
+  watch: {
+    '$route' (to, from) {
+      if (to.name === 'deployVnfAppliance') {
+        this.resetData()
+      }
+    },
+    formModel: {
+      deep: true,
+      handler (vnfAppConfig) {
+        this.vnfAppConfig = toRaw(vnfAppConfig)
+        Object.keys(vnfAppConfig).forEach(field => {
+          this.vm[field] = this.vnfAppConfig[field]
+        })
+        this.template = ''
+        for (const key in this.options.templates) {
+          var template = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === vnfAppConfig.templateid)
+          if (template) {
+            this.template = template
+            break
+          }
+        }
+
+        this.iso = ''
+        for (const key in this.options.isos) {
+          var iso = _.find(_.get(this.options.isos[key], 'iso', []), (option) => option.id === vnfAppConfig.isoid)
+          if (iso) {
+            this.iso = iso
+            break
+          }
+        }
+
+        if (vnfAppConfig.hypervisor) {
+          var hypervisorItem = _.find(this.options.hypervisors, (option) => option.name === vnfAppConfig.hypervisor)
+          this.hypervisor = hypervisorItem ? hypervisorItem.name : null
+        }
+
+        this.serviceOffering = _.find(this.options.serviceOfferings, (option) => option.id === vnfAppConfig.computeofferingid)
+        if (this.serviceOffering?.diskofferingid) {
+          if (iso) {
+            this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === this.serviceOffering.diskofferingid)
+          } else {
+            vnfAppConfig.overridediskofferingid = this.serviceOffering.diskofferingid
+          }
+        }
+        if (!iso && this.diskSelected) {
+          this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === vnfAppConfig.diskofferingid)
+        }
+        if (this.rootDiskSelected?.id) {
+          vnfAppConfig.overridediskofferingid = this.rootDiskSelected.id
+        }
+        if (vnfAppConfig.overridediskofferingid) {
+          this.overrideDiskOffering = _.find(this.options.diskOfferings, (option) => option.id === vnfAppConfig.overridediskofferingid)
+        } else {
+          this.overrideDiskOffering = null
+        }
+
+        if (!iso && this.diskSelected) {
+          this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === vnfAppConfig.diskofferingid)
+        }
+        if (this.rootDiskSelected?.id) {
+          vnfAppConfig.overridediskofferingid = this.rootDiskSelected.id
+        }
+        if (vnfAppConfig.overridediskofferingid) {
+          this.overrideDiskOffering = _.find(this.options.diskOfferings, (option) => option.id === vnfAppConfig.overridediskofferingid)
+        } else {
+          this.overrideDiskOffering = null
+        }
+        this.zone = _.find(this.options.zones, (option) => option.id === vnfAppConfig.zoneid)
+        this.affinityGroups = _.filter(this.options.affinityGroups, (option) => _.includes(vnfAppConfig.affinitygroupids, option.id))
+        this.networks = this.getSelectedNetworksWithExistingConfig(_.filter(this.options.networks, (option) => _.includes(vnfAppConfig.networkids, option.id)))
+
+        this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === vnfAppConfig.diskofferingid)
+        this.sshKeyPair = _.find(this.options.sshKeyPairs, (option) => option.name === vnfAppConfig.keypair)
+
+        if (this.zone) {
+          this.vm.zoneid = this.zone.id
+          this.vm.zonename = this.zone.name
+        }
+
+        const pod = _.find(this.options.pods, (option) => option.id === vnfAppConfig.podid)
+        if (pod) {
+          this.vm.podid = pod.id
+          this.vm.podname = pod.name
+        }
+
+        const cluster = _.find(this.options.clusters, (option) => option.id === vnfAppConfig.clusterid)
+        if (cluster) {
+          this.vm.clusterid = cluster.id
+          this.vm.clustername = cluster.name
+        }
+
+        const host = _.find(this.options.hosts, (option) => option.id === vnfAppConfig.hostid)
+        if (host) {
+          this.vm.hostid = host.id
+          this.vm.hostname = host.name
+        }
+
+        if (this.serviceOffering?.rootdisksize) {
+          this.vm.disksizetotalgb = this.serviceOffering.rootdisksize
+        } else if (this.diskSize) {
+          this.vm.disksizetotalgb = this.diskSize
+        } else {
+          this.vm.disksizetotalgb = null
+        }
+
+        if (this.diskSize) {
+          this.vm.disksizetotalgb = this.diskSize
+        }
+
+        if (this.networks) {
+          this.vm.networks = this.networks
+          this.vm.defaultnetworkid = this.defaultnetworkid
+        }
+
+        if (this.template) {
+          this.vm.templateid = this.template.id
+          this.vm.templatename = this.template.displaytext
+          this.vm.ostypeid = this.template.ostypeid
+          this.vm.ostypename = this.template.ostypename
+        }
+
+        if (this.iso) {
+          this.vm.isoid = this.iso.id
+          this.vm.templateid = this.iso.id
+          this.vm.templatename = this.iso.displaytext
+          this.vm.ostypeid = this.iso.ostypeid
+          this.vm.ostypename = this.iso.ostypename
+          if (this.hypervisor) {
+            this.vm.hypervisor = this.hypervisor
+          }
+        }
+
+        if (this.serviceOffering) {
+          this.vm.serviceofferingid = this.serviceOffering.id
+          this.vm.serviceofferingname = this.serviceOffering.displaytext
+          if (this.serviceOffering.cpunumber) {
+            this.vm.cpunumber = this.serviceOffering.cpunumber
+          }
+          if (this.serviceOffering.cpuspeed) {
+            this.vm.cpuspeed = this.serviceOffering.cpuspeed
+          }
+          if (this.serviceOffering.memory) {
+            this.vm.memory = this.serviceOffering.memory
+          }
+        }
+
+        if (!this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) {
+          this.vm.diskofferingid = ''
+          this.vm.diskofferingname = ''
+          this.vm.diskofferingsize = ''
+        } else if (this.diskOffering) {
+          this.vm.diskofferingid = this.diskOffering.id
+          this.vm.diskofferingname = this.diskOffering.displaytext
+          this.vm.diskofferingsize = this.diskOffering.disksize
+        }
+
+        if (this.affinityGroups) {
+          this.vm.affinitygroup = this.affinityGroups
+        }
+
+        if (this.sshKeyPairs && this.sshKeyPairs.length > 0) {
+          this.vm.keypairs = this.sshKeyPairs
+        }
+      }
+    }
+  },
+  serviceOffering (oldValue, newValue) {
+    if (oldValue && newValue && oldValue.id !== newValue.id) {
+      this.dynamicscalingenabled = this.isDynamicallyScalable()
+    }
+  },
+  template (oldValue, newValue) {
+    if (oldValue && newValue && oldValue.id !== newValue.id) {
+      this.dynamicscalingenabled = this.isDynamicallyScalable()
+      this.doUserdataOverride = false
+      this.doUserdataAppend = false
+    }
+  },
+  created () {
+    this.initForm()
+    this.dataPreFill = this.preFillContent && Object.keys(this.preFillContent).length > 0 ? this.preFillContent : {}
+    this.fetchData()
+  },
+  provide () {
+    return {
+      vmFetchTemplates: this.fetchAllTemplates,
+      vmFetchIsos: this.fetchAllIsos,
+      vmFetchNetworks: this.fetchNetwork
+    }
+  },
+  methods: {
+    updateTemplateKey () {
+      this.templateKey += 1
+    },
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({
+        zoneid: [{ required: true, message: `${this.$t('message.error.select')}` }],
+        hypervisor: [{ required: true, message: `${this.$t('message.error.select')}` }]
+      })
+
+      if (this.zoneSelected) {
+        this.form.startvm = true
+        this.form.vnfconfiguremanagement = false
+        this.form.vnfcidrlist = '0.0.0.0/0'
+      }
+
+      if (this.zone && this.zone.networktype !== 'Basic') {
+        if (this.zoneSelected && this.vm.templateid && this.templateNics && this.templateNics.length > 0) {
+          this.templateNics.forEach((nic, nicIndex) => {
+            this.form['networkMap.nic-' + nic.InstanceID.toString()] = this.options.networks && this.options.networks.length > 0
+              ? this.options.networks[Math.min(nicIndex, this.options.networks.length - 1)].id
+              : null
+          })
+        }
+
+        if (this.vm.templateid && this.templateProperties && Object.keys(this.templateProperties).length > 0) {
+          this.templateProperties.forEach((props, category) => {
+            props.forEach((property, propertyIndex) => {
+              if (property.type && property.type === 'boolean') {
+                this.form['properties.' + this.escapePropertyKey(property.key)] = property.value === 'TRUE'
+              } else if (property.type && (property.type === 'int' || property.type === 'real')) {
+                this.form['properties.' + this.escapePropertyKey(property.key)] = property.value
+              } else if (property.type && property.type === 'string' && property.qualifiers && property.qualifiers.startsWith('ValueMap')) {
+                this.form['properties.' + this.escapePropertyKey(property.key)] = property.value && property.value.length > 0
+                  ? property.value
+                  : this.getPropertyQualifiers(property.qualifiers, 'select')[0]
+              } else if (property.type && property.type === 'string' && property.password) {
+                this.form['properties.' + this.escapePropertyKey(property.key)] = property.value
+                this.rules['properties.' + this.escapePropertyKey(property.key)] = [{
+                  validator: async (rule, value) => {
+                    if (!property.qualifiers) {
+                      return Promise.resolve()
+                    }
+                    var minlength = this.getPropertyQualifiers(property.qualifiers, 'number-select').min
+                    var maxlength = this.getPropertyQualifiers(property.qualifiers, 'number-select').max
+                    var errorMessage = ''
+                    var isPasswordInvalidLength = function () {
+                      return false
+                    }
+                    if (minlength) {
+                      errorMessage = this.$t('message.validate.minlength').replace('{0}', minlength)
+                      isPasswordInvalidLength = function () {
+                        return !value || value.length < minlength
+                      }
+                    }
+                    if (maxlength !== Number.MAX_SAFE_INTEGER) {
+                      if (minlength) {
+                        errorMessage = this.$t('message.validate.range.length').replace('{0}', minlength).replace('{1}', maxlength)
+                        isPasswordInvalidLength = function () {
+                          return !value || (maxlength < value.length || value.length < minlength)
+                        }
+                      } else {
+                        errorMessage = this.$t('message.validate.maxlength').replace('{0}', maxlength)
+                        isPasswordInvalidLength = function () {
+                          return !value || value.length > maxlength
+                        }
+                      }
+                    }
+                    if (isPasswordInvalidLength()) {
+                      return Promise.reject(errorMessage)
+                    }
+                    return Promise.resolve()
+                  }
+                }]
+              } else {
+                this.form['properties.' + this.escapePropertyKey(property.key)] = property.value
+              }
+            })
+          })
+        }
+
+        if (this.vm.templateid && this.templateLicenses && this.templateLicenses.length > 0) {
+          this.rules.licensesaccepted = [{
+            validator: async (rule, value) => {
+              if (!value) {
+                return Promise.reject(this.$t('message.license.agreements.not.accepted'))
+              }
+              return Promise.resolve()
+            }
+          }]
+        }
+      }
+    },
+    getPropertyQualifiers (qualifiers, type) {
+      var result = ''
+      switch (type) {
+        case 'select':
+          result = []
+          if (qualifiers && qualifiers.includes('ValueMap')) {
+            result = qualifiers.replace('ValueMap', '').substr(1).slice(0, -1).split(',')
+            for (var i = 0; i < result.length; i++) {
+              result[i] = result[i].replace(/"/g, '')
+            }
+          }
+          break
+        case 'number-select':
+          var min = 0
+          var max = Number.MAX_SAFE_INTEGER
+          if (qualifiers) {
+            var match = qualifiers.match(/MinLen\((\d+)\)/)
+            if (match) {
+              min = parseInt(match[1])
+            }
+            match = qualifiers.match(/MaxLen\((\d+)\)/)
+            if (match) {
+              max = parseInt(match[1])
+            }
+          }
+          result = { min: min, max: max }
+          break
+        default:
+      }
+      return result
+    },
+    fillValue (field) {
+      this.form[field] = this.dataPreFill[field]
+    },
+    fetchZoneByQuery () {
+      return new Promise(resolve => {
+        let zones = []
+        let apiName = ''
+        const params = {}
+        if (this.templateId) {
+          apiName = 'listVnfTemplates'
+          params.listall = true
+          params.templatefilter = this.isNormalAndDomainUser ? 'executable' : 'all'
+          params.id = this.templateId
+        } else if (this.isoId) {
+          params.listall = true
+          params.isofilter = this.isNormalAndDomainUser ? 'executable' : 'all'
+          params.id = this.isoId
+          apiName = 'listIsos'
+        } else if (this.networkId) {
+          params.listall = true
+          params.id = this.networkId
+          apiName = 'listNetworks'
+        }
+        if (!apiName) return resolve(zones)
+
+        api(apiName, params).then(json => {
+          let objectName
+          const responseName = [apiName.toLowerCase(), 'response'].join('')
+          for (const key in json[responseName]) {
+            if (key === 'count') {
+              continue
+            }
+            objectName = key
+            break
+          }
+          const data = json?.[responseName]?.[objectName] || []
+          zones = data.map(item => item.zoneid)
+          return resolve(zones)
+        }).catch(() => {
+          return resolve(zones)
+        })
+      })
+    },
+    async fetchData () {
+      const zones = await this.fetchZoneByQuery()
+      if (zones && zones.length === 1) {
+        this.selectedZone = zones[0]
+        this.dataPreFill.zoneid = zones[0]
+      }
+      if (this.dataPreFill.zoneid) {
+        this.fetchDataByZone(this.dataPreFill.zoneid)
+      } else {
+        this.fetchZones(null, zones)
+        _.each(this.params, (param, name) => {
+          if (param.isLoad) {
+            this.fetchOptions(param, name)
+          }
+        })
+      }
+      this.fetchBootTypes()
+      this.fetchBootModes()
+      this.fetchInstaceGroups()
+      this.fetchIoPolicyTypes()
+      nextTick().then(() => {
+        ['name', 'keyboard', 'boottype', 'bootmode', 'userdata', 'iothreadsenabled', 'iodriverpolicy', 'nicmultiqueuenumber', 'nicpackedvirtqueues'].forEach(this.fillValue)
+        this.form.boottype = this.defaultBootType ? this.defaultBootType : this.options.bootTypes && this.options.bootTypes.length > 0 ? this.options.bootTypes[0].id : undefined
+        this.form.bootmode = this.defaultBootMode ? this.defaultBootMode : this.options.bootModes && this.options.bootModes.length > 0 ? this.options.bootModes[0].id : undefined
+        this.vnfAppConfig = toRaw(this.form)
+      })
+    },
+    isDynamicallyScalable () {
+      return this.serviceOffering && this.serviceOffering.dynamicscalingenabled && this.template && this.template.isdynamicallyscalable && this.dynamicScalingVmConfigValue
+    },
+    isOfferingConstrained (serviceOffering) {
+      return 'serviceofferingdetails' in serviceOffering && 'mincpunumber' in serviceOffering.serviceofferingdetails &&
+        'maxmemory' in serviceOffering.serviceofferingdetails && 'maxcpunumber' in serviceOffering.serviceofferingdetails &&
+        'minmemory' in serviceOffering.serviceofferingdetails
+    },
+    updateOverrideRootDiskShowParam (val) {
+      if (val) {
+        this.showRootDiskSizeChanger = false
+      } else {
+        this.rootDiskSelected = null
+      }
+      this.showOverrideDiskOfferingOption = val
+    },
+    async fetchDataByZone (zoneId) {
+      this.fillValue('zoneid')
+      this.options.zones = await this.fetchZones(zoneId)
+      this.onSelectZoneId(zoneId)
+    },
+    fetchBootTypes () {
+      this.options.bootTypes = [
+        { id: 'BIOS', description: 'BIOS' },
+        { id: 'UEFI', description: 'UEFI' }
+      ]
+    },
+    fetchBootModes (bootType) {
+      const bootModes = [
+        { id: 'LEGACY', description: 'LEGACY' }
+      ]
+      if (bootType === 'UEFI') {
+        bootModes.unshift(
+          { id: 'SECURE', description: 'SECURE' }
+        )
+      }
+      this.options.bootModes = bootModes
+    },
+    fetchIoPolicyTypes () {
+      this.options.ioPolicyTypes = [
+        { id: 'native', description: 'native' },
+        { id: 'threads', description: 'threads' },
+        { id: 'io_uring', description: 'io_uring' },
+        { id: 'storage_specific', description: 'storage_specific' }
+      ]
+    },
+    fetchInstaceGroups () {
+      this.options.instanceGroups = []
+      api('listInstanceGroups', {
+        account: this.$store.getters.userInfo.account,
+        domainid: this.$store.getters.userInfo.domainid,
+        listall: true
+      }).then(response => {
+        const groups = response.listinstancegroupsresponse.instancegroup || []
+        groups.forEach(x => {
+          this.options.instanceGroups.push({ label: x.name, value: x.name })
+        })
+      })
+    },
+    fetchNetwork () {
+      const param = this.params.networks
+      this.fetchOptions(param, 'networks')
+    },
+    resetData () {
+      this.vm = {
+        name: null,
+        zoneid: null,
+        zonename: null,
+        hypervisor: null,
+        templateid: null,
+        templatename: null,
+        keyboard: null,
+        keypair: null,
+        group: null,
+        affinitygroupids: [],
+        affinitygroup: [],
+        serviceofferingid: null,
+        serviceofferingname: null,
+        ostypeid: null,
+        ostypename: null,
+        rootdisksize: null,
+        disksize: null
+      }
+      this.zoneSelected = false
+      this.formRef.value.resetFields()
+      this.fetchData()
+    },
+    updateFieldValue (name, value) {
+      if (name === 'templateid') {
+        this.tabKey = 'templateid'
+        this.form.templateid = value
+        this.form.isoid = null
+        this.resetFromTemplateConfiguration()
+        let template = ''
+        for (const key in this.options.templates) {
+          var t = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === value)
+          if (t) {
+            this.template = t
+            this.templateConfigurations = []
+            this.selectedTemplateConfiguration = {}
+            this.templateNics = []
+            this.templateLicenses = []
+            this.templateProperties = {}
+            this.templateVnfNics = []
+            this.updateTemplateParameters()
+            template = t
+            break
+          }
+        }
+        if (template) {
+          var size = template.size / (1024 * 1024 * 1024) || 0 // bytes to GB
+          this.dataPreFill.minrootdisksize = Math.ceil(size)
+          this.updateTemplateLinkedUserData(template.userdataid)
+          this.userdataDefaultOverridePolicy = template.userdatapolicy
+          this.form.dynamicscalingenabled = template.isdynamicallyscalable
+          this.defaultBootType = template.details?.UEFI ? 'UEFI' : 'BIOS'
+          this.form.boottype = this.defaultBootType
+          this.fetchBootModes(this.form.boottype)
+          this.defaultBootMode = template.details?.UEFI || this.options.bootModes?.[0]?.id || undefined
+          this.form.bootmode = this.defaultBootMode
+          this.form.iothreadsenabled = template.details && Object.prototype.hasOwnProperty.call(template.details, 'iothreads')
+          this.form.iodriverpolicy = template.details?.['io.policy']
+          this.form.keyboard = template.details?.keyboard
+        }
+      } else if (name === 'isoid') {
+        this.templateConfigurations = []
+        this.selectedTemplateConfiguration = {}
+        this.templateNics = []
+        this.templateLicenses = []
+        this.templateProperties = {}
+        this.templateVnfNics = []
+        this.tabKey = 'isoid'
+        this.resetFromTemplateConfiguration()
+        this.form.isoid = value
+        this.form.templateid = null
+        this.updateTemplateLinkedUserData(this.iso.userdataid)
+        this.userdataDefaultOverridePolicy = this.iso.userdatapolicy
+      } else if (['cpuspeed', 'cpunumber', 'memory'].includes(name)) {
+        this.vm[name] = value
+        this.form[name] = value
+      } else {
+        this.form[name] = value
+      }
+    },
+    updateComputeOffering (id) {
+      this.form.computeofferingid = id
+      setTimeout(() => {
+        this.updateTemplateConfigurationOfferingDetails(id)
+      }, 500)
+    },
+    updateDiskOffering (id) {
+      if (id === '0') {
+        this.form.diskofferingid = undefined
+        return
+      }
+      this.form.diskofferingid = id
+    },
+    updateOverrideDiskOffering (id) {
+      if (id === '0') {
+        this.form.overridediskofferingid = undefined
+        return
+      }
+      this.form.overridediskofferingid = id
+    },
+    updateMultiDiskOffering (value) {
+      this.form.multidiskoffering = value
+    },
+    updateAffinityGroups (ids) {
+      this.form.affinitygroupids = ids
+    },
+    updateNetworks (ids) {
+      this.form.networkids = ids
+      if (!ids || ids.length === 0) {
+        this.updateDefaultNetworks(null)
+      } else if (ids && ids.length === 1) {
+        this.updateDefaultNetworks(ids[0])
+      }
+    },
+    updateDefaultNetworks (id) {
+      this.form.defaultnetworkid = id
+    },
+    updateNetworkConfig (networks) {
+      this.networkConfig = networks
+    },
+    updateVnfNicNetworks (vnfNicNetworks) {
+      this.vnfNicNetworks = vnfNicNetworks || {}
+    },
+    updateSshKeyPairs (names) {
+      this.form.keypairs = names
+      this.sshKeyPairs = names.map((sshKeyPair) => { return sshKeyPair.name })
+    },
+    updateUserData (id) {
+      if (id === '0') {
+        this.form.userdataid = undefined
+        return
+      }
+      this.form.userdataid = id
+      this.userDataParams = []
+      api('listUserData', { id: id }).then(json => {
+        const resp = json.listuserdataresponse?.userdata || []
+        if (resp.length > 0) {
+          var params = resp[0]?.params || null
+          if (params) {
+            var dataParams = params.split(',')
+          }
+          var that = this
+          dataParams.forEach(function (val, index) {
+            that.userDataParams.push({
+              id: index,
+              key: val
+            })
+          })
+        }
+      })
+    },
+    updateTemplateLinkedUserData (id) {
+      if (id === '0') {
+        return
+      }
+      this.templateUserDataParams = []
+
+      api('listUserData', { id: id }).then(json => {
+        const resp = json?.listuserdataresponse?.userdata || []
+        if (resp) {
+          var params = resp[0]?.params || null
+          if (params) {
+            var dataParams = params.split(',')
+          }
+          var that = this
+          that.templateUserDataParams = []
+          if (dataParams) {
+            dataParams.forEach(function (val, index) {
+              that.templateUserDataParams.push({
+                id: index,
+                key: val
+              })
+            })
+          }
+        }
+      })
+    },
+    escapePropertyKey (key) {
+      return key.split('.').join('\\002E')
+    },
+    updateSecurityGroups (securitygroupids) {
+      this.securitygroupids = securitygroupids || []
+    },
+    getText (option) {
+      return _.get(option, 'displaytext', _.get(option, 'name'))
+    },
+    handleSubmitAndStay (e) {
+      this.form.stayonpage = true
+      this.handleSubmit(e.domEvent)
+    },
+    handleSubmit (e) {
+      console.log('wizard submit')
+      e.preventDefault()
+      if (this.loading.deploy) return
+      this.formRef.value.validate().then(async () => {
+        const values = toRaw(this.form)
+
+        if (!values.templateid && !values.isoid) {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: this.$t('message.template.iso')
+          })
+          return
+        } else if (values.isoid && (!values.diskofferingid || values.diskofferingid === '0')) {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: this.$t('message.step.3.continue')
+          })
+          return
+        }
+        if (!values.computeofferingid) {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: this.$t('message.step.2.continue')
+          })
+          return
+        }
+
+        // Verify VNF networks
+        // All checked networks should be used and only once.
+        // Required NIC must be associated to a network
+        // DeviceID must be consequent
+        if (this.templateVnfNics && this.templateVnfNics.length > 0) {
+          let nextDeviceId = 0
+          const usedNetworkIds = []
+          const keys = Object.keys(this.vnfNicNetworks)
+          if (keys.length === 0) {
+            this.$notification.error({
+              message: this.$t('message.request.failed'),
+              description: this.$t('message.vnf.error.no.networks')
+            })
+            return
+          }
+          for (var templateVnfNic of this.templateVnfNics) {
+            if (templateVnfNic.required && !keys.includes(String(templateVnfNic.deviceid))) {
+              this.$notification.error({
+                message: this.$t('message.request.failed'),
+                description: this.$t('message.vnf.error.no.network.for.required.deviceid')
+              })
+              return
+            }
+            const networkId = this.vnfNicNetworks[String(templateVnfNic.deviceid)]?.id || null
+            if (networkId) {
+              if (templateVnfNic.deviceid !== nextDeviceId) {
+                this.$notification.error({
+                  message: this.$t('message.request.failed'),
+                  description: this.$t('message.vnf.error.deviceid.should.be.consecutive')
+                })
+                return
+              }
+              if (usedNetworkIds.includes(networkId)) {
+                this.$notification.error({
+                  message: this.$t('message.request.failed'),
+                  description: this.$t('message.vnf.error.network.is.already.used')
+                })
+                return
+              }
+              usedNetworkIds.push(networkId)
+              nextDeviceId = templateVnfNic.deviceid + 1
+            }
+          }
+          for (var networkid of values.networkids) {
+            if (!usedNetworkIds.includes(networkid)) {
+              this.$notification.error({
+                message: this.$t('message.request.failed'),
+                description: this.$t('message.vnf.error.network.should.be.used')
+              })
+              return
+            }
+          }
+        }
+
+        if (this.error) {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: this.$t('error.form.message')
+          })
+          return
+        }
+
+        this.loading.deploy = true
+
+        let networkIds = []
+
+        let createVnfAppData = {}
+        // step 1 : select zone
+        createVnfAppData.zoneid = values.zoneid
+        createVnfAppData.podid = values.podid
+        createVnfAppData.clusterid = values.clusterid
+        createVnfAppData.hostid = values.hostid
+        createVnfAppData.keyboard = values.keyboard
+        if (!this.template?.deployasis) {
+          createVnfAppData.boottype = values.boottype
+          createVnfAppData.bootmode = values.bootmode
+        }
+        createVnfAppData.dynamicscalingenabled = values.dynamicscalingenabled
+        createVnfAppData.iothreadsenabled = values.iothreadsenabled
+        createVnfAppData.iodriverpolicy = values.iodriverpolicy
+        createVnfAppData.nicmultiqueuenumber = values.nicmultiqueuenumber
+        createVnfAppData.nicpackedvirtqueuesenabled = values.nicpackedvirtqueuesenabled
+        const isUserdataAllowed = !this.userdataDefaultOverridePolicy || (this.userdataDefaultOverridePolicy === 'ALLOWOVERRIDE' && this.doUserdataOverride) || (this.userdataDefaultOverridePolicy === 'APPEND' && this.doUserdataAppend)
+        if (isUserdataAllowed && values.userdata && values.userdata.length > 0) {
+          createVnfAppData.userdata = this.$toBase64AndURIEncoded(values.userdata)
+        }
+        // step 2: select template/iso
+        if (this.tabKey === 'templateid') {
+          createVnfAppData.templateid = values.templateid
+          values.hypervisor = null
+        } else {
+          createVnfAppData.templateid = values.isoid
+        }
+
+        if (this.showRootDiskSizeChanger && values.rootdisksize && values.rootdisksize > 0) {
+          createVnfAppData.rootdisksize = values.rootdisksize
+        } else if (this.rootDiskSizeFixed > 0 && !this.template.deployasis) {
+          createVnfAppData.rootdisksize = this.rootDiskSizeFixed
+        }
+
+        if (values.hypervisor && values.hypervisor.length > 0) {
+          createVnfAppData.hypervisor = values.hypervisor
+        }
+
+        createVnfAppData.startvm = values.startvm
+        createVnfAppData.vnfconfiguremanagement = values.vnfconfiguremanagement
+        createVnfAppData.vnfcidrlist = values.vnfcidrlist
+
+        // step 3: select service offering
+        createVnfAppData.serviceofferingid = values.computeofferingid
+        if (this.serviceOffering && this.serviceOffering.iscustomized) {
+          if (values.cpunumber) {
+            createVnfAppData['details[0].cpuNumber'] = values.cpunumber
+          }
+          if (values.cpuspeed) {
+            createVnfAppData['details[0].cpuSpeed'] = values.cpuspeed
+          }
+          if (values.memory) {
+            createVnfAppData['details[0].memory'] = values.memory
+          }
+        }
+        if (this.selectedTemplateConfiguration) {
+          createVnfAppData['details[0].configurationId'] = this.selectedTemplateConfiguration.id
+        }
+        if (!this.serviceOffering.diskofferingstrictness && values.overridediskofferingid) {
+          createVnfAppData.overridediskofferingid = values.overridediskofferingid
+          if (values.rootdisksize && values.rootdisksize > 0) {
+            createVnfAppData.rootdisksize = values.rootdisksize
+          }
+        }
+        if (this.isCustomizedIOPS) {
+          createVnfAppData['details[0].minIops'] = this.minIops
+          createVnfAppData['details[0].maxIops'] = this.maxIops
+        }
+        // step 4: select disk offering
+        if (!this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) {
+          if (values.multidiskoffering) {
+            let i = 0
+            Object.entries(values.multidiskoffering).forEach(([disk, offering]) => {
+              const diskKey = `datadiskofferinglist[${i}].datadisktemplateid`
+              const offeringKey = `datadiskofferinglist[${i}].diskofferingid`
+              createVnfAppData[diskKey] = disk
+              createVnfAppData[offeringKey] = offering
+              i++
+            })
+          }
+        } else {
+          createVnfAppData.diskofferingid = values.diskofferingid
+          if (values.size) {
+            createVnfAppData.size = values.size
+          }
+        }
+        if (this.isCustomizedDiskIOPS) {
+          createVnfAppData['details[0].minIopsDo'] = this.diskIOpsMin
+          createVnfAppData['details[0].maxIopsDo'] = this.diskIOpsMax
+        }
+        // step 5: select an affinity group
+        createVnfAppData.affinitygroupids = (values.affinitygroupids || []).join(',')
+        // step 6: select network
+        if (this.zone.networktype !== 'Basic') {
+          if (this.nicToNetworkSelection && this.nicToNetworkSelection.length > 0) {
+            for (var j in this.nicToNetworkSelection) {
+              var nicNetwork = this.nicToNetworkSelection[j]
+              createVnfAppData['nicnetworklist[' + j + '].nic'] = nicNetwork.nic
+              createVnfAppData['nicnetworklist[' + j + '].network'] = nicNetwork.network
+            }
+          } else {
+            const arrNetwork = []
+            networkIds = values.networkids
+            if (networkIds.length > 0) {
+              if (this.templateVnfNics && this.templateVnfNics.length > 0) {
+                for (const templateVnfNic of this.templateVnfNics) {
+                  const vnfNicNetworkId = this.vnfNicNetworks[String(templateVnfNic.deviceid)]?.id || null
+                  if (vnfNicNetworkId) {
+                    const ipToNetwork = {
+                      networkid: vnfNicNetworkId
+                    }
+                    arrNetwork.push(ipToNetwork)
+                  }
+                }
+              } else {
+                for (let i = 0; i < networkIds.length; i++) {
+                  if (networkIds[i] === this.defaultnetworkid) {
+                    const ipToNetwork = {
+                      networkid: this.defaultnetworkid
+                    }
+                    arrNetwork.unshift(ipToNetwork)
+                  } else {
+                    const ipToNetwork = {
+                      networkid: networkIds[i]
+                    }
+                    arrNetwork.push(ipToNetwork)
+                  }
+                }
+              }
+            } else {
+              this.$notification.error({
+                message: this.$t('message.request.failed'),
+                description: this.$t('message.step.4.continue')
+              })
+              this.loading.deploy = false
+              return
+            }
+            for (let j = 0; j < arrNetwork.length; j++) {
+              createVnfAppData['iptonetworklist[' + j + '].networkid'] = arrNetwork[j].networkid
+              if (this.networkConfig.length > 0) {
+                const networkConfig = this.networkConfig.filter((item) => item.key === arrNetwork[j].networkid)
+                if (networkConfig && networkConfig.length > 0) {
+                  createVnfAppData['iptonetworklist[' + j + '].ip'] = networkConfig[0].ipAddress ? networkConfig[0].ipAddress : undefined
+                  createVnfAppData['iptonetworklist[' + j + '].mac'] = networkConfig[0].macAddress ? networkConfig[0].macAddress : undefined
+                }
+              }
+            }
+          }
+        }
+        if (this.securitygroupids.length > 0) {
+          createVnfAppData.securitygroupids = this.securitygroupids.join(',')
+        }
+        // step 7: select ssh key pair
+        createVnfAppData.keypairs = this.sshKeyPairs.join(',')
+        if (isUserdataAllowed) {
+          createVnfAppData.userdataid = values.userdataid
+        }
+
+        if (values.name) {
+          createVnfAppData.name = values.name
+          createVnfAppData.displayname = values.name
+        }
+        if (values.group) {
+          createVnfAppData.group = values.group
+        }
+        // step 8: enter setup
+        if ('properties' in values) {
+          const keys = Object.keys(values.properties)
+          for (var i = 0; i < keys.length; ++i) {
+            const propKey = keys[i].split('\\002E').join('.')
+            createVnfAppData['properties[' + i + '].key'] = propKey
+            createVnfAppData['properties[' + i + '].value'] = values.properties[keys[i]]
+          }
+        }
+        if ('bootintosetup' in values) {
+          createVnfAppData.bootintosetup = values.bootintosetup
+        }
+
+        const title = this.$t('label.launch.vnf.appliance')
+        const description = values.name || ''
+
+        createVnfAppData = Object.fromEntries(
+          Object.entries(createVnfAppData).filter(([key, value]) => value !== undefined))
+
+        var idx = 0
+        if (this.templateUserDataValues) {
+          for (const [key, value] of Object.entries(this.templateUserDataValues)) {
+            createVnfAppData['userdatadetails[' + idx + '].' + `${key}`] = value
+            idx++
+          }
+        }
+        if (isUserdataAllowed && this.userDataValues) {
+          for (const [key, value] of Object.entries(this.userDataValues)) {
+            createVnfAppData['userdatadetails[' + idx + '].' + `${key}`] = value
+            idx++
+          }
+        }
+
+        const httpMethod = createVnfAppData.userdata ? 'POST' : 'GET'
+        const args = httpMethod === 'POST' ? {} : createVnfAppData
+        const data = httpMethod === 'POST' ? createVnfAppData : {}
+
+        api('deployVnfAppliance', args, httpMethod, data).then(response => {
+          const jobId = response.deployvirtualmachineresponse.jobid
+          if (jobId) {
+            this.$pollJob({
+              jobId,
+              title,
+              description,
+              successMethod: result => {
+                const vm = result.jobresult.virtualmachine
+                const name = vm.displayname || vm.name || vm.id
+                const username = vm.vnfdetails?.username || null
+                const password = vm.vnfdetails?.password || null
+                const sshUsername = vm.vnfdetails?.ssh_user || null
+                const sshPassword = vm.vnfdetails?.ssh_password || null
+                const webUsername = vm.vnfdetails?.web_user || null
+                const webPassword = vm.vnfdetails?.web_password || null
+                const credentials = []
+                if (username) {
+                  credentials.push(this.$t('label.username') + ' : ' + username)
+                }
+                if (password) {
+                  credentials.push(this.$t('label.password.default') + ' : ' + password)
+                }
+                if (webUsername) {
+                  credentials.push('Web ' + this.$t('label.username') + ' : ' + webUsername)
+                }
+                if (webPassword) {
+                  credentials.push('Web ' + this.$t('label.password.default') + ' : ' + webPassword)
+                }
+                if (sshUsername) {
+                  credentials.push('SSH ' + this.$t('label.username') + ' : ' + sshUsername)
+                }
+                if (sshPassword) {
+                  credentials.push('SSH ' + this.$t('label.password.default') + ' : ' + sshPassword)
+                }
+                if (vm.password) {
+                  credentials.push('New password : ' + vm.password)
+                }
+                if (credentials.length > 0) {
+                  credentials.push(this.$t('message.vnf.credentials.change'))
+                } else {
+                  credentials.push(this.$t('message.vnf.no.credentials'))
+                }
+                this.$notification.success({
+                  message: `${this.$t('message.vnf.credentials.default')} ` + name,
+                  description: (<span v-html={credentials.join('<br>')}></span>),
+                  duration: 0
+                })
+                eventBus.emit('vm-refresh-data')
+              },
+              loadingMessage: `${title} ${this.$t('label.in.progress')}`,
+              catchMessage: this.$t('error.fetching.async.job.result'),
+              action: {
+                isFetchData: false
+              }
+            })
+          }
+          // Sending a refresh in case it hasn't picked up the new VM
+          new Promise(resolve => setTimeout(resolve, 3000)).then(() => {
+            eventBus.emit('vm-refresh-data')
+          })
+          if (!values.stayonpage) {
+            this.$router.back()
+          }
+        }).catch(error => {
+          this.$notifyError(error)
+          this.loading.deploy = false
+        }).finally(() => {
+          this.form.stayonpage = false
+          this.loading.deploy = false
+        })
+      }).catch(err => {
+        this.formRef.value.scrollToField(err.errorFields[0].name)
+        if (err) {
+          if (err.licensesaccepted) {
+            this.$notification.error({
+              message: this.$t('message.license.agreements.not.accepted'),
+              description: this.$t('message.step.license.agreements.continue')
+            })
+            return
+          }
+
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: this.$t('error.form.message')
+          })
+        }
+      })
+    },
+    fetchZones (zoneId, listZoneAllow) {
+      this.zones = []
+      return new Promise((resolve) => {
+        this.loading.zones = true
+        const param = this.params.zones
+        const args = { showicon: true }
+        if (zoneId) args.id = zoneId
+        api(param.list, args).then(json => {
+          const zoneResponse = json.listzonesresponse.zone || []
+          if (listZoneAllow && listZoneAllow.length > 0) {
+            zoneResponse.map(zone => {
+              if (listZoneAllow.includes(zone.id)) {
+                this.zones.push(zone)
+              }
+            })
+          } else {
+            this.zones = zoneResponse
+          }
+
+          resolve(this.zones)
+        }).catch(function (error) {
+          console.log(error.stack)
+        }).finally(() => {
+          this.loading.zones = false
+        })
+      })
+    },
+    fetchOptions (param, name, exclude) {
+      if (exclude && exclude.length > 0) {
+        if (exclude.includes(name)) {
+          return
+        }
+      }
+      this.loading[name] = true
+      param.loading = true
+      param.opts = []
+      const options = param.options || {}
+      if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'dynamicScalingVmConfig', 'hypervisors'].includes(name)) {
+        options.listall = true
+      }
+      api(param.list, options).then((response) => {
+        param.loading = false
+        _.map(response, (responseItem, responseKey) => {
+          if (Object.keys(responseItem).length === 0) {
+            this.rowCount[name] = 0
+            this.options[name] = []
+            return
+          }
+          if (!responseKey.includes('response')) {
+            return
+          }
+          _.map(responseItem, (response, key) => {
+            if (key === 'count') {
+              this.rowCount[name] = response
+              return
+            }
+            param.opts = response
+            this.options[name] = response
+
+            if (name === 'hypervisors') {
+              const hypervisorFromResponse = response[0] && response[0].name ? response[0].name : null
+              this.dataPreFill.hypervisor = hypervisorFromResponse
+              this.form.hypervisor = hypervisorFromResponse
+            }
+
+            if (param.field) {
+              this.fillValue(param.field)
+            }
+          })
+
+          if (name === 'zones') {
+            let zoneid = ''
+            if (this.$route.query.zoneid) {
+              zoneid = this.$route.query.zoneid
+            } else if (this.options.zones.length === 1) {
+              zoneid = this.options.zones[0].id
+            }
+            if (zoneid) {
+              this.form.zoneid = zoneid
+              this.onSelectZoneId(zoneid)
+            }
+          }
+        })
+      }).catch(function (error) {
+        console.log(error.stack)
+        param.loading = false
+      }).finally(() => {
+        this.loading[name] = false
+      })
+    },
+    fetchTemplates (templateFilter, params) {
+      const args = Object.assign({}, params)
+      if (args.keyword || args.category !== templateFilter) {
+        args.page = 1
+        args.pageSize = args.pageSize || 10
+      }
+      args.zoneid = _.get(this.zone, 'id')
+      args.templatefilter = templateFilter
+      args.details = 'all'
+      args.showicon = 'true'
+      args.id = this.templateId
+
+      return new Promise((resolve, reject) => {
+        api('listVnfTemplates', args).then((response) => {
+          resolve(response)
+        }).catch((reason) => {
+          // ToDo: Handle errors
+          reject(reason)
+        })
+      })
+    },
+    fetchIsos (isoFilter, params) {
+      const args = Object.assign({}, params)
+      if (args.keyword || args.category !== isoFilter) {
+        args.page = 1
+        args.pageSize = args.pageSize || 10
+      }
+      args.zoneid = _.get(this.zone, 'id')
+      args.isoFilter = isoFilter
+      args.bootable = true
+      args.showicon = 'true'
+      args.id = this.isoId
+
+      return new Promise((resolve, reject) => {
+        api('listIsos', args).then((response) => {
+          resolve(response)
+        }).catch((reason) => {
+          // ToDo: Handle errors
+          reject(reason)
+        })
+      })
+    },
+    fetchAllTemplates (params) {
+      const promises = []
+      const templates = {}
+      this.loading.templates = true
+      this.templateFilter.forEach((filter) => {
+        templates[filter] = { count: 0, template: [] }
+        promises.push(this.fetchTemplates(filter, params))
+      })
+      this.options.templates = templates
+      Promise.all(promises).then((response) => {
+        response.forEach((resItem, idx) => {
+          templates[this.templateFilter[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse
+          this.options.templates = { ...templates }
+        })
+      }).catch((reason) => {
+        console.log(reason)
+      }).finally(() => {
+        this.loading.templates = false
+      })
+    },
+    fetchAllIsos (params) {
+      const promises = []
+      const isos = {}
+      this.loading.isos = true
+      this.isoFilter.forEach((filter) => {
+        isos[filter] = { count: 0, iso: [] }
+        promises.push(this.fetchIsos(filter, params))
+      })
+      this.options.isos = isos
+      Promise.all(promises).then((response) => {
+        response.forEach((resItem, idx) => {
+          isos[this.isoFilter[idx]] = _.isEmpty(resItem.listisosresponse) ? { count: 0, iso: [] } : resItem.listisosresponse
+          this.options.isos = { ...isos }
+        })
+      }).catch((reason) => {
+        console.log(reason)
+      }).finally(() => {
+        this.loading.isos = false
+      })
+    },
+    filterOption (input, option) {
+      return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0
+    },
+    onSelectZoneId (value) {
+      this.dataPreFill = {}
+      this.zoneId = value
+      this.podId = null
+      this.clusterId = null
+      this.zone = _.find(this.options.zones, (option) => option.id === value)
+      this.zoneSelected = true
+      this.form.startvm = true
+      this.form.vnfconfiguremanagement = false
+      this.form.vnfcidrlist = '0.0.0.0/0'
+      this.selectedZone = this.zoneId
+      this.form.zoneid = this.zoneId
+      this.form.clusterid = undefined
+      this.form.podid = undefined
+      this.form.hostid = undefined
+      this.form.templateid = undefined
+      this.form.isoid = undefined
+      this.tabKey = 'templateid'
+      if (this.isoId) {
+        this.tabKey = 'isoid'
+      }
+      _.each(this.params, (param, name) => {
+        if (this.networkId && name === 'networks') {
+          param.options = {
+            id: this.networkId
+          }
+        }
+        if (!('isLoad' in param) || param.isLoad) {
+          this.fetchOptions(param, name, ['zones'])
+        }
+      })
+      if (this.tabKey === 'templateid') {
+        this.fetchAllTemplates()
+      } else {
+        this.fetchAllIsos()
+      }
+      this.updateTemplateKey()
+      this.formModel = toRaw(this.form)
+    },
+    onSelectPodId (value) {
+      this.podId = value
+      if (this.podId === null) {
+        this.form.podid = undefined
+      }
+
+      this.fetchOptions(this.params.clusters, 'clusters')
+      this.fetchOptions(this.params.hosts, 'hosts')
+    },
+    onSelectClusterId (value) {
+      this.clusterId = value
+      if (this.clusterId === null) {
+        this.form.clusterid = undefined
+      }
+
+      this.fetchOptions(this.params.hosts, 'hosts')
+    },
+    onSelectHostId (value) {
+      this.hostId = value
+      if (this.hostId === null) {
+        this.form.hostid = undefined
+      }
+    },
+    handleSearchFilter (name, options) {
+      this.params[name].options = { ...this.params[name].options, ...options }
+      this.fetchOptions(this.params[name], name)
+    },
+    onTabChange (key, type) {
+      this[type] = key
+      if (key === 'isoid') {
+        this.fetchAllIsos()
+      }
+    },
+    onUserdataTabChange (key, type) {
+      this[type] = key
+      this.userDataParams = []
+    },
+    fetchTemplateNics (template) {
+      var nics = []
+      this.nicToNetworkSelection = []
+      if (template && template.deployasisdetails && Object.keys(template.deployasisdetails).length > 0) {
+        var keys = Object.keys(template.deployasisdetails)
+        keys = keys.filter(key => key.startsWith('network-'))
+        for (var key of keys) {
+          var propertyMap = JSON.parse(template.deployasisdetails[key])
+          nics.push(propertyMap)
+        }
+        nics.sort(function (a, b) {
+          return a.InstanceID - b.InstanceID
+        })
+        if (this.options.networks && this.options.networks.length > 0) {
+          for (var i = 0; i < nics.length; ++i) {
+            var nic = nics[i]
+            nic.id = nic.InstanceID
+            var network = this.options.networks[Math.min(i, this.options.networks.length - 1)]
+            nic.selectednetworkid = network.id
+            nic.selectednetworkname = network.name
+            this.nicToNetworkSelection.push({ nic: nic.id, network: network.id })
+          }
+        }
+      }
+      return nics
+    },
+    groupBy (array, key) {
+      const result = {}
+      array.forEach(item => {
+        if (!result[item[key]]) {
+          result[item[key]] = []
+        }
+        result[item[key]].push(item)
+      })
+      return result
+    },
+    fetchTemplateProperties (template) {
+      var properties = []
+      if (template && template.deployasisdetails && Object.keys(template.deployasisdetails).length > 0) {
+        var keys = Object.keys(template.deployasisdetails)
+        keys = keys.filter(key => key.startsWith('property-'))
+        for (var key of keys) {
+          var propertyMap = JSON.parse(template.deployasisdetails[key])
+          properties.push(propertyMap)
+        }
+        properties.sort(function (a, b) {
+          return a.index - b.index
+        })
+      }
+      return this.groupBy(properties, 'category')
+    },
+    fetchTemplateVnfNics (template) {
+      return template?.vnfnics || []
+    },
+    fetchTemplateConfigurations (template) {
+      var configurations = []
+      if (template && template.deployasisdetails && Object.keys(template.deployasisdetails).length > 0) {
+        var keys = Object.keys(template.deployasisdetails)
+        keys = keys.filter(key => key.startsWith('configuration-'))
+        for (var key of keys) {
+          var configuration = JSON.parse(template.deployasisdetails[key])
+          configuration.name = configuration.label
+          configuration.displaytext = configuration.label
+          configuration.iscustomized = true
+          configuration.cpunumber = 0
+          configuration.cpuspeed = 0
+          configuration.memory = 0
+          for (var harwareItem of configuration.hardwareItems) {
+            if (harwareItem.resourceType === 'Processor') {
+              configuration.cpunumber = harwareItem.virtualQuantity
+              configuration.cpuspeed = harwareItem.reservation
+            } else if (harwareItem.resourceType === 'Memory') {
+              configuration.memory = harwareItem.virtualQuantity
+            }
+          }
+          configurations.push(configuration)
+        }
+        configurations.sort(function (a, b) {
+          return a.index - b.index
+        })
+      }
+      return configurations
+    },
+    fetchTemplateLicenses (template) {
+      var licenses = []
+      if (template && template.deployasisdetails && Object.keys(template.deployasisdetails).length > 0) {
+        var keys = Object.keys(template.deployasisdetails)
+        const prefix = /eula-\d-/
+        keys = keys.filter(key => key.startsWith('eula-')).sort()
+        for (var key of keys) {
+          var license = {
+            id: this.escapePropertyKey(key.replace(' ', '-')),
+            name: key.replace(prefix, ''),
+            text: template.deployasisdetails[key]
+          }
+          licenses.push(license)
+        }
+      }
+      return licenses
+    },
+    deleteFrom (options, values) {
+      for (const value of values) {
+        delete options[value]
+      }
+    },
+    resetFromTemplateConfiguration () {
+      this.deleteFrom(this.params.serviceOfferings.options, ['cpuspeed', 'cpunumber', 'memory'])
+      this.deleteFrom(this.dataPreFill, ['cpuspeed', 'cpunumber', 'memory'])
+      this.handleSearchFilter('serviceOfferings', {
+        page: 1,
+        pageSize: 10
+      })
+    },
+    handleTemplateConfiguration () {
+      if (!this.selectedTemplateConfiguration) {
+        return
+      }
+      const params = {
+        cpunumber: this.selectedTemplateConfiguration.cpunumber,
+        cpuspeed: this.selectedTemplateConfiguration.cpuspeed,
+        memory: this.selectedTemplateConfiguration.memory,
+        page: 1,
+        pageSize: 10
+      }
+      this.dataPreFill.cpunumber = params.cpunumber
+      this.dataPreFill.cpuspeed = params.cpuspeed
+      this.dataPreFill.memory = params.memory
+      this.handleSearchFilter('serviceOfferings', params)
+    },
+    updateTemplateParameters () {
+      if (this.template) {
+        this.templateNics = this.fetchTemplateNics(this.template)
+        this.templateConfigurations = this.fetchTemplateConfigurations(this.template)
+        this.templateLicenses = this.fetchTemplateLicenses(this.template)
+        this.templateProperties = this.fetchTemplateProperties(this.template)
+        this.templateVnfNics = this.fetchTemplateVnfNics(this.template)
+        this.selectedTemplateConfiguration = {}
+        setTimeout(() => {
+          if (this.templateConfigurationExists) {
+            this.selectedTemplateConfiguration = this.templateConfigurations[0]
+            this.handleTemplateConfiguration()
+            if ('templateConfiguration' in this.form.fieldsStore.fieldsMeta) {
+              this.updateFieldValue('templateConfiguration', this.selectedTemplateConfiguration.id)
+            }
+            this.updateComputeOffering(null) // reset as existing selection may be incompatible
+          }
+        }, 500)
+      }
+    },
+    onSelectTemplateConfigurationId (value) {
+      this.selectedTemplateConfiguration = _.find(this.templateConfigurations, (option) => option.id === value)
+      this.handleTemplateConfiguration()
+      this.updateComputeOffering(null)
+    },
+    updateTemplateConfigurationOfferingDetails (offeringId) {
+      this.rootDiskSizeFixed = 0
+      var offering = this.serviceOffering
+      if (!offering || offering.id !== offeringId) {
+        offering = _.find(this.options.serviceOfferings, (option) => option.id === offeringId)
+      }
+      if (offering && offering.iscustomized && this.templateConfigurationExists && this.selectedTemplateConfiguration) {
+        if ('cpunumber' in this.form.fieldsStore.fieldsMeta) {
+          this.updateFieldValue('cpunumber', this.selectedTemplateConfiguration.cpunumber)
+        }
+        if ((offering.cpuspeed == null || offering.cpuspeed === undefined) && 'cpuspeed' in this.form.fieldsStore.fieldsMeta) {
+          this.updateFieldValue('cpuspeed', this.selectedTemplateConfiguration.cpuspeed)
+        }
+        if ('memory' in this.form.fieldsStore.fieldsMeta) {
+          this.updateFieldValue('memory', this.selectedTemplateConfiguration.memory)
+        }
+      }
+      if (offering && offering.rootdisksize > 0) {
+        this.rootDiskSizeFixed = offering.rootdisksize
+        this.showRootDiskSizeChanger = false
+      }
+      this.form.rootdisksizeitem = this.showRootDiskSizeChanger && this.rootDiskSizeFixed > 0
+      this.formModel = toRaw(this.form)
+    },
+    handlerError (error) {
+      this.error = error
+    },
+    onSelectDiskSize (rowSelected) {
+      this.diskSelected = rowSelected
+    },
+    onSelectRootDiskSize (rowSelected) {
+      this.rootDiskSelected = rowSelected
+    },
+    updateIOPSValue (input, value) {
+      this[input] = value
+    },
+    onBootTypeChange (value) {
+      this.fetchBootModes(value)
+      this.defaultBootMode = this.options.bootModes?.[0]?.id || undefined
+      this.updateFieldValue('bootmode', this.defaultBootMode)
+    },
+    handleNicsNetworkSelection (nicToNetworkSelection) {
+      this.nicToNetworkSelection = nicToNetworkSelection
+    },
+    getSelectedNetworksWithExistingConfig (networks) {
+      for (var i in this.networks) {
+        var n = this.networks[i]
+        for (var c of this.networkConfig) {
+          if (n.id === c.key) {
+            n = { ...n, ...c }
+            networks[i] = n
+            break
+          }
+        }
+      }
+      return networks
+    },
+    copyToClipboard (txt) {
+      const parent = this
+      this.$copyText(txt, document.body, function (err) {
+        if (!err) {
+          parent.$message.success(parent.$t('label.copied.clipboard'))
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  .card-footer {
+    text-align: right;
+    margin-top: 2rem;
+
+    button + button {
+      margin-left: 8px;
+    }
+  }
+
+  .ant-list-item-meta-avatar {
+    font-size: 1rem;
+  }
+
+  .ant-collapse {
+    margin: 2rem 0;
+  }
+</style>
+
+<style lang="less">
+  @import url('../../style/index');
+
+  .ant-table-selection-column {
+    // Fix for the table header if the row selection use radio buttons instead of checkboxes
+    > div:empty {
+      width: 16px;
+    }
+  }
+
+  .ant-collapse-borderless > .ant-collapse-item {
+    border: 1px solid @border-color-split;
+    border-radius: @border-radius-base !important;
+    margin: 0 0 1.2rem;
+  }
+
+  .zone-radio-button {
+    width:100%;
+    min-width: 345px;
+    height: 60px;
+    display: flex;
+    padding-left: 20px;
+    align-items: center;
+  }
+
+  .vm-info-card {
+    .ant-card-body {
+      min-height: 250px;
+      max-height: calc(100vh - 150px);
+      overflow-y: auto;
+      scroll-behavior: smooth;
+    }
+
+    .resource-detail-item__label {
+      font-weight: normal;
+    }
+
+    .resource-detail-item__details, .resource-detail-item {
+      a {
+        color: rgba(0, 0, 0, 0.65);
+        cursor: default;
+        pointer-events: none;
+      }
+    }
+  }
+
+  .form-item-hidden {
+    display: none;
+  }
+
+  .btn-stay-on-page {
+    &.ant-dropdown-menu-dark {
+      .ant-dropdown-menu-item:hover {
+        background: transparent !important;
+      }
+    }
+  }
+</style>
diff --git a/ui/src/views/compute/DestroyVM.vue b/ui/src/views/compute/DestroyVM.vue
index de7b6da..c66c3bd 100644
--- a/ui/src/views/compute/DestroyVM.vue
+++ b/ui/src/views/compute/DestroyVM.vue
@@ -54,9 +54,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="volume in volumes" :key="volume.id">
+              <a-select-option v-for="volume in volumes" :key="volume.id" :label="volume.name">
                 {{ volume.name }}
               </a-select-option>
             </a-select>
@@ -102,12 +102,12 @@
                     showSearch
                     optionFilterProp="label"
                     :filterOption="(input, option) => {
-                      return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }"
                     :loading="listVolumes[record.id].loading"
                     :placeholder="$t('label.delete.volumes')"
                     @change="(value) => onChangeVolume(record.id, value)">
-                    <a-select-option v-for="item in listVolumes[record.id].opts" :key="item.id">
+                    <a-select-option v-for="item in listVolumes[record.id].opts" :key="item.id" :label="item.name || item.description">
                       {{ item.name || item.description }}
                     </a-select-option>
                   </a-select>
@@ -317,9 +317,9 @@
       this.selectedItemsProgress = Array.from(this.selectedItems)
       this.selectedItemsProgress = this.selectedItemsProgress.map(v => ({ ...v, status: 'InProgress' }))
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        scopedSlots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
diff --git a/ui/src/views/compute/EditVM.vue b/ui/src/views/compute/EditVM.vue
index 1fbbd5e..3601901 100644
--- a/ui/src/views/compute/EditVM.vue
+++ b/ui/src/views/compute/EditVM.vue
@@ -52,11 +52,11 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }"
           :loading="osTypes.loading"
           v-model:value="form.ostypeid">
-          <a-select-option v-for="(ostype) in osTypes.opts" :key="ostype.id">
+          <a-select-option v-for="(ostype) in osTypes.opts" :key="ostype.id" :label="ostype.description">
             {{ ostype.description }}
           </a-select-option>
         </a-select>
@@ -103,7 +103,7 @@
           }"
           :loading="securitygroups.loading"
           v-focus="true">
-          <a-select-option v-for="securitygroup in securitygroups.opts" :key="securitygroup.id" :label="securitygroup.name">
+          <a-select-option v-for="securitygroup in securitygroups.opts" :key="securitygroup.id" :label="securitygroup.name ||  securitygroup.id">
             <div>
               {{ securitygroup.name ||  securitygroup.id }}
             </div>
diff --git a/ui/src/views/compute/InstanceSchedules.vue b/ui/src/views/compute/InstanceSchedules.vue
new file mode 100644
index 0000000..67e1f6e
--- /dev/null
+++ b/ui/src/views/compute/InstanceSchedules.vue
@@ -0,0 +1,536 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div>
+    <a-button
+      type="dashed"
+      style="width: 100%; margin-bottom: 10px"
+      @click="showAddModal"
+      :loading="loading"
+      :disabled="!('createVMSchedule' in $store.getters.apis)"
+    >
+      <template #icon><plus-outlined /></template> {{ $t('label.schedule.add') }}
+    </a-button>
+    <list-view
+      :loading="tabLoading"
+      :columns="columns"
+      :items="schedules"
+      :columnKeys="columnKeys"
+      :selectedColumns="selectedColumnKeys"
+      ref="listview"
+      @update-selected-columns="updateSelectedColumns"
+      @update-vm-schedule="updateVMSchedule"
+      @remove-vm-schedule="removeVMSchedule"
+      @refresh="this.fetchData"
+    />
+    <a-pagination
+      class="row-element"
+      style="margin-top: 10px"
+      size="small"
+      :current="page"
+      :pageSize="pageSize"
+      :total="totalCount"
+      :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1 + ((page - 1) * pageSize))}-${Math.min(page * pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
+      :pageSizeOptions="pageSizeOptions"
+      @change="changePage"
+      @showSizeChange="changePage"
+      showSizeChanger
+      showQuickJumper
+    >
+      <template #buildOptionText="props">
+        <span>{{ props.value }} / {{ $t('label.page') }}</span>
+      </template>
+    </a-pagination>
+  </div>
+
+  <a-modal
+    :visible="showModal"
+    :title="$t('label.schedule')"
+    :maskClosable="false"
+    :closable="true"
+    @cancel="closeModal"
+    @ok="submitForm"
+  >
+    <a-form
+      layout="vertical"
+      :ref="formRef"
+      :model="form"
+      :rules="rules"
+      @finish="submitForm"
+      v-ctrl-enter="submitForm"
+    >
+      <a-form-item
+        name="description"
+        ref="description"
+        :wrapperCol="{ span: 24 }"
+      >
+        <template #label>
+          <tooltip-label
+            :title="$t('label.description')"
+            :tooltip="apiParams.description.description"
+          />
+        </template>
+        <a-input
+          v-model:value="form.description"
+          v-focus="true"
+        />
+      </a-form-item>
+      <a-form-item
+        name="action"
+        ref="action"
+        :wrapperCol="{ span: 24 }"
+      >
+        <template #label>
+          <tooltip-label
+            :title="$t('label.action')"
+            :tooltip="apiParams.action.description"
+          />
+        </template>
+        <a-radio-group
+          v-model:value="form.action"
+          button-style="solid"
+          :disabled="isEdit"
+        >
+          <a-radio-button
+            v-for="action in actions"
+            :key="action.id"
+            :value="action.value"
+          >
+            {{ $t(action.label) }}
+          </a-radio-button>
+        </a-radio-group>
+      </a-form-item>
+      <a-form-item
+        name="timezone"
+        ref="timezone"
+        :wrapperCol="{ span: 24 }"
+      >
+        <template #label>
+          <tooltip-label
+            :title="$t('label.timezone')"
+            :tooltip="apiParams.timezone.description"
+          />
+        </template>
+        <a-select
+          showSearch
+          v-model:value="form.timezone"
+          optionFilterProp="label"
+          :filterOption="(input, option) => {
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          }"
+          :loading="fetching"
+        >
+          <a-select-option
+            v-for="opt in timeZoneMap"
+            :key="opt.id"
+            :label="opt.name || opt.description"
+          >
+            {{ opt.name || opt.description }}
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-row justify="space-between">
+        <a-col>
+          <a-form-item
+            name="startDate"
+            ref="startDate"
+          >
+            <template #label>
+              <tooltip-label
+                :title="$t('label.start.date.and.time')"
+                :tooltip="apiParams.startdate.description"
+              />
+            </template>
+            <a-date-picker
+              v-model:value="form.startDate"
+              show-time
+              :locale="this.$i18n.locale"
+              :placeholder="$t('message.select.start.date.and.time')"
+            />
+          </a-form-item>
+        </a-col>
+        <a-col>
+          <a-form-item
+            name="endDate"
+            ref="endDate"
+          >
+            <template #label>
+              <tooltip-label
+                :title="$t('label.end.date.and.time')"
+                :tooltip="apiParams.enddate.description"
+              />
+            </template>
+            <a-date-picker
+              v-model:value="form.endDate"
+              show-time
+              :locale="this.$i18n.locale"
+              :placeholder="$t('message.select.end.date.and.time')"
+            />
+          </a-form-item>
+        </a-col>
+      </a-row>
+      <a-form-item
+        name="schedule"
+        ref="schedule"
+        :wrapperCol="{ span: 24 }"
+      >
+        <template #label>
+          <tooltip-label
+            :title="$t('label.schedule')"
+            :tooltip="apiParams.schedule.description"
+          />
+        </template>
+        <a-row
+          style="margin-bottom: 15px; text-align: center;"
+          justify="space-around"
+          align="middle"
+        >
+          <cron-ant
+            v-if="!form.useCronFormat"
+            v-model="form.schedule"
+            :periods="periods"
+            :button-props="{ type: 'primary', size: 'small', disabled: form.useCronFormat }"
+            @error="error = $event"
+          />
+          <label
+            v-if="form.useCronFormat">
+            {{ generateHumanReadableSchedule(form.schedule) }}
+          </label>
+        </a-row>
+        <a-row
+          justify="space-between"
+          align="middle"
+        >
+          <a-col>
+            <label>{{ $t('label.cron.mode') }}</label>
+          </a-col>
+          <a-col>
+            <a-switch v-model:checked="form.useCronFormat">
+            </a-switch>
+          </a-col>
+          <a-col :span="12">
+            <a-input
+              :addonBefore="$t('label.cron')"
+              v-model:value="form.schedule"
+              :disabled="!form.useCronFormat"
+              v-focus="true"
+            />
+          </a-col>
+        </a-row>
+      </a-form-item>
+      <a-form-item
+        name="enabled"
+        ref="enabled"
+        :wrapperCol="{ span: 24}"
+      >
+        <template #label>
+          <tooltip-label
+            :title="$t('label.enabled')"
+            :tooltip="apiParams.enabled.description"
+          />
+        </template>
+        <a-switch v-model:checked="form.enabled" />
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script>
+
+import { reactive, ref, toRaw } from 'vue'
+import { api } from '@/api'
+import ListView from '@/components/view/ListView'
+import Status from '@/components/widgets/Status'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import { mixinForm } from '@/utils/mixin'
+import { timeZone } from '@/utils/timezone'
+import debounce from 'lodash/debounce'
+import cronstrue from 'cronstrue/i18n'
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+import timezone from 'dayjs/plugin/timezone'
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
+export default {
+  name: 'InstanceSchedules',
+  mixins: [mixinForm],
+  components: {
+    Status,
+    ListView,
+    TooltipLabel
+  },
+  props: {
+    virtualmachine: {
+      type: Object,
+      required: true
+    },
+    loading: {
+      type: Boolean,
+      required: true
+    }
+  },
+  data () {
+    this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
+    return {
+      tabLoading: false,
+      columnKeys: ['action', 'enabled', 'description', 'schedule', 'timezone', 'startdate', 'enddate', 'created', 'vmScheduleActions'],
+      selectedColumnKeys: [],
+      columns: [],
+      schedules: [],
+      timeZoneMap: [],
+      actions: [
+        { value: 'START', label: 'label.start' },
+        { value: 'STOP', label: 'label.stop' },
+        { value: 'REBOOT', label: 'label.reboot' },
+        { value: 'FORCE_STOP', label: 'label.force.stop' },
+        { value: 'FORCE_REBOOT', label: 'label.force.reboot' }
+      ],
+      periods: [
+        { id: 'year', value: ['month', 'day', 'dayOfWeek', 'hour', 'minute'] },
+        { id: 'month', value: ['day', 'dayOfWeek', 'hour', 'minute'] },
+        { id: 'week', value: ['dayOfWeek', 'hour', 'minute'] },
+        { id: 'day', value: ['hour', 'minute'] }
+      ],
+      page: 1,
+      pageSize: 20,
+      totalCount: 0,
+      showModal: false,
+      isSubmitted: false,
+      isEdit: false,
+      error: '',
+      pattern: 'YYYY-MM-DD HH:mm:ss'
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('createVMSchedule')
+  },
+  computed: {
+    pageSizeOptions () {
+      var sizes = [20, 50, 100, 200, this.$store.getters.defaultListViewPageSize]
+      if (this.device !== 'desktop') {
+        sizes.unshift(10)
+      }
+      return [...new Set(sizes)].sort(function (a, b) {
+        return a - b
+      }).map(String)
+    }
+  },
+  created () {
+    this.selectedColumnKeys = this.columnKeys
+    this.updateColumns()
+    this.pageSize = this.pageSizeOptions[0] * 1
+    this.initForm()
+    this.fetchData()
+    this.fetchTimeZone()
+  },
+  watch: {
+    virtualmachine: {
+      handler () {
+        this.fetchSchedules()
+      }
+    }
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({
+        action: 'START',
+        schedule: '* * * * *',
+        description: '',
+        timezone: 'UTC',
+        startDate: '',
+        endDate: '',
+        enabled: true,
+        useCronFormat: false
+      })
+      this.rules = reactive({
+        schedule: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
+        action: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
+        timezone: [{ required: true, message: `${this.$t('message.error.select')}` }],
+        startDate: [{ required: false, message: `${this.$t('message.error.select')}` }],
+        endDate: [{ required: false, message: `${this.$t('message.error.select')}` }]
+      })
+    },
+    createVMSchedule (schedule) {
+      this.resetForm()
+      this.showAddModal()
+    },
+    removeVMSchedule (schedule) {
+      api('deleteVMSchedule', {
+        id: schedule.id,
+        virtualmachineid: this.virtualmachine.id
+      }).then(() => {
+        if (this.totalCount - 1 === this.pageSize * (this.page - 1)) {
+          this.page = this.page - 1 > 0 ? this.page - 1 : 1
+        }
+        const message = `${this.$t('label.removing')} ${schedule.description}`
+        this.$message.success(message)
+      }).catch(error => {
+        console.error(error)
+        this.$message.error(this.$t('message.error.remove.vm.schedule'))
+        this.$notification.error({
+          message: this.$t('label.error'),
+          description: this.$t('message.error.remove.vm.schedule')
+        })
+      }).finally(() => {
+        this.fetchData()
+      })
+    },
+    updateVMSchedule (schedule) {
+      this.resetForm()
+      this.isEdit = true
+      Object.assign(this.form, schedule)
+      // Some weird issue when we directly pass in the moment with tz object
+      this.form.startDate = dayjs(schedule.startdate).tz(schedule.timezone)
+      this.form.endDate = schedule.enddate ? dayjs(dayjs(schedule.enddate).tz(schedule.timezone)) : null
+      this.showAddModal()
+    },
+    showAddModal () {
+      this.showModal = true
+    },
+    submitForm () {
+      if (this.isSubmitted) return
+      this.isSubmitted = true
+      this.formRef.value.validate().then(() => {
+        const formRaw = toRaw(this.form)
+        const values = this.handleRemoveFields(formRaw)
+        var params = {
+          description: values.description,
+          schedule: values.schedule,
+          timezone: values.timezone,
+          action: values.action,
+          virtualmachineid: this.virtualmachine.id,
+          enabled: values.enabled,
+          startdate: (values.startDate) ? values.startDate.format(this.pattern) : null,
+          enddate: (values.endDate) ? values.endDate.format(this.pattern) : null
+        }
+        let command = null
+        if (this.form.id === null || this.form.id === undefined) {
+          command = 'createVMSchedule'
+        } else {
+          params.id = this.form.id
+          command = 'updateVMSchedule'
+        }
+
+        api(command, params).then(response => {
+          this.$notification.success({
+            message: this.$t('label.schedule'),
+            description: this.$t('message.success.config.vm.schedule')
+          })
+          this.isSubmitted = false
+          this.fetchData()
+          this.closeModal()
+        }).catch(error => {
+          this.$notifyError(error)
+          this.isSubmitted = false
+        })
+      }).catch(error => {
+        this.$notifyError(error)
+        if (error.errorFields !== undefined) {
+          this.formRef.value.scrollToField(error.errorFields[0].name)
+        }
+      }).finally(() => {
+        this.isSubmitted = false
+      })
+    },
+    resetForm () {
+      this.isEdit = false
+      if (this.formRef.value) {
+        this.formRef.value.resetFields()
+      }
+    },
+    fetchTimeZone (value) {
+      this.timeZoneMap = []
+      this.fetching = true
+
+      timeZone(value).then(json => {
+        this.timeZoneMap = json
+        this.fetching = false
+      })
+    },
+    closeModal () {
+      this.resetForm()
+      this.initForm()
+      this.showModal = false
+    },
+    fetchData () {
+      this.fetchSchedules()
+    },
+    fetchSchedules () {
+      this.schedules = []
+      if (!this.virtualmachine.id) {
+        return
+      }
+      const params = {
+        page: this.page,
+        pagesize: this.pageSize,
+        virtualmachineid: this.virtualmachine.id,
+        listall: true
+      }
+      this.tabLoading = true
+      api('listVMSchedule', params).then(json => {
+        this.schedules = []
+        this.totalCount = json?.listvmscheduleresponse?.count || 0
+        this.schedules = json?.listvmscheduleresponse?.vmschedule || []
+        this.tabLoading = false
+      })
+    },
+    changePage (page, pageSize) {
+      this.page = page
+      this.pageSize = pageSize
+      this.fetchData()
+    },
+    updateSelectedColumns (key) {
+      if (this.selectedColumnKeys.includes(key)) {
+        this.selectedColumnKeys = this.selectedColumnKeys.filter(x => x !== key)
+      } else {
+        this.selectedColumnKeys.push(key)
+      }
+      this.updateColumns()
+    },
+    generateHumanReadableSchedule (schedule) {
+      return cronstrue.toString(schedule, { locale: this.$i18n.locale, throwExceptionOnParseError: false, verbose: true })
+    },
+    updateColumns () {
+      this.columns = []
+      for (var columnKey of this.columnKeys) {
+        if (!this.selectedColumnKeys.includes(columnKey)) continue
+        this.columns.push({
+          key: columnKey,
+          // If columnKey is 'enabled', then title is 'state'
+          // If columnKey is 'startdate', then the title is `start.date.and.time`
+          // else title is columnKey
+          title: columnKey === 'enabled'
+            ? this.$t('label.state')
+            : columnKey === 'startdate'
+              ? this.$t('label.start.date.and.time')
+              : columnKey === 'enddate'
+                ? this.$t('label.end.date.and.time')
+                : this.$t('label.' + String(columnKey).toLowerCase()),
+          dataIndex: columnKey
+        })
+      }
+      if (this.columns.length > 0) {
+        this.columns[this.columns.length - 1].customFilterDropdown = true
+      }
+    }
+  }
+}
+</script>
diff --git a/ui/src/views/compute/InstanceTab.vue b/ui/src/views/compute/InstanceTab.vue
index 4ab7fb6..a69d044 100644
--- a/ui/src/views/compute/InstanceTab.vue
+++ b/ui/src/views/compute/InstanceTab.vue
@@ -124,6 +124,11 @@
           :routerlinks="(record) => { return { name: '/securitygroups/' + record.id } }"
           :showSearch="false"/>
       </a-tab-pane>
+      <a-tab-pane :tab="$t('label.schedules')" key="schedules" v-if="'listVMSchedule' in $store.getters.apis">
+        <InstanceSchedules
+          :virtualmachine="vm"
+          :loading="loading"/>
+      </a-tab-pane>
       <a-tab-pane :tab="$t('label.settings')" key="settings">
         <DetailSettings :resource="dataResource" :loading="loading" />
       </a-tab-pane>
@@ -210,9 +215,9 @@
             :loading="listIps.loading"
             v-focus="editNicResource.type==='Shared'"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
             <a-select-option v-for="ip in listIps.opts" :key="ip.ipaddress">
               {{ ip.ipaddress }}
@@ -253,9 +258,9 @@
             :loading="listIps.loading"
             v-focus="editNicResource.type==='Shared'"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
             <a-select-option v-for="ip in listIps.opts" :key="ip.ipaddress">
               {{ ip.ipaddress }}
@@ -309,6 +314,7 @@
 import DetailSettings from '@/components/view/DetailSettings'
 import CreateVolume from '@/views/storage/CreateVolume'
 import NicsTable from '@/views/network/NicsTable'
+import InstanceSchedules from '@/views/compute/InstanceSchedules.vue'
 import ListResourceTable from '@/components/view/ListResourceTable'
 import TooltipButton from '@/components/widgets/TooltipButton'
 import ResourceIcon from '@/components/view/ResourceIcon'
@@ -325,6 +331,7 @@
     DetailSettings,
     CreateVolume,
     NicsTable,
+    InstanceSchedules,
     ListResourceTable,
     TooltipButton,
     ResourceIcon,
diff --git a/ui/src/views/compute/KubernetesServiceTab.vue b/ui/src/views/compute/KubernetesServiceTab.vue
index 96a9958..b3556cb6 100644
--- a/ui/src/views/compute/KubernetesServiceTab.vue
+++ b/ui/src/views/compute/KubernetesServiceTab.vue
@@ -25,7 +25,7 @@
       <a-tab-pane :tab="$t('label.details')" key="details">
         <DetailsTab :resource="resource" :loading="loading" />
       </a-tab-pane>
-      <a-tab-pane :tab="$t('label.access')" key="access">
+      <a-tab-pane v-if="resource.clustertype == 'CloudManaged'" :tab="$t('label.access')" key="access">
         <a-card :title="$t('label.kubeconfig.cluster')" :loading="versionLoading">
           <div v-if="clusterConfig !== ''">
             <a-textarea :value="clusterConfig" :rows="5" readonly />
@@ -106,36 +106,37 @@
           :rowKey="item => item.id"
           :pagination="false"
         >
-          <template #name="{ text, record }" :name="text">
-            <router-link :to="{ path: '/vm/' + record.id }">{{ record.name }}</router-link>
-          </template>
-          <template #state="{ text }">
-            <status :text="text ? text : ''" displayText />
-          </template>
-          <template #port="{ text, record, index }" :name="text" :record="record">
-            {{ cksSshStartingPort + index }}
-          </template>
-          <template #action="{record}">
-            <a-tooltip placement="bottom" >
-              <template #title>
-                {{ $t('label.action.delete.node') }}
-              </template>
-              <a-popconfirm
-                :title="$t('message.action.delete.node')"
-                @confirm="deleteNode(record)"
-                :okText="$t('label.yes')"
-                :cancelText="$t('label.no')"
-                :disabled="!['Created', 'Running'].includes(resource.state) || resource.autoscalingenabled"
-              >
-                <a-button
-                  danger
-                  type="primary"
-                  shape="circle"
-                  :disabled="!['Created', 'Running'].includes(resource.state) || resource.autoscalingenabled">
-                  <template #icon><delete-outlined /></template>
-                </a-button>
-              </a-popconfirm>
-            </a-tooltip>
+          <template #bodyCell="{ column, text, record, index }">
+            <template v-if="column.key === 'name'" :name="text">
+              <router-link :to="{ path: '/vm/' + record.id }">{{ record.name }}</router-link>
+            </template>
+            <template v-if="column.key === 'state'">
+              <status :text="text ? text : ''" displayText />
+            </template>
+            <template v-if="column.key === 'port'" :name="text" :record="record">
+              {{ cksSshStartingPort + index }}
+            </template>
+            <template v-if="column.key === 'actions'">
+              <a-tooltip placement="bottom" >
+                <template #title>
+                  {{ $t('label.action.delete.node') }}
+                </template>
+                <a-popconfirm
+                  :title="$t('message.action.delete.node')"
+                  @confirm="deleteNode(record)"
+                  :okText="$t('label.yes')"
+                  :cancelText="$t('label.no')"
+                  :disabled="!['Created', 'Running'].includes(resource.state) || resource.autoscalingenabled"
+                >
+                  <a-button
+                    type="danger"
+                    shape="circle"
+                    :disabled="!['Created', 'Running'].includes(resource.state) || resource.autoscalingenabled">
+                    <template #icon><delete-outlined /></template>
+                  </a-button>
+                </a-popconfirm>
+              </a-tooltip>
+            </template>
           </template>
         </a-table>
       </a-tab-pane>
@@ -214,14 +215,14 @@
   created () {
     this.vmColumns = [
       {
+        key: 'name',
         title: this.$t('label.name'),
-        dataIndex: 'name',
-        slots: { customRender: 'name' }
+        dataIndex: 'name'
       },
       {
+        key: 'state',
         title: this.$t('label.state'),
-        dataIndex: 'state',
-        slots: { customRender: 'state' }
+        dataIndex: 'state'
       },
       {
         title: this.$t('label.instancename'),
@@ -232,9 +233,9 @@
         dataIndex: 'ipaddress'
       },
       {
+        key: 'port',
         title: this.$t('label.ssh.port'),
-        dataIndex: 'port',
-        slots: { customRender: 'port' }
+        dataIndex: 'port'
       },
       {
         title: this.$t('label.zonename'),
@@ -244,6 +245,9 @@
     if (!isAdmin()) {
       this.vmColumns = this.vmColumns.filter(x => x.dataIndex !== 'instancename')
     }
+    if (this.resource.clustertype === 'ExternalManaged') {
+      this.vmColumns = this.vmColumns.filter(x => x.dataIndex !== 'port')
+    }
     this.handleFetchData()
     const self = this
     window.addEventListener('popstate', function () {
@@ -271,9 +275,9 @@
   mounted () {
     if (this.$store.getters.apis.scaleKubernetesCluster.params.filter(x => x.name === 'nodeids').length > 0) {
       this.vmColumns.push({
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        slots: { customRender: 'action' }
+        key: 'actions',
+        title: this.$t('label.actions'),
+        dataIndex: 'actions'
       })
     }
     this.handleFetchData()
diff --git a/ui/src/views/compute/MigrateVMStorage.vue b/ui/src/views/compute/MigrateVMStorage.vue
index 4d0aab4..a67e7a5 100644
--- a/ui/src/views/compute/MigrateVMStorage.vue
+++ b/ui/src/views/compute/MigrateVMStorage.vue
@@ -134,7 +134,7 @@
     },
     submitForm () {
       var isUserVm = true
-      if (this.$route.meta.name !== 'vm') {
+      if (this.$route.meta.resourceType !== 'UserVm') {
         isUserVm = false
       }
       var migrateApi = isUserVm ? 'migrateVirtualMachine' : 'migrateSystemVm'
diff --git a/ui/src/views/compute/MigrateWizard.vue b/ui/src/views/compute/MigrateWizard.vue
index 9687138..70f40f7 100644
--- a/ui/src/views/compute/MigrateWizard.vue
+++ b/ui/src/views/compute/MigrateWizard.vue
@@ -37,45 +37,47 @@
       :dataSource="hosts"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #name="{ record }">
-        {{ record.name }}
-        <a-tooltip v-if="record.name === $t('label.auto.assign')" :title="$t('message.migrate.instance.host.auto.assign')" placement="top">
-          <info-circle-outlined class="table-tooltip-icon" />
-        </a-tooltip>
-      </template>
-      <template #suitability="{ record }">
-        <check-circle-two-tone
-          class="host-item__suitability-icon"
-          twoToneColor="#52c41a"
-          v-if="record.suitableformigration" />
-        <close-circle-two-tone
-          class="host-item__suitability-icon"
-          twoToneColor="#f5222d"
-          v-else />
-      </template>
-      <template #memused="{ record }">
-        <span v-if="record.memoryused">
-          {{ $bytesToHumanReadableSize(record.memoryused) }}
-        </span>
-      </template>
-      <template #memoryallocatedpercentage="{ record }">
-        {{ record.memoryallocatedpercentage }}
-      </template>
-      <template #cluster="{ record }">
-        {{ record.clustername }}
-      </template>
-      <template #pod="{ record }">
-        {{ record.podname }}
-      </template>
-      <template #requiresstoragemigration="{ record }">
-        {{ record.requiresStorageMotion ? $t('label.yes') : $t('label.no') }}
-      </template>
-      <template #select="{record}">
-        <a-radio
-          class="host-item__radio"
-          @click="handleSelectedHostChange(record)"
-          :checked="record.id === selectedHost.id"
-          :disabled="!record.suitableformigration"></a-radio>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          {{ record.name }}
+          <a-tooltip v-if="record.name === $t('label.auto.assign')" :title="$t('message.migrate.instance.host.auto.assign')" placement="top">
+            <info-circle-outlined class="table-tooltip-icon" />
+          </a-tooltip>
+        </template>
+        <template v-if="column.key === 'suitability'">
+          <check-circle-two-tone
+            class="host-item__suitability-icon"
+            twoToneColor="#52c41a"
+            v-if="record.suitableformigration" />
+          <close-circle-two-tone
+            class="host-item__suitability-icon"
+            twoToneColor="#f5222d"
+            v-else />
+        </template>
+        <template v-if="column.key === 'memused'">
+          <span v-if="record.memoryused">
+            {{ $bytesToHumanReadableSize(record.memoryused) }}
+          </span>
+        </template>
+        <template v-if="column.key === 'memoryallocatedpercentage'">
+          {{ record.memoryallocatedpercentage }}
+        </template>
+        <template v-if="column.key === 'cluster'">
+          {{ record.clustername }}
+        </template>
+        <template v-if="column.key === 'pod'">
+          {{ record.podname }}
+        </template>
+        <template v-if="column.key === 'requiresstoragemigration'">
+          {{ record.requiresStorageMotion ? $t('label.yes') : $t('label.no') }}
+        </template>
+        <template v-if="column.key === 'select'">
+          <a-radio
+            class="host-item__radio"
+            @click="handleSelectedHostChange(record)"
+            :checked="record.id === selectedHost.id"
+            :disabled="!record.suitableformigration"></a-radio>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -149,44 +151,45 @@
       pageSize: 10,
       columns: [
         {
-          title: this.$t('label.hostid'),
-          slots: { customRender: 'name' }
+          key: 'name',
+          title: this.$t('label.hostid')
         },
         {
-          title: this.$t('label.suitability'),
-          slots: { customRender: 'suitability' }
+          key: 'suitability',
+          title: this.$t('label.suitability')
         },
         {
           title: this.$t('label.cpuused'),
           dataIndex: 'cpuused'
         },
         {
-          title: this.$t('label.memoryallocated'),
-          slots: { customRender: 'memoryallocatedpercentage' }
+          key: 'memoryallocatedpercentage',
+          title: this.$t('label.memoryallocated')
         },
         {
-          title: this.$t('label.memused'),
-          slots: { customRender: 'memused' }
+          key: 'memused',
+          title: this.$t('label.memused')
         },
         {
-          title: this.$t('label.cluster'),
-          slots: { customRender: 'cluster' }
+          key: 'cluster',
+          title: this.$t('label.cluster')
         },
         {
-          title: this.$t('label.pod'),
-          slots: { customRender: 'pod' }
+          key: 'pod',
+          title: this.$t('label.pod')
         },
         {
-          title: this.$t('label.storage.migration.required'),
-          slots: { customRender: 'requiresstoragemigration' }
+          key: 'requiresstoragemigration',
+          title: this.$t('label.storage.migration.required')
         },
         {
-          title: this.$t('label.select'),
-          slots: { customRender: 'select' }
+          key: 'select',
+          title: this.$t('label.select')
         }
       ],
       migrateWithStorage: false,
-      volumeToPoolSelection: []
+      volumeToPoolSelection: [],
+      volumes: []
     }
   },
   created () {
@@ -194,7 +197,7 @@
   },
   computed: {
     isUserVm () {
-      return this.$route.meta.name === 'vm'
+      return this.$route.meta.resourceType === 'UserVm'
     }
   },
   watch: {
@@ -246,6 +249,7 @@
     handleSelectedHostChange (host) {
       if (host.id === -1) {
         this.migrateWithStorage = false
+        this.fetchVolumes()
       }
       this.selectedHost = host
       this.selectedVolumeForStoragePoolSelection = {}
@@ -257,6 +261,31 @@
     handleVolumeToPoolChange (volumeToPool) {
       this.volumeToPoolSelection = volumeToPool
     },
+    fetchVolumes () {
+      this.loading = true
+      this.volumes = []
+      api('listVolumes', {
+        listAll: true,
+        virtualmachineid: this.resource.id
+      }).then(response => {
+        this.volumes = response.listvolumesresponse.volume
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    requiresStorageMigration () {
+      if (this.selectedHost.requiresStorageMotion || this.volumeToPoolSelection.length > 0) {
+        return true
+      }
+      if (this.selectedHost.id === -1 && this.volumes && this.volumes.length > 0) {
+        for (var volume of this.volumes) {
+          if (volume.storagetype === 'local') {
+            return true
+          }
+        }
+      }
+      return false
+    },
     handleKeyboardSubmit () {
       if (this.selectedHost.id) {
         this.submitForm()
@@ -269,7 +298,7 @@
       if (this.loading) return
       this.loading = true
       const migrateApi = this.isUserVm
-        ? (this.selectedHost.requiresStorageMotion || this.volumeToPoolSelection.length > 0)
+        ? this.requiresStorageMigration()
           ? 'migrateVirtualMachineWithVolume'
           : 'migrateVirtualMachine'
         : 'migrateSystemVm'
diff --git a/ui/src/views/compute/RegisterUserData.vue b/ui/src/views/compute/RegisterUserData.vue
index a8e5577..990e59f 100644
--- a/ui/src/views/compute/RegisterUserData.vue
+++ b/ui/src/views/compute/RegisterUserData.vue
@@ -51,9 +51,9 @@
             mode="tags"
             v-model:value="form.params"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="apiParams.params.description">
             <a-select-option v-for="opt in params" :key="opt">
@@ -71,12 +71,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
               :loading="domainLoading"
             :placeholder="apiParams.domainid.description"
             @change="val => { handleDomainChanged(domains[val]) }">
-            <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+            <a-select-option
+              v-for="(opt, optIndex) in domains"
+              :key="optIndex"
+              :label="opt.path || opt.name || opt.description || ''">
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -208,7 +211,7 @@
           params.params = userdataparams
         }
 
-        api('registerUserData', params).then(json => {
+        api('registerUserData', {}, 'POST', params).then(json => {
           this.$message.success(this.$t('message.success.register.user.data') + ' ' + values.name)
         }).catch(error => {
           this.$notifyError(error)
diff --git a/ui/src/views/compute/ReinstallVm.vue b/ui/src/views/compute/ReinstallVm.vue
new file mode 100644
index 0000000..ee07011
--- /dev/null
+++ b/ui/src/views/compute/ReinstallVm.vue
@@ -0,0 +1,307 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-form
+    v-ctrl-enter="handleSubmit"
+    @finish="handleSubmit"
+    layout="vertical"
+  >
+    <a-alert
+      type="warning"
+      show-icon
+    >
+      <template #message><span
+          style="margin-bottom: 5px"
+          v-html="$t('message.reinstall.vm')"
+        /></template>
+    </a-alert>
+    <a-form-item>
+      <template-iso-selection
+        input-decorator="templateid"
+        :items="templates"
+        :selected="tabKey"
+        :loading="loading.templates"
+        :preFillContent="resource.templateid"
+        :key="templateKey"
+        @handle-search-filter="($event) => fetchAllTemplates($event)"
+        @update-template-iso="updateFieldValue"
+      />
+    </a-form-item>
+    <a-form-item>
+      <template #label>
+        <tooltip-label
+          :title="$t('label.override.root.diskoffering')"
+          :tooltip="apiParams.diskofferingid.description"
+        />
+      </template>
+      <a-switch
+        v-model:checked="overrideDiskOffering"
+        @change="val => { overrideDiskOffering = val }"
+      />
+    </a-form-item>
+    <a-form-item v-if="overrideDiskOffering">
+      <disk-offering-selection
+        :items="diskOfferings"
+        :row-count="diskOfferingCount"
+        :zoneId="resource.zoneId"
+        :value="diskOffering ? diskOffering.id : ''"
+        :loading="loading.diskOfferings"
+        :preFillContent="resource.diskofferingid"
+        :isIsoSelected="false"
+        :isRootDiskOffering="true"
+        @on-selected-disk-size="onSelectDiskSize"
+        @handle-search-filter="($event) => fetchDiskOfferings($event)"
+      />
+    </a-form-item>
+    <a-form-item v-if="diskOffering && (diskOffering.iscustomized || diskOffering.iscustomizediops)">
+      <disk-size-selection
+        input-decorator="rootdisksize"
+        :diskSelected="diskOffering"
+        :isCustomized="diskOffering.iscustomized"
+        @handler-error="handlerError"
+        @update-disk-size="updateFieldValue"
+        @update-root-disk-iops-value="updateFieldValue"
+      />
+    </a-form-item>
+    <a-form-item v-if="!(diskOffering && diskOffering.iscustomized)">
+      <template #label>
+        <tooltip-label
+          :title="$t('label.override.rootdisk.size')"
+          :tooltip="apiParams.rootdisksize.description"
+        />
+      </template>
+      <a-switch
+        v-model:checked="overrideDiskSize"
+        @change="val => { overrideDiskSize = val }"
+      />
+      <disk-size-selection
+        v-if="overrideDiskSize"
+        input-decorator="rootdisksize"
+        :isCustomized="true"
+        @update-disk-size="(input, value) => updateFieldValue('overrideRootDiskSize', value)"
+        style="margin-top: 10px;"
+      />
+    </a-form-item>
+    <a-form-item>
+      <template #label>
+        <tooltip-label
+          :title="$t('label.expunge')"
+          :tooltip="apiParams.expunge.description"
+        />
+      </template>
+      <a-switch
+        v-model:checked="expungeDisk"
+        @change="val => { expungeDisk = val }"
+      />
+    </a-form-item>
+    <div
+      :span="24"
+      class="action-button"
+    >
+      <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
+      <a-button
+        ref="submit"
+        type="primary"
+        @click="handleSubmit"
+      >{{ $t('label.ok') }}</a-button>
+    </div>
+  </a-form>
+</template>
+
+<script>
+import { api } from '@/api'
+import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
+import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
+import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import _ from 'lodash'
+
+export default {
+  name: 'ReinstallVM',
+  components: {
+    DiskOfferingSelection,
+    DiskSizeSelection,
+    TemplateIsoSelection,
+    TooltipLabel
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  inject: ['parentFetchData'],
+  data () {
+    return {
+      overrideDiskOffering: false,
+      overrideDiskSize: false,
+      expungeDisk: false,
+      selectedDiskOffering: {},
+      loading: {
+        templates: false,
+        diskOfferings: false
+      },
+      rootDiskSizeKey: 'details[0].rootdisksize',
+      minIopsKey: 'details[0].minIops',
+      maxIopsKey: 'details[0].maxIops',
+      rootdisksize: 0,
+      minIops: 0,
+      maxIops: 0,
+      templateFilter: [
+        'featured',
+        'community',
+        'selfexecutable',
+        'sharedexecutable'
+      ],
+      diskOffering: {},
+      diskOfferingCount: 0,
+      templateKey: 0
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('restoreVirtualMachine')
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      this.fetchDiskOfferings({})
+      this.fetchAllTemplates()
+    },
+    closeAction () {
+      this.$emit('close-action')
+    },
+    handlerError (error) {
+      this.error = error
+    },
+    handleSubmit () {
+      const params = {
+        virtualmachineid: this.resource.id,
+        templateid: this.templateid
+      }
+      if (this.overrideDiskOffering) {
+        params.diskofferingid = this.diskOffering.id
+        if (this.diskOffering.iscustomized) {
+          params[this.rootDiskSizeKey] = this.rootdisksize
+        }
+        if (this.diskOffering.iscustomizediops) {
+          params[this.minIopsKey] = this.minIops
+          params[this.maxIopsKey] = this.maxIops
+        }
+      }
+      if (this.overrideDiskSize && this.overrideRootDiskSize) {
+        params.rootdisksize = this.overrideRootDiskSize
+      }
+      params.expunge = this.expungeDisk
+      api('restoreVirtualMachine', params).then(response => {
+        this.$pollJob({
+          jobId: response.restorevmresponse.jobid,
+          successMessage: this.$t('label.reinstall.vm') + ' ' + this.$t('label.success'),
+          successMethod: (result) => {
+            const vm = result.jobresult.virtualmachine || {}
+            const name = vm.displayname || vm.name || vm.id
+            if (result.jobstatus === 1 && vm.password) {
+              this.$notification.success({
+                message: `${this.$t('label.reinstall.vm')}: ` + name,
+                description: `${this.$t('label.password.reset.confirm')}: ` + vm.password,
+                duration: 0
+              })
+            }
+          },
+          errorMessage: this.$t('label.reinstall.vm') + ' ' + this.$t('label.failed'),
+          errorMethod: (result) => {
+            this.closeAction()
+          },
+          loadingMessage: this.$t('label.reinstall.vm') + ': ' + this.resource.name,
+          catchMessage: this.$t('error.fetching.async.job.result')
+        })
+      }).catch(error => {
+        this.$notifyError(error)
+        this.closeAction()
+      }).finally(() => {
+        this.closeAction()
+      })
+    },
+    fetchAllTemplates (params) {
+      const promises = []
+      const templates = {}
+      this.loading.templates = true
+      this.templateFilter.forEach((filter) => {
+        templates[filter] = { count: 0, template: [] }
+        promises.push(this.fetchTemplates(filter, params))
+      })
+      this.templates = templates
+      Promise.all(promises).then((response) => {
+        response.forEach((resItem, idx) => {
+          templates[this.templateFilter[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse
+          this.templates = { ...templates }
+        })
+      }).catch((reason) => {
+        console.log(reason)
+      }).finally(() => {
+        this.loading.templates = false
+      })
+    },
+    fetchTemplates (templateFilter, params) {
+      const args = Object.assign({}, params)
+      if (args.keyword || args.category !== templateFilter) {
+        args.page = 1
+        args.pageSize = args.pageSize || 10
+      }
+      args.zoneid = _.get(this.zone, 'id')
+      args.templatefilter = templateFilter
+      args.details = 'all'
+      args.showicon = 'true'
+
+      return new Promise((resolve, reject) => {
+        api('listTemplates', args).then((response) => {
+          resolve(response)
+        }).catch((reason) => {
+          reject(reason)
+        })
+      })
+    },
+    fetchDiskOfferings (params) {
+      api('listDiskOfferings', { zoneid: this.resource.zoneid, listall: true, ...params }).then((response) => {
+        this.diskOfferings = response?.listdiskofferingsresponse?.diskoffering || []
+        this.diskOfferingCount = response?.listdiskofferingsresponse?.count || 0
+      })
+    },
+    onSelectDiskSize (rowSelected) {
+      this.diskOffering = rowSelected
+    },
+    updateFieldValue (input, value) {
+      this[input] = value
+    }
+  }
+}
+</script>
+
+<style
+  scoped
+  lang="scss"
+>
+.ant-form {
+  width: 90vw;
+
+  @media (min-width: 700px) {
+    width: 50vw;
+  }
+}
+</style>
diff --git a/ui/src/views/compute/ResetSshKeyPair.vue b/ui/src/views/compute/ResetSshKeyPair.vue
index 12c0e6c..cc64caa 100644
--- a/ui/src/views/compute/ResetSshKeyPair.vue
+++ b/ui/src/views/compute/ResetSshKeyPair.vue
@@ -42,8 +42,10 @@
           @handle-search-filter="handleTableChange"
           style="overflow-y: auto" >
 
-          <template #account><user-outlined /> {{ $t('label.account') }}</template>
-          <template #domain><block-outlined /> {{ $t('label.domain') }}</template>
+          <template #headerCell="{ column }">
+            <template v-if="column.key === 'account'"><user-outlined /> {{ $t('label.account') }}</template>
+            <template v-if="column.key === 'domain'"><block-outlined /> {{ $t('label.domain') }}</template>
+          </template>
 
         </a-table>
       </div>
@@ -77,17 +79,17 @@
         {
           dataIndex: 'name',
           title: this.$t('label.name'),
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') },
+          sorter: (a, b) => genericCompare(a?.name || '', b?.name || ''),
           width: '40%'
         },
         {
+          key: 'account',
           dataIndex: 'account',
-          slots: { title: 'account' },
           width: '30%'
         },
         {
+          key: 'domain',
           dataIndex: 'domain',
-          slots: { title: 'domain' },
           width: '30%'
         }
       ],
diff --git a/ui/src/views/compute/ResetUserData.vue b/ui/src/views/compute/ResetUserData.vue
index b9753f4..6c57d13 100644
--- a/ui/src/views/compute/ResetUserData.vue
+++ b/ui/src/views/compute/ResetUserData.vue
@@ -41,8 +41,10 @@
               :dataSource="templateUserDataParams"
               :pagination="false"
               :rowKey="record => record.key">
-              <template #value="{ record }">
-                <a-input v-model:value="templateUserDataValues[record.key]" />
+              <template #bodyCell="{ column, record }">
+                <template v-if="column.key === 'value'">
+                  <a-input v-model:value="templateUserDataValues[record.key]" />
+                </template>
               </template>
             </a-table>
           </a-input-group>
@@ -87,8 +89,10 @@
                               :dataSource="userDataParams"
                               :pagination="false"
                               :rowKey="record => record.key">
-                              <template #value="{ record }">
-                                <a-input v-model:value="userDataValues[record.key]" />
+                              <template #bodyCell="{ column, record }">
+                                <template v-if="column.key === 'value'">
+                                  <a-input v-model:value="userDataValues[record.key]" />
+                                </template>
                               </template>
                             </a-table>
                           </a-input-group>
@@ -147,17 +151,17 @@
         {
           dataIndex: 'name',
           title: this.$t('label.name'),
-          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') },
+          sorter: (a, b) => genericCompare(a?.name || '', b?.name || ''),
           width: '40%'
         },
         {
           dataIndex: 'account',
-          slots: { title: 'account' },
+          title: this.$t('account'),
           width: '30%'
         },
         {
           dataIndex: 'domain',
-          slots: { title: 'domain' },
+          title: this.$t('domain'),
           width: '30%'
         }
       ],
@@ -188,7 +192,7 @@
         {
           title: this.$t('label.value'),
           dataIndex: 'value',
-          slots: { customRender: 'value' }
+          key: 'value'
         }
       ],
       userDataValues: {},
@@ -301,7 +305,7 @@
       this.userDataParams = []
       api('listUserData', { id: id }).then(json => {
         const resp = json?.listuserdataresponse?.userdata || []
-        if (resp) {
+        if (resp.length > 0) {
           var params = resp[0].params
           if (params) {
             var dataParams = params.split(',')
diff --git a/ui/src/views/compute/ScaleKubernetesCluster.vue b/ui/src/views/compute/ScaleKubernetesCluster.vue
index f9af2ef..8d73ac8 100644
--- a/ui/src/views/compute/ScaleKubernetesCluster.vue
+++ b/ui/src/views/compute/ScaleKubernetesCluster.vue
@@ -38,11 +38,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="serviceOfferingLoading"
             :placeholder="apiParams.serviceofferingid.description">
-            <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in serviceOfferings" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/compute/StartVirtualMachine.vue b/ui/src/views/compute/StartVirtualMachine.vue
index 4440153..7438ebc 100644
--- a/ui/src/views/compute/StartVirtualMachine.vue
+++ b/ui/src/views/compute/StartVirtualMachine.vue
@@ -38,13 +38,13 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="podsLoading"
               :placeholder="apiParams.podid.description"
               @change="handlePodChange"
               v-focus="$store.getters.userInfo.roletype === 'Admin'">
-              <a-select-option v-for="pod in pods" :key="pod.id">
+              <a-select-option v-for="pod in pods" :key="pod.id" :label="pod.name">
                 {{ pod.name }}
               </a-select-option>
             </a-select>
@@ -59,12 +59,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="clustersLoading"
               :placeholder="apiParams.clusterid.description"
               @change="handleClusterChange">
-              <a-select-option v-for="cluster in clusters" :key="cluster.id">
+              <a-select-option v-for="cluster in clusters" :key="cluster.id" :label="cluster.name">
                 {{ cluster.name }}
               </a-select-option>
             </a-select>
@@ -79,11 +79,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="hostsLoading"
               :placeholder="apiParams.hostid.description">
-              <a-select-option v-for="host in hosts" :key="host.id">
+              <a-select-option v-for="host in hosts" :key="host.id" :label="host.name">
                 {{ host.name }}
               </a-select-option>
             </a-select>
diff --git a/ui/src/views/compute/UpgradeKubernetesCluster.vue b/ui/src/views/compute/UpgradeKubernetesCluster.vue
index 02d4ffe..22cfb0f 100644
--- a/ui/src/views/compute/UpgradeKubernetesCluster.vue
+++ b/ui/src/views/compute/UpgradeKubernetesCluster.vue
@@ -38,12 +38,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="kubernetesVersionLoading"
             :placeholder="apiParams.kubernetesversionid.description"
             v-focus="true" >
-            <a-select-option v-for="(opt, optIndex) in this.kubernetesVersions" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in this.kubernetesVersions" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/compute/backup/BackupSchedule.vue b/ui/src/views/compute/backup/BackupSchedule.vue
index 32da2d4..ffa53aa 100644
--- a/ui/src/views/compute/backup/BackupSchedule.vue
+++ b/ui/src/views/compute/backup/BackupSchedule.vue
@@ -24,52 +24,54 @@
       :rowKey="record => record.virtualmachineid"
       :pagination="false"
       :loading="loading">
-      <template #icon="{ text, record }" :name="text">
-        <label class="interval-icon">
-          <span v-if="record.intervaltype==='HOURLY'">
-            <clock-circle-outlined />
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'icon'" :name="text">
+          <label class="interval-icon">
+            <span v-if="record.intervaltype==='HOURLY'">
+              <clock-circle-outlined />
+            </span>
+            <span class="custom-icon icon-daily" v-else-if="record.intervaltype==='DAILY'">
+              <calendar-outlined />
+            </span>
+            <span class="custom-icon icon-weekly" v-else-if="record.intervaltype==='WEEKLY'">
+              <calendar-outlined />
+            </span>
+            <span class="custom-icon icon-monthly" v-else-if="record.intervaltype==='MONTHLY'">
+              <calendar-outlined />
+            </span>
+          </label>
+        </template>
+        <template v-if="column.key === 'intervaltype'" :name="text">
+          <label>{{ record.intervaltype }}</label>
+        </template>
+        <template v-if="column.key === 'time'" :name="text">
+          <label class="interval-content">
+            <span v-if="record.intervaltype==='HOURLY'">{{ record.schedule + ' ' + $t('label.min.past.hour') }}</span>
+            <span v-else>{{ record.schedule.split(':')[1] + ':' + record.schedule.split(':')[0] }}</span>
+          </label>
+        </template>
+        <template v-if="column.key === 'interval'" :name="text">
+          <span v-if="record.intervaltype==='WEEKLY'">
+            {{ `${$t('label.every')} ${$t(listDayOfWeek[record.schedule.split(':')[2] - 1])}` }}
           </span>
-          <span class="custom-icon icon-daily" v-else-if="record.intervaltype==='DAILY'">
-            <calendar-outlined />
+          <span v-else-if="record.intervaltype==='MONTHLY'">
+            {{ `${$t('label.day')} ${record.schedule.split(':')[2]} ${$t('label.of.month')}` }}
           </span>
-          <span class="custom-icon icon-weekly" v-else-if="record.intervaltype==='WEEKLY'">
-            <calendar-outlined />
-          </span>
-          <span class="custom-icon icon-monthly" v-else-if="record.intervaltype==='MONTHLY'">
-            <calendar-outlined />
-          </span>
-        </label>
-      </template>
-      <template #intervaltype="{ text, record }" :name="text">
-        <label>{{ record.intervaltype }}</label>
-      </template>
-      <template #time="{ text, record }" :name="text">
-        <label class="interval-content">
-          <span v-if="record.intervaltype==='HOURLY'">{{ record.schedule + ' ' + $t('label.min.past.hour') }}</span>
-          <span v-else>{{ record.schedule.split(':')[1] + ':' + record.schedule.split(':')[0] }}</span>
-        </label>
-      </template>
-      <template #interval="{ text, record }" :name="text">
-        <span v-if="record.intervaltype==='WEEKLY'">
-          {{ `${$t('label.every')} ${$t(listDayOfWeek[record.schedule.split(':')[2] - 1])}` }}
-        </span>
-        <span v-else-if="record.intervaltype==='MONTHLY'">
-          {{ `${$t('label.day')} ${record.schedule.split(':')[2]} ${$t('label.of.month')}` }}
-        </span>
-      </template>
-      <template #timezone="{ text, record }" :name="text">
-        <label>{{ getTimeZone(record.timezone) }}</label>
-      </template>
-      <template #action="{ text, record }" class="account-button-action" :name="text">
-        <tooltip-button
-          tooltipPlacement="top"
-          :tooltip="$t('label.delete')"
-          type="primary"
-          :danger="true"
-          icon="close-outlined"
-          size="small"
-          :loading="actionLoading"
-          @onClick="handleClickDelete(record)"/>
+        </template>
+        <template v-if="column.key === 'timezone'" :name="text">
+          <label>{{ getTimeZone(record.timezone) }}</label>
+        </template>
+        <template v-if="column.key === 'actions'" class="account-button-action" :name="text">
+          <tooltip-button
+            tooltipPlacement="top"
+            :tooltip="$t('label.delete')"
+            type="primary"
+            :danger="true"
+            icon="close-outlined"
+            size="small"
+            :loading="actionLoading"
+            @onClick="handleClickDelete(record)"/>
+        </template>
       </template>
     </a-table>
   </div>
@@ -110,36 +112,34 @@
     columns () {
       return [
         {
+          key: 'icon',
           title: '',
           dataIndex: 'icon',
-          width: 30,
-          slots: { customRender: 'icon' }
+          width: 30
         },
         {
           title: this.$t('label.intervaltype'),
-          dataIndex: 'intervaltype',
-          slots: { customRender: 'intervaltype' }
+          dataIndex: 'intervaltype'
         },
         {
           title: this.$t('label.time'),
-          dataIndex: 'schedule',
-          slots: { customRender: 'time' }
+          dataIndex: 'schedule'
         },
         {
+          key: 'interval',
           title: '',
-          dataIndex: 'interval',
-          slots: { customRender: 'interval' }
+          dataIndex: 'interval'
         },
         {
+          key: 'timezone',
           title: this.$t('label.timezone'),
-          dataIndex: 'timezone',
-          slots: { customRender: 'timezone' }
+          dataIndex: 'timezone'
         },
         {
-          title: this.$t('label.action'),
-          dataIndex: 'action',
-          width: 80,
-          slots: { customRender: 'action' }
+          key: 'actions',
+          title: this.$t('label.actions'),
+          dataIndex: 'actions',
+          width: 80
         }
       ]
     }
diff --git a/ui/src/views/compute/backup/FormSchedule.vue b/ui/src/views/compute/backup/FormSchedule.vue
index d1512623..3b8db9d 100644
--- a/ui/src/views/compute/backup/FormSchedule.vue
+++ b/ui/src/views/compute/backup/FormSchedule.vue
@@ -70,7 +70,8 @@
                 <a-time-picker
                   use12Hours
                   format="h:mm A"
-                  v-model:value="form.timeSelect" />
+                  v-model:value="form.timeSelect"
+                  style="width: 100%;" />
               </a-form-item>
             </a-col>
             <a-col :md="24" :lg="12" v-if="form.intervaltype==='weekly'">
@@ -80,9 +81,9 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }" >
-                  <a-select-option v-for="(opt, optIndex) in dayOfWeek" :key="optIndex">
+                  <a-select-option v-for="(opt, optIndex) in dayOfWeek" :key="optIndex" :label="opt.name || opt.description">
                     {{ opt.name || opt.description }}
                   </a-select-option>
                 </a-select>
@@ -95,9 +96,9 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }">
-                  <a-select-option v-for="opt in dayOfMonth" :key="opt.name">
+                  <a-select-option v-for="opt in dayOfMonth" :key="opt.name" :label="opt.name || opt.description">
                     {{ opt.name }}
                   </a-select-option>
                 </a-select>
@@ -110,10 +111,10 @@
                   v-model:value="form.timezone"
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }"
                   :loading="fetching">
-                  <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+                  <a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
                     {{ opt.name || opt.description }}
                   </a-select-option>
                 </a-select>
diff --git a/ui/src/views/compute/wizard/ComputeOfferingSelection.vue b/ui/src/views/compute/wizard/ComputeOfferingSelection.vue
index fb1ab66..4450ce1 100644
--- a/ui/src/views/compute/wizard/ComputeOfferingSelection.vue
+++ b/ui/src/views/compute/wizard/ComputeOfferingSelection.vue
@@ -32,8 +32,10 @@
       size="middle"
       :scroll="{ y: 225 }"
     >
-      <template #cpuTitle><appstore-outlined /> {{ $t('label.cpu') }}</template>
-      <template #ramTitle><bulb-outlined /> {{ $t('label.memory') }}</template>
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'cpu'"><appstore-outlined /> {{ $t('label.cpu') }}</template>
+        <template v-if="column.key === 'ram'"><bulb-outlined /> {{ $t('label.memory') }}</template>
+      </template>
     </a-table>
 
     <div style="display: block; text-align: right;">
@@ -102,6 +104,11 @@
     minimumMemory: {
       type: Number,
       default: 0
+    },
+    allowAllOfferings: {
+      type: Boolean,
+      required: false,
+      default: false
     }
   },
   data () {
@@ -109,18 +116,19 @@
       filter: '',
       columns: [
         {
+          key: 'name',
           dataIndex: 'name',
           title: this.$t('label.serviceofferingid'),
           width: '40%'
         },
         {
+          key: 'cpu',
           dataIndex: 'cpu',
-          slots: { title: 'cpuTitle' },
           width: '30%'
         },
         {
+          key: 'ram',
           dataIndex: 'ram',
-          slots: { title: 'ramTitle' },
           width: '30%'
         }
       ],
@@ -165,7 +173,7 @@
           disabled = true
         }
         if (disabled === false && maxMemory && this.minimumMemory > 0 &&
-          ((item.iscustomized === false && maxMemory < this.minimumMemory) ||
+          ((item.iscustomized === false && ((maxMemory < this.minimumMemory) || this.exactMatch && maxMemory !== this.minimumMemory)) ||
             (item.iscustomized === true && maxMemory < this.minimumMemory))) {
           disabled = true
         }
@@ -175,6 +183,9 @@
         if (this.autoscale && item.iscustomized) {
           disabled = true
         }
+        if (this.allowAllOfferings) {
+          disabled = false
+        }
         return {
           key: item.id,
           name: item.name,
diff --git a/ui/src/views/compute/wizard/DiskOfferingSelection.vue b/ui/src/views/compute/wizard/DiskOfferingSelection.vue
index 3785baa..b6c022e 100644
--- a/ui/src/views/compute/wizard/DiskOfferingSelection.vue
+++ b/ui/src/views/compute/wizard/DiskOfferingSelection.vue
@@ -32,18 +32,23 @@
       size="middle"
       :scroll="{ y: 225 }"
     >
-      <template #diskSizeTitle><hdd-outlined /> {{ $t('label.disksize') }}</template>
-      <template #iopsTitle><rocket-outlined /> {{ $t('label.minmaxiops') }}</template>
-      <template #diskSize="{ record }">
-        <div v-if="record.isCustomized">{{ $t('label.iscustomized') }}</div>
-        <div v-else-if="record.diskSize">{{ record.diskSize }} GB</div>
-        <div v-else>-</div>
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'diskSize'"><hdd-outlined /> {{ $t('label.disksize') }}</template>
+        <template v-if="column.key === 'iops'"><rocket-outlined /> {{ $t('label.minmaxiops') }}</template>
       </template>
-      <template #iops="{ record }">
-        <span v-if="record.miniops && record.maxiops">{{ record.miniops }} - {{ record.maxiops }}</span>
-        <span v-else-if="record.miniops && !record.maxiops">{{ record.miniops }}</span>
-        <span v-else-if="!record.miniops && record.maxiops">{{ record.maxiops }}</span>
-        <span v-else>-</span>
+
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'diskSize'">
+          <div v-if="record.isCustomized">{{ $t('label.iscustomized') }}</div>
+          <div v-else-if="record.diskSize">{{ record.diskSize }} GB</div>
+          <div v-else>-</div>
+        </template>
+        <template v-if="column.key === 'iops'">
+          <span v-if="record.miniops && record.maxiops">{{ record.miniops }} - {{ record.maxiops }}</span>
+          <span v-else-if="record.miniops && !record.maxiops">{{ record.miniops }}</span>
+          <span v-else-if="!record.miniops && record.maxiops">{{ record.maxiops }}</span>
+          <span v-else>-</span>
+        </template>
       </template>
     </a-table>
 
@@ -108,19 +113,20 @@
       filter: '',
       columns: [
         {
+          key: 'name',
           dataIndex: 'name',
           title: this.$t('label.diskoffering'),
           width: '40%'
         },
         {
+          key: 'diskSize',
           dataIndex: 'disksize',
-          width: '30%',
-          slots: { customRender: 'diskSize', title: 'diskSizeTitle' }
+          width: '30%'
         },
         {
+          key: 'iops',
           dataIndex: 'iops',
-          width: '30%',
-          slots: { customRender: 'iops', title: 'iopsTitle' }
+          width: '30%'
         }
       ],
       selectedRowKeys: ['0'],
diff --git a/ui/src/views/compute/wizard/LoadBalancerSelection.vue b/ui/src/views/compute/wizard/LoadBalancerSelection.vue
index f29b229..e2ffb99 100644
--- a/ui/src/views/compute/wizard/LoadBalancerSelection.vue
+++ b/ui/src/views/compute/wizard/LoadBalancerSelection.vue
@@ -32,9 +32,9 @@
       :rowSelection="rowSelection"
       size="middle"
       :scroll="{ y: 225 }">
-      <template #publicip><environment-outlined /> {{ $t('label.publicip') }}</template>
-      <template #publicport>{{ $t('label.publicport') }}</template>
-      <template #privateport>{{ $t('label.privateport') }}</template>
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'publicip'"><environment-outlined /> {{ $t('label.publicip') }}</template>
+      </template>
     </a-table>
 
     <div style="display: block; text-align: right;">
@@ -113,6 +113,7 @@
           width: '40%'
         },
         {
+          key: 'publicip',
           title: this.$t('label.publicip'),
           dataIndex: 'publicip'
         },
diff --git a/ui/src/views/compute/wizard/MultiDiskSelection.vue b/ui/src/views/compute/wizard/MultiDiskSelection.vue
index 55c30c3..8344508 100644
--- a/ui/src/views/compute/wizard/MultiDiskSelection.vue
+++ b/ui/src/views/compute/wizard/MultiDiskSelection.vue
@@ -26,44 +26,46 @@
       :rowSelection="rowSelection"
       :scroll="{ y: 225 }" >
 
-      <template #name="{ record }">
-        <span>{{ record.displaytext || record.name }}</span>
-        <div v-if="record.meta">
-          <div v-for="meta in record.meta" :key="meta.key">
-            <a-tag style="margin-top: 5px" :key="meta.key">{{ meta.key + ': ' + meta.value }}</a-tag>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          <span>{{ record.displaytext || record.name }}</span>
+          <div v-if="record.meta">
+            <div v-for="meta in record.meta" :key="meta.key">
+              <a-tag v-if="(isKVMUnmanage && meta.key !== 'datastore') || !isKVMUnmanage" style="margin-top: 5px" :key="meta.key">{{ meta.key + ': ' + meta.value }}</a-tag>
+            </div>
           </div>
-        </div>
-      </template>
-      <template #offering="{ record }">
-        <span
-          style="width: 50%"
-          v-if="validOfferings[record.id] && validOfferings[record.id].length > 0">
-          <check-box-select-pair
-            v-if="selectedCustomDiskOffering!=null"
-            layout="vertical"
-            :resourceKey="record.id"
-            :selectOptions="validOfferings[record.id]"
-            :checkBoxLabel="autoSelectLabel"
-            :defaultCheckBoxValue="true"
-            :reversed="true"
-            @handle-checkselectpair-change="updateOfferingCheckPairSelect" />
-          <a-select
-            v-else
-            @change="updateOfferingSelect($event, record.id)"
-            :defaultValue="validOfferings[record.id][0].id"
-            showSearch
-            optionFilterProp="label"
-            :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-            }" >
-            <a-select-option v-for="offering in validOfferings[record.id]" :key="offering.id">
-              {{ offering.displaytext }}
-            </a-select-option>
-          </a-select>
-        </span>
-        <span v-else style="width: 50%">
-          {{ $t('label.no.matching.offering') }}
-        </span>
+        </template>
+        <template v-if="column.key === 'offering'">
+          <span
+            style="width: 50%"
+            v-if="validOfferings[record.id] && validOfferings[record.id].length > 0">
+            <check-box-select-pair
+              v-if="selectedCustomDiskOffering!=null"
+              layout="vertical"
+              :resourceKey="record.id"
+              :selectOptions="validOfferings[record.id]"
+              :checkBoxLabel="autoSelectLabel"
+              :defaultCheckBoxValue="true"
+              :reversed="true"
+              @handle-checkselectpair-change="updateOfferingCheckPairSelect" />
+            <a-select
+              v-else
+              @change="updateOfferingSelect($event, record.id)"
+              :defaultValue="validOfferings[record.id][0].id"
+              showSearch
+              optionFilterProp="label"
+              :filterOption="(input, option) => {
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              }" >
+              <a-select-option v-for="offering in validOfferings[record.id]" :key="offering.id" :label="offering.displaytext">
+                {{ offering.displaytext }}
+              </a-select-option>
+            </a-select>
+          </span>
+          <span v-else style="width: 50%">
+            {{ $t('label.no.matching.offering') }}
+          </span>
+        </template>
       </template>
     </a-table>
   </div>
@@ -102,20 +104,24 @@
     autoSelectLabel: {
       type: String,
       default: ''
+    },
+    isKVMUnmanage: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
       columns: [
         {
+          key: 'name',
           dataIndex: 'name',
-          title: this.$t('label.data.disk'),
-          slots: { customRender: 'name' }
+          title: this.$t('label.data.disk')
         },
         {
+          key: 'offering',
           dataIndex: 'offering',
-          title: this.$t('label.data.disk.offering'),
-          slots: { customRender: 'offering' }
+          title: this.$t('label.data.disk.offering')
         }
       ],
       loading: false,
diff --git a/ui/src/views/compute/wizard/MultiNetworkSelection.vue b/ui/src/views/compute/wizard/MultiNetworkSelection.vue
index bdbd23e..048a706 100644
--- a/ui/src/views/compute/wizard/MultiNetworkSelection.vue
+++ b/ui/src/views/compute/wizard/MultiNetworkSelection.vue
@@ -26,41 +26,55 @@
       :rowSelection="rowSelection"
       :scroll="{ y: 225 }" >
 
-      <template #name="{record}">
-        <span>{{ record.displaytext || record.name }}</span>
-        <div v-if="record.meta">
-          <div v-for="meta in record.meta" :key="meta.key">
-            <a-tag style="margin-top: 5px" :key="meta.key">{{ meta.key + ': ' + meta.value }}</a-tag>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          <span>{{ record.displaytext || record.name }}</span>
+          <div v-if="record.meta">
+            <div v-for="meta in record.meta" :key="meta.key">
+              <a-tag style="margin-top: 5px" :key="meta.key">{{ meta.key + ': ' + meta.value }}</a-tag>
+            </div>
           </div>
-        </div>
-      </template>
-      <template #network="{record}">
-        <a-select
-          v-if="validNetworks[record.id] && validNetworks[record.id].length > 0"
-          :defaultValue="validNetworks[record.id][0].id"
-          @change="val => handleNetworkChange(record, val)"
-          showSearch
-          optionFilterProp="label"
-          :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-          }" >
-          <a-select-option v-for="network in validNetworks[record.id]" :key="network.id">
-            {{ network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '') }}
-          </a-select-option>
-        </a-select>
-        <span v-else>
-          {{ $t('label.no.matching.network') }}
-        </span>
-      </template>
-      <template #ipaddress="{record}">
-        <check-box-input-pair
-          layout="vertical"
-          :resourceKey="record.id"
-          :checkBoxLabel="$t('label.auto.assign.random.ip')"
-          :defaultCheckBoxValue="true"
-          :reversed="true"
-          :visible="(indexNum > 0 && ipAddressesEnabled[record.id])"
-          @handle-checkinputpair-change="setIpAddress" />
+        </template>
+        <template v-if="column.key === 'network'">
+          <a-alert
+            v-if="hypervisor === 'KVM' && unableToMatch"
+            type="warning"
+            showIcon
+            banner
+            style="margin-bottom: 10px"
+            :message="$t('message.select.nic.network')"
+          />
+          <a-select
+            style="width: 100%"
+            v-if="validNetworks[record.id] && validNetworks[record.id].length > 0"
+            :defaultValue="validNetworks[record.id][0].id"
+            @change="val => handleNetworkChange(record, val)"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+            <a-select-option
+              v-for="network in hypervisor !== 'KVM' ? validNetworks[record.id] : networks"
+              :key="network.id"
+              :label="network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '')">
+              <div>{{ network.displaytext + (network.broadcasturi ? ' (' + network.broadcasturi + ')' : '') }}</div>
+            </a-select-option>
+          </a-select>
+          <span v-else>
+            {{ $t('label.no.matching.network') }}
+          </span>
+        </template>
+        <template v-if="column.key === 'ipaddress'">
+          <check-box-input-pair
+            layout="vertical"
+            :resourceKey="record.id"
+            :checkBoxLabel="$t('label.auto.assign.random.ip')"
+            :defaultCheckBoxValue="true"
+            :reversed="true"
+            :visible="(indexNum > 0 && ipAddressesEnabled[record.id])"
+            @handle-checkinputpair-change="setIpAddress" />
+        </template>
       </template>
     </a-table>
   </div>
@@ -96,31 +110,36 @@
     filterMatchKey: {
       type: String,
       default: null
+    },
+    hypervisor: {
+      type: String,
+      default: null
     }
   },
   data () {
     return {
       columns: [
         {
+          key: 'name',
           dataIndex: 'name',
-          title: this.$t('label.nic'),
-          slots: { customRender: 'name' }
+          title: this.$t('label.nic')
         },
         {
+          key: 'network',
           dataIndex: 'network',
-          title: this.$t('label.network'),
-          slots: { customRender: 'network' }
+          title: this.$t('label.network')
         },
         {
+          key: 'ipaddress',
           dataIndex: 'ipaddress',
-          title: this.$t('label.ipaddress'),
-          slots: { customRender: 'ipaddress' }
+          title: this.$t('label.ipaddress')
         }
       ],
       loading: false,
       selectedRowKeys: [],
       networks: [],
       validNetworks: {},
+      unableToMatch: false,
       values: {},
       ipAddressesEnabled: {},
       ipAddresses: {},
@@ -196,7 +215,13 @@
           this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => (x.state === 'Implemented' || (x.state === 'Setup' && ['Shared', 'L2'].includes(x.type))))
         }
         if (this.filterMatchKey) {
-          this.validNetworks[item.id] = this.validNetworks[item.id].filter(x => x[this.filterMatchKey] === item[this.filterMatchKey])
+          const filtered = this.networks.filter(x => x[this.filterMatchKey] === item[this.filterMatchKey])
+          if (this.hypervisor === 'KVM') {
+            this.unableToMatch = filtered.length === 0
+            this.validNetworks[item.id] = filtered.length === 0 ? this.networks : filtered.concat(this.networks.filter(x => filtered.includes(x)))
+          } else {
+            this.validNetworks[item.id] = filtered
+          }
         }
       }
       this.setDefaultValues()
@@ -204,6 +229,8 @@
     },
     setIpAddressEnabled (nic, network) {
       this.ipAddressesEnabled[nic.id] = network && network.type !== 'L2'
+      this.ipAddresses[nic.id] = (!network || network.type === 'L2') ? null : 'auto'
+      this.values[nic.id] = network ? network.id : null
       this.indexNum = (this.indexNum % 2) + 1
     },
     setIpAddress (nicId, autoAssign, ipAddress) {
@@ -222,7 +249,11 @@
       this.sendValuesTimed()
     },
     handleNetworkChange (nic, networkId) {
-      this.setIpAddressEnabled(nic, _.find(this.validNetworks[nic.id], (option) => option.id === networkId))
+      if (this.hypervisor === 'KVM') {
+        this.setIpAddressEnabled(nic, _.find(this.networks, (option) => option.id === networkId))
+      } else {
+        this.setIpAddressEnabled(nic, _.find(this.validNetworks[nic.id], (option) => option.id === networkId))
+      }
       this.sendValuesTimed()
     },
     sendValuesTimed () {
diff --git a/ui/src/views/compute/wizard/NetworkConfiguration.vue b/ui/src/views/compute/wizard/NetworkConfiguration.vue
index a31f7f4..ac2194c 100644
--- a/ui/src/views/compute/wizard/NetworkConfiguration.vue
+++ b/ui/src/views/compute/wizard/NetworkConfiguration.vue
@@ -16,6 +16,12 @@
 // under the License.
 
 <template>
+  <div style="margin-top: 10px;" v-if="this.vnf">
+    <label>{{ $t('message.configure.network.ip.and.mac') }}</label>
+  </div>
+  <div style="margin-top: 10px;" v-else>
+    <label>{{ $t('message.configure.network.select.default.network') }}</label>
+  </div>
   <a-form
     :ref="formRef"
     :model="form"
@@ -29,42 +35,46 @@
       :rowKey="record => record.id"
       size="middle"
       :scroll="{ y: 225 }">
-      <template #name="{ text, record }">
-        <div>{{ text }}</div>
-        <small v-if="record.type!=='L2'">{{ $t('label.cidr') + ': ' + record.cidr }}</small>
-      </template>
-      <template #ipAddress="{ record }" v-if="!this.autoscale">
-        <a-form-item
-          style="display: block"
-          v-if="record.type !== 'L2'"
-          :name="'ipAddress' + record.id">
-          <a-input
-            style="width: 150px;"
-            v-model:value="form['ipAddress' + record.id]"
-            :placeholder="record.cidr"
-            @change="($event) => updateNetworkData('ipAddress', record.id, $event.target.value)">
-            <template #suffix>
-              <a-tooltip :title="getIpRangeDescription(record)">
-                <info-circle-outlined style="color: rgba(0,0,0,.45)" />
-              </a-tooltip>
-            </template>
-          </a-input>
-        </a-form-item>
-      </template>
-      <template #macAddress="{ record }" v-if="!this.autoscale">
-        <a-form-item style="display: block" :name="'macAddress' + record.id">
-          <a-input
-            style="width: 150px;"
-            :placeholder="$t('label.macaddress')"
-            v-model:value="form[`macAddress` + record.id]"
-            @change="($event) => updateNetworkData('macAddress', record.id, $event.target.value)">
-            <template #suffix>
-              <a-tooltip :title="$t('label.macaddress.example')">
-                <info-circle-outlined style="color: rgba(0,0,0,.45)" />
-              </a-tooltip>
-            </template>
-          </a-input>
-        </a-form-item>
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'name'">
+          <div>{{ text }}</div>
+          <small v-if="record.type!=='L2'">{{ $t('label.cidr') + ': ' + record.cidr }}</small>
+        </template>
+        <template  v-if="!this.autoscale">
+          <template v-if="column.key === 'ipAddress'">
+            <a-form-item
+              style="display: block"
+              v-if="record.type !== 'L2'"
+              :name="'ipAddress' + record.id">
+              <a-input
+                style="width: 150px;"
+                v-model:value="form['ipAddress' + record.id]"
+                :placeholder="record.cidr"
+                @change="($event) => updateNetworkData('ipAddress', record.id, $event.target.value)">
+                <template #suffix>
+                  <a-tooltip :title="getIpRangeDescription(record)">
+                    <info-circle-outlined style="color: rgba(0,0,0,.45)" />
+                  </a-tooltip>
+                </template>
+              </a-input>
+            </a-form-item>
+          </template>
+          <template v-if="column.key === 'macAddress'">
+            <a-form-item style="display: block" :name="'macAddress' + record.id">
+              <a-input
+                style="width: 150px;"
+                :placeholder="$t('label.macaddress')"
+                v-model:value="form[`macAddress` + record.id]"
+                @change="($event) => updateNetworkData('macAddress', record.id, $event.target.value)">
+                <template #suffix>
+                  <a-tooltip :title="$t('label.macaddress.example')">
+                    <info-circle-outlined style="color: rgba(0,0,0,.45)" />
+                  </a-tooltip>
+                </template>
+              </a-input>
+            </a-form-item>
+          </template>
+        </template>
       </template>
     </a-table>
   </a-form>
@@ -87,6 +97,10 @@
       type: Boolean,
       default: () => false
     },
+    vnf: {
+      type: Boolean,
+      default: () => false
+    },
     preFillContent: {
       type: Object,
       default: () => {}
@@ -97,22 +111,22 @@
       networks: [],
       columns: [
         {
+          key: 'name',
           dataIndex: 'name',
-          title: this.$t('label.defaultnetwork'),
-          width: '30%',
-          slots: { customRender: 'name' }
+          title: this.$t('label.network'),
+          width: '30%'
         },
         {
+          key: 'ipAddress',
           dataIndex: 'ip',
           title: this.$t('label.ip'),
-          width: '30%',
-          slots: { customRender: 'ipAddress' }
+          width: '30%'
         },
         {
+          key: 'macAddress',
           dataIndex: 'mac',
           title: this.$t('label.macaddress'),
-          width: '30%',
-          slots: { customRender: 'macAddress' }
+          width: '30%'
         }
       ],
       selectedRowKeys: [],
@@ -134,6 +148,9 @@
   },
   computed: {
     rowSelection () {
+      if (this.vnf) {
+        return null
+      }
       return {
         type: 'radio',
         selectedRowKeys: this.selectedRowKeys,
@@ -171,6 +188,8 @@
       const form = {}
       const rules = {}
 
+      let presetMacAddressIndex = 0
+
       this.dataItems.forEach(record => {
         const ipAddressKey = 'ipAddress' + record.id
         const macAddressKey = 'macAddress' + record.id
@@ -185,6 +204,9 @@
         rules[macAddressKey] = [{ validator: this.validatorMacAddress }]
         if (record.macAddress) {
           form[macAddressKey] = record.macAddress
+        } else if (this.preFillContent.macAddressArray && this.preFillContent.macAddressArray[presetMacAddressIndex]) {
+          form[macAddressKey] = this.preFillContent.macAddressArray[presetMacAddressIndex]
+          presetMacAddressIndex++
         }
       })
       this.form = reactive(form)
diff --git a/ui/src/views/compute/wizard/NetworkSelection.vue b/ui/src/views/compute/wizard/NetworkSelection.vue
index 96a6d28..4856d25 100644
--- a/ui/src/views/compute/wizard/NetworkSelection.vue
+++ b/ui/src/views/compute/wizard/NetworkSelection.vue
@@ -22,7 +22,7 @@
       :placeholder="$t('label.search')"
       v-model:value="filter"
       @search="handleSearch" />
-    <a-button type="primary" @click="onCreateNetworkClick" style="float: right; margin-right: 5px; z-index: 8" v-if="showCreateButton">
+    <a-button type="primary" @click="onCreateNetworkClick" style="float: right; margin-right: 5px; z-index: 8" v-if="showCreateButton && !this.vnf">
       {{ $t('label.create.network') }}
     </a-button>
     <a-table
@@ -34,14 +34,16 @@
       :rowSelection="rowSelection"
       :scroll="{ y: 225 }"
     >
-      <template #name="{record}">
-        <resource-icon
-          v-if="record.icon"
-          :image="record.icon.base64image"
-          size="1x"
-          style="margin-right: 5px"/>
-        <apartment-outlined v-else style="margin-right: 5px" />
-        {{ record.name }}
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          <resource-icon
+            v-if="record.icon"
+            :image="record.icon.base64image"
+            size="1x"
+            style="margin-right: 5px"/>
+          <apartment-outlined v-else style="margin-right: 5px" />
+          {{ record.name }}
+        </template>
       </template>
       <template #expandedRowRender="{ record }">
         <a-list
@@ -136,6 +138,10 @@
       type: Boolean,
       default: () => false
     },
+    vnf: {
+      type: Boolean,
+      default: () => false
+    },
     preFillContent: {
       type: Object,
       default: () => {}
@@ -173,29 +179,35 @@
           }
         })
       }
+      const vpcCol = {
+        key: 'vpcName',
+        dataIndex: 'vpcName',
+        title: this.$t('label.vpc'),
+        width: '30%'
+      }
+      if (vpcFilter.length > 0) {
+        vpcCol.filters = vpcFilter
+        vpcCol.filteredValue = _.get(this.filteredInfo, 'id')
+        vpcCol.onFilter = (value, record) => {
+          return record.vpcid === value
+        }
+      }
       return [
         {
+          key: 'name',
           dataIndex: 'name',
           title: this.$t('label.networks'),
-          slots: { customRender: 'name' },
           width: '40%'
         },
         {
+          key: 'type',
           dataIndex: 'type',
           title: this.$t('label.guestiptype'),
           width: '15%'
         },
+        vpcCol,
         {
-          dataIndex: 'vpcName',
-          title: this.$t('label.vpc'),
-          width: '20%',
-          filters: vpcFilter,
-          filteredValue: _.get(this.filteredInfo, 'id'),
-          onFilter: (value, record) => {
-            return record.vpcid === value
-          }
-        },
-        {
+          key: 'supportsvmautoscaling',
           dataIndex: 'supportsvmautoscaling',
           title: this.$t('label.supportsvmautoscaling'),
           width: '25%'
diff --git a/ui/src/views/compute/wizard/SshKeyPairSelection.vue b/ui/src/views/compute/wizard/SshKeyPairSelection.vue
index 848c1a2..f6dda72 100644
--- a/ui/src/views/compute/wizard/SshKeyPairSelection.vue
+++ b/ui/src/views/compute/wizard/SshKeyPairSelection.vue
@@ -31,8 +31,10 @@
       :pagination="false"
       size="middle"
       :scroll="{ y: 225 }">
-      <template #account><user-outlined /> {{ $t('label.account') }}</template>
-      <template #domain><block-outlined /> {{ $t('label.domain') }}</template>
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'account'"><user-outlined /> {{ $t('label.account') }}</template>
+        <template v-if="column.key === 'domain'"><block-outlined /> {{ $t('label.domain') }}</template>
+      </template>
     </a-table>
     <div style="display: block; text-align: right;">
       <a-pagination
@@ -87,18 +89,19 @@
       filter: '',
       columns: [
         {
+          key: 'name',
           dataIndex: 'name',
           title: this.$t('label.sshkeypairs'),
           width: '40%'
         },
         {
+          key: 'account',
           dataIndex: 'account',
-          slots: { title: 'account' },
           width: '30%'
         },
         {
+          key: 'domain',
           dataIndex: 'domain',
-          slots: { title: 'domain' },
           width: '30%'
         }
       ],
diff --git a/ui/src/views/compute/wizard/UserDataSelection.vue b/ui/src/views/compute/wizard/UserDataSelection.vue
index 4dfc14e..87e0bb3 100644
--- a/ui/src/views/compute/wizard/UserDataSelection.vue
+++ b/ui/src/views/compute/wizard/UserDataSelection.vue
@@ -32,8 +32,10 @@
       size="middle"
       :scroll="{ y: 225 }"
     >
-      <template #account><user-outlined /> {{ $t('label.account') }}</template>
-      <template #domain><block-outlined /> {{ $t('label.domain') }}</template>
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'account'"><user-outlined /> {{ $t('label.account') }}</template>
+        <template v-if="column.key === 'domain'"><block-outlined /> {{ $t('label.domain') }}</template>
+      </template>
     </a-table>
   </div>
 </template>
@@ -81,13 +83,13 @@
           width: '40%'
         },
         {
+          key: 'account',
           dataIndex: 'account',
-          slots: { title: 'account' },
           width: '30%'
         },
         {
+          key: 'domain',
           dataIndex: 'domain',
-          slots: { title: 'domain' },
           width: '30%'
         }
       ],
diff --git a/ui/src/views/compute/wizard/VnfNicsSelection.vue b/ui/src/views/compute/wizard/VnfNicsSelection.vue
new file mode 100644
index 0000000..fdd5276
--- /dev/null
+++ b/ui/src/views/compute/wizard/VnfNicsSelection.vue
@@ -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.
+
+<template>
+  <div style="margin-top: 10px;">
+    <label>{{ $t('message.vnf.select.networks') }}</label>
+  </div>
+  <a-form
+    :ref="formRef"
+    :model="form"
+    :rules="rules">
+    <a-table
+      :columns="columns"
+      :dataSource="items"
+      :pagination="false"
+      :rowKey="record => record.deviceid"
+      size="middle"
+      :scroll="{ y: 225 }">
+      <template #deviceid="{ text }">
+        <div>{{ text }}</div>
+      </template>
+      <template #name="{ text }">
+        <div>{{ text }}</div>
+      </template>
+      <template #required="{ record }">
+        <span v-if="record.required">{{ $t('label.yes') }}</span>
+        <span v-else>{{ $t('label.no') }}</span>
+      </template>
+      <template #management="{ record }">
+        <span v-if="record.management">{{ $t('label.yes') }}</span>
+        <span v-else>{{ $t('label.no') }}</span>
+      </template>
+      <template #description="{record}">
+        <span> {{ record.description }} </span>
+      </template>
+      <template #network="{ record }">
+        <a-form-item style="display: block" :name="'nic-' + record.deviceid">
+          <a-select
+            @change="updateNicNetworkValue($event, record.deviceid)"
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+            <a-select-option key="" >{{ }}</a-select-option>
+            <a-select-option v-for="network in networks" :key="network.id">
+              {{ network.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+      </template>
+    </a-table>
+  </a-form>
+</template>
+
+<script>
+import { ref, reactive } from 'vue'
+export default {
+  name: 'VnfNicsSelection',
+  props: {
+    items: {
+      type: Array,
+      default: () => []
+    },
+    networks: {
+      type: Array,
+      default: () => []
+    },
+    preFillContent: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data () {
+    return {
+      values: {},
+      columns: [
+        {
+          dataIndex: 'deviceid',
+          title: this.$t('label.deviceid'),
+          width: '10%',
+          slots: { customRender: 'deviceid' }
+        },
+        {
+          dataIndex: 'name',
+          title: this.$t('label.name'),
+          width: '15%',
+          slots: { customRender: 'name' }
+        },
+        {
+          dataIndex: 'required',
+          title: this.$t('label.required'),
+          width: '10%',
+          slots: { customRender: 'required' }
+        },
+        {
+          dataIndex: 'management',
+          title: this.$t('label.vnf.nic.management'),
+          width: '15%',
+          slots: { customRender: 'management' }
+        },
+        {
+          dataIndex: 'description',
+          title: this.$t('label.description'),
+          width: '35%',
+          slots: { customRender: 'description' }
+        },
+        {
+          dataIndex: 'network',
+          title: this.$t('label.network'),
+          width: '25%',
+          slots: { customRender: 'network' }
+        }
+      ]
+    }
+  },
+  created () {
+    this.initForm()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({})
+
+      const form = {}
+      const rules = {}
+
+      this.form = reactive(form)
+      this.rules = reactive(rules)
+    },
+    updateNicNetworkValue (value, deviceid) {
+      this.values[deviceid] = this.networks.filter(network => network.id === value)?.[0] || null
+      this.$emit('update-vnf-nic-networks', this.values)
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  .ant-table-wrapper {
+    margin: 2rem 0;
+  }
+
+  :deep(.ant-table-tbody) > tr > td {
+    cursor: pointer;
+  }
+
+  .ant-form .ant-form-item {
+    margin-bottom: 0;
+    padding-bottom: 0;
+  }
+</style>
diff --git a/ui/src/views/dashboard/CapacityDashboard.vue b/ui/src/views/dashboard/CapacityDashboard.vue
index a174e60..2fc41b6 100644
--- a/ui/src/views/dashboard/CapacityDashboard.vue
+++ b/ui/src/views/dashboard/CapacityDashboard.vue
@@ -521,7 +521,7 @@
           this.data.systemvms = 0
         }
       })
-      api('listRouters', { zoneid: zone.id, listall: true }).then(json => {
+      api('listRouters', { zoneid: zone.id, listall: true, projectid: '-1' }).then(json => {
         this.loading = false
         this.data.routers = json?.listroutersresponse?.count
         if (!this.data.routers) {
diff --git a/ui/src/views/dashboard/SetupTwoFaAtLogin.vue b/ui/src/views/dashboard/SetupTwoFaAtLogin.vue
index a710afb..2300a7a 100644
--- a/ui/src/views/dashboard/SetupTwoFaAtLogin.vue
+++ b/ui/src/views/dashboard/SetupTwoFaAtLogin.vue
@@ -38,10 +38,6 @@
           <a-form-item v-ctrl-enter="submitPin" ref="selectedProvider" name="selectedProvider">
              <a-select
               v-model:value="form.selectedProvider"
-              optionFilterProp="label"
-              :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }"
               @change="val => { handleSelectChange(val) }">
               <a-select-option
                 v-for="(opt) in providers"
diff --git a/ui/src/views/dashboard/UsageDashboard.vue b/ui/src/views/dashboard/UsageDashboard.vue
index 9f7d988..e9edd0c 100644
--- a/ui/src/views/dashboard/UsageDashboard.vue
+++ b/ui/src/views/dashboard/UsageDashboard.vue
@@ -287,6 +287,34 @@
         </div>
       </chart-card>
     </a-col>
+    <a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }" class="dashboard-card">
+      <chart-card :loading="loading" class="dashboard-card">
+        <template #title>
+          <div class="center">
+            <h3><render-icon :icon="$config.userCard.icon" /> {{ $t($config.userCard.title) }}</h3>
+          </div>
+        </template>
+        <a-divider style="margin: 6px 0px; border-width: 0px"/>
+        <a-list item-layout="horizontal" :data-source="$config.userCard.links">
+          <template #renderItem="{ item }">
+            <a-list-item>
+              <a-list-item-meta :description="item.text">
+                <template #title>
+                  <a :href="item.link" target="_blank"><h4>{{ item.title }}</h4></a>
+                </template>
+                <template #avatar>
+                  <a-avatar :style="{ backgroundColor: $config.theme['@primary-color'] }">
+                    <template #icon>
+                      <render-icon :icon="item.icon" />
+                    </template>
+                  </a-avatar>
+                </template>
+              </a-list-item-meta>
+            </a-list-item>
+          </template>
+        </a-list>
+      </chart-card>
+    </a-col>
     <a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
       <chart-card :loading="loading" class="dashboard-card dashboard-event">
         <template #title>
diff --git a/ui/src/views/dashboard/VerifyOauth.vue b/ui/src/views/dashboard/VerifyOauth.vue
new file mode 100644
index 0000000..ab3062a
--- /dev/null
+++ b/ui/src/views/dashboard/VerifyOauth.vue
@@ -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.
+
+<template>
+  <div>
+  </div>
+</template>
+
+<script>
+import store from '@/store'
+import { mapActions } from 'vuex'
+import { api } from '@/api'
+import { OAUTH_DOMAIN, OAUTH_PROVIDER } from '@/store/mutation-types'
+
+export default {
+  name: 'VerifyOauth',
+  data () {
+    return {
+      state: {
+        time: 60,
+        loginBtn: false,
+        loginType: 0
+      }
+    }
+  },
+  created () {
+    this.verifyOauth()
+  },
+  methods: {
+    ...mapActions(['Login', 'Logout', 'OauthLogin']),
+    verifyOauth () {
+      const params = new URLSearchParams(window.location.search)
+      const code = params.get('code')
+      const provider = this.$localStorage.get(OAUTH_PROVIDER)
+      this.state.loginBtn = true
+      api('verifyOAuthCodeAndGetUser', { provider: provider, secretcode: code }).then(response => {
+        const email = response.verifyoauthcodeandgetuserresponse.oauthemail.email
+        const loginParams = {}
+        loginParams.email = email
+        loginParams.provider = provider
+        loginParams.secretcode = code
+        loginParams.domain = this.$localStorage.get(OAUTH_DOMAIN)
+        this.OauthLogin(loginParams)
+          .then((res) => this.loginSuccess(res))
+          .catch(err => {
+            this.requestFailed(err)
+            this.state.loginBtn = false
+          })
+      }).catch(err => {
+        this.requestFailed(err)
+        this.state.loginBtn = false
+      })
+    },
+    loginSuccess (res) {
+      this.$notification.destroy()
+      this.$store.commit('SET_COUNT_NOTIFY', 0)
+      if (store.getters.twoFaEnabled === true && store.getters.twoFaProvider !== '' && store.getters.twoFaProvider !== undefined) {
+        this.$router.push({ path: '/verify2FA' }).catch(() => {})
+      } else if (store.getters.twoFaEnabled === true && (store.getters.twoFaProvider === '' || store.getters.twoFaProvider === undefined)) {
+        this.$router.push({ path: '/setup2FA' }).catch(() => {})
+      } else {
+        this.$store.commit('SET_LOGIN_FLAG', true)
+        this.$router.push({ path: '/dashboard' }).catch(() => {})
+      }
+    },
+    requestFailed (err) {
+      this.$store.dispatch('Logout').then(() => {
+        this.$router.replace({ path: '/user/login' })
+      })
+      if (err && err.response && err.response.data && err.response.data.oauthloginresponse) {
+        const error = err.response.data.oauthloginresponse.errorcode + ': ' + err.response.data.oauthloginresponse.errortext
+        this.$message.error(`${this.$t('label.error')} ${error}`)
+      } else {
+        this.$message.error(this.$t('message.login.failed'))
+      }
+    }
+  }
+}
+</script>
diff --git a/ui/src/views/iam/AddAccount.vue b/ui/src/views/iam/AddAccount.vue
index 88bf848..232dbea 100644
--- a/ui/src/views/iam/AddAccount.vue
+++ b/ui/src/views/iam/AddAccount.vue
@@ -37,9 +37,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
-            <a-select-option v-for="role in roles" :key="role.id">
+            <a-select-option v-for="role in roles" :key="role.id" :label="role.name + ' (' + role.type + ')'">
               {{ role.name + ' (' + role.type + ')' }}
             </a-select-option>
           </a-select>
@@ -109,7 +109,7 @@
             <tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
           </template>
           <a-select
-            :loading="domainLoading"
+            :loading="domain.loading"
             v-model:value="form.domainid"
             :placeholder="apiParams.domainid.description"
             showSearch
@@ -143,9 +143,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
-            <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+            <a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -173,9 +173,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }">
-              <a-select-option v-for="idp in idps" :key="idp.id">
+              <a-select-option v-for="idp in idps" :key="idp.id" :label="idp.orgName">
                 {{ idp.orgName }}
               </a-select-option>
             </a-select>
@@ -207,7 +207,7 @@
     this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
     return {
       loading: false,
-      domainLoading: false,
+      domain: { loading: false },
       domainsList: [],
       roleLoading: false,
       roles: [],
@@ -282,21 +282,27 @@
       }
     },
     fetchDomains () {
-      this.domainLoading = true
-      api('listDomains', {
-        listAll: true,
-        showicon: true,
-        details: 'min'
-      }).then(response => {
-        this.domainsList = response.listdomainsresponse.domain || []
-        this.form.domain = this.domainsList[0].id || ''
-      }).catch(error => {
-        this.$notification.error({
-          message: `${this.$t('label.error')} ${error.response.status}`,
-          description: error.response.data.errorresponse.errortext
-        })
+      this.domain.loading = true
+      this.loadMore('listDomains', 1, this.domain)
+    },
+    loadMore (apiToCall, page, sema) {
+      console.log('sema.loading ' + sema.loading)
+      const params = {}
+      params.listAll = true
+      params.details = 'min'
+      params.pagesize = 100
+      params.page = page
+      var count
+      api(apiToCall, params).then(json => {
+        const listDomains = json.listdomainsresponse.domain
+        count = json.listdomainsresponse.count
+        this.domainsList = this.domainsList.concat(listDomains)
       }).finally(() => {
-        this.domainLoading = false
+        if (count <= this.domainsList.length) {
+          sema.loading = false
+        } else {
+          this.loadMore(apiToCall, page + 1, sema)
+        }
       })
     },
     fetchRoles () {
diff --git a/ui/src/views/iam/AddLdapAccount.vue b/ui/src/views/iam/AddLdapAccount.vue
index ecf8cb0..fadb538 100644
--- a/ui/src/views/iam/AddLdapAccount.vue
+++ b/ui/src/views/iam/AddLdapAccount.vue
@@ -59,9 +59,9 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }">
-                <a-select-option v-for="opt in filters" :key="opt.id" >
+                <a-select-option v-for="opt in filters" :key="opt.id" :label="opt.name">
                   {{ opt.name }}
                 </a-select-option>
               </a-select>
@@ -73,9 +73,9 @@
                 :loading="domainLoading"
                 @change="fetchListLdapUsers($event)"
                 showSearch
-                optionFilterProp="label"
+                optionFilterProp="value"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
                 <a-select-option v-for="opt in listDomains" :key="opt.name">
                   {{ opt.name }}
@@ -94,9 +94,9 @@
                 :placeholder="apiParams.roleid.description"
                 :loading="roleLoading"
                 showSearch
-                optionFilterProp="label"
+                optionFilterProp="value"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }">
                 <a-select-option v-for="opt in listRoles" :key="opt.name">
                   {{ opt.name }}
@@ -111,9 +111,9 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }">
-                <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+                <a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -142,9 +142,9 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }">
-                  <a-select-option v-for="(idp, idx) in listIdps" :key="idx">
+                  <a-select-option v-for="(idp, idx) in listIdps" :key="idx" :label="idp.orgName">
                     {{ idp.orgName }}
                   </a-select-option>
                 </a-select>
@@ -212,26 +212,26 @@
     this.listIdps = []
     this.columns = [
       {
+        key: 'name',
         title: this.$t('label.name'),
         dataIndex: 'name',
-        width: 120,
-        slots: { customRender: 'name' }
+        width: 120
       },
       {
+        key: 'username',
         title: this.$t('label.username'),
         dataIndex: 'username',
-        width: 120,
-        slots: { customRender: 'username' }
+        width: 120
       },
       {
+        key: 'email',
         title: this.$t('label.email'),
-        dataIndex: 'email',
-        slots: { customRender: 'email' }
+        dataIndex: 'email'
       },
       {
+        key: 'conflictingusersource',
         title: this.$t('label.user.conflict'),
-        dataIndex: 'conflictingusersource',
-        slots: { customRender: 'conflictingusersource' }
+        dataIndex: 'conflictingusersource'
       }
     ]
     this.filters = [
@@ -487,9 +487,9 @@
     },
     handleEntityRule () {
       if (this.form.samlEnable) {
-        this.rules.push({
-          samlEntity: [{ required: true, message: `${this.$t('message.error.select')}` }]
-        })
+        this.rules.samlEntity = [{ required: true, message: `${this.$t('message.error.select')}` }]
+      } else {
+        delete this.rules.samlEntity
       }
     }
   }
diff --git a/ui/src/views/iam/AddUser.vue b/ui/src/views/iam/AddUser.vue
index c4b1a12..49bca32 100644
--- a/ui/src/views/iam/AddUser.vue
+++ b/ui/src/views/iam/AddUser.vue
@@ -140,9 +140,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+            <a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -161,9 +161,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="idp in idps" :key="idp.id">
+              <a-select-option v-for="idp in idps" :key="idp.id" :label="idp.orgName">
                 {{ idp.orgName }}
               </a-select-option>
             </a-select>
diff --git a/ui/src/views/iam/ConfigureSamlSsoAuth.vue b/ui/src/views/iam/ConfigureSamlSsoAuth.vue
index 4f2e540..15e2b78 100644
--- a/ui/src/views/iam/ConfigureSamlSsoAuth.vue
+++ b/ui/src/views/iam/ConfigureSamlSsoAuth.vue
@@ -36,9 +36,9 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option v-for="idp in idps" :key="idp.id">
+          <a-select-option v-for="idp in idps" :key="idp.id" :label="idp.orgName">
             {{ idp.orgName }}
           </a-select-option>
         </a-select>
diff --git a/ui/src/views/iam/CreateRole.vue b/ui/src/views/iam/CreateRole.vue
index ea3d380..e3513b6 100644
--- a/ui/src/views/iam/CreateRole.vue
+++ b/ui/src/views/iam/CreateRole.vue
@@ -67,9 +67,9 @@
             v-model:value="form.type"
             :placeholder="apiParams.type.description"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="role in defaultRoles" :key="role">
               {{ role }}
@@ -87,17 +87,25 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="role in roles"
               :value="role.id"
-              :key="role.id">
+              :key="role.id"
+              :label="role.name">
               {{ role.name }}
             </a-select-option>
           </a-select>
         </a-form-item>
 
+        <a-form-item name="ispublic" ref="ispublic">
+          <template #label>
+            <tooltip-label :title="$t('label.ispublic')" :tooltip="apiParams.ispublic.description"/>
+          </template>
+          <a-switch v-model:checked="form.ispublic"/>
+        </a-form-item>
+
         <div :span="24" class="action-button">
           <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
           <a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
@@ -149,7 +157,8 @@
     initForm () {
       this.formRef = ref()
       this.form = reactive({
-        using: 'type'
+        using: 'type',
+        ispublic: true
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.required.input') }],
diff --git a/ui/src/views/iam/DomainActionForm.vue b/ui/src/views/iam/DomainActionForm.vue
index fe55c51..4d7c727 100644
--- a/ui/src/views/iam/DomainActionForm.vue
+++ b/ui/src/views/iam/DomainActionForm.vue
@@ -65,9 +65,9 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="(opt, optIndex) in action.mapping[field.name].options" :key="optIndex">
+                <a-select-option v-for="(opt, optIndex) in action.mapping[field.name].options" :key="optIndex" :label="opt">
                   {{ opt }}
                 </a-select-option>
               </a-select>
@@ -79,11 +79,14 @@
                 :loading="field.loading"
                 :placeholder="field.description"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 v-focus="fieldIndex === firstIndex"
               >
-                <a-select-option v-for="(opt, optIndex) in field.opts" :key="optIndex">
+                <a-select-option
+                  v-for="(opt, optIndex) in field.opts"
+                  :key="optIndex"
+                  :label="opt.name || opt.description || opt.traffictype || opt.publicip">
                   {{ opt.name || opt.description || opt.traffictype || opt.publicip }}
                 </a-select-option>
               </a-select>
@@ -97,9 +100,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="(opt, optIndex) in field.opts" :key="optIndex">
+                <a-select-option
+                  v-for="(opt, optIndex) in field.opts"
+                  :key="optIndex"
+                  :label="opt.name && opt.type ? opt.name + ' (' + opt.type + ')' : opt.name || opt.description">
                   {{ opt.name && opt.type ? opt.name + ' (' + opt.type + ')' : opt.name || opt.description }}
                 </a-select-option>
               </a-select>
diff --git a/ui/src/views/iam/DomainView.vue b/ui/src/views/iam/DomainView.vue
index 2b15d8b..997f900 100644
--- a/ui/src/views/iam/DomainView.vue
+++ b/ui/src/views/iam/DomainView.vue
@@ -56,13 +56,13 @@
         :loading="loading"
         :tabs="$route.meta.tabs" />
       <tree-view
-        v-else
         :key="treeViewKey"
         :treeData="treeData"
         :treeSelected="treeSelected"
         :treeStore="domainStore"
         :loading="loading"
         :tabs="$route.meta.tabs"
+        :treeDeletedKey="treeDeletedKey"
         @change-resource="changeResource"
         @change-tree-store="changeDomainStore"/>
     </div>
@@ -109,7 +109,8 @@
       showAction: false,
       action: {},
       dataView: false,
-      domainStore: {}
+      domainStore: {},
+      treeDeletedKey: null
     }
   },
   computed: {
@@ -194,6 +195,7 @@
       })
     },
     execAction (action) {
+      this.treeDeletedKey = action.api === 'deleteDomain' ? this.resource.key : null
       this.actionData = []
       this.action = action
       this.action.params = store.getters.apis[this.action.api].params
@@ -286,9 +288,8 @@
 
       rootItem[0].title = rootItem[0].title ? rootItem[0].title : rootItem[0].name
       rootItem[0].key = rootItem[0].id ? rootItem[0].id : 0
-      rootItem[0].slots = {
-        icon: 'leaf'
-      }
+      rootItem[0].resourceIcon = rootItem[0].icon || {}
+      delete rootItem[0].icon
 
       if (!rootItem[0].haschild) {
         rootItem[0].isLeaf = true
diff --git a/ui/src/views/iam/EditUser.vue b/ui/src/views/iam/EditUser.vue
index 9940b20..e082fd1 100644
--- a/ui/src/views/iam/EditUser.vue
+++ b/ui/src/views/iam/EditUser.vue
@@ -74,9 +74,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+            <a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/iam/ImportRole.vue b/ui/src/views/iam/ImportRole.vue
index a4811fa..8577872 100644
--- a/ui/src/views/iam/ImportRole.vue
+++ b/ui/src/views/iam/ImportRole.vue
@@ -31,7 +31,7 @@
           <a-upload-dragger
             :multiple="false"
             :fileList="fileList"
-            :remove="handleRemove"
+            @remove="handleRemove"
             :beforeUpload="beforeUpload"
             @change="handleChange"
             v-model:value="form.file">
@@ -70,9 +70,9 @@
             v-model:value="form.type"
             :placeholder="apiParams.type.description"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="role in defaultRoles" :key="role">
               {{ role }}
@@ -80,6 +80,13 @@
           </a-select>
         </a-form-item>
 
+        <a-form-item name="ispublic" ref="ispublic">
+          <template #label>
+            <tooltip-label :title="$t('label.ispublic')" :tooltip="apiParams.ispublic.description"/>
+          </template>
+          <a-switch v-model:checked="form.ispublic"/>
+        </a-form-item>
+
         <a-form-item name="forced" ref="forced">
           <template #label>
             <tooltip-label :title="$t('label.forced')" :tooltip="apiParams.forced.description"/>
@@ -124,7 +131,7 @@
   methods: {
     initForm () {
       this.formRef = ref()
-      this.form = reactive({})
+      this.form = reactive({ ispublic: true })
       this.rules = reactive({
         file: [
           { required: true, message: this.$t('message.error.required.input') },
diff --git a/ui/src/views/iam/PermissionEditable.vue b/ui/src/views/iam/PermissionEditable.vue
index 10add51..22007d8 100644
--- a/ui/src/views/iam/PermissionEditable.vue
+++ b/ui/src/views/iam/PermissionEditable.vue
@@ -23,10 +23,10 @@
     showSearch
     optionFilterProp="label"
     :filterOption="(input, option) => {
-      return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
     }" >
-    <a-select-option value="allow">{{ $t('label.allow') }}</a-select-option>
-    <a-select-option value="deny">{{ $t('label.deny') }}</a-select-option>
+    <a-select-option value="allow" :label="$t('label.allow')">{{ $t('label.allow') }}</a-select-option>
+    <a-select-option value="deny" :label="$t('label.deny')">{{ $t('label.deny') }}</a-select-option>
   </a-select>
 </template>
 
diff --git a/ui/src/views/iam/RolePermissionTab.vue b/ui/src/views/iam/RolePermissionTab.vue
index 7a0a622..49a9e98 100644
--- a/ui/src/views/iam/RolePermissionTab.vue
+++ b/ui/src/views/iam/RolePermissionTab.vue
@@ -69,7 +69,6 @@
         handle=".drag-handle"
         animation="200"
         ghostClass="drag-ghost"
-        tag="transition-group"
         :component-data="{type: 'transition'}"
         item-key="id">
         <template #item="{element}">
diff --git a/ui/src/views/iam/SSLCertificateTab.vue b/ui/src/views/iam/SSLCertificateTab.vue
index 6a3e161..4015e20 100644
--- a/ui/src/views/iam/SSLCertificateTab.vue
+++ b/ui/src/views/iam/SSLCertificateTab.vue
@@ -28,23 +28,25 @@
           :pagination="false"
           v-if="!quickview"
         >
-          <template #action="{ record }" class="cert-button-action">
-            <tooltip-button
-              tooltipPlacement="top"
-              :tooltip="$t('label.quickview')"
-              type="primary"
-              icon="eye-outlined"
-              size="small"
-              @onClick="onQuickView(record.id)" />
-            <tooltip-button
-              tooltipPlacement="top"
-              :tooltip="$t('label.delete.sslcertificate')"
-              :disabled="!('deleteSslCert' in $store.getters.apis)"
-              type="primary"
-              :danger="true"
-              icon="delete-outlined"
-              size="small"
-              @onClick="onShowConfirm(record)" />
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.key === 'actions'" class="cert-button-action">
+              <tooltip-button
+                tooltipPlacement="top"
+                :tooltip="$t('label.quickview')"
+                type="primary"
+                icon="eye-outlined"
+                size="small"
+                @onClick="onQuickView(record.id)" />
+              <tooltip-button
+                tooltipPlacement="top"
+                :tooltip="$t('label.delete.sslcertificate')"
+                :disabled="!('deleteSslCert' in $store.getters.apis)"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined"
+                size="small"
+                @onClick="onShowConfirm(record)" />
+            </template>
           </template>
         </a-table>
 
@@ -128,22 +130,22 @@
   created () {
     this.columns = [
       {
+        key: 'name',
         title: this.$t('label.name'),
-        dataIndex: 'name',
-        slots: { customRender: 'name' }
+        dataIndex: 'name'
       },
       {
+        key: 'id',
         title: this.$t('label.certificateid'),
         dataIndex: 'id',
-        width: 450,
-        slots: { customRender: 'id' }
+        width: 450
       },
       {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
+        key: 'actions',
+        title: this.$t('label.actions'),
+        dataIndex: 'actions',
         fixed: 'right',
-        width: 80,
-        slots: { customRender: 'action' }
+        width: 80
       }
     ]
     this.detailColumn = ['name', 'certificate', 'certchain']
diff --git a/ui/src/views/iam/SetupTwoFaAtUserProfile.vue b/ui/src/views/iam/SetupTwoFaAtUserProfile.vue
index 6f4f729..df6466a 100644
--- a/ui/src/views/iam/SetupTwoFaAtUserProfile.vue
+++ b/ui/src/views/iam/SetupTwoFaAtUserProfile.vue
@@ -29,10 +29,6 @@
           <a-form-item v-ctrl-enter="submitPin" ref="selectedProvider" name="selectedProvider">
              <a-select
               v-model:value="form.selectedProvider"
-              optionFilterProp="label"
-              :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }"
               @change="val => { handleSelectChange(val) }">
               <a-select-option
                 v-for="(opt) in providers"
diff --git a/ui/src/views/image/IsoZones.vue b/ui/src/views/image/IsoZones.vue
index 7718b1c..daf1e7e 100644
--- a/ui/src/views/image/IsoZones.vue
+++ b/ui/src/views/image/IsoZones.vue
@@ -34,44 +34,81 @@
       :dataSource="dataSource"
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
-      :rowKey="record => record.zoneid">
-      <template #zonename="{record}">
-        <span v-if="fetchZoneIcon(record.zoneid)">
-          <resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/>
-        </span>
-        <global-outlined v-else style="margin-right: 5px" />
-        <span> {{ record.zonename }} </span>
-      </template>
-      <template #isready="{ record }">
-        <span v-if="record.isready">{{ $t('label.yes') }}</span>
-        <span v-else>{{ $t('label.no') }}</span>
-      </template>
-      <template #action="{ record }">
-        <span style="margin-right: 5px">
-          <tooltip-button
-            :tooltip="$t('label.action.copy.iso')"
-            :disabled="!('copyIso' in $store.getters.apis && record.isready)"
-            icon="copy-outlined"
-            :loading="copyLoading"
-            @click="showCopyIso(record)" />
-        </span>
-        <span style="margin-right: 5px">
-          <a-popconfirm
-            v-if="'deleteIso' in $store.getters.apis"
-            placement="topRight"
-            :title="$t('message.action.delete.iso')"
-            :ok-text="$t('label.yes')"
-            :cancel-text="$t('label.no')"
-            :loading="deleteLoading"
-            @confirm="deleteIso(record)"
-          >
+      :rowKey="record => record.zoneid"
+      :rowExpandable="(record) => record.downloaddetails.length > 0">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'zonename'">
+          <span v-if="fetchZoneIcon(record.zoneid)">
+            <resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/>
+          </span>
+          <global-outlined v-else style="margin-right: 5px" />
+          <span> {{ record.zonename }} </span>
+        </template>
+        <template v-if="column.key === 'isready'">
+          <span v-if="record.isready">{{ $t('label.yes') }}</span>
+          <span v-else>{{ $t('label.no') }}</span>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <span style="margin-right: 5px">
             <tooltip-button
-              :tooltip="$t('label.action.delete.iso')"
-              type="primary"
-              :danger="true"
-              icon="delete-outlined" />
-          </a-popconfirm>
-        </span>
+              :tooltip="$t('label.action.copy.iso')"
+              :disabled="!('copyIso' in $store.getters.apis && record.isready)"
+              icon="copy-outlined"
+              :loading="copyLoading"
+              @click="showCopyIso(record)" />
+          </span>
+          <span style="margin-right: 5px">
+            <a-popconfirm
+              v-if="'deleteIso' in $store.getters.apis"
+              placement="topRight"
+              :title="$t('message.action.delete.iso')"
+              :ok-text="$t('label.yes')"
+              :cancel-text="$t('label.no')"
+              :loading="deleteLoading"
+              @confirm="deleteIso(record)"
+            >
+              <tooltip-button
+                :tooltip="$t('label.action.delete.iso')"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined" />
+            </a-popconfirm>
+          </span>
+        </template>
+      </template>
+      <template #expandedRowRender="{ record }">
+        <a-table
+          style="margin: 10px 0;"
+          :columns="storagePoolInnerColumns"
+          v-if="record.downloaddetails.filter((row) => row.datastoreRole === 'Primary').length > 0"
+          :data-source="record.downloaddetails.filter((row) => row.datastoreRole === 'Primary')"
+          :pagination="false"
+          :bordered="true"
+          :rowKey="record => record.zoneid">
+          <template #bodyCell="{ text, record, column }">
+            <template v-if="column.dataIndex === 'datastore' && record.datastoreId">
+                <router-link :to="{ path: '/storagepool/' + record.datastoreId }">
+                {{ text }}
+              </router-link>
+            </template>
+          </template>
+        </a-table>
+        <a-table
+          style="margin: 10px 0;"
+          :columns="imageStoreInnerColumns"
+          v-if="record.downloaddetails.filter((row) => row.datastoreRole === 'Image').length > 0"
+          :data-source="record.downloaddetails.filter((row) => row.datastoreRole === 'Image')"
+          :pagination="false"
+          :bordered="true"
+          :rowKey="record => record.zoneid">
+          <template #bodyCell="{ text, record, column }">
+            <template v-if="column.dataIndex === 'datastore' && record.datastoreId">
+                <router-link :to="{ path: '/imagestore/' + record.datastoreId }">
+                {{ text }}
+              </router-link>
+            </template>
+          </template>
+        </a-table>
       </template>
     </a-table>
     <a-pagination
@@ -221,27 +258,55 @@
     this.initForm()
     this.columns = [
       {
+        key: 'zonename',
         title: this.$t('label.zonename'),
-        dataIndex: 'zonename',
-        slots: { customRender: 'zonename' }
+        dataIndex: 'zonename'
       },
       {
         title: this.$t('label.status'),
         dataIndex: 'status'
       },
       {
+        key: 'isready',
         title: this.$t('label.isready'),
-        dataIndex: 'isready',
-        slots: { customRender: 'isready' }
+        dataIndex: 'isready'
+      }
+    ]
+    this.storagePoolInnerColumns = [
+      {
+        title: this.$t('label.primary.storage'),
+        dataIndex: 'datastore'
+      },
+      {
+        title: this.$t('label.download.percent'),
+        dataIndex: 'downloadPercent'
+      },
+      {
+        title: this.$t('label.download.state'),
+        dataIndex: 'downloadState'
+      }
+    ]
+    this.imageStoreInnerColumns = [
+      {
+        title: this.$t('label.secondary.storage'),
+        dataIndex: 'datastore'
+      },
+      {
+        title: this.$t('label.download.percent'),
+        dataIndex: 'downloadPercent'
+      },
+      {
+        title: this.$t('label.download.state'),
+        dataIndex: 'downloadState'
       }
     ]
     if (this.isActionPermitted()) {
       this.columns.push({
+        key: 'actions',
         title: '',
-        dataIndex: 'action',
+        dataIndex: 'actions',
         fixed: 'right',
-        width: 100,
-        slots: { customRender: 'action' }
+        width: 100
       })
     }
 
@@ -355,9 +420,9 @@
     deleteIsos (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
diff --git a/ui/src/views/image/RegisterOrUploadIso.vue b/ui/src/views/image/RegisterOrUploadIso.vue
index aa127a3..1a461ee 100644
--- a/ui/src/views/image/RegisterOrUploadIso.vue
+++ b/ui/src/views/image/RegisterOrUploadIso.vue
@@ -52,7 +52,7 @@
           <a-upload-dragger
             :multiple="false"
             :fileList="fileList"
-            :remove="handleRemove"
+            @remove="handleRemove"
             :beforeUpload="beforeUpload"
             v-model:value="form.file">
             <p class="ant-upload-drag-icon">
@@ -111,6 +111,48 @@
           </a-select>
         </a-form-item>
 
+        <a-form-item name="domainid" ref="domainid" v-if="'listDomains' in $store.getters.apis">
+          <template #label>
+            <tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
+          </template>
+          <a-select
+            v-model:value="form.domainid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="domainLoading"
+            :placeholder="apiParams.domainid.description"
+            @change="val => { handleDomainChange(val) }">
+            <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex" :label="opt.path || opt.name || opt.description" :value="opt.id">
+              <span>
+                <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <block-outlined v-else style="margin-right: 5px" />
+                {{ opt.path || opt.name || opt.description }}
+              </span>
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="account" ref="account" v-if="domainid">
+          <template #label>
+            <tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
+          </template>
+          <a-select
+            v-model:value="form.account"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :placeholder="apiParams.account.description"
+            @change="val => { handleAccountChange(val) }">
+            <a-select-option v-for="(acc, index) in accounts" :value="acc.name" :key="index">
+              {{ acc.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+
         <a-form-item ref="bootable" name="bootable">
           <template #label>
             <tooltip-label :title="$t('label.bootable')" :tooltip="apiParams.bootable.description"/>
@@ -131,11 +173,11 @@
             }"
             :loading="osTypeLoading"
             :placeholder="apiParams.ostypeid.description">
-            <a-select-option :value="opt.id" v-for="(opt, optIndex) in osTypes" :key="optIndex" :label="opt.name || opt.description">
+            <a-select-option :value="opt.id" v-for="(opt, optIndex) in osTypes" :key="optIndex" :label="opt.name">
               <span>
                 <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
                 <global-outlined v-else style="margin-right: 5px" />
-                {{ opt.name || opt.description }}
+                {{ opt.name }}
               </span>
             </a-select-option>
           </a-select>
@@ -153,12 +195,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 v-model:value="userdataid"
                 :placeholder="linkUserDataParams.userdataid.description"
                 :loading="userdata.loading">
-                <a-select-option v-for="opt in userdata.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdata.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -170,13 +212,14 @@
                 <tooltip-label :title="$t('label.userdatapolicy')" :tooltip="linkUserDataParams.userdatapolicy.description"/>
               </template>
               <a-select
+                showSearch
                 v-model:value="userdatapolicy"
                 :placeholder="linkUserDataParams.userdatapolicy.description"
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id" :label="opt.id || opt.description">
                   {{ opt.id || opt.description }}
                 </a-select-option>
               </a-select>
@@ -258,7 +301,12 @@
       allowed: false,
       uploadParams: null,
       uploadPercentage: 0,
-      currentForm: ['plus-outlined', 'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload'
+      currentForm: ['plus-outlined', 'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload',
+      domains: [],
+      accounts: [],
+      domainLoading: false,
+      domainid: null,
+      account: null
     }
   },
   beforeCreate () {
@@ -290,7 +338,6 @@
         url: [{ required: true, message: this.$t('label.upload.iso.from.local') }],
         file: [{ required: true, message: this.$t('message.error.required.input') }],
         name: [{ required: true, message: this.$t('message.error.required.input') }],
-        displaytext: [{ required: true, message: this.$t('message.error.required.input') }],
         zoneid: [{ required: true, message: this.$t('message.error.select') }],
         ostypeid: [{ required: true, message: this.$t('message.error.select') }]
       })
@@ -300,6 +347,9 @@
       this.fetchOsType()
       this.fetchUserData()
       this.fetchUserdataPolicy()
+      if ('listDomains' in this.$store.getters.apis) {
+        this.fetchDomains()
+      }
     },
     fetchZoneData () {
       const params = {}
@@ -508,6 +558,43 @@
       }).finally(() => {
         this.loading = false
       })
+    },
+    fetchDomains () {
+      const params = {}
+      params.listAll = true
+      params.showicon = true
+      params.details = 'min'
+      this.domainLoading = true
+      api('listDomains', params).then(json => {
+        this.domains = json.listdomainsresponse.domain
+      }).finally(() => {
+        this.domainLoading = false
+        this.handleDomainChange(null)
+      })
+    },
+    handleDomainChange (domain) {
+      this.domainid = domain
+      this.form.account = null
+      this.account = null
+      if ('listAccounts' in this.$store.getters.apis) {
+        this.fetchAccounts()
+      }
+    },
+    fetchAccounts () {
+      api('listAccounts', {
+        domainid: this.domainid
+      }).then(response => {
+        this.accounts = response.listaccountsresponse.account || []
+      }).catch(error => {
+        this.$notifyError(error)
+      })
+    },
+    handleAccountChange (acc) {
+      if (acc) {
+        this.account = acc.name
+      } else {
+        this.account = acc
+      }
     }
   }
 }
diff --git a/ui/src/views/image/RegisterOrUploadTemplate.vue b/ui/src/views/image/RegisterOrUploadTemplate.vue
index 9369887..999a1b8 100644
--- a/ui/src/views/image/RegisterOrUploadTemplate.vue
+++ b/ui/src/views/image/RegisterOrUploadTemplate.vue
@@ -17,7 +17,7 @@
 
 <template>
   <div
-    class="form-layout"
+    :class="'form-layout'"
     @keyup.ctrl.enter="handleSubmit">
     <span v-if="uploadPercentage > 0">
       <loading-outlined />
@@ -42,12 +42,12 @@
               :placeholder="apiParams.url.description" />
           </a-form-item>
         </div>
-        <div v-if="currentForm === 'Upload'">
+        <div v-else-if="currentForm === 'Upload'">
           <a-form-item :label="$t('label.templatefileupload')" name="file" ref="file">
             <a-upload-dragger
               :multiple="false"
               :fileList="fileList"
-              :remove="handleRemove"
+              @remove="handleRemove"
               :beforeUpload="beforeUpload"
               v-model:value="form.file">
               <p class="ant-upload-drag-icon">
@@ -66,7 +66,7 @@
           <a-input
             v-model:value="form.name"
             :placeholder="apiParams.name.description"
-            v-focus="currentForm !== 'Create'"/>
+            v-focus="currentForm === 'Upload'"/>
         </a-form-item>
         <a-form-item ref="displaytext" name="displaytext">
           <template #label>
@@ -130,6 +130,47 @@
             </a-select>
           </a-form-item>
         </div>
+        <a-form-item name="domainid" ref="domainid" v-if="'listDomains' in $store.getters.apis">
+          <template #label>
+            <tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
+          </template>
+          <a-select
+            v-model:value="form.domainid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="domainLoading"
+            :placeholder="apiParams.domainid.description"
+            @change="val => { handleDomainChange(val) }">
+            <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex" :label="opt.path || opt.name || opt.description" :value="opt.id">
+              <span>
+                <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <block-outlined v-else style="margin-right: 5px" />
+                {{ opt.path || opt.name || opt.description }}
+              </span>
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="account" ref="account" v-if="domainid">
+          <template #label>
+            <tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
+          </template>
+          <a-select
+            v-model:value="form.account"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :placeholder="apiParams.account.description"
+            @change="val => { handleAccountChange(val) }">
+            <a-select-option v-for="(acc, index) in accounts" :value="acc.name" :key="index">
+              {{ acc.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
         <a-row :gutter="12">
           <a-col :md="24" :lg="12">
             <a-form-item ref="hypervisor" name="hypervisor">
@@ -144,9 +185,9 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="(opt, optIndex) in hyperVisor.opts" :key="optIndex">
+                <a-select-option v-for="(opt, optIndex) in hyperVisor.opts" :key="optIndex" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -164,16 +205,16 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="opt in format.opts" :key="opt.id">
+                <a-select-option v-for="opt in format.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
             </a-form-item>
           </a-col>
         </a-row>
-        <a-row :gutter="12" v-if="allowed && hyperKVMShow && currentForm !== 'Upload'">
+        <a-row :gutter="12" v-if="allowed && (hyperKVMShow || hyperCustomShow) && currentForm === 'Create'">
           <a-col :md="24" :lg="12">
             <a-form-item ref="directdownload" name="directdownload">
               <template #label>
@@ -219,9 +260,9 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="opt in rootDisk.opts" :key="opt.id">
+                <a-select-option v-for="opt in rootDisk.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -234,10 +275,10 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 :placeholder="$t('label.nicadaptertype')">
-                <a-select-option v-for="opt in nicAdapterType.opts" :key="opt.id">
+                <a-select-option v-for="opt in nicAdapterType.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -254,10 +295,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="$t('label.keyboard')">
-            <a-select-option v-for="opt in keyboardType.opts" :key="opt.id">
+            <a-select-option v-for="opt in keyboardType.opts" :key="opt.id" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -273,12 +314,31 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-model:value="form.ostypeid"
             :loading="osTypes.loading"
             :placeholder="apiParams.ostypeid.description">
-            <a-select-option v-for="opt in osTypes.opts" :key="opt.id">
+            <a-select-option v-for="opt in osTypes.opts" :key="opt.id" :label="opt.name || opt.description">
+              {{ opt.name || opt.description }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item
+          name="templatetype"
+          ref="templatetype">
+          <template #label>
+            <tooltip-label :title="$t('label.templatetype')" :tooltip="apiParams.templatetype.description"/>
+          </template>
+          <a-select
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            v-model:value="form.templatetype"
+            :placeholder="apiParams.templatetype.description">
+            <a-select-option v-for="opt in templateTypes.opts" :key="opt.id">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -295,12 +355,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 v-model:value="userdataid"
                 :placeholder="linkUserDataParams.userdataid.description"
                 :loading="userdata.loading">
-                <a-select-option v-for="opt in userdata.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdata.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -312,13 +372,14 @@
                 <tooltip-label :title="$t('label.userdatapolicy')" :tooltip="linkUserDataParams.userdatapolicy.description"/>
               </template>
               <a-select
+                showSearch
                 v-model:value="userdatapolicy"
                 :placeholder="linkUserDataParams.userdatapolicy.description"
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id" :label="opt.id || opt.description">
                   {{ opt.id || opt.description }}
                 </a-select-option>
               </a-select>
@@ -363,17 +424,11 @@
                       {{ $t('label.ispublic') }}
                     </a-checkbox>
                   </a-col>
-                  <a-col :span="12" v-if="isAdminRole">
-                    <a-checkbox value="isrouting">
-                      {{ $t('label.isrouting') }}
-                    </a-checkbox>
-                  </a-col>
                 </a-row>
               </a-checkbox-group>
             </a-form-item>
           </a-col>
         </a-row>
-
         <div :span="24" class="action-button">
           <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
           <a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
@@ -423,12 +478,14 @@
       format: {},
       osTypes: {},
       defaultOsType: '',
+      templateTypes: {},
       userdata: {},
       userdataid: null,
       userdatapolicy: null,
       userdatapolicylist: {},
       defaultOsId: null,
       hyperKVMShow: false,
+      hyperCustomShow: false,
       hyperXenServerShow: false,
       hyperVMWShow: false,
       selectedFormat: '',
@@ -439,7 +496,13 @@
       allowed: false,
       allowDirectDownload: false,
       uploadParams: null,
-      currentForm: ['plus-outlined', 'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload'
+      currentForm: ['plus-outlined', 'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload',
+      domains: [],
+      accounts: [],
+      domainLoading: false,
+      domainid: null,
+      account: null,
+      customHypervisorName: 'Custom'
     }
   },
   beforeCreate () {
@@ -473,7 +536,6 @@
         url: [{ required: true, message: this.$t('message.error.required.input') }],
         file: [{ required: true, message: this.$t('message.error.required.input') }],
         name: [{ required: true, message: this.$t('message.error.required.input') }],
-        displaytext: [{ required: true, message: this.$t('message.error.required.input') }],
         zoneids: [
           { type: 'array', required: true, message: this.$t('message.error.select') },
           {
@@ -486,13 +548,17 @@
         ostypeid: [{ required: true, message: this.$t('message.error.select') }],
         groupenabled: [{ type: 'array' }]
       })
-      console.log(this.form)
     },
     fetchData () {
+      this.fetchCustomHypervisorName()
       this.fetchZone()
       this.fetchOsTypes()
+      this.fetchTemplateTypes()
       this.fetchUserData()
       this.fetchUserdataPolicy()
+      if ('listDomains' in this.$store.getters.apis) {
+        this.fetchDomains()
+      }
       if (Object.prototype.hasOwnProperty.call(store.getters.apis, 'listConfigurations')) {
         if (this.allowed && this.hyperXenServerShow) {
           this.fetchXenServerProvider()
@@ -549,6 +615,23 @@
         })
       })
     },
+    fetchCustomHypervisorName () {
+      const params = {
+        name: 'hypervisor.custom.display.name'
+      }
+      this.loading = true
+      api('listConfigurations', params).then(json => {
+        if (json.listconfigurationsresponse.configuration !== null) {
+          const config = json.listconfigurationsresponse.configuration[0]
+          if (config && config.name === params.name) {
+            this.customHypervisorName = config.value
+            store.dispatch('SetCustomHypervisorName', this.customHypervisorName)
+          }
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
     fetchZone () {
       const params = {}
       let listZones = []
@@ -590,7 +673,7 @@
         if (listResponse) {
           listhyperVisors = listhyperVisors.concat(listResponse)
         }
-        if (this.currentForm !== 'Upload') {
+        if (this.currentForm === 'Create') {
           listhyperVisors.push({
             name: 'Simulator'
           })
@@ -608,11 +691,38 @@
         const listOsTypes = json.listostypesresponse.ostype
         this.osTypes.opts = listOsTypes
         this.defaultOsType = this.osTypes.opts[1].description
-        this.defaultOsId = this.osTypes.opts[1].id
+        this.defaultOsId = this.osTypes.opts[1].name
       }).finally(() => {
         this.osTypes.loading = false
       })
     },
+    fetchTemplateTypes () {
+      this.templateTypes.opts = []
+      const templatetypes = []
+      templatetypes.push({
+        id: 'USER',
+        description: 'USER'
+      })
+      templatetypes.push({
+        id: 'VNF',
+        description: 'VNF'
+      })
+      if (this.isAdminRole) {
+        templatetypes.push({
+          id: 'SYSTEM',
+          description: 'SYSTEM'
+        })
+        templatetypes.push({
+          id: 'BUILTIN',
+          description: 'BUILTIN'
+        })
+        templatetypes.push({
+          id: 'ROUTING',
+          description: 'ROUTING'
+        })
+      }
+      this.templateTypes.opts = templatetypes
+    },
     fetchUserData () {
       const params = {}
       params.listAll = true
@@ -814,6 +924,13 @@
             description: 'TAR'
           })
           break
+        case this.customHypervisorName:
+          this.hyperCustomShow = true
+          format.push({
+            id: 'RAW',
+            description: 'RAW'
+          })
+          break
         default:
           break
       }
@@ -872,6 +989,7 @@
       this.hyperXenServerShow = false
       this.hyperVMWShow = false
       this.hyperKVMShow = false
+      this.hyperCustomShow = false
       this.deployasis = false
       this.allowDirectDownload = false
       this.selectedFormat = null
@@ -968,7 +1086,7 @@
           }).finally(() => {
             this.loading = false
           })
-        } else {
+        } else if (this.currentForm === 'Upload') {
           this.loading = true
           if (this.fileList.length > 1) {
             this.$notification.error({
@@ -1031,6 +1149,43 @@
       arrSelectReset.forEach(name => {
         this.form[name] = undefined
       })
+    },
+    fetchDomains () {
+      const params = {}
+      params.listAll = true
+      params.showicon = true
+      params.details = 'min'
+      this.domainLoading = true
+      api('listDomains', params).then(json => {
+        this.domains = json.listdomainsresponse.domain
+      }).finally(() => {
+        this.domainLoading = false
+        this.handleDomainChange(null)
+      })
+    },
+    handleDomainChange (domain) {
+      this.domainid = domain
+      this.form.account = null
+      this.account = null
+      if ('listAccounts' in this.$store.getters.apis) {
+        this.fetchAccounts()
+      }
+    },
+    fetchAccounts () {
+      api('listAccounts', {
+        domainid: this.domainid
+      }).then(response => {
+        this.accounts = response.listaccountsresponse.account || []
+      }).catch(error => {
+        this.$notifyError(error)
+      })
+    },
+    handleAccountChange (acc) {
+      if (acc) {
+        this.account = acc.name
+      } else {
+        this.account = acc
+      }
     }
   }
 }
diff --git a/ui/src/views/image/TemplateVnfSettings.vue b/ui/src/views/image/TemplateVnfSettings.vue
new file mode 100644
index 0000000..401e6a4
--- /dev/null
+++ b/ui/src/views/image/TemplateVnfSettings.vue
@@ -0,0 +1,881 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div>
+    <a-divider/>
+
+    <div class="title">
+      <div class="form__label">
+        <tooltip-label :title="$t('label.vnf.nics')" :tooltip="apiParams.vnfnics.description"/>
+      </div>
+    </div>
+    <div>
+      <a-button
+        type="dashed"
+        style="width: 100%"
+        :disabled="!('updateVnfTemplate' in $store.getters.apis && isAdminOrOwner())"
+        @click="onShowAddVnfNic">
+        <template #icon><plus-outlined /></template>
+        {{ $t('label.vnf.nic.add') }}
+      </a-button>
+    </div>
+
+    <a-table
+      size="small"
+      style="overflow-y: auto"
+      :loading="loading"
+      :columns="columns"
+      :dataSource="vnfNics"
+      :pagination="false"
+      :rowKey="record => record.deviceid">
+      <template #deviceid="{record}">
+        <span> {{ record.deviceid }} </span>
+      </template>
+      <template #name="{record}">
+        <span> {{ record.name }} </span>
+      </template>
+      <template #required="{ record }">
+        <span v-if="record.required">{{ $t('label.yes') }}</span>
+        <span v-else>{{ $t('label.no') }}</span>
+      </template>
+      <template #management="{ record }">
+        <span v-if="record.management">{{ $t('label.yes') }}</span>
+        <span v-else>{{ $t('label.no') }}</span>
+      </template>
+      <template #description="{record}">
+        <span> {{ record.description }} </span>
+      </template>
+      <template #actions="{ record }">
+        <div class="shift-btns" v-if="'updateVnfTemplate' in $store.getters.apis && isAdminOrOwner()">
+          <a-tooltip placement="top">
+            <template #title>{{ $t('label.vnf.nic.edit') }}</template>
+            <a-button shape="round" @click="onShowEditVnfNic(record)" class="shift-btn">
+              <EditOutlined class="shift-btn" />
+            </a-button>
+          </a-tooltip>
+          <a-tooltip placement="top">
+            <template #title>{{ $t('label.move.up.row') }}</template>
+            <a-button shape="round" @click="moveVnfNicUp(record)" class="shift-btn">
+              <CaretUpOutlined class="shift-btn" />
+            </a-button>
+          </a-tooltip>
+          <a-tooltip placement="top">
+            <template #title>{{ $t('label.move.down.row') }}</template>
+            <a-button shape="round" @click="moveVnfNicDown(record)" class="shift-btn">
+              <CaretDownOutlined class="shift-btn" />
+            </a-button>
+          </a-tooltip>
+          <a-popconfirm
+            :title="$t('label.vnf.nic.delete') + '?'"
+            @confirm="deleteVnfNic(record)"
+            :okText="$t('label.yes')"
+            :cancelText="$t('label.no')"
+          >
+            <template #title>{{ $t('label.vnf.nic.delete') }}</template>
+            <a-button shape="round" class="shift-btn" :danger="true" type="primary">
+              <DeleteOutlined class="shift-btn" />
+            </a-button>
+          </a-popconfirm>
+        </div>
+      </template>
+    </a-table>
+
+    <a-modal
+      :title="$t('label.vnf.nic.add')"
+      :visible="showAddVnfNic"
+      :afterClose="closeVnfNicModal"
+      :maskClosable="false"
+      :closable="true"
+      :footer="null"
+      @cancel="closeVnfNicModal">
+
+      <div class="new-vnf-nic"  v-ctrl-enter="addVnfNic">
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.deviceid')" :tooltip="$t('label.vnf.nic.deviceid')"/>
+          </div>
+          <a-input v-model:value="newVnfNic.deviceid" type="number" v-focus="true"></a-input>
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.name')" :tooltip="$t('label.vnf.nic.name')"/>
+          </div>
+          <a-input v-model:value="newVnfNic.name"></a-input>
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.required')" :tooltip="$t('label.vnf.nic.required')"/>
+          </div>
+          <a-switch v-model:checked="newVnfNic.required" :checked="true" />
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.vnf.nic.management')" :tooltip="$t('label.vnf.nic.management.description')"/>
+          </div>
+          <a-switch v-model:checked="newVnfNic.management" :checked="false" />
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <tooltip-label :title="$t('label.description')"  :tooltip="$t('label.vnf.nic.description')"/>
+          </div>
+          <a-input v-model:value="newVnfNic.description"></a-input>
+        </div>
+      </div>
+      <div :span="24" class="action-button">
+        <a-button @click="showAddVnfNic = false">{{ $t('label.cancel') }}</a-button>
+        <a-button ref="submit" type="primary" @click="addVnfNic">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-modal>
+
+    <a-modal
+      :title="$t('label.vnf.nic.add')"
+      :visible="showAddVnfNic"
+      :afterClose="closeVnfNicModal"
+      :maskClosable="false"
+      :closable="true"
+      :footer="null"
+      @cancel="closeVnfNicModal">
+
+      <div class="new-vnf-nic"  v-ctrl-enter="addVnfNic">
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.deviceid')" :tooltip="$t('label.vnf.nic.deviceid')"/>
+          </div>
+          <a-input v-model:value="newVnfNic.deviceid" type="number" v-focus="true"></a-input>
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.name')" :tooltip="$t('label.vnf.nic.name')"/>
+          </div>
+          <a-input v-model:value="newVnfNic.name"></a-input>
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.required')" :tooltip="$t('label.vnf.nic.required')"/>
+          </div>
+          <a-switch v-model:checked="newVnfNic.required" :checked="true" />
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <span class="new-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.vnf.nic.management')" :tooltip="$t('label.vnf.nic.management.description')"/>
+          </div>
+          <a-switch v-model:checked="newVnfNic.management" :checked="false" />
+        </div>
+        <div class="new-vnf-nic__item">
+          <div class="new-vnf-nic__label">
+            <tooltip-label :title="$t('label.description')"  :tooltip="$t('label.vnf.nic.description')"/>
+          </div>
+          <a-input v-model:value="newVnfNic.description"></a-input>
+        </div>
+      </div>
+      <div :span="24" class="action-button">
+        <a-button @click="showAddVnfNic = false">{{ $t('label.cancel') }}</a-button>
+        <a-button ref="submit" type="primary" @click="addVnfNic">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-modal>
+
+    <a-modal
+      :title="$t('label.vnf.nic.edit')"
+      :visible="showEditVnfNic"
+      :afterClose="closeVnfNicModal"
+      :maskClosable="false"
+      :closable="true"
+      :footer="null"
+      @cancel="closeVnfNicModal">
+
+      <div class="update-vnf-nic"  v-ctrl-enter="editVnfNic">
+        <div class="update-vnf-nic__item">
+          <div class="update-vnf-nic__label">
+            <span class="update-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.deviceid')" :tooltip="$t('label.vnf.nic.deviceid')"/>
+          </div>
+          <span> {{ updateVnfNic.deviceid }} </span>
+        </div>
+        <div class="update-vnf-nic__item">
+          <div class="update-vnf-nic__label">
+            <span class="update-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.name')" :tooltip="$t('label.vnf.nic.name')"/>
+          </div>
+          <a-input v-model:value="updateVnfNic.name"></a-input>
+        </div>
+        <div class="update-vnf-nic__item">
+          <div class="update-vnf-nic__label">
+            <span class="update-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.required')" :tooltip="$t('label.vnf.nic.required')"/>
+          </div>
+          <a-switch v-model:checked="updateVnfNic.required" :checked="true" />
+        </div>
+        <div class="update-vnf-nic__item">
+          <div class="update-vnf-nic__label">
+            <span class="update-vnf-nic__required">*</span>
+            <tooltip-label :title="$t('label.vnf.nic.management')" :tooltip="$t('label.vnf.nic.management.description')"/>
+          </div>
+          <a-switch v-model:checked="updateVnfNic.management" :checked="false" />
+        </div>
+        <div class="update-vnf-nic__item">
+          <div class="update-vnf-nic__label">
+            <tooltip-label :title="$t('label.description')"  :tooltip="$t('label.vnf.nic.description')"/>
+          </div>
+          <a-input v-model:value="updateVnfNic.description"></a-input>
+        </div>
+      </div>
+      <div :span="24" class="action-button">
+        <a-button @click="showEditVnfNic = false">{{ $t('label.cancel') }}</a-button>
+        <a-button ref="submit" type="primary" @click="editVnfNic">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-modal>
+
+    <a-divider/>
+    <div class="title">
+      <div class="form__label">
+        <tooltip-label :title="$t('label.vnf.details')" :tooltip="apiParams.vnfdetails.description"/>
+      </div>
+    </div>
+    <div v-show="!showAddVnfDetail">
+      <a-button
+        type="dashed"
+        style="width: 100%"
+        :disabled="!('updateVnfTemplate' in $store.getters.apis && isAdminOrOwner())"
+        @click="onShowAddVnfDetail">
+        <template #icon><plus-outlined /></template>
+        {{ $t('label.vnf.detail.add') }}
+      </a-button>
+    </div>
+
+    <div v-show="showAddVnfDetail">
+      <a-input-group
+        type="text"
+        compact>
+        <a-auto-complete
+          class="detail-input"
+          ref="keyElm"
+          :filterOption="filterOption"
+          v-model:value="newKey"
+          :options="detailKeys"
+          :placeholder="$t('label.name')" />
+        <a-input
+          class="tag-disabled-input"
+          style=" width: 30px; border-left: 0; pointer-events: none; text-align: center"
+          placeholder="="
+          disabled />
+        <a-select
+          v-if="newKey === 'access_methods'"
+          class="detail-input"
+          v-model:value="newValues"
+          mode="multiple"
+          :placeholder="$t('label.value')"
+          :filterOption="filterOption">
+          <a-select-option v-for="opt in detailValues" :key="opt.value" :label="opt.value">
+              <span>
+                {{ opt.value }}
+              </span>
+          </a-select-option>
+        </a-select>
+        <a-input
+          v-else
+          class="detail-input"
+          :filterOption="filterOption"
+          v-model:value="newValue"
+          :options="detailValues"
+          :placeholder="$t('label.value')" />
+        <tooltip-button :tooltip="$t('label.add.setting')" :shape="null" icon="check-outlined" @onClick="addVnfDetail" buttonClass="detail-button" />
+        <tooltip-button :tooltip="$t('label.cancel')" :shape="null" icon="close-outlined" @onClick="closeVnfDetail" buttonClass="detail-button" />
+      </a-input-group>
+      <p v-if="error" style="color: red"> {{ $t(error) }} </p>
+    </div>
+
+    <a-list size="large">
+      <a-list-item :key="index" v-for="(item, index) in vnfDetails">
+        <a-list-item-meta>
+          <template #title>
+            {{ item.name }}
+          </template>
+          <template #description>
+            <div v-if="item.edit" style="display: flex">
+              <a-select
+                v-if="item.name === 'access_methods'"
+                class="detail-input"
+                v-model:value="item.values"
+                mode="multiple"
+                :placeholder="$t('label.value')"
+                :filterOption="filterOption">
+                <a-select-option v-for="opt in getEditDetailOptions(vnfDetailOptions[item.name])" :key="opt.value" :label="opt.value">
+                  <span>
+                    {{ opt.value }}
+                  </span>
+                </a-select-option>
+              </a-select>
+              <a-auto-complete
+                v-else
+                style="width: 100%"
+                v-model:value="item.displayvalue"
+                :options="getEditDetailOptions(vnfDetailOptions[item.name])"
+                @change="val => handleInputChange(val, index)"
+                @pressEnter="e => updateVnfDetail(index)" />
+              <tooltip-button
+                buttonClass="edit-button"
+                :tooltip="$t('label.cancel')"
+                @onClick="hideEditVnfDetail(index)"
+                v-if="item.edit"
+                iconType="close-circle-two-tone"
+                iconTwoToneColor="#f5222d" />
+              <tooltip-button
+                buttonClass="edit-button"
+                :tooltip="$t('label.ok')"
+                @onClick="updateVnfDetail(index)"
+                v-if="item.edit"
+                iconType="check-circle-two-tone"
+                iconTwoToneColor="#52c41a" />
+            </div>
+            <span v-else style="word-break: break-all">{{ item.displayvalue }}</span>
+          </template>
+        </a-list-item-meta>
+        <template #actions>
+          <div
+            v-if="'updateVnfTemplate' in $store.getters.apis && isAdminOrOwner()">
+            <tooltip-button
+              :tooltip="$t('label.edit')"
+              icon="edit-outlined"
+              @onClick="showEditVnfDetail(index)" />
+          </div>
+          <div
+            v-if="'updateVnfTemplate' in $store.getters.apis && isAdminOrOwner()">
+            <a-popconfirm
+              :title="`${$t('label.delete.setting')}?`"
+              @confirm="deleteVnfDetail(index)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')"
+              placement="left"
+            >
+              <tooltip-button :tooltip="$t('label.delete')" type="primary" :danger="true" icon="delete-outlined" />
+            </a-popconfirm>
+          </div>
+        </template>
+      </a-list-item>
+    </a-list>
+  </div>
+</template>
+
+<script>
+import { ref, reactive } from 'vue'
+import { api } from '@/api'
+import Status from '@/components/widgets/Status'
+import TooltipButton from '@/components/widgets/TooltipButton'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+
+export default {
+  name: 'TemplateVnfSettings',
+  components: {
+    TooltipButton,
+    TooltipLabel,
+    Status
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      resourceType: 'VnfTemplate',
+      vnfDetailOptions: {},
+      columns: [],
+      vnfNics: [],
+      previousVnfNics: [],
+      showAddVnfNic: false,
+      showEditVnfNic: false,
+      newVnfNic: {
+        deviceid: null,
+        name: null,
+        required: true,
+        management: false,
+        description: null
+      },
+      updateVnfNic: {
+        deviceid: null,
+        name: null,
+        required: true,
+        management: false,
+        description: null
+      },
+      vnfDetails: [],
+      previousVnfDetails: [],
+      showAddVnfDetail: false,
+      newKey: '',
+      newValue: '',
+      newValues: [],
+      error: false
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('updateVnfTemplate')
+  },
+  computed: {
+    detailKeys () {
+      return Object.keys(this.vnfDetailOptions).map(key => {
+        return { value: key }
+      })
+    },
+    detailValues () {
+      if (!this.newKey) {
+        return []
+      }
+      if (!Array.isArray(this.vnfDetailOptions[this.newKey])) {
+        if (this.vnfDetailOptions[this.newKey]) {
+          return { value: this.vnfDetailOptions[this.newKey] }
+        } else {
+          return ''
+        }
+      }
+      return this.vnfDetailOptions[this.newKey].map(value => {
+        return { value: value }
+      })
+    }
+  },
+  created () {
+    this.columns = [
+      {
+        title: this.$t('label.deviceid'),
+        dataIndex: 'deviceid',
+        slots: { customRender: 'deviceid' }
+      },
+      {
+        title: this.$t('label.name'),
+        dataIndex: 'name'
+      },
+      {
+        title: this.$t('label.required'),
+        dataIndex: 'required',
+        slots: { customRender: 'required' }
+      },
+      {
+        title: this.$t('label.vnf.nic.management'),
+        dataIndex: 'management',
+        slots: { customRender: 'management' }
+      },
+      {
+        title: this.$t('label.description'),
+        dataIndex: 'description',
+        slots: { customRender: 'description' }
+      },
+      {
+        title: this.$t('label.action'),
+        slots: { customRender: 'actions' }
+      }
+    ]
+    this.columns.push({
+      title: '',
+      dataIndex: 'action',
+      width: 100,
+      slots: { customRender: 'action' }
+    })
+
+    const userInfo = this.$store.getters.userInfo
+    if (!['Admin'].includes(userInfo.roletype) &&
+      (userInfo.account !== this.resource.account || userInfo.domain !== this.resource.domain)) {
+      this.columns = this.columns.filter(col => { return col.dataIndex !== 'status' })
+    }
+    this.initForm()
+    this.fetchData()
+  },
+  watch: {
+    loading (newData, oldData) {
+      if (!newData && !this.showGroupActionModal) {
+        this.fetchData()
+      }
+    }
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({
+        zoneid: [{ type: 'array', required: true, message: this.$t('message.error.select') }]
+      })
+      this.previousVnfNics = this.resource.vnfnics?.slice() || []
+      this.previousVnfDetails = []
+      if (this.resource.vnfdetails) {
+        this.previousVnfDetails = Object.keys(this.resource.vnfdetails).map(k => {
+          return {
+            name: k,
+            value: this.resource.vnfdetails[k],
+            displayvalue: this.getDisplayValue(k, this.resource.vnfdetails[k]),
+            values: k === 'access_methods' ? this.resource.vnfdetails[k].split(',') : null,
+            edit: false
+          }
+        })
+      }
+      api('listDetailOptions', { resourcetype: this.resourceType }).then(json => {
+        this.vnfDetailOptionsInApi = json.listdetailoptionsresponse.detailoptions.details
+        this.vnfDetailOptions = {}
+        Object.keys(this.vnfDetailOptionsInApi).sort().forEach(k => {
+          this.vnfDetailOptions[k] = this.vnfDetailOptionsInApi[k]
+        })
+      })
+    },
+    fetchData () {
+      this.vnfNics = this.previousVnfNics.slice() || []
+      this.vnfDetails = this.previousVnfDetails.slice() || []
+    },
+    isAdminOrOwner () {
+      return ['Admin'].includes(this.$store.getters.userInfo.roletype) ||
+        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
+        this.resource.project && this.resource.projectid === this.$store.getters.project.id
+    },
+    onShowAddVnfNic () {
+      this.showAddVnfNic = true
+    },
+    filterOption (input, option) {
+      return (
+        option.value.toUpperCase().indexOf(input.toUpperCase()) >= 0
+      )
+    },
+    addVnfNic () {
+      if (!this.newVnfNic.deviceid || !this.newVnfNic.name) {
+        this.$notification.error({
+          message: this.$t('message.request.failed'),
+          description: this.$t('message.please.enter.valid.value')
+        })
+        return
+      }
+      if (this.newVnfNic && this.newVnfNic.deviceid && this.newVnfNic.name) {
+        this.vnfNics.push({
+          deviceid: this.newVnfNic.deviceid,
+          name: this.newVnfNic.name,
+          required: this.newVnfNic.required,
+          management: this.newVnfNic.management,
+          description: this.newVnfNic.description
+        })
+      }
+      this.updateVnfTemplateNics()
+    },
+    deleteVnfNic (record) {
+      for (var index = 0; index < this.vnfNics.length; index++) {
+        var nic = this.vnfNics[index]
+        if (nic.deviceid === record.deviceid) {
+          this.vnfNics.splice(index, 1)
+          break
+        }
+      }
+      this.updateVnfTemplateNics()
+    },
+    onShowEditVnfNic (record) {
+      this.updateVnfNic.deviceid = record.deviceid
+      this.updateVnfNic.name = record.name
+      this.updateVnfNic.required = record.required
+      this.updateVnfNic.management = record.management
+      this.updateVnfNic.description = record.description
+      this.showEditVnfNic = true
+    },
+    getEditDetailOptions (values) {
+      if (!values) {
+        return
+      }
+      var data = values.map(value => { return { value: value } })
+      return data
+    },
+    editVnfNic () {
+      if (!this.updateVnfNic.name) {
+        this.$notification.error({
+          message: this.$t('message.request.failed'),
+          description: this.$t('message.please.enter.valid.value')
+        })
+        return
+      }
+      if (this.updateVnfNic && this.updateVnfNic.name) {
+        for (var index = 0; index < this.vnfNics.length; index++) {
+          var nic = this.vnfNics[index]
+          if (nic.deviceid === this.updateVnfNic.deviceid) {
+            this.vnfNics[index] = this.updateVnfNic
+          }
+        }
+      }
+      this.updateVnfTemplateNics()
+    },
+    moveVnfNicUp (record) {
+      const deviceid = record.deviceid
+      let currentNic = null
+      let previousNic = null
+      for (var index = 0; index < this.vnfNics.length; index++) {
+        var nic = this.vnfNics[index]
+        if (nic.deviceid === record.deviceid) {
+          currentNic = JSON.parse(JSON.stringify(nic))
+          this.vnfNics[index] = currentNic
+        } else if (nic.deviceid === record.deviceid - 1) {
+          previousNic = JSON.parse(JSON.stringify(nic))
+          this.vnfNics[index] = previousNic
+        }
+      }
+      if (currentNic && previousNic) {
+        currentNic.deviceid = deviceid - 1
+        previousNic.deviceid = deviceid
+        const currentRequired = currentNic.required
+        currentNic.required = previousNic.required
+        previousNic.required = currentRequired
+        this.updateVnfTemplateNics()
+      } else {
+        this.$notification.error({
+          message: this.$t('message.request.failed'),
+          description: this.$t('message.vnf.nic.move.up.fail')
+        })
+      }
+    },
+    moveVnfNicDown (record) {
+      const deviceid = record.deviceid
+      let currentNic = null
+      let nextNic = null
+      for (var index = 0; index < this.vnfNics.length; index++) {
+        var nic = this.vnfNics[index]
+        if (nic.deviceid === record.deviceid) {
+          currentNic = JSON.parse(JSON.stringify(nic))
+          this.vnfNics[index] = currentNic
+        } else if (nic.deviceid === record.deviceid + 1) {
+          nextNic = JSON.parse(JSON.stringify(nic))
+          this.vnfNics[index] = nextNic
+        }
+      }
+      if (currentNic && nextNic) {
+        currentNic.deviceid = deviceid + 1
+        nextNic.deviceid = deviceid
+        const currentRequired = currentNic.required
+        currentNic.required = nextNic.required
+        nextNic.required = currentRequired
+        this.updateVnfTemplateNics()
+      } else {
+        this.$notification.error({
+          message: this.$t('message.request.failed'),
+          description: this.$t('message.vnf.nic.move.down.fail')
+        })
+      }
+    },
+    updateVnfTemplateDetails () {
+      this.updateVnfTemplate(true, false)
+    },
+    updateVnfTemplateNics () {
+      this.updateVnfTemplate(false, true)
+    },
+    updateVnfTemplate (areDetailsChanged, areNicsChanged) {
+      const apiName = 'updateVnfTemplate'
+      if (!(apiName in this.$store.getters.apis)) {
+        this.$notification.error({
+          message: this.$t('error.execute.api.failed') + ' ' + apiName,
+          description: this.$t('message.user.not.permitted.api')
+        })
+        return
+      }
+
+      const params = { id: this.resource.id }
+      if (areDetailsChanged) {
+        if (this.vnfDetails.length === 0) {
+          params.cleanupvnfdetails = true
+        } else {
+          this.vnfDetails.forEach(function (item, index) {
+            params['vnfdetails[0].' + item.name] = item.value
+          })
+        }
+      }
+
+      if (areNicsChanged) {
+        let i = 0
+        if (this.vnfNics.length === 0) {
+          params.cleanupvnfnics = true
+        }
+        for (var index = 0; index < this.vnfNics.length; index++) {
+          var nic = this.vnfNics[index]
+          params['vnfnics[' + i + '].deviceid'] = nic.deviceid
+          params['vnfnics[' + i + '].name'] = nic.name
+          params['vnfnics[' + i + '].required'] = nic.required
+          params['vnfnics[' + i + '].management'] = nic.management
+          params['vnfnics[' + i + '].description'] = nic.description
+          i++
+        }
+      }
+
+      api(apiName, params).then(json => {
+        this.vnfNics = json.updatetemplateresponse.template.vnfnics || []
+        const details = json.updatetemplateresponse.template.vnfdetails || []
+        this.vnfDetails = Object.keys(details).map(k => {
+          return {
+            name: k,
+            value: details[k],
+            displayvalue: this.getDisplayValue(k, details[k]),
+            values: k === 'access_methods' ? details[k].split(',') : null,
+            edit: false
+          }
+        })
+        this.previousVnfNics = this.vnfNics.slice()
+        this.previousVnfDetails = this.vnfDetails.slice()
+        this.closeVnfDetail()
+        this.closeVnfNicModal()
+      }).catch(error => {
+        this.$notifyError(error)
+        this.fetchData()
+      }).finally(f => {
+      })
+    },
+    getDisplayValue (name, value) {
+      return name.includes('password') ? '********' : value
+    },
+    showEditVnfDetail (index) {
+      this.vnfDetails[index].edit = true
+      this.vnfDetails[index].originalValue = this.vnfDetails[index].value
+    },
+    hideEditVnfDetail (index) {
+      this.vnfDetails[index].edit = false
+      this.vnfDetails[index].value = this.vnfDetails[index].originalValue
+    },
+    handleInputChange (val, index) {
+      this.vnfDetails[index].value = val
+    },
+    updateVnfDetail (index) {
+      if (Array.isArray(this.vnfDetails[index].values) && this.vnfDetails[index].values.length > 0) {
+        this.vnfDetails[index].value = this.vnfDetails[index].values.join(',')
+      } else {
+        this.vnfDetails[index].value = this.vnfDetails[index].displayvalue
+      }
+      this.vnfDetails[index].displayvalue = this.getDisplayValue(this.vnfDetails[index].name, this.vnfDetails[index].value)
+      this.updateVnfTemplateDetails()
+    },
+    onShowAddVnfDetail () {
+      this.showAddVnfDetail = true
+      setTimeout(() => {
+        this.$refs.keyElm.focus()
+      })
+    },
+    addVnfDetail () {
+      if (this.newKey === '' || (this.newValue === '' && this.newValues.length === 0)) {
+        this.error = this.$t('message.error.provide.setting')
+        return
+      }
+      this.error = false
+      this.newValueString = ''
+      if (this.newValues.length > 0) {
+        this.newValueString = this.newValues.join(',')
+      } else {
+        this.newValueString = this.newValue
+      }
+      this.vnfDetails.push({ name: this.newKey, value: this.newValueString })
+      this.updateVnfTemplateDetails()
+    },
+    deleteVnfDetail (index) {
+      this.vnfDetails.splice(index, 1)
+      this.updateVnfTemplateDetails()
+    },
+    closeVnfDetail () {
+      this.newKey = ''
+      this.newValue = ''
+      this.newValues = []
+      this.error = false
+      this.showAddVnfDetail = false
+    },
+    closeVnfNicModal () {
+      this.showAddVnfNic = false
+      this.showEditVnfNic = false
+      this.newVnfNic = {
+        deviceid: null,
+        name: null,
+        required: true,
+        management: false,
+        description: null
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.row-element {
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+
+.detail-input {
+  width: calc(calc(100% / 2) - 45px);
+}
+
+.detail-button {
+  width: 30px;
+}
+
+.shift-btns {
+  display: flex;
+}
+
+.shift-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 20px;
+  height: 20px;
+  font-size: 12px;
+
+  &:not(:last-child) {
+    margin-right: 5px;
+  }
+
+  &--rotated {
+    font-size: 10px;
+    transform: rotate(90deg);
+  }
+}
+
+.new-vnf-nic {
+  &__item {
+    margin-bottom: 10px;
+  }
+
+  &__label {
+    margin-bottom: 5px;
+    font-weight: bold;
+  }
+
+  &__required {
+    margin-right: 5px;
+    color: red;
+  }
+}
+
+.update-vnf-nic {
+  &__item {
+    margin-bottom: 10px;
+  }
+
+  &__label {
+    margin-bottom: 5px;
+    font-weight: bold;
+  }
+
+  &__required {
+    margin-right: 5px;
+    color: red;
+  }
+}
+</style>
diff --git a/ui/src/views/image/TemplateZones.vue b/ui/src/views/image/TemplateZones.vue
index b189bcf..3507bc8 100644
--- a/ui/src/views/image/TemplateZones.vue
+++ b/ui/src/views/image/TemplateZones.vue
@@ -34,26 +34,70 @@
       :dataSource="dataSource"
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
-      :rowKey="record => record.zoneid">
-      <template #zonename="{record}">
-        <span v-if="fetchZoneIcon(record.zoneid)">
-          <resource-icon :image="zoneIcon" size="2x" style="margin-right: 5px"/>
-        </span>
-        <global-outlined v-else style="margin-right: 5px" />
-        <span> {{ record.zonename }} </span>
-      </template>
-      <template #isready="{ record }">
-        <span v-if="record.isready">{{ $t('label.yes') }}</span>
-        <span v-else>{{ $t('label.no') }}</span>
+      :rowKey="record => record.zoneid"
+      :rowExpandable="(record) => record.downloaddetails.length > 0">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'zonename'">
+          <span v-if="fetchZoneIcon(record.zoneid)">
+            <resource-icon :image="zoneIcon" size="2x" style="margin-right: 5px"/>
+          </span>
+          <global-outlined v-else style="margin-right: 5px" />
+          <span> {{ record.zonename }} </span>
+        </template>
+        <template v-if="column.key === 'isready'">
+          <span v-if="record.isready">{{ $t('label.yes') }}</span>
+          <span v-else>{{ $t('label.no') }}</span>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            style="margin-right: 5px"
+            :disabled="!('copyTemplate' in $store.getters.apis && record.isready)"
+            :title="$t('label.action.copy.template')"
+            icon="copy-outlined"
+            :loading="copyLoading"
+            @onClick="showCopyTemplate(record)" />
+          <tooltip-button
+            style="margin-right: 5px"
+            :disabled="!('deleteTemplate' in $store.getters.apis)"
+            :title="$t('label.action.delete.template')"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="onShowDeleteModal(record)"/>
+        </template>
       </template>
       <template #expandedRowRender="{ record }">
         <a-table
-          style="marginLeft: -50px; marginTop: 10px; marginBottom: 10px"
-          :columns="innerColumns"
-          :data-source="record.downloaddetails"
+          style="margin: 10px 0;"
+          :columns="storagePoolInnerColumns"
+          :data-source="record.downloaddetails.filter((row) => row.datastoreRole === 'Primary')"
+          v-if="record.downloaddetails.filter((row) => row.datastoreRole === 'Primary').length > 0"
           :pagination="false"
           :bordered="true"
-          :rowKey="record => record.zoneid">
+          :rowKey="record => record.datastoreId">
+          <template #bodyCell="{ text, record, column }">
+            <template v-if="column.dataIndex === 'datastore' && record.datastoreId">
+                <router-link :to="{ path: '/storagepool/' + record.datastoreId }">
+                {{ text }}
+              </router-link>
+            </template>
+          </template>
+        </a-table>
+        <a-table
+          style="margin: 10px 0;"
+          :columns="imageStoreInnerColumns"
+          :data-source="record.downloaddetails.filter((row) => row.datastoreRole !== 'Primary')"
+          v-if="record.downloaddetails.filter((row) => row.datastoreRole !== 'Primary').length > 0"
+          :pagination="false"
+          :bordered="true"
+          :rowKey="record => record.datastoreId">
+          <template #bodyCell="{ text, record, column }">
+            <template v-if="column.dataIndex === 'datastore' && record.datastoreId">
+                <router-link :to="{ path: '/imagestore/' + record.datastoreId }">
+                {{ text }}
+              </router-link>
+            </template>
+          </template>
         </a-table>
       </template>
       <template #action="{ record }">
@@ -124,7 +168,7 @@
               <a-select-option v-for="zone in zones" :key="zone.id" :label="zone.name">
                 <div>
                   <span v-if="zone.icon && zone.icon.base64image">
-                    <resource-icon :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+                    <resource-icon :image="zone.icon.base64image" size="2x" style="margin-right: 5px"/>
                   </span>
                   <global-outlined v-else style="margin-right: 5px" />
                   {{ zone.name }}
@@ -171,12 +215,12 @@
           size="middle"
           :columns="selectedColumns"
           :dataSource="selectedItems"
-          :rowKey="(record, idx) => record.zoneid || record.name"
+          :rowKey="record => record.zoneid || record.name"
           :pagination="true"
           style="overflow-y: auto">
         </a-table>
         <a-spin :spinning="deleteLoading">
-          <a-form-item :label="$t('label.isforced')" style="margin-bottom: 0;">
+          <a-form-item ref="forcedDelete" name="forcedDelete" :label="$t('label.isforced')" style="margin-bottom: 0;">
             <a-switch v-model:checked="forcedDelete" v-focus="true"></a-switch>
           </a-form-item>
           <div :span="24" class="action-button">
@@ -260,21 +304,21 @@
   created () {
     this.columns = [
       {
+        key: 'zonename',
         title: this.$t('label.zonename'),
-        dataIndex: 'zonename',
-        slots: { customRender: 'zonename' }
+        dataIndex: 'zonename'
       },
       {
         title: this.$t('label.status'),
         dataIndex: 'status'
       },
       {
+        key: 'isready',
         title: this.$t('label.isready'),
-        dataIndex: 'isready',
-        slots: { customRender: 'isready' }
+        dataIndex: 'isready'
       }
     ]
-    this.innerColumns = [
+    this.imageStoreInnerColumns = [
       {
         title: this.$t('label.secondary.storage'),
         dataIndex: 'datastore'
@@ -288,12 +332,26 @@
         dataIndex: 'downloadState'
       }
     ]
+    this.storagePoolInnerColumns = [
+      {
+        title: this.$t('label.primary.storage'),
+        dataIndex: 'datastore'
+      },
+      {
+        title: this.$t('label.download.percent'),
+        dataIndex: 'downloadPercent'
+      },
+      {
+        title: this.$t('label.download.state'),
+        dataIndex: 'downloadState'
+      }
+    ]
     if (this.isActionPermitted()) {
       this.columns.push({
+        key: 'actions',
         title: '',
-        dataIndex: 'action',
-        width: 100,
-        slots: { customRender: 'action' }
+        dataIndex: 'actions',
+        width: 100
       })
     }
 
@@ -422,9 +480,9 @@
     deleteTemplates (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
diff --git a/ui/src/views/image/UpdateISO.vue b/ui/src/views/image/UpdateISO.vue
index 2fa7979..4d7140d 100644
--- a/ui/src/views/image/UpdateISO.vue
+++ b/ui/src/views/image/UpdateISO.vue
@@ -48,12 +48,15 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-model:value="form.ostypeid"
             :loading="osTypes.loading"
             :placeholder="apiParams.ostypeid.description">
-            <a-select-option v-for="opt in osTypes.opts" :key="opt.id">
+            <a-select-option
+              v-for="opt in osTypes.opts"
+              :key="opt.id"
+              :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -69,12 +72,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 v-model:value="userdataid"
                 :placeholder="linkUserDataParams.userdataid.description"
                 :loading="userdata.loading">
-                <a-select-option v-for="opt in userdata.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdata.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -86,13 +89,17 @@
                 <tooltip-label :title="$t('label.userdatapolicy')" :tooltip="$t('label.userdatapolicy.tooltip')"/>
               </template>
               <a-select
+                showSearch
                 v-model:value="userdatapolicy"
                 :placeholder="linkUserDataParams.userdatapolicy.description"
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id">
+                <a-select-option
+                  v-for="opt in userdatapolicylist.opts"
+                  :key="opt.id"
+                  :label="opt.name || opt.description">
                   {{ opt.id || opt.description }}
                 </a-select-option>
               </a-select>
diff --git a/ui/src/views/image/UpdateKubernetesSupportedVersion.vue b/ui/src/views/image/UpdateKubernetesSupportedVersion.vue
index 9081eb0..8d2b4e4 100644
--- a/ui/src/views/image/UpdateKubernetesSupportedVersion.vue
+++ b/ui/src/views/image/UpdateKubernetesSupportedVersion.vue
@@ -30,12 +30,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="stateLoading"
             :placeholder="apiParams.state.description"
             v-focus="true" >
-            <a-select-option v-for="(opt, optIndex) in states" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in states" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/image/UpdateTemplate.vue b/ui/src/views/image/UpdateTemplate.vue
index 441c685..0a3103f 100644
--- a/ui/src/views/image/UpdateTemplate.vue
+++ b/ui/src/views/image/UpdateTemplate.vue
@@ -92,12 +92,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 v-model:value="form.ostypeid"
                 :loading="osTypes.loading"
                 :placeholder="apiParams.ostypeid.description">
-                <a-select-option v-for="opt in osTypes.opts" :key="opt.id">
+                <a-select-option v-for="opt in osTypes.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -114,12 +114,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }"
                 v-model:value="userdataid"
                 :placeholder="linkUserDataParams.userdataid.description"
                 :loading="userdata.loading">
-                <a-select-option v-for="opt in userdata.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdata.opts" :key="opt.id" :label="opt.name || opt.description">
                   {{ opt.name || opt.description }}
                 </a-select-option>
               </a-select>
@@ -131,13 +131,14 @@
                 <tooltip-label :title="$t('label.userdatapolicy')" :tooltip="$t('label.userdatapolicy.tooltip')"/>
               </template>
               <a-select
+                showSearch
                 v-model:value="userdatapolicy"
                 :placeholder="linkUserDataParams.userdatapolicy.description"
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
-                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id">
+                <a-select-option v-for="opt in userdatapolicylist.opts" :key="opt.id" :label="opt.id || opt.description">
                   {{ opt.id || opt.description }}
                 </a-select-option>
               </a-select>
@@ -164,9 +165,9 @@
           </span>
           <a-select
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-model:value="form.templatetype"
             :placeholder="apiParams.templatetype.description"
@@ -204,7 +205,7 @@
   },
   data () {
     return {
-      templatetypes: ['BUILTIN', 'USER', 'SYSTEM', 'ROUTING'],
+      templatetypes: ['BUILTIN', 'USER', 'SYSTEM', 'ROUTING', 'VNF'],
       rootDisk: {},
       nicAdapterType: {},
       keyboardType: {},
diff --git a/ui/src/views/image/UpdateTemplateIsoPermissions.vue b/ui/src/views/image/UpdateTemplateIsoPermissions.vue
index 05840c6..c7b0f9e 100644
--- a/ui/src/views/image/UpdateTemplateIsoPermissions.vue
+++ b/ui/src/views/image/UpdateTemplateIsoPermissions.vue
@@ -29,9 +29,9 @@
         @change="fetchData"
         v-focus="true"
         showSearch
-        optionFilterProp="label"
+        optionFilterProp="value"
         :filterOption="(input, option) => {
-          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
         }" >
         <a-select-option :value="$t('label.add')">{{ $t('label.add') }}</a-select-option>
         <a-select-option :value="$t('label.remove')">{{ $t('label.remove') }}</a-select-option>
@@ -50,9 +50,9 @@
           :defaultValue="$t('label.account')"
           @change="fetchData"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }">
           <a-select-option :value="$t('label.account')">{{ $t('label.account') }}</a-select-option>
           <a-select-option :value="$t('label.project')">{{ $t('label.project') }}</a-select-option>
diff --git a/ui/src/views/infra/AddObjectStorage.vue b/ui/src/views/infra/AddObjectStorage.vue
new file mode 100644
index 0000000..4aacd6a
--- /dev/null
+++ b/ui/src/views/infra/AddObjectStorage.vue
@@ -0,0 +1,171 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div class="form-layout" v-ctrl-enter="handleSubmit">
+    <a-spin :spinning="loading">
+      <a-form
+        :ref="formRef"
+        :model="form"
+        :rules="rules"
+        layout="vertical"
+        @finish="handleSubmit"
+       >
+        <a-form-item name="name" ref="name" :label="$t('label.name')">
+          <a-input v-model:value="form.name" v-focus="true" />
+        </a-form-item>
+        <a-form-item name="provider" ref="provider" :label="$t('label.providername')">
+          <a-select
+            v-model:value="form.provider"
+            @change="val => { form.provider = val }"
+            showSearch
+            optionFilterProp="value"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+            <a-select-option
+              :value="prov"
+              v-for="(prov,idx) in providers"
+              :key="idx"
+            >{{ prov }}</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="url" ref="url" :label="$t('label.url')">
+          <a-input v-model:value="form.url" />
+        </a-form-item>
+        <a-form-item name="accessKey" ref="accessKey" :label="$t('label.access.key')">
+          <a-input v-model:value="form.accessKey" />
+        </a-form-item>
+        <a-form-item name="secretKey" ref="secretKey" :label="$t('label.secret.key')">
+          <a-input v-model:value="form.secretKey" />
+        </a-form-item>
+        <div :span="24" class="action-button">
+          <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+          <a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+        </div>
+      </a-form>
+    </a-spin>
+  </div>
+</template>
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+import { mixinForm } from '@/utils/mixin'
+import ResourceIcon from '@/components/view/ResourceIcon'
+
+export default {
+  name: 'AddObjectStorage',
+  mixins: [mixinForm],
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  components: {
+    ResourceIcon
+  },
+  inject: ['parentFetchData'],
+  data () {
+    return {
+      providers: ['MinIO', 'Simulator'],
+      zones: [],
+      loading: false
+    }
+  },
+  created () {
+    this.initForm()
+    this.fetchData()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({
+        provider: 'MinIO'
+      })
+      this.rules = reactive({
+        url: [{ required: true, message: this.$t('label.required') }],
+        name: [{ required: true, message: this.$t('label.required') }],
+        accessKey: [{ required: true, message: this.$t('label.required') }],
+        secretKey: [{ required: true, message: this.$t('label.required') }]
+      })
+    },
+    fetchData () {
+    },
+    closeModal () {
+      this.$emit('close-action')
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      if (this.loading) return
+      this.formRef.value.validate().then(async () => {
+        const formRaw = toRaw(this.form)
+        const values = this.handleRemoveFields(formRaw)
+
+        var data = {
+          name: values.name
+        }
+        var provider = values.provider
+
+        data.provider = provider
+        data.url = values.url
+        data['details[0].key'] = 'accesskey'
+        data['details[0].value'] = values.accessKey
+        data['details[1].key'] = 'secretkey'
+        data['details[1].value'] = values.secretKey
+
+        this.loading = true
+
+        try {
+          await this.addObjectStore(data)
+
+          this.$notification.success({
+            message: this.$t('label.add.object.storage'),
+            description: this.$t('message.success.add.object.storage')
+          })
+          this.loading = false
+          this.closeModal()
+          this.parentFetchData()
+        } catch (error) {
+          this.$notifyError(error)
+          this.loading = false
+        }
+      }).catch(error => {
+        this.formRef.value.scrollToField(error.errorFields[0].name)
+      })
+    },
+    addObjectStore (params) {
+      return new Promise((resolve, reject) => {
+        api('addObjectStoragePool', params).then(json => {
+          resolve()
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.form-layout {
+  width: 85vw;
+
+  @media (min-width: 1000px) {
+    width: 35vw;
+  }
+}
+</style>
diff --git a/ui/src/views/infra/AddPrimaryStorage.vue b/ui/src/views/infra/AddPrimaryStorage.vue
index c1c733d..730a806 100644
--- a/ui/src/views/infra/AddPrimaryStorage.vue
+++ b/ui/src/views/infra/AddPrimaryStorage.vue
@@ -32,14 +32,16 @@
           <a-select
             v-model:value="form.scope"
             v-focus="true"
+            @change="val => changeScope(val)"
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="apiParams.scope.description" >
-            <a-select-option :value="'cluster'"> {{ $t('label.clusterid') }} </a-select-option>
-            <a-select-option :value="'zone'"> {{ $t('label.zoneid') }} </a-select-option>
+            <a-select-option :value="'cluster'" :label="$t('label.clusterid')"> {{ $t('label.clusterid') }} </a-select-option>
+            <a-select-option :value="'zone'" :label="$t('label.zoneid')"> {{ $t('label.zoneid') }} </a-select-option>
+            <a-select-option :value="'host'" :label="$t('label.hostid')"> {{ $t('label.hostid') }} </a-select-option>
           </a-select>
         </a-form-item>
         <div v-if="form.scope === 'zone'">
@@ -50,9 +52,9 @@
             <a-select
               v-model:value="form.hypervisor"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :placeholder="apiParams.hypervisor.description" >
               <a-select-option :value="hypervisor" v-for="(hypervisor, idx) in hypervisors" :key="idx">
@@ -93,10 +95,10 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :placeholder="apiParams.podid.description">
-              <a-select-option :value="pod.id" v-for="(pod) in pods" :key="pod.id">
+              <a-select-option :value="pod.id" v-for="(pod) in pods" :key="pod.id" :label="pod.name">
                 {{ pod.name }}
               </a-select-option>
             </a-select>
@@ -111,10 +113,10 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :placeholder="apiParams.clusterid.description">
-              <a-select-option :value="cluster.id" v-for="cluster in clusters" :key="cluster.id">
+              <a-select-option :value="cluster.id" v-for="cluster in clusters" :key="cluster.id" :label="cluster.name">
                 {{ cluster.name }}
               </a-select-option>
             </a-select>
@@ -127,9 +129,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option :value="host.id" v-for="host in hosts" :key="host.id">
+              <a-select-option :value="host.id" v-for="host in hosts" :key="host.id" :label="host.name">
                 {{ host.name }}
               </a-select-option>
             </a-select>
@@ -148,9 +150,9 @@
           <a-select
             v-model:value="form.protocol"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="apiParams.clusterid.description">
             <a-select-option :value="protocol" v-for="(protocol,idx) in protocols" :key="idx">
@@ -168,7 +170,7 @@
             <a-input v-model:value="form.server" :placeholder="$t('message.server.description')" />
           </a-form-item>
         </div>
-        <div v-if="form.protocol === 'nfs' || form.protocol === 'SMB' || form.protocol === 'ocfs2' || (form.protocol === 'PreSetup' && hypervisorType !== 'VMware') || form.protocol === 'SharedMountPoint'">
+        <div v-if="form.protocol === 'nfs' || form.protocol === 'SMB' || form.protocol === 'ocfs2' || form.protocol === 'Filesystem' || (form.protocol === 'PreSetup' && hypervisorType !== 'VMware') || form.protocol === 'SharedMountPoint'">
           <a-form-item name="path" ref="path">
             <template #label>
               <tooltip-label :title="$t('label.path')" :tooltip="$t('message.path.description')"/>
@@ -218,9 +220,9 @@
               v-model:value="form.provider"
               @change="updateProviderAndProtocol"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :placeholder="apiParams.provider.description">
               <a-select-option :value="provider" v-for="(provider,idx) in providers" :key="idx">
@@ -229,7 +231,7 @@
             </a-select>
           </a-form-item>
         </div>
-        <div v-if="form.provider !== 'DefaultPrimary' && form.provider !== 'PowerFlex' && form.provider !== 'Linstor'">
+        <div v-if="form.provider !== 'DefaultPrimary' && form.provider !== 'PowerFlex' && form.provider !== 'Linstor' && form.protocol !== 'FiberChannel'">
           <a-form-item name="managed" ref="managed">
             <template #label>
               <tooltip-label :title="$t('label.ismanaged')" :tooltip="apiParams.managed.description"/>
@@ -283,6 +285,70 @@
             <a-input v-model:value="form.powerflexStoragePool" :placeholder="$t('label.powerflex.storage.pool')"/>
           </a-form-item>
         </div>
+        <div v-if="form.provider === 'Primera'">
+          <a-form-item name="primeraURL" ref="primeraURL">
+            <template #label>
+              <tooltip-label :title="$t('label.url')" :tooltip="$t('label.primera.url.tooltip')"/>
+            </template>
+            <a-input v-model:value="form.primeraURL" :placeholder="$t('label.primera.url.tooltip')"/>
+          </a-form-item>
+          <a-form-item name="primeraUsername" ref="primeraUsername">
+            <template #label>
+              <tooltip-label :title="$t('label.username')" :tooltip="$t('label.primera.username.tooltip')"/>
+            </template>
+            <a-input v-model:value="form.primeraUsername" :placeholder="$t('label.primera.username.tooltip')"/>
+          </a-form-item>
+          <a-form-item name="primeraPassword" ref="primeraPassword">
+            <template #label>
+              <tooltip-label :title="$t('label.password')" :tooltip="$t('label.primera.password')"/>
+            </template>
+            <a-input-password v-model:value="form.primeraPassword" :placeholder="$t('label.primera.password')"/>
+          </a-form-item>
+          <a-form-item name="capacityBytes" ref="capacityBytes">
+            <template #label>
+              <tooltip-label :title="$t('label.capacitybytes')" :tooltip="apiParams.capacitybytes.description"/>
+            </template>
+            <a-input v-model:value="form.capacityBytes" :placeholder="apiParams.capacitybytes.description" />
+          </a-form-item>
+          <a-form-item name="capacityIops" ref="capacityIops">
+            <template #label>
+              <tooltip-label :title="$t('label.capacityiops')" :tooltip="apiParams.capacityiops.description"/>
+            </template>
+            <a-input v-model:value="form.capacityIops" :placeholder="apiParams.capacityiops.description" />
+          </a-form-item>
+        </div>
+        <div v-if="form.provider === 'Flash Array'">
+          <a-form-item name="flashArrayURL" ref="flashArrayURL">
+            <template #label>
+              <tooltip-label :title="$t('label.url')" :tooltip="$t('label.flashArray.url.tooltip')"/>
+            </template>
+            <a-input v-model:value="form.flashArrayURL" :placeholder="$t('label.flashArray.url.tooltip')"/>
+          </a-form-item>
+          <a-form-item name="flashArrayUsername" ref="flashArrayUsername">
+            <template #label>
+              <tooltip-label :title="$t('label.username')" :tooltip="$t('label.flashArray.username.tooltip')"/>
+            </template>
+            <a-input v-model:value="form.flashArrayUsername" :placeholder="$t('label.flashArray.username.tooltip')"/>
+          </a-form-item>
+          <a-form-item name="flashArrayPassword" ref="flashArrayPassword">
+            <template #label>
+              <tooltip-label :title="$t('label.password')" :tooltip="$t('label.flashArray.password')"/>
+            </template>
+            <a-input-password v-model:value="form.flashArrayPassword" :placeholder="$t('label.flashArray.password')"/>
+          </a-form-item>
+          <a-form-item name="capacityBytes" ref="capacityBytes">
+            <template #label>
+              <tooltip-label :title="$t('label.capacitybytes')" :tooltip="apiParams.capacitybytes.description"/>
+            </template>
+            <a-input v-model:value="form.capacityBytes" :placeholder="apiParams.capacitybytes.description" />
+          </a-form-item>
+          <a-form-item name="capacityIops" ref="capacityIops">
+            <template #label>
+              <tooltip-label :title="$t('label.capacityiops')" :tooltip="apiParams.capacityiops.description"/>
+            </template>
+            <a-input v-model:value="form.capacityIops" :placeholder="apiParams.capacityiops.description" />
+          </a-form-item>
+        </div>
         <div v-if="form.protocol === 'RBD'">
           <a-form-item name="radosmonitor" ref="radosmonitor">
             <template #label>
@@ -421,7 +487,15 @@
         powerflexGateway: [{ required: true, message: this.$t('label.required') }],
         powerflexGatewayUsername: [{ required: true, message: this.$t('label.required') }],
         powerflexGatewayPassword: [{ required: true, message: this.$t('label.required') }],
-        powerflexStoragePool: [{ required: true, message: this.$t('label.required') }]
+        powerflexStoragePool: [{ required: true, message: this.$t('label.required') }],
+        username: [{ required: true, message: this.$t('label.required') }],
+        password: [{ required: true, message: this.$t('label.required') }],
+        primeraURL: [{ required: true, message: this.$t('label.url') }],
+        primeraUsername: [{ required: true, message: this.$t('label.username') }],
+        primeraPassword: [{ required: true, message: this.$t('label.password') }],
+        flashArrayURL: [{ required: true, message: this.$t('label.url') }],
+        flashArrayUsername: [{ required: true, message: this.$t('label.username') }],
+        flashArrayPassword: [{ required: true, message: this.$t('label.password') }]
       })
     },
     fetchData () {
@@ -438,6 +512,15 @@
         this.loading = false
       })
     },
+    changeScope (value) {
+      if (value === 'host') {
+        const cluster = this.clusters.find(cluster => cluster.id === this.form.cluster)
+        this.hypervisorType = cluster.hypervisortype
+        if (this.hypervisorType === 'KVM') {
+          this.protocols.push('Filesystem')
+        }
+      }
+    },
     changeZone (value) {
       this.form.zone = value
       if (this.form.zone === '') {
@@ -503,7 +586,10 @@
       const cluster = this.clusters.find(cluster => cluster.id === this.form.cluster)
       this.hypervisorType = cluster.hypervisortype
       if (this.hypervisorType === 'KVM') {
-        this.protocols = ['nfs', 'SharedMountPoint', 'RBD', 'CLVM', 'Gluster', 'Linstor', 'custom']
+        this.protocols = ['nfs', 'SharedMountPoint', 'RBD', 'CLVM', 'Gluster', 'Linstor', 'custom', 'FiberChannel']
+        if (this.form.scope === 'host') {
+          this.protocols.push('Filesystem')
+        }
       } else if (this.hypervisorType === 'XenServer') {
         this.protocols = ['nfs', 'PreSetup', 'iscsi', 'custom']
       } else if (this.hypervisorType === 'VMware') {
@@ -524,6 +610,20 @@
         this.form.protocol = this.protocols[0]
       }
     },
+    filesystemURL (hostId, path) {
+      var url
+      if (path.substring(0, 1) !== '/') {
+        path = '/' + path
+      }
+      var hostName
+      this.hosts.forEach(host => {
+        if (host.id === hostId) {
+          hostName = host.name
+        }
+      })
+      url = 'file://' + hostName + path
+      return url
+    },
     nfsURL (server, path) {
       var url
       if (path.substring(0, 1) !== '/') {
@@ -644,6 +744,9 @@
       if (value === 'PowerFlex') {
         this.protocols = ['custom']
         this.form.protocol = 'custom'
+      } else if (value === 'Flash Array' || value === 'Primera') {
+        this.protocols = ['FiberChannel']
+        this.form.protocol = 'FiberChannel'
       } else {
         this.fetchHypervisor(value)
       }
@@ -756,6 +859,16 @@
           if (values.capacityIops && values.capacityIops.length > 0) {
             params.capacityIops = values.capacityIops.split(',').join('')
           }
+        } else if (values.protocol === 'Filesystem') {
+          url = this.filesystemURL(values.host, path)
+        } else if (values.provider === 'Primera') {
+          params['details[0].api_username'] = values.primeraUsername
+          params['details[0].api_password'] = values.primeraPassword
+          url = values.primeraURL
+        } else if (values.provider === 'Flash Array') {
+          params['details[0].api_username'] = values.flashArrayUsername
+          params['details[0].api_password'] = values.flashArrayPassword
+          url = values.flashArrayURL
         }
         params.url = url
         if (values.provider !== 'DefaultPrimary' && values.provider !== 'PowerFlex') {
diff --git a/ui/src/views/infra/AddSecondaryStorage.vue b/ui/src/views/infra/AddSecondaryStorage.vue
index 3dbf8cc..4adc4e9 100644
--- a/ui/src/views/infra/AddSecondaryStorage.vue
+++ b/ui/src/views/infra/AddSecondaryStorage.vue
@@ -33,9 +33,9 @@
             v-model:value="form.provider"
             @change="val => { form.provider = val }"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               :value="prov"
diff --git a/ui/src/views/infra/AsyncJobsTab.vue b/ui/src/views/infra/AsyncJobsTab.vue
new file mode 100644
index 0000000..7b514a4
--- /dev/null
+++ b/ui/src/views/infra/AsyncJobsTab.vue
@@ -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.
+
+<template>
+  <a-table
+    class="table"
+    size="small"
+    :columns="columns"
+    :dataSource="jobs"
+    :rowKey="item => item.id"
+    :pagination="false" >
+    <template #cmd="{ text }">
+      {{ text.split('.').pop() }}
+    </template>
+    <template #account="{ text, record }">
+      <router-link :to="{ path: '/account/' + record.accountid }">{{ text }}</router-link>
+    </template>
+    <template #domainpath="{ text, record }">
+      <router-link :to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }">{{ text }}</router-link>
+    </template>
+  </a-table>
+</template>
+
+<script>
+import { api } from '@/api'
+import Status from '@/components/widgets/Status'
+
+export default {
+  name: 'AsyncJobsTab',
+  components: {
+    Status
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      jobs: [],
+      columns: [
+        {
+          title: this.$t('label.command'),
+          dataIndex: 'cmd',
+          slots: { customRender: 'cmd' }
+        },
+        {
+          title: this.$t('label.resourcetype'),
+          dataIndex: 'jobinstancetype'
+        },
+        {
+          title: this.$t('label.account'),
+          dataIndex: 'account',
+          slots: { customRender: 'account' }
+        },
+        {
+          title: this.$t('label.domain'),
+          dataIndex: 'domainpath',
+          slots: { customRender: 'domainpath' }
+        },
+        {
+          title: this.$t('label.created'),
+          dataIndex: 'created'
+        }
+      ]
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  watch: {
+    resource: function (newItem) {
+      this.fetchData()
+    }
+  },
+  methods: {
+    fetchData () {
+      this.jobs = []
+      api('listAsyncJobs', {
+        listall: true,
+        isrecursive: true,
+        managementserverid: this.resource.id
+      }).then(json => {
+        this.jobs = json.listasyncjobsresponse.asyncjobs || []
+      })
+    }
+  }
+}
+</script>
diff --git a/ui/src/views/infra/ClusterAdd.vue b/ui/src/views/infra/ClusterAdd.vue
index 5450db9..862b458 100644
--- a/ui/src/views/infra/ClusterAdd.vue
+++ b/ui/src/views/infra/ClusterAdd.vue
@@ -49,9 +49,9 @@
           v-model:value="hypervisor"
           @change="resetAllFields"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option
             v-for="hv in hypervisorsList"
@@ -69,12 +69,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option
             v-for="pod in podsList"
             :value="pod.id"
-            :key="pod.id">
+            :key="pod.id"
+            :label="pod.name">
             {{ pod.name }}
           </a-select-option>
         </a-select>
diff --git a/ui/src/views/infra/ClusterDRSTab.vue b/ui/src/views/infra/ClusterDRSTab.vue
new file mode 100644
index 0000000..d53199c
--- /dev/null
+++ b/ui/src/views/infra/ClusterDRSTab.vue
@@ -0,0 +1,305 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-row>
+    {{ $t('message.drs.plan.description') }}
+  </a-row>
+  <a-row>
+    <strong>{{ $t('label.algorithm') }}:</strong>&nbsp;{{ algorithm }}
+  </a-row>
+  <br/>
+  <a-row>
+    <a-col>
+      <a-input-number
+          v-model:value="maxMigrations"
+          :addonBefore="$t('label.max.migrations')"
+          :min="1"
+          :step="1"
+        />
+        &nbsp;&nbsp;
+    </a-col>
+    <a-col>
+      <a-button
+        type="primary"
+        @click="generateDrsPlan"
+        :loading="loading"
+        :disabled="!('generateClusterDrsPlan' in $store.getters.apis)">
+        {{ $t('label.drs.generate.plan') }}
+      </a-button>
+    </a-col>
+  </a-row>
+  <br/>
+  <a-table
+    size="small"
+    :columns="drsPlanColumns"
+    :dataSource="drsPlans"
+    :rowKey="item => item.id"
+    :pagination="{hideOnSinglePage: true, showSizeChanger: true}"
+  >
+    <template #expandedRowRender="{ record }">
+      <a-table
+        size="small"
+        :columns="migrationColumns"
+        :dataSource="record.migrations"
+        :rowKey="(record, index) => index"
+        :pagination="{hideOnSinglePage: true, showSizeChanger: true}"
+        @resizeColumn="resizeColumn">
+        <template #bodyCell="{ column, text, record }">
+          <template v-if="column.key === 'vm'">
+            <router-link :to="{ path: '/vm/' + record.virtualmachineid }">
+              <desktop-outlined/> {{ record.virtualmachinename }}
+            </router-link>
+          </template>
+          <template v-else-if="column.key === 'sourcehost'">
+            <router-link :to="{ path: '/host/' + record.sourcehostid }">
+              <cluster-outlined/> {{ record.sourcehostname }}
+            </router-link>
+          </template>
+          <template v-else-if="column.key === 'destinationhost'">
+            <router-link :to="{ path: '/host/' + record.destinationhostid }">
+              <cluster-outlined/> {{ record.destinationhostname }}
+            </router-link>
+          </template>
+          <template v-else>
+            {{ text }}
+          </template>
+        </template>
+      </a-table>
+      <br/>
+    </template>
+    <template #bodyCell="{ column, text }">
+      <template v-if="column.key === 'successfulMigrations'">
+        {{  text.migrations.filter(m => m.jobstatus === 'SUCCEEDED').length }} / {{  text.migrations.length }}
+        <!-- {{  text.migrations }} -->
+      </template>
+      <template v-else-if="column.key === 'created'">
+        {{ $toLocaleDate(text) }}
+      </template>
+      <template v-else-if="column.key === 'eventid'" >
+        <router-link :to="{ path: '/event', query: { startid: text} }" target="_blank">
+          <schedule-outlined /> {{ $t('label.events') }}
+        </router-link>
+      </template>
+      <template v-else>
+        {{ text }}
+      </template>
+    </template>
+  </a-table>
+
+  <a-modal
+    width="50%"
+    :visible="showModal"
+    :title="$t('label.drs.plan')"
+    :maskClosable="false"
+    :closable="true"
+    :okButtonProps="{ style: { display: generatedMigrations.length === 0 ? 'none' : null } }"
+    :okText="$t('label.execute')"
+    :cancelText="$t('label.cancel')"
+    @ok="executeDrsPlan"
+    @cancel="closeModal">
+    <a-table
+      v-if="generatedMigrations.length > 0"
+      size="small"
+      :columns="generatedPlanMigrationColumns"
+      :dataSource="generatedMigrations"
+      :rowKey="(record, index) => index"
+      :pagination="{ showTotal: (total, range) => [range[0], '-', range[1], $t('label.of'), total, $t('label.items')].join(' ') }"
+      @resizeColumn="resizeColumn" >
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'vm'">
+          <router-link :to="{ path: '/vm/' + record.virtualmachineid }">
+            <desktop-outlined/> {{ record.virtualmachinename }}
+          </router-link>
+        </template>
+        <template v-else-if="column.key === 'sourcehost'">
+          <router-link :to="{ path: '/host/' + record.sourcehostid }">
+            <cluster-outlined/> {{ record.sourcehostname }}
+          </router-link>
+        </template>
+        <template v-else-if="column.key === 'destinationhost'">
+          <router-link :to="{ path: '/host/' + record.destinationhostid }">
+            <cluster-outlined/> {{ record.destinationhostname }}
+          </router-link>
+        </template>
+        <template v-else>
+          {{ text }}
+        </template>
+      </template>
+    </a-table>
+    <a-p v-else>
+      {{ $t('label.drs.no.plan.generated') }}
+    </a-p>
+
+  </a-modal>
+
+</template>
+
+<script>
+
+import { reactive } from 'vue'
+import { api } from '@/api'
+
+export default {
+  name: 'ClusterDrsTab',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    const generatedPlanMigrationColumns = [
+      {
+        key: 'vm',
+        title: this.$t('label.vm'),
+        dataIndex: 'vm',
+        ellipsis: true,
+        resizable: true
+      },
+      {
+        key: 'sourcehost',
+        title: this.$t('label.sourcehost'),
+        dataIndex: 'sourcehost',
+        ellipsis: true,
+        resizable: true
+      },
+      {
+        key: 'destinationhost',
+        title: this.$t('label.desthost'),
+        dataIndex: 'created',
+        ellipsis: true,
+        resizable: true
+      }
+    ]
+    return {
+      drsPlanColumns: [
+        {
+          title: this.$t('label.type'),
+          dataIndex: 'type'
+        },
+        {
+          title: this.$t('label.success.migrations'),
+          key: 'successfulMigrations'
+        },
+        {
+          title: this.$t('label.status'),
+          dataIndex: 'status'
+        },
+        {
+          key: 'created',
+          title: this.$t('label.created'),
+          dataIndex: 'created'
+        },
+        {
+          key: 'eventid',
+          title: this.$t('label.events'),
+          dataIndex: 'eventid'
+        }
+      ],
+      generatedPlanMigrationColumns: generatedPlanMigrationColumns,
+      migrationColumns: generatedPlanMigrationColumns.concat([
+        {
+          key: 'jobstatus',
+          title: this.$t('label.status'),
+          dataIndex: 'jobstatus'
+        }
+      ]),
+      loading: false,
+      drsPlans: [],
+      algorithm: '',
+      maxMigrations: 0,
+      generatedMigrations: reactive([]),
+      showModal: false
+    }
+  },
+  watch: {
+    resource: {
+      deep: true,
+      handler (newItem, oldItem) {
+        if (newItem && (!oldItem || (newItem.id !== oldItem.id))) {
+          this.fetchDRSPlans()
+        }
+      }
+    }
+  },
+  created () {
+    this.fetchDRSPlans()
+    this.fetchDrsConfig()
+  },
+  methods: {
+    fetchDRSPlans () {
+      if (!this.resource || !this.resource.id) return
+      api('listClusterDrsPlan', { page: 1, pageSize: 500, clusterid: this.resource.id }).then(json => {
+        this.drsPlans = json.listclusterdrsplanresponse.drsPlan
+      })
+    },
+    executeDrsPlan () {
+      if (this.generatedMigrations.length === 0) return
+
+      var params = { id: this.resource.id }
+
+      for (var i = 0; i < this.generatedMigrations.length; i++) {
+        const mapping = this.generatedMigrations[i]
+        params['migrateto[' + i + '].vm'] = mapping.virtualmachineid
+        params['migrateto[' + i + '].host'] = mapping.destinationhostid
+      }
+
+      api('executeClusterDrsPlan', params).then(json => {
+        this.$message.success(this.$t('message.drs.plan.executed'))
+      }).catch(error => {
+        console.error(error)
+        this.$message.error(this.$t('message.drs.plan.execution.failed'))
+      }).finally(() => {
+        this.fetchDRSPlans()
+        this.closeModal()
+      })
+    },
+    generateDrsPlan () {
+      this.loading = true
+      api('generateClusterDrsPlan', { id: this.resource.id, migrations: this.maxMigrations }).then(json => {
+        this.generatedMigrations = json?.generateclusterdrsplanresponse?.generateclusterdrsplanresponse?.migrations || []
+        this.loading = false
+        this.showModal = true
+      })
+    },
+    fetchDrsConfig () {
+      this.loading = true
+      api('listConfigurations', { clusterid: this.resource.id, name: 'drs.algorithm' }).then(json => {
+        this.algorithm = json.listconfigurationsresponse.configuration[0].value
+        api('listConfigurations', { clusterid: this.resource.id, name: 'drs.max.migrations' }).then(json => {
+          this.maxMigrations = json.listconfigurationsresponse.configuration[0].value
+          this.loading = false
+        }).catch((err) => {
+          console.error(err)
+          this.loading = false
+        })
+      }).catch((err) => {
+        console.error(err)
+        this.loading = false
+      })
+    },
+    closeModal () {
+      this.showModal = false
+      this.generatedMigrations = reactive([])
+    },
+    resizeColumn (w, col) {
+      col.width = w
+    }
+  }
+}
+</script>
diff --git a/ui/src/views/infra/Confirmation.vue b/ui/src/views/infra/Confirmation.vue
new file mode 100644
index 0000000..96b7f6f
--- /dev/null
+++ b/ui/src/views/infra/Confirmation.vue
@@ -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.
+
+<template>
+  <a-spin :spinning="loading">
+    <div class="form-layout" v-ctrl-enter="handleSubmit">
+      <a-form
+        :ref="formRef"
+        :model="form"
+        :rules="rules"
+        @finish="handleSubmit"
+      >
+        <a-alert type="error">
+          <template #message>
+            <span v-html="$t(action.currentAction.message)" />
+          </template>
+        </a-alert>
+        <a-alert type="warning" style="margin-top: 10px">
+          <template #message>
+            <span>{{ $t('message.confirm.type') }} "{{ action.currentAction.confirmationText }}"</span>
+          </template>
+        </a-alert>
+        <a-form-item ref="confirmation" name="confirmation" style="margin-top: 10px">
+            <a-input v-model:value="form.confirmation" />
+        </a-form-item>
+      </a-form>
+
+      <div :span="24" class="action-button">
+        <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
+        <a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+      </div>
+
+    </div>
+  </a-spin>
+</template>
+
+<script>
+
+import { api } from '@/api'
+import { ref, reactive } from 'vue'
+
+export default {
+  name: 'Confirmation',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    action: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  inject: ['parentFetchData'],
+  data () {
+    return {
+      loading: false
+    }
+  },
+  created () {
+    this.initForm()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({
+        confirmation: [{
+          validator: this.checkConfirmation,
+          message: this.$t('message.error.confirm.text')
+        }]
+      })
+    },
+    async checkConfirmation (rule, value) {
+      if (value && value === 'SHUTDOWN') {
+        return Promise.resolve()
+      }
+      return Promise.reject(rule.message)
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      if (this.loading) return
+      this.formRef.value.validate().then(() => {
+        this.loading = true
+        const params = { managementserverid: this.resource.id }
+        api(this.action.currentAction.api, params).then(() => {
+          this.$message.success(this.$t(this.action.currentAction.label) + ' : ' + this.resource.name)
+          this.closeAction()
+          this.parentFetchData()
+        }).catch(error => {
+          this.$notifyError(error)
+        }).finally(() => {
+          this.loading = false
+        })
+      })
+    },
+    closeAction () {
+      this.$emit('close-action')
+    }
+  }
+}
+
+</script>
+<style lang="scss" scoped>
+.form-layout {
+  width: 80vw;
+  @media (min-width: 700px) {
+    width: 600px;
+  }
+}
+
+.form {
+  margin: 10px 0;
+}
+</style>
diff --git a/ui/src/views/infra/CpuSockets.vue b/ui/src/views/infra/CpuSockets.vue
index 7b6798e..97b2722 100644
--- a/ui/src/views/infra/CpuSockets.vue
+++ b/ui/src/views/infra/CpuSockets.vue
@@ -135,18 +135,18 @@
       this.columns.push({
         dataIndex: 'name',
         title: this.$t('label.hypervisor'),
-        sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+        sorter: (a, b) => genericCompare(a?.name || '', b?.name || '')
       })
 
       this.columns.push({
         dataIndex: 'hosts',
         title: this.$t('label.hosts'),
-        sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+        sorter: (a, b) => genericCompare(a?.hosts || '', b?.hosts || '')
       })
       this.columns.push({
         dataIndex: 'cpusockets',
         title: this.$t('label.cpu.sockets'),
-        sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+        sorter: (a, b) => genericCompare(a?.cpusockets || '', b?.cpusockets || '')
       })
 
       this.items = []
diff --git a/ui/src/views/infra/HostAdd.vue b/ui/src/views/infra/HostAdd.vue
index 063e8dd..def050a 100644
--- a/ui/src/views/infra/HostAdd.vue
+++ b/ui/src/views/infra/HostAdd.vue
@@ -62,13 +62,14 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               @change="fetchClusters">
               <a-select-option
                 v-for="pod in podsList"
                 :value="pod.id"
-                :key="pod.id">
+                :key="pod.id"
+                :label="pod.name">
                 {{ pod.name }}
               </a-select-option>
             </a-select>
@@ -83,13 +84,14 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               @change="handleChangeCluster">
               <a-select-option
                 v-for="cluster in clustersList"
                 :value="cluster.id"
-                :key="cluster.id">
+                :key="cluster.id"
+                :label="cluster.name">
                 {{ cluster.name }}
               </a-select-option>
             </a-select>
@@ -206,9 +208,9 @@
             <a-select
               mode="tags"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               v-model:value="form.hosttags"
               :placeholder="placeholder.hosttags">
diff --git a/ui/src/views/infra/HostEnableDisable.vue b/ui/src/views/infra/HostEnableDisable.vue
new file mode 100644
index 0000000..bc71aa2
--- /dev/null
+++ b/ui/src/views/infra/HostEnableDisable.vue
@@ -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.
+
+<template>
+  <div class="form-layout">
+    <a-form
+      :ref="formRef"
+      :model="form"
+      :rules="rules"
+      @finish="handleSubmit"
+      v-ctrl-enter="handleSubmit"
+      class="form"
+      layout="vertical"
+    >
+      <a-alert type="warning">
+        <template #message>
+          <span v-html="$t('message.confirm.enable.host')" />
+        </template>
+      </a-alert>
+      <div v-show="enableKVMAutoEnableDisableSetting" class="reason">
+        <a-form-item
+          class="form__item"
+          name="reason"
+          ref="reason"
+          :label="'The setting \'enable.kvm.host.auto.enable.disable\' is enabled, ' +
+            ' can specify a reason for ' + (resourcestate === 'Enabled' ? 'disabling' : 'enabling') + ' this host'">
+          <a-textarea
+            v-model:value="form.reason"
+            :placeholder="'(Optional) Reason to ' + (resourcestate === 'Enabled' ? 'disable' : 'enable') + ' this host'"
+            rows="3"
+          />
+        </a-form-item>
+      </div>
+      <div :span="24" class="action-button">
+        <a-button @click="$emit('close-action')">{{ $t('label.cancel') }}</a-button>
+        <a-button type="primary" @click="handleSubmit" ref="submit">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-form>
+  </div>
+</template>
+
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+
+export default {
+  name: 'HostEnableDisable',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      resourcestate: '',
+      allocationstate: '',
+      enableKVMAutoEnableDisableSetting: false
+    }
+  },
+  created () {
+    this.initForm()
+    this.fetchAutoEnableDisableKVMSetting()
+    this.resourcestate = this.resource.resourcestate
+    this.allocationstate = this.resourcestate === 'Enabled' ? 'Disable' : 'Enable'
+  },
+  beforeCreate () {
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({})
+    },
+    fetchAutoEnableDisableKVMSetting () {
+      if (this.resource.hypervisor !== 'KVM') {
+        return
+      }
+      api('listConfigurations', { name: 'enable.kvm.host.auto.enable.disable', clusterid: this.resource.clusterid }).then(json => {
+        if (json.listconfigurationsresponse.configuration[0]) {
+          this.enableKVMAutoEnableDisableSetting = json.listconfigurationsresponse.configuration[0].value
+        }
+      })
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      this.formRef.value.validate().then(() => {
+        const values = toRaw(this.form)
+
+        var data = {
+          allocationstate: this.allocationstate,
+          id: this.resource.id
+        }
+        if (values.reason) {
+          data.annotation = values.reason
+        }
+        api('updateHost', data).then(_ => {
+          this.$emit('close-action')
+        })
+      })
+    }
+  }
+}
+
+</script>
+
+<style scoped>
+.reason {
+  padding-top: 20px
+}
+
+.form-layout {
+    width: 30vw;
+
+    @media (min-width: 500px) {
+      width: 450px;
+    }
+  }
+</style>
diff --git a/ui/src/views/infra/InfraSummary.vue b/ui/src/views/infra/InfraSummary.vue
index 941d15b..a35db46 100644
--- a/ui/src/views/infra/InfraSummary.vue
+++ b/ui/src/views/infra/InfraSummary.vue
@@ -149,8 +149,10 @@
     </a-col>
     <template v-for="(section, index) in sections" :key="index">
       <a-col
+        :xs="12"
+        :sm="8"
         :md="6"
-        style="margin-bottom: 12px"
+        :style="{ marginBottom: '12px' }"
         v-if="routes[section]">
         <chart-card :loading="loading">
           <div class="chart-card-inner">
@@ -185,7 +187,7 @@
     return {
       loading: true,
       routes: {},
-      sections: ['zones', 'pods', 'clusters', 'hosts', 'storagepools', 'imagestores', 'systemvms', 'routers', 'cpusockets', 'managementservers', 'alerts', 'ilbvms', 'metrics'],
+      sections: ['zones', 'pods', 'clusters', 'hosts', 'storagepools', 'imagestores', 'objectstores', 'systemvms', 'routers', 'cpusockets', 'managementservers', 'alerts', 'ilbvms', 'metrics'],
       sslFormVisible: false,
       stats: {},
       intermediateCertificates: [],
diff --git a/ui/src/views/infra/Metrics.vue b/ui/src/views/infra/Metrics.vue
index 9f222c6..010059b 100644
--- a/ui/src/views/infra/Metrics.vue
+++ b/ui/src/views/infra/Metrics.vue
@@ -183,13 +183,13 @@
       this.columns.push({
         dataIndex: 'name',
         title: this.$t('label.name'),
-        sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+        sorter: (a, b) => genericCompare(a?.name || '', b?.name || '')
       })
 
       this.columns.push({
         dataIndex: 'value',
         title: this.$t('label.value'),
-        sorter: function (a, b) { return genericCompare(a[this.dataIndex] || '', b[this.dataIndex] || '') }
+        sorter: (a, b) => genericCompare(a?.value || '', b?.value || '')
       })
     },
     getRowClassName (record, index) {
diff --git a/ui/src/views/infra/MigrateData.vue b/ui/src/views/infra/MigrateData.vue
index fa42579..f6c9f1f 100644
--- a/ui/src/views/infra/MigrateData.vue
+++ b/ui/src/views/infra/MigrateData.vue
@@ -37,11 +37,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="store in imageStores"
-              :key="store.id"> {{ store.name || opt.url }}
+              :key="store.id"
+              :label="store.name || opt.url"> {{ store.name || opt.url }}
             </a-select-option>
           </a-select>
         </a-form-item>
@@ -56,11 +57,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="store in destStores"
-              :key="store.id"> {{ store.name || opt.url }}
+              :key="store.id"
+              :label="store.name || opt.url"> {{ store.name || opt.url }}
             </a-select-option>
           </a-select>
         </a-form-item>
@@ -71,10 +73,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="Complete">{{ $t('label.complete') }}</a-select-option>
-            <a-select-option value="Balance">{{ $t('label.balance') }}</a-select-option>
+            <a-select-option value="Complete" :label="$t('label.complete')">{{ $t('label.complete') }}</a-select-option>
+            <a-select-option value="Balance" :label="$t('label.balance')">{{ $t('label.balance') }}</a-select-option>
           </a-select>
         </a-form-item>
         <div :span="24" class="action-button">
diff --git a/ui/src/views/infra/StorageBrowser.vue b/ui/src/views/infra/StorageBrowser.vue
new file mode 100644
index 0000000..ce3464b
--- /dev/null
+++ b/ui/src/views/infra/StorageBrowser.vue
@@ -0,0 +1,359 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div>
+    <a-card class="breadcrumb-card">
+      <a-row>
+        <a-col :span="24" style="padding-left: 12px">
+          <a-breadcrumb :routes="getRoutes()">
+            <template #itemRender="{ route }">
+              <span v-if="['/', ''].includes(route.path) && route.breadcrumbName === 'root'">
+                <a @click="openDir('')">
+                  <home-outlined/>
+                </a>
+              </span>
+              <span v-else>
+                <a @click="openDir(route.path)">
+                {{ route.breadcrumbName }}
+                </a>
+              </span>
+            </template>
+          </a-breadcrumb>
+          </a-col>
+          <a-col>
+          <a-tooltip placement="bottom">
+            <template #title>{{ $t('label.refresh') }}</template>
+            <a-button
+              style="margin-top: 4px"
+              :loading="loading"
+              shape="round"
+              size="small"
+              @click="fetchData()"
+            >
+            <template #icon><ReloadOutlined /></template>
+            {{ $t('label.refresh') }}
+          </a-button>
+          </a-tooltip>
+        </a-col>
+      </a-row>
+    </a-card>
+
+    <a-modal
+      :title="$t('message.data.migration')"
+      :visible="showMigrateModal"
+      :maskClosable="true"
+      :confirmLoading="migrateModalLoading"
+      @cancel="showMigrateModal = false"
+      :footer="null"
+      width="50%"
+      :okText="$t('label.ok')"
+      :cancelText="$t('label.cancel')">
+      <div>
+        <migrate-image-store-resource
+          :sourceImageStore="resource"
+          :templateIdsToMigrate="templateIdsToMigrate"
+          :snapshotIdsToMigrate="snapshotIdsToMigrate"
+          @close-action="showMigrateModal = false"
+        />
+      </div>
+    </a-modal>
+
+    <div>
+      <a-table
+        :columns="columns"
+        :row-key="record => record.name"
+        :data-source="dataSource"
+        :pagination="{ current: page, pageSize: pageSize, total: total }"
+        @change="handleTableChange">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key == 'name'">
+            <template v-if="record.isdirectory">
+              <a @click="openDir(`${this.browserPath}${record.name}/`)">
+                <folder-outlined /> {{ record.name }}
+              </a>
+            </template>
+            <template v-else-if="resourceType === 'ImageStore'">
+              <a @click="downloadFile(record)">
+                <template v-if="record.snapshotid">
+                  <build-outlined/>
+                </template>
+                <template v-else-if="record.volumeid">
+                    <hdd-outlined/>
+                </template>
+                <template v-else-if="record.templateid">
+                    <usb-outlined v-if="record.format === 'ISO'"/>
+                    <save-outlined v-else />
+                </template>
+                {{ record.name }}
+              </a>
+            </template>
+            <template v-else>
+              <template v-if="record.snapshotid">
+                  <build-outlined/>
+                </template>
+                <template v-else-if="record.volumeid">
+                    <hdd-outlined/>
+                </template>
+                <template v-else-if="record.templateid">
+                    <usb-outlined v-if="record.format === 'ISO'"/>
+                    <save-outlined v-else />
+                </template>
+                {{ record.name }}
+            </template>
+          </template>
+          <template v-if="column.key == 'size'">
+            <template v-if="!record.isdirectory">
+            {{ convertBytes(record.size) }}
+            </template>
+          </template>
+          <template v-if="column.key == 'lastupdated'">
+            {{ $toLocaleDate(record.lastupdated) }}
+          </template>
+          <template v-if="column.key == 'associatedResource'">
+            <template v-if="record.snapshotid">
+              <router-link :to="{ path: '/snapshot/' + record.snapshotid }" target='_blank' >
+                {{ $t('label.snapshot') }}
+              </router-link>
+            </template>
+            <template v-else-if="record.volumeid">
+              <router-link :to="{ path: '/volume/' + record.volumeid }" target='_blank' >
+                {{ $t('label.volume') }}
+              </router-link>
+            </template>
+            <template v-else-if="record.templateid">
+              <router-link v-if="record.format === 'ISO'" :to="{ path: '/iso/' + record.templateid }" target='_blank' >
+                {{ $t('label.iso') }}
+              </router-link>
+              <router-link v-else :to="{ path: '/template/' + record.templateid }" target='_blank'>
+                {{ $t('label.templatename') }}
+              </router-link>
+            </template>
+            <template v-else>
+              {{ $t('label.unknown') }}
+            </template>
+          </template>
+          <template v-else-if="column.key === 'actions' && (record.templateid || record.snapshotid)">
+              <tooltip-button
+              tooltipPlacement="top"
+              :tooltip="$t('label.migrate.data.from.image.store')"
+              icon="arrows-alt-outlined"
+              :copyResource="String(resource.id)"
+              @onClick="openMigrationModal(record)" />
+            </template>
+        </template>
+      </a-table>
+    </div>
+  </div>
+
+</template>
+
+<script>
+import { api } from '@/api'
+import InfoCard from '@/components/view/InfoCard'
+import TooltipButton from '@/components/widgets/TooltipButton'
+import MigrateImageStoreResource from '@/views/storage/MigrateImageStoreResource'
+
+export default {
+  name: 'StorageBrowser',
+  components: {
+    InfoCard,
+    MigrateImageStoreResource,
+    TooltipButton
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    resourceType: {
+      type: String,
+      required: true
+    }
+  },
+  data () {
+    var columns = [
+      {
+        key: 'name',
+        title: this.$t('label.name')
+      },
+      {
+        key: 'size',
+        title: this.$t('label.size')
+      },
+      {
+        key: 'lastupdated',
+        title: this.$t('label.last.updated')
+      },
+      {
+        key: 'associatedResource',
+        title: this.$t('label.associated.resource')
+      }
+    ]
+    if (this.resourceType === 'ImageStore') {
+      columns.push({
+        key: 'actions',
+        title: this.$t('label.actions')
+      })
+    }
+    return {
+      loading: false,
+      dataSource: [],
+      browserPath: this.$route.query.browserPath || '',
+      page: parseInt(this.$route.query.browserPage) || 1,
+      pageSize: parseInt(this.$route.query.browserPageSize) || 10,
+      total: 0,
+      columns: columns,
+      migrateModalLoading: false,
+      showMigrateModal: false,
+      templateIdsToMigrate: [],
+      snapshotIdsToMigrate: []
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    openMigrationModal (record) {
+      if (record.snapshotid) {
+        this.snapshotIdsToMigrate.push(record.snapshotid)
+      } else if (record.templateid) {
+        this.templateIdsToMigrate.push(record.templateid)
+      }
+      this.showMigrateModal = true
+    },
+    handleTableChange (pagination, filters, sorter) {
+      this.page = pagination.current
+      this.pageSize = pagination.pageSize
+      this.fetchData()
+    },
+    fetchImageStoreObjects () {
+      this.loading = true
+      api('listImageStoreObjects', {
+        path: this.browserPath,
+        id: this.resource.id,
+        page: this.page,
+        pagesize: this.pageSize
+      }).then(json => {
+        this.dataSource = json.listimagestoreobjectsresponse.datastoreobject
+        this.total = json.listimagestoreobjectsresponse.count
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    fetchPrimaryStoreObjects () {
+      this.loading = true
+      api('listStoragePoolObjects', {
+        path: this.browserPath,
+        id: this.resource.id,
+        page: this.page,
+        pagesize: this.pageSize
+      }).then(json => {
+        this.dataSource = json.liststoragepoolobjectsresponse.datastoreobject
+        this.total = json.liststoragepoolobjectsresponse.count
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    fetchData () {
+      this.dataSource = []
+      this.$router.replace(
+        {
+          path: this.$route.path,
+          query: {
+            ...this.$route.query,
+            browserPath: this.browserPath,
+            browserPage: this.page,
+            browserPageSize: this.browserPageSize
+          }
+        }
+      )
+      if (this.resourceType === 'ImageStore') {
+        this.fetchImageStoreObjects()
+      } else if (this.resourceType === 'PrimaryStorage') {
+        this.fetchPrimaryStoreObjects()
+      }
+    },
+    getRoutes () {
+      let path = ''
+      const routeList = [{
+        path: path,
+        breadcrumbName: 'root'
+      }]
+      for (const route of this.browserPath.split('/')) {
+        if (route) {
+          path = `${path}${route}/`
+          routeList.push({
+            path: path,
+            breadcrumbName: route
+          })
+        }
+      }
+      return routeList
+    },
+    convertBytes (val) {
+      if (val < 1024 * 1024) return `${(val / 1024).toFixed(2)} KB`
+      if (val < 1024 * 1024 * 1024) return `${(val / 1024 / 1024).toFixed(2)} MB`
+      if (val < 1024 * 1024 * 1024 * 1024) return `${(val / 1024 / 1024 / 1024).toFixed(2)} GB`
+      if (val < 1024 * 1024 * 1024 * 1024 * 1024) return `${(val / 1024 / 1024 / 1024 / 1024).toFixed(2)} TB`
+      return val
+    },
+    openDir (name) {
+      this.browserPath = name
+      this.page = 1
+      this.pageSize = 10
+      this.fetchData()
+    },
+    downloadFile (record) {
+      this.loading = true
+      const params = {
+        id: this.resource.id,
+        path: `${this.browserPath}${record.name}`
+      }
+      api('downloadImageStoreObject', params).then(response => {
+        const jobId = response.downloadimagestoreobjectresponse.jobid
+        this.$pollJob({
+          jobId: jobId,
+          successMethod: (result) => {
+            const url = result.jobresult.downloadimagestoreobjectresponse.url
+            const name = result.jobresult.downloadimagestoreobjectresponse.name
+            var elem = window.document.createElement('a')
+            elem.setAttribute('href', new URL(url))
+            elem.setAttribute('download', name)
+            elem.setAttribute('target', '_blank')
+            document.body.appendChild(elem)
+            elem.click()
+            document.body.removeChild(elem)
+            this.loading = false
+          },
+          errorMethod: () => {
+            this.loading = false
+          },
+          catchMessage: this.$t('error.fetching.async.job.result'),
+          catchMethod: () => {
+            this.loading = false
+          }
+        })
+      }).catch(error => {
+        console.error(error)
+        this.$message.error(error)
+        this.loading = false
+      })
+    }
+  }
+}
+</script>
diff --git a/ui/src/views/infra/network/DedicatedVLANTab.vue b/ui/src/views/infra/network/DedicatedVLANTab.vue
index 7e59c55..7343b30 100644
--- a/ui/src/views/infra/network/DedicatedVLANTab.vue
+++ b/ui/src/views/infra/network/DedicatedVLANTab.vue
@@ -33,21 +33,23 @@
       :dataSource="items"
       :pagination="false"
       :rowKey="record => record.id">
-      <template #actions="{record}">
-        <a-popconfirm
-          :title="`${$t('label.delete')}?`"
-          @confirm="handleDelete(record)"
-          :okText="$t('label.yes')"
-          :cancelText="$t('label.no')"
-          placement="top"
-        >
-          <tooltip-button
-            :tooltip="$t('label.delete')"
-            :disabled="!('releaseDedicatedGuestVlanRange' in $store.getters.apis)"
-            icon="delete-outlined"
-            type="primary"
-            :danger="true" />
-        </a-popconfirm>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'actions'">
+          <a-popconfirm
+            :title="`${$t('label.delete')}?`"
+            @confirm="handleDelete(record)"
+            :okText="$t('label.yes')"
+            :cancelText="$t('label.no')"
+            placement="top"
+          >
+            <tooltip-button
+              :tooltip="$t('label.delete')"
+              :disabled="!('releaseDedicatedGuestVlanRange' in $store.getters.apis)"
+              icon="delete-outlined"
+              type="primary"
+              :danger="true" />
+          </a-popconfirm>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -91,10 +93,10 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option value="account">{{ $t('label.account') }}</a-select-option>
-              <a-select-option value="project">{{ $t('label.project') }}</a-select-option>
+              <a-select-option value="account" :label="$t('label.account')">{{ $t('label.account') }}</a-select-option>
+              <a-select-option value="project" :label="$t('label.project')">{{ $t('label.project') }}</a-select-option>
             </a-select>
           </a-form-item>
 
@@ -121,7 +123,7 @@
             <a-select
               v-model:value="form.account"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
                 return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
@@ -219,8 +221,8 @@
           dataIndex: 'account'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ]
     }
diff --git a/ui/src/views/infra/network/EditTrafficLabel.vue b/ui/src/views/infra/network/EditTrafficLabel.vue
index f147bab..f8f44d9 100644
--- a/ui/src/views/infra/network/EditTrafficLabel.vue
+++ b/ui/src/views/infra/network/EditTrafficLabel.vue
@@ -39,9 +39,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="type in trafficTypes" :key="type.id">
+            <a-select-option v-for="type in trafficTypes" :key="type.id" :label="type.traffictype">
               {{ type.traffictype }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/infra/network/IpRangesTabGuest.vue b/ui/src/views/infra/network/IpRangesTabGuest.vue
index 4de5fd2..2c575d7 100644
--- a/ui/src/views/infra/network/IpRangesTabGuest.vue
+++ b/ui/src/views/infra/network/IpRangesTabGuest.vue
@@ -33,20 +33,22 @@
       :rowKey="record => record.id + record.prefix"
       :pagination="false"
     >
-      <template #allocated="{ record }">
-        {{ record.usedsubnets + '/' + record.totalsubnets }}
-      </template>
-      <template #actions="{ record }">
-        <div class="actions">
-          <tooltip-button
-            tooltipPlacement="bottom"
-            :tooltip="$t('label.delete.ip.v6.prefix')"
-            type="primary"
-            icon="delete-outlined"
-            :danger="true"
-            @click="handleDeleteIpv6Prefix(record)"
-            :disabled="!('deleteGuestNetworkIpv6Prefix' in $store.getters.apis)" />
-        </div>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'allocated'">
+          {{ record.usedsubnets + '/' + record.totalsubnets }}
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="actions">
+            <tooltip-button
+              tooltipPlacement="bottom"
+              :tooltip="$t('label.delete.ip.v6.prefix')"
+              type="primary"
+              icon="delete-outlined"
+              :danger="true"
+              @click="handleDeleteIpv6Prefix(record)"
+              :disabled="!('deleteGuestNetworkIpv6Prefix' in $store.getters.apis)" />
+          </div>
+        </template>
       </template>
     </a-table>
     <br>
@@ -69,12 +71,14 @@
       :rowKey="record => record.id"
       :pagination="false"
     >
-      <template #name="{ text, record }">
-        <resource-icon v-if="record.icon" :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
-        <apartment-outlined v-else style="margin-right: 5px"/>
-        <router-link :to="{ path: '/guestnetwork/' + record.id }">
-          {{ text }}
-        </router-link>
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'name'">
+          <resource-icon v-if="record.icon" :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
+          <apartment-outlined v-else style="margin-right: 5px"/>
+          <router-link :to="{ path: '/guestnetwork/' + record.id }">
+            {{ text }}
+          </router-link>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -170,9 +174,9 @@
       pageSize: 10,
       columns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
           title: this.$t('label.type'),
@@ -202,12 +206,12 @@
           dataIndex: 'prefix'
         },
         {
-          title: this.$t('label.allocated'),
-          slots: { customRender: 'allocated' }
+          key: 'allocated',
+          title: this.$t('label.allocated')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ],
       addIpv6PrefixModal: false
diff --git a/ui/src/views/infra/network/IpRangesTabManagement.vue b/ui/src/views/infra/network/IpRangesTabManagement.vue
index e7abfa8..f6d0029 100644
--- a/ui/src/views/infra/network/IpRangesTabManagement.vue
+++ b/ui/src/views/infra/network/IpRangesTabManagement.vue
@@ -34,20 +34,22 @@
       :rowKey="record => record.id + record.startip"
       :pagination="false"
     >
-      <template #forsystemvms="{ record }">
-        <a-checkbox v-model:checked="record.forsystemvms" />
-      </template>
-      <template #actions="{ record }">
-        <div class="actions">
-          <tooltip-button
-            tooltipPlacement="bottom"
-            :tooltip="$t('label.remove.ip.range')"
-            :disabled="!('deleteManagementNetworkIpRange' in $store.getters.apis)"
-            icon="delete-outlined"
-            type="primary"
-            :danger="true"
-            @onClick="handleDeleteIpRange(record)" />
-        </div>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'forsystemvms'">
+          <a-checkbox v-model:checked="record.forsystemvms" />
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="actions">
+            <tooltip-button
+              tooltipPlacement="bottom"
+              :tooltip="$t('label.remove.ip.range')"
+              :disabled="!('deleteManagementNetworkIpRange' in $store.getters.apis)"
+              icon="delete-outlined"
+              type="primary"
+              :danger="true"
+              @onClick="handleDeleteIpRange(record)" />
+          </div>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -90,9 +92,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="item in pods" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
+            <a-select-option v-for="item in pods" :key="item.id" :value="item.id" :label="item.name">{{ item.name }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item name="gateway" ref="gateway" :label="$t('label.gateway')" class="form__item">
@@ -170,28 +172,28 @@
           dataIndex: 'netmask'
         },
         {
+          key: 'vlan',
           title: this.$t('label.vlan'),
-          dataIndex: 'vlanid',
-          slots: { customRender: 'vlan' }
+          dataIndex: 'vlanid'
         },
         {
+          key: 'startip',
           title: this.$t('label.startip'),
-          dataIndex: 'startip',
-          slots: { customRender: 'startip' }
+          dataIndex: 'startip'
         },
         {
+          key: 'endip',
           title: this.$t('label.endip'),
-          dataIndex: 'endip',
-          slots: { customRender: 'endip' }
+          dataIndex: 'endip'
         },
         {
+          key: 'forsystemvms',
           title: this.$t('label.system.vms'),
-          dataIndex: 'forsystemvms',
-          slots: { customRender: 'forsystemvms' }
+          dataIndex: 'forsystemvms'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ]
     }
diff --git a/ui/src/views/infra/network/IpRangesTabPublic.vue b/ui/src/views/infra/network/IpRangesTabPublic.vue
index dc55897..40a3140 100644
--- a/ui/src/views/infra/network/IpRangesTabPublic.vue
+++ b/ui/src/views/infra/network/IpRangesTabPublic.vue
@@ -34,64 +34,66 @@
       :rowKey="record => record.id"
       :pagination="false"
     >
-      <template #gateway="{record}">
-        {{ record.gateway || record.ip6gateway }}
-      </template>
-      <template #cidr="{record}">
-        {{ record.cidr || record.ip6cidr }}
-      </template>
-      <template #startip="{record}">
-        {{ record.startip || record.startipv6 }}
-      </template>
-      <template #endip="{record}">
-        {{ record.endip || record.endipv6 }}
-      </template>
-      <template #account="{record}" v-if="!basicGuestNetwork">
-        <a-button @click="() => handleOpenAccountModal(record)">{{ record.domain === undefined ? `${$t('label.system.ip.pool')}` : `[ ${record.domain}] ${record.account === undefined ? '' : record.account}` }}</a-button>
-      </template>
-      <template #actions="{record}">
-        <div
-          class="actions"
-          style="text-align: right" >
-          <router-link :to="{ name: 'publicip', query: { vlanid: record.id }}" target="_blank">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'gateway'">
+          {{ record.gateway || record.ip6gateway }}
+        </template>
+        <template v-if="column.key === 'cidr'">
+          {{ record.cidr || record.ip6cidr }}
+        </template>
+        <template v-if="column.key === 'startip'">
+          {{ record.startip || record.startipv6 }}
+        </template>
+        <template v-if="column.key === 'endip'">
+          {{ record.endip || record.endipv6 }}
+        </template>
+        <template v-if="column.key === 'account' && !basicGuestNetwork">
+          <a-button @click="() => handleOpenAccountModal(record)">{{ record.domain === undefined ? `${$t('label.system.ip.pool')}` : `[ ${record.domain}] ${record.account === undefined ? '' : record.account}` }}</a-button>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div
+            class="actions"
+            style="text-align: right" >
+            <router-link :to="{ name: 'publicip', query: { vlanid: record.id }}" target="_blank">
+              <tooltip-button
+                tooltipPlacement="bottom"
+                :tooltip="$t('label.view') + ' ' + $t('label.public.ip.addresses')"
+                icon="environment-outlined"/>
+            </router-link>
+            <tooltip-button
+              v-if="!record.domain && !basicGuestNetwork && record.gateway && !record.ip6gateway"
+              tooltipPlacement="bottom"
+              :tooltip="$t('label.add.account')"
+              icon="user-add-outlined"
+              @onClick="() => handleOpenAddAccountModal(record)"
+              :disabled="!('dedicatePublicIpRange' in $store.getters.apis)" />
+            <tooltip-button
+              v-if="record.domain && !basicGuestNetwork"
+              tooltipPlacement="bottom"
+              :tooltip="$t('label.release.account')"
+              icon="user-delete-outlined"
+              type="primary"
+              :danger="true"
+              @onClick="() => handleRemoveAccount(record.id)"
+              :disabled="!('releasePublicIpRange' in $store.getters.apis)" />
             <tooltip-button
               tooltipPlacement="bottom"
-              :tooltip="$t('label.view') + ' ' + $t('label.public.ip.addresses')"
-              icon="environment-outlined"/>
-          </router-link>
-          <tooltip-button
-            v-if="!record.domain && !basicGuestNetwork && record.gateway && !record.ip6gateway"
-            tooltipPlacement="bottom"
-            :tooltip="$t('label.add.account')"
-            icon="user-add-outlined"
-            @onClick="() => handleOpenAddAccountModal(record)"
-            :disabled="!('dedicatePublicIpRange' in $store.getters.apis)" />
-          <tooltip-button
-            v-if="record.domain && !basicGuestNetwork"
-            tooltipPlacement="bottom"
-            :tooltip="$t('label.release.account')"
-            icon="user-delete-outlined"
-            type="primary"
-            :danger="true"
-            @onClick="() => handleRemoveAccount(record.id)"
-            :disabled="!('releasePublicIpRange' in $store.getters.apis)" />
-          <tooltip-button
-            tooltipPlacement="bottom"
-            :tooltip="$t('label.update.ip.range')"
-            icon="edit-outlined"
-            type="primary"
-            :danger="true"
-            @onClick="() => handleUpdateIpRangeModal(record)"
-            :disabled="!('updateVlanIpRange' in $store.getters.apis)" />
-          <tooltip-button
-            tooltipPlacement="bottom"
-            :tooltip="$t('label.remove.ip.range')"
-            icon="delete-outlined"
-            type="primary"
-            :danger="true"
-            @onClick="handleDeleteIpRange(record.id)"
-            :disabled="!('deleteVlanIpRange' in $store.getters.apis)" />
-        </div>
+              :tooltip="$t('label.update.ip.range')"
+              icon="edit-outlined"
+              type="primary"
+              :danger="true"
+              @onClick="() => handleUpdateIpRangeModal(record)"
+              :disabled="!('updateVlanIpRange' in $store.getters.apis)" />
+            <tooltip-button
+              tooltipPlacement="bottom"
+              :tooltip="$t('label.remove.ip.range')"
+              icon="delete-outlined"
+              type="primary"
+              :danger="true"
+              @onClick="handleDeleteIpRange(record.id)"
+              :disabled="!('deleteVlanIpRange' in $store.getters.apis)" />
+          </div>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -158,12 +160,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="domain in domains"
               :key="domain.id"
-              :value="domain.id">{{ domain.path || domain.name || domain.description }}
+              :value="domain.id"
+              :label="domain.path || domain.name || domain.description">{{ domain.path || domain.name || domain.description }}
             </a-select-option>
           </a-select>
         </div>
@@ -211,10 +214,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             v-focus="true">
-            <a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id">{{ pod.name }}</a-select-option>
+            <a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id" :label="pod.name">{{ pod.name }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item name="vlan" ref="vlan" :label="$t('label.vlan')" class="form__item" v-if="!basicGuestNetwork">
@@ -261,12 +264,13 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
                 <a-select-option
                   v-for="domain in domains"
                   :key="domain.id"
-                  :value="domain.id">{{ domain.path || domain.name || domain.description }}
+                  :value="domain.id"
+                  :label="domain.path || domain.name || domain.description">{{ domain.path || domain.name || domain.description }}
                 </a-select-option>
               </a-select>
             </a-form-item>
@@ -383,28 +387,28 @@
       pageSize: 10,
       columns: [
         {
-          title: this.$t('label.gateway'),
-          slots: { customRender: 'gateway' }
+          key: 'gateway',
+          title: this.$t('label.gateway')
         },
         {
-          title: this.$t('label.cidr'),
-          slots: { customRender: 'cidr' }
+          key: 'cidr',
+          title: this.$t('label.cidr')
         },
         {
           title: this.$t('label.vlan'),
           dataIndex: 'vlan'
         },
         {
-          title: this.$t('label.startip'),
-          slots: { customRender: 'startip' }
+          key: 'startip',
+          title: this.$t('label.startip')
         },
         {
-          title: this.$t('label.endip'),
-          slots: { customRender: 'endip' }
+          key: 'endip',
+          title: this.$t('label.endip')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ]
     }
@@ -415,8 +419,8 @@
     if (!this.basicGuestNetwork) {
       this.columns.splice(6, 0,
         {
-          title: this.$t('label.account'),
-          slots: { customRender: 'account' }
+          key: 'account',
+          title: this.$t('label.account')
         }
       )
     } else {
diff --git a/ui/src/views/infra/network/IpRangesTabStorage.vue b/ui/src/views/infra/network/IpRangesTabStorage.vue
index 29e146d..c8665d6 100644
--- a/ui/src/views/infra/network/IpRangesTabStorage.vue
+++ b/ui/src/views/infra/network/IpRangesTabStorage.vue
@@ -34,17 +34,19 @@
       :rowKey="record => record.id"
       :pagination="false"
     >
-      <template #name="{record}">
-        <div>{{ returnPodName(record.podid) }}</div>
-      </template>
-      <template #actions="{record}">
-        <tooltip-button
-          :tooltip="$t('label.remove.ip.range')"
-          :disabled="!('deleteStorageNetworkIpRange' in $store.getters.apis)"
-          icon="delete-outlined"
-          type="primary"
-          :danger="true"
-          @onClick="handleDeleteIpRange(record.id)" />
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          <div>{{ returnPodName(record.podid) }}</div>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            :tooltip="$t('label.remove.ip.range')"
+            :disabled="!('deleteStorageNetworkIpRange' in $store.getters.apis)"
+            icon="delete-outlined"
+            type="primary"
+            :danger="true"
+            @onClick="handleDeleteIpRange(record.id)" />
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -88,9 +90,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id">{{ pod.name }}</a-select-option>
+            <a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id" :label="pod.name">{{ pod.name }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item name="gateway" ref="gateway" :label="$t('label.gateway')" class="form__item">
@@ -151,8 +153,8 @@
       defaultSelectedPod: null,
       columns: [
         {
-          title: this.$t('label.podid'),
-          slots: { customRender: 'name' }
+          key: 'name',
+          title: this.$t('label.podid')
         },
         {
           title: this.$t('label.gateway'),
@@ -175,8 +177,8 @@
           dataIndex: 'endip'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ],
       page: 1,
diff --git a/ui/src/views/infra/network/ServiceProvidersTab.vue b/ui/src/views/infra/network/ServiceProvidersTab.vue
index 184804e..4985389 100644
--- a/ui/src/views/infra/network/ServiceProvidersTab.vue
+++ b/ui/src/views/infra/network/ServiceProvidersTab.vue
@@ -27,8 +27,10 @@
           v-for="item in hardcodedNsps"
           :key="item.title">
           <template #tab>
-            {{ $t(item.title) }}
-            <status :text="item.title in nsps ? nsps[item.title].state : $t('label.disabled')" style="margin-bottom: 6px; margin-left: 6px" />
+            <span>
+              {{ $t(item.title) }}
+              <status :text="item.title in nsps ? nsps[item.title].state : $t('label.disabled')" style="margin-bottom: 6px; margin-left: 6px" />
+            </span>
           </template>
           <provider-item
             v-if="tabKey===item.title"
@@ -109,11 +111,12 @@
                 showSearch
                 optionFilterProp="label"
                 :filterOption="(input, option) => {
-                  return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                 }" >
                 <a-select-option
                   v-for="(opt, idx) in field.opts"
-                  :key="idx">{{ opt.name || opt.description }}</a-select-option>
+                  :key="idx"
+                  :label="opt.name || opt.description">{{ opt.name || opt.description }}</a-select-option>
               </a-select>
             </span>
             <span v-else>
@@ -356,7 +359,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['hostname', 'action']
+              columns: ['hostname', 'actions']
             }
           ]
         },
@@ -415,7 +418,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['hostname', 'action']
+              columns: ['hostname', 'actions']
             }
           ]
         },
@@ -496,7 +499,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['hostname', 'insideportprofile', 'action']
+              columns: ['hostname', 'insideportprofile', 'actions']
             }
           ]
         },
@@ -587,7 +590,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['ipaddress', 'lbdevicestate', 'action']
+              columns: ['ipaddress', 'lbdevicestate', 'actions']
             }
           ]
         },
@@ -737,7 +740,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['ipaddress', 'lbdevicestate', 'action']
+              columns: ['ipaddress', 'lbdevicestate', 'actions']
             }
           ]
         },
@@ -796,7 +799,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['hostname', 'transportzoneuuid', 'l3gatewayserviceuuid', 'l2gatewayserviceuuid', 'action']
+              columns: ['hostname', 'transportzoneuuid', 'l3gatewayserviceuuid', 'l2gatewayserviceuuid', 'actions']
             }
           ]
         },
@@ -855,7 +858,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['name', 'url', 'username', 'action']
+              columns: ['name', 'url', 'username', 'actions']
             }
           ]
         },
@@ -871,7 +874,7 @@
                   value: (record) => { return record.id }
                 }
               },
-              columns: ['account', 'domain', 'enabled', 'project', 'action']
+              columns: ['account', 'domain', 'enabled', 'project', 'actions']
             }
           ]
         },
@@ -930,7 +933,7 @@
                   value: (record) => { return record.physicalnetworkid }
                 }
               },
-              columns: ['ipaddress', 'fwdevicestate', 'type', 'action']
+              columns: ['ipaddress', 'fwdevicestate', 'type', 'actions']
             }
           ]
         },
@@ -1364,5 +1367,15 @@
       }
     }
   }
+
+  &-tab {
+    justify-content: end;
+  }
+
+  &-tab-btn {
+    span {
+      display: flex;
+    }
+  }
 }
 </style>
diff --git a/ui/src/views/infra/network/TrafficTypesTab.vue b/ui/src/views/infra/network/TrafficTypesTab.vue
index 86bedfe..41da48d 100644
--- a/ui/src/views/infra/network/TrafficTypesTab.vue
+++ b/ui/src/views/infra/network/TrafficTypesTab.vue
@@ -19,6 +19,20 @@
   <a-spin :spinning="fetchLoading">
     <a-tabs :tabPosition="device === 'mobile' ? 'top' : 'left'" :animated="false">
       <a-tab-pane v-for="(item, index) in traffictypes" :tab="item.traffictype" :key="index">
+        <a-popconfirm
+          :title="$t('message.confirm.delete.traffic.type')"
+          @confirm="deleteTrafficType(itemd)"
+          :okText="$t('label.yes')"
+          :cancelText="$t('label.no')" >
+          <a-button
+            type="primary"
+            danger
+            style="width: 100%; margin-bottom: 10px"
+            :loading="loading"
+            :disabled="!('deleteTrafficType' in $store.getters.apis)">
+            <template #icon><delete-outlined /></template> {{ $t('label.delete.traffic.type') }}
+          </a-button>
+        </a-popconfirm>
         <div
           v-for="(type, idx) in ['kvmnetworklabel', 'vmwarenetworklabel', 'xennetworklabel', 'hypervnetworklabel', 'ovm3networklabel']"
           :key="idx"
@@ -113,14 +127,7 @@
   },
   methods: {
     async fetchData () {
-      this.fetchLoading = true
-      api('listTrafficTypes', { physicalnetworkid: this.resource.id }).then(json => {
-        this.traffictypes = json.listtraffictypesresponse.traffictype
-      }).catch(error => {
-        this.$notifyError(error)
-      }).finally(() => {
-        this.fetchLoading = false
-      })
+      this.fetchTrafficTypes()
       this.fetchLoading = true
       api('listNetworks', {
         listAll: true,
@@ -143,6 +150,16 @@
         this.fetchGuestNetwork()
       }
     },
+    fetchTrafficTypes () {
+      this.fetchLoading = true
+      api('listTrafficTypes', { physicalnetworkid: this.resource.id }).then(json => {
+        this.traffictypes = json.listtraffictypesresponse.traffictype
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.fetchLoading = false
+      })
+    },
     fetchZones () {
       return new Promise((resolve, reject) => {
         this.fetchLoading = true
@@ -174,6 +191,36 @@
       }).finally(() => {
         this.fetchLoading = false
       })
+    },
+    deleteTrafficType (trafficType) {
+      api('deleteTrafficType', { id: trafficType.id }).then(response => {
+        this.$pollJob({
+          jobId: response.deletetraffictyperesponse.jobid,
+          title: this.$t('label.delete.traffic.type'),
+          description: trafficType.traffictype,
+          successMessage: this.$t('message.traffic.type.deleted') + ' ' + trafficType.traffictype,
+          successMethod: () => {
+            this.fetchLoading = false
+            this.fetchTrafficTypes()
+          },
+          errorMessage: this.$t('message.delete.failed'),
+          errorMethod: () => {
+            this.fetchLoading = false
+            this.fetchTrafficTypes()
+          },
+          loadingMessage: this.$t('message.delete.traffic.type.processing'),
+          catchMessage: this.$t('error.fetching.async.job.result'),
+          catchMethod: () => {
+            this.fetchLoading = false
+            this.fetchTrafficTypes()
+          }
+        })
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.fetchLoading = false
+        this.fetchTrafficTypes()
+      })
     }
   }
 }
diff --git a/ui/src/views/infra/network/providers/AddF5LoadBalancer.vue b/ui/src/views/infra/network/providers/AddF5LoadBalancer.vue
index fa1173a..7fd7f03 100644
--- a/ui/src/views/infra/network/providers/AddF5LoadBalancer.vue
+++ b/ui/src/views/infra/network/providers/AddF5LoadBalancer.vue
@@ -61,11 +61,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option
                 v-for="opt in networkDeviceType"
-                :key="opt.id">{{ $t(opt.description) }}</a-select-option>
+                :key="opt.id"
+                :label="$t(opt.description)">{{ $t(opt.description) }}</a-select-option>
             </a-select>
           </a-form-item>
         </a-col>
diff --git a/ui/src/views/infra/network/providers/AddNetscalerLoadBalancer.vue b/ui/src/views/infra/network/providers/AddNetscalerLoadBalancer.vue
index a89990c..e3a051d 100644
--- a/ui/src/views/infra/network/providers/AddNetscalerLoadBalancer.vue
+++ b/ui/src/views/infra/network/providers/AddNetscalerLoadBalancer.vue
@@ -61,11 +61,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option
                 v-for="opt in networkDeviceType"
-                :key="opt.id">{{ $t(opt.description) }}</a-select-option>
+                :key="opt.id"
+                :label="$t(opt.description)">{{ $t(opt.description) }}</a-select-option>
             </a-select>
           </a-form-item>
         </a-col>
diff --git a/ui/src/views/infra/network/providers/AddPaloAltoFirewall.vue b/ui/src/views/infra/network/providers/AddPaloAltoFirewall.vue
index 545c32c..1dd9150 100644
--- a/ui/src/views/infra/network/providers/AddPaloAltoFirewall.vue
+++ b/ui/src/views/infra/network/providers/AddPaloAltoFirewall.vue
@@ -61,11 +61,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option
                 v-for="opt in networkDeviceType"
-                :key="opt.id">{{ $t(opt.description) }}</a-select-option>
+                :key="opt.id"
+                :label="$t(opt.description)">{{ $t(opt.description) }}</a-select-option>
             </a-select>
           </a-form-item>
         </a-col>
diff --git a/ui/src/views/infra/network/providers/ProviderItem.vue b/ui/src/views/infra/network/providers/ProviderItem.vue
index 69d0d1b..125455b 100644
--- a/ui/src/views/infra/network/providers/ProviderItem.vue
+++ b/ui/src/views/infra/network/providers/ProviderItem.vue
@@ -154,25 +154,25 @@
       params.pageSize = this.pageSize
 
       let length = args.columns.length
-      if (args.columns.includes('action')) {
+      if (args.columns.includes('actions')) {
         length--
       }
       const columns = args.columns.map(col => {
-        if (col === 'action') {
+        if (col === 'actions') {
           return {
+            key: col,
             title: this.$t('label.' + col),
             dataIndex: col,
             width: 80,
-            fixed: 'right',
-            slots: { customRender: col }
+            fixed: 'right'
           }
         }
         const width = 100 / (length) + '%'
         return {
+          key: col,
           title: this.$t('label.' + col),
           width: width,
-          dataIndex: col,
-          slots: { customRender: col }
+          dataIndex: col
         }
       })
 
diff --git a/ui/src/views/infra/network/providers/ProviderListView.vue b/ui/src/views/infra/network/providers/ProviderListView.vue
index baa6517..6c107e6 100644
--- a/ui/src/views/infra/network/providers/ProviderListView.vue
+++ b/ui/src/views/infra/network/providers/ProviderListView.vue
@@ -28,69 +28,73 @@
       :rowKey="record => record.id || record.name || record.nvpdeviceid || record.resourceid"
       :pagination="false"
       :scroll="scrollable">
-      <template #name="{text, record}">
-        <span v-if="record.role==='VIRTUAL_ROUTER'">
-          <router-link :to="{ path: '/router' + '/' + record.id }" v-if="record.id">{{ text }}</router-link>
-          <label v-else>{{ text }}</label>
-        </span>
-        <span v-else>{{ text }}</span>
-      </template>
-      <template #hostname="{text, record}">
-        <span v-if="record.role==='VIRTUAL_ROUTER'">
-          <router-link :to="{ path: '/host' + '/' + record.hostid }" v-if="record.hostid">{{ text }}</router-link>
-          <label v-else>{{ text }}</label>
-        </span>
-        <span v-else>{{ text }}</span>
-      </template>
-      <template #zonename="{text, record}">
-        <span v-if="record.role==='VIRTUAL_ROUTER'">
-          <router-link :to="{ path: '/zone' + '/' + record.zoneid }" v-if="record.zoneid">{{ text }}</router-link>
-          <label v-else>{{ text }}</label>
-        </span>
-        <span v-else>{{ text }}</span>
-      </template>
-      <template #action="{record}">
-        <a-tooltip placement="top">
-          <template #title>
-            <span v-if="resource.name==='BigSwitchBcf'">{{ $t('label.delete.bigswitchbcf') }}</span>
-            <span v-else-if="resource.name==='BrocadeVcs'">{{ $t('label.delete.brocadevcs') }}</span>
-            <span v-else-if="resource.name==='NiciraNvp'">{{ $t('label.delete.niciranvp') }}</span>
-            <span v-else-if="resource.name==='Netscaler'">{{ $t('label.delete.netscaler') }}</span>
-            <span v-else-if="resource.name==='Opendaylight'">{{ $t('label.delete.opendaylight.device') }}</span>
-            <span v-else-if="resource.name==='PaloAlto'">{{ $t('label.delete.pa') }}</span>
-            <span v-else-if="resource.name==='CiscoVnmc' && title==='listCiscoVnmcResources'">
-              {{ $t('label.delete.ciscovnmc.resource') }}
-            </span>
-            <span v-else-if="resource.name==='CiscoVnmc' && title==='listCiscoAsa1000vResources'">
-              {{ $t('label.delete.ciscoasa1000v') }}
-            </span>
-          </template>
-          <tooltip-button
-            v-if="resource.name==='Ovs'"
-            :tooltip="$t('label.configure')"
-            icon="setting-outlined"
-            size="small"
-            :loading="actionLoading"
-            @onClick="onConfigureOvs(record)"/>
-          <tooltip-button
-            v-else
-            :tooltip="$t('label.delete')"
-            type="primary"
-            :danger="true"
-            icon="close-outlined"
-            size="small"
-            :loading="actionLoading"
-            @onClick="onDelete(record)"/>
-        </a-tooltip>
-      </template>
-      <template #lbdevicestate="{text}">
-        <status :text="text ? text : ''" displayText />
-      </template>
-      <template #status="{text}">
-        <status :text="text ? text : ''" displayText />
-      </template>
-      <template #state="{text}">
-        <status :text="text ? text : ''" displayText />
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'name'">
+          <span v-if="record.role==='VIRTUAL_ROUTER'">
+            <router-link :to="{ path: '/router' + '/' + record.id }" v-if="record.id">{{ text }}</router-link>
+            <label v-else>{{ text }}</label>
+          </span>
+          <span v-else>{{ text }}</span>
+        </template>
+        <template v-if="column.key === 'hostname'">
+          <span v-if="record.role==='VIRTUAL_ROUTER'">
+            <router-link :to="{ path: '/host' + '/' + record.hostid }" v-if="record.hostid">{{ text }}</router-link>
+            <label v-else>{{ text }}</label>
+          </span>
+          <span v-else>{{ text }}</span>
+        </template>
+        <template v-if="column.key === 'zonename'">
+          <span v-if="record.role==='VIRTUAL_ROUTER'">
+            <router-link :to="{ path: '/zone' + '/' + record.zoneid }" v-if="record.zoneid">{{ text }}</router-link>
+            <label v-else>{{ text }}</label>
+          </span>
+          <span v-else>{{ text }}</span>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <a-tooltip placement="top">
+            <template #title>
+              <span v-if="resource.name==='BigSwitchBcf'">{{ $t('label.delete.bigswitchbcf') }}</span>
+              <span v-else-if="resource.name==='BrocadeVcs'">{{ $t('label.delete.brocadevcs') }}</span>
+              <span v-else-if="resource.name==='NiciraNvp'">{{ $t('label.delete.niciranvp') }}</span>
+              <span v-else-if="resource.name==='F5BigIp'">{{ $t('label.delete.f5') }}</span>
+              <span v-else-if="resource.name==='JuniperSRX'">{{ $t('label.delete.srx') }}</span>
+              <span v-else-if="resource.name==='Netscaler'">{{ $t('label.delete.netscaler') }}</span>
+              <span v-else-if="resource.name==='Opendaylight'">{{ $t('label.delete.opendaylight.device') }}</span>
+              <span v-else-if="resource.name==='PaloAlto'">{{ $t('label.delete.pa') }}</span>
+              <span v-else-if="resource.name==='CiscoVnmc' && title==='listCiscoVnmcResources'">
+                {{ $t('label.delete.ciscovnmc.resource') }}
+              </span>
+              <span v-else-if="resource.name==='CiscoVnmc' && title==='listCiscoAsa1000vResources'">
+                {{ $t('label.delete.ciscoasa1000v') }}
+              </span>
+            </template>
+            <tooltip-button
+              v-if="resource.name==='Ovs'"
+              :tooltip="$t('label.configure')"
+              icon="setting-outlined"
+              size="small"
+              :loading="actionLoading"
+              @onClick="onConfigureOvs(record)"/>
+            <tooltip-button
+              v-else
+              :tooltip="$t('label.delete')"
+              type="primary"
+              :danger="true"
+              icon="close-outlined"
+              size="small"
+              :loading="actionLoading"
+              @onClick="onDelete(record)"/>
+          </a-tooltip>
+        </template>
+        <template v-if="column.key === 'lbdevicestate'">
+          <status :text="text ? text : ''" displayText />
+        </template>
+        <template v-if="column.key === 'status'">
+          <status :text="text ? text : ''" displayText />
+        </template>
+        <template v-if="column.key === 'state'">
+          <status :text="text ? text : ''" displayText />
+        </template>
       </template>
     </a-table>
     <a-pagination
diff --git a/ui/src/views/infra/routers/RouterHealthCheck.vue b/ui/src/views/infra/routers/RouterHealthCheck.vue
index abadc12..a6f54f7 100644
--- a/ui/src/views/infra/routers/RouterHealthCheck.vue
+++ b/ui/src/views/infra/routers/RouterHealthCheck.vue
@@ -33,8 +33,10 @@
         :pagination="false"
         :rowKey="record => record.checkname"
         size="large">
-        <template #status="{record}">
-          <status class="status" :text="record.success === true ? 'True' : 'False'" displayText />
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'status'">
+            <status class="status" :text="record.success === true ? 'True' : 'False'" displayText />
+          </template>
         </template>
       </a-table>
 
@@ -110,8 +112,8 @@
           dataIndex: 'checktype'
         },
         {
-          title: this.$t('label.router.health.check.success'),
-          slots: { customRender: 'status' }
+          key: 'status',
+          title: this.$t('label.router.health.check.success')
         },
         {
           title: this.$t('label.router.health.check.last.updated'),
diff --git a/ui/src/views/infra/zone/IpAddressRangeForm.vue b/ui/src/views/infra/zone/IpAddressRangeForm.vue
index b851ad9..2233295 100644
--- a/ui/src/views/infra/zone/IpAddressRangeForm.vue
+++ b/ui/src/views/infra/zone/IpAddressRangeForm.vue
@@ -30,13 +30,15 @@
         :columns="columns"
         :pagination="false"
         style="margin-bottom: 24px; width: 100%" >
-        <template #actions="{ record }">
-          <tooltip-button
-            :tooltip="$t('label.delete')"
-            type="primary"
-            :danger="true"
-            icon="delete-outlined"
-            @onClick="onDelete(record.key)" />
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'actions'">
+            <tooltip-button
+              :tooltip="$t('label.delete')"
+              type="primary"
+              :danger="true"
+              icon="delete-outlined"
+              @onClick="onDelete(record.key)" />
+          </template>
         </template>
         <template #footer>
           <a-form
@@ -193,9 +195,9 @@
           width: 140
         },
         {
+          key: 'actions',
           title: '',
           dataIndex: 'actions',
-          slots: { customRender: 'actions' },
           width: 70
         }
       ],
diff --git a/ui/src/views/infra/zone/PhysicalNetworksTab.vue b/ui/src/views/infra/zone/PhysicalNetworksTab.vue
index f175f99..cf5297a 100644
--- a/ui/src/views/infra/zone/PhysicalNetworksTab.vue
+++ b/ui/src/views/infra/zone/PhysicalNetworksTab.vue
@@ -17,6 +17,14 @@
 
 <template>
   <a-spin :spinning="fetchLoading">
+    <a-button
+      type="primary"
+      style="width: 100%; margin-bottom: 10px"
+      @click="showAddPhyNetModal"
+      :loading="loading"
+      :disabled="!('createPhysicalNetwork' in $store.getters.apis)">
+      <template #icon><plus-outlined /></template> {{ $t('label.add.physical.network') }}
+    </a-button>
     <a-list class="list">
       <a-list-item v-for="network in networks" :key="network.id" class="list__item">
         <div class="list__item-outer-container">
@@ -65,17 +73,94 @@
         </div>
       </a-list-item>
     </a-list>
+    <a-modal
+      :visible="addPhyNetModal"
+      :title="$t('label.add.physical.network')"
+      :maskClosable="false"
+      :closable="true"
+      :footer="null"
+      @cancel="closeModals">
+      <a-form
+        :ref="formRef"
+        :model="form"
+        :rules="rules"
+        @finish="handleAddPhyNet"
+        v-ctrl-enter="handleAddPhyNet"
+        layout="vertical"
+        class="form"
+
+      >
+        <a-form-item name="name" ref="name">
+          <template #label>
+            <tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
+          </template>
+          <a-input v-model:value="form.name" :placeholder="apiParams.name.description" />
+        </a-form-item>
+        <a-form-item name="isolationmethods" ref="isolationmethods">
+          <template #label>
+            <tooltip-label :title="$t('label.isolationmethods')" :tooltip="apiParams.isolationmethods.description"/>
+          </template>
+          <a-select
+            v-model:value="form.isolationmethods"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            v-focus="true"
+            :placeholder="apiParams.isolationmethods.description">
+            <a-select-option v-for="i in isolationMethods" :key="i" :value="i" :label="i">{{ i }}</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="vlan" ref="vlan">
+          <template #label>
+            <tooltip-label :title="$t('label.vlan')" :tooltip="apiParams.vlan.description"/>
+          </template>
+          <a-input v-model:value="form.vlan" :placeholder="apiParams.vlan.description" />
+        </a-form-item>
+        <a-form-item name="tags" ref="tags">
+          <template #label>
+            <tooltip-label :title="$t('label.tags')" :tooltip="apiParams.tags.description"/>
+          </template>
+          <a-select
+            mode="tags"
+            v-model:value="form.tags"
+            :placeholder="apiParams.tags.description">
+          </a-select>
+        </a-form-item>
+        <a-form-item name="networkspeed" ref="networkspeed">
+          <template #label>
+            <tooltip-label :title="$t('label.networkspeed')" :tooltip="apiParams.networkspeed.description"/>
+          </template>
+          <a-input v-model:value="form.networkspeed" :placeholder="apiParams.networkspeed.description" />
+        </a-form-item>
+        <a-form-item name="broadcastdomainrange" ref="broadcastdomainrange">
+          <template #label>
+            <tooltip-label :title="$t('label.broadcastdomainrange')" :tooltip="apiParams.broadcastdomainrange.description"/>
+          </template>
+          <a-input v-model:value="form.broadcastdomainrange" :placeholder="apiParams.broadcastdomainrange.description" />
+        </a-form-item>
+
+        <div :span="24" class="action-button">
+          <a-button @click="closeModals">{{ $t('label.cancel') }}</a-button>
+          <a-button type="primary" ref="submit" @click="handleAddPhyNet">{{ $t('label.ok') }}</a-button>
+        </div>
+      </a-form>
+    </a-modal>
   </a-spin>
 </template>
 
 <script>
+import { ref, reactive, toRaw } from 'vue'
 import { api } from '@/api'
 import Status from '@/components/widgets/Status'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'PhysicalNetworksTab',
   components: {
-    Status
+    Status,
+    TooltipLabel
   },
   props: {
     resource: {
@@ -90,11 +175,17 @@
   data () {
     return {
       networks: [],
-      fetchLoading: false
+      fetchLoading: false,
+      addPhyNetModal: false
     }
   },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('createPhysicalNetwork')
+    console.log(this.apiParams)
+  },
   created () {
     this.fetchData()
+    this.initAddPhyNetForm()
   },
   watch: {
     resource: {
@@ -107,6 +198,11 @@
       }
     }
   },
+  computed: {
+    isolationMethods () {
+      return ['VLAN', 'VXLAN', 'GRE', 'STT', 'BCF_SEGMENT', 'SSP', 'ODL', 'L3VPN', 'VCS']
+    }
+  },
   methods: {
     fetchData () {
       this.fetchLoading = true
@@ -122,7 +218,9 @@
       for (const network of this.networks) {
         promises.push(new Promise((resolve, reject) => {
           api('listTrafficTypes', { physicalnetworkid: network.id }).then(json => {
-            network.traffictype = json.listtraffictypesresponse.traffictype.filter(e => { return e.traffictype }).map(e => { return e.traffictype }).join(', ')
+            if (json.listtraffictypesresponse.traffictype) {
+              network.traffictype = json.listtraffictypesresponse.traffictype.filter(e => { return e.traffictype }).map(e => { return e.traffictype }).join(', ')
+            }
             resolve()
           }).catch(error => {
             this.$notifyError(error)
@@ -133,6 +231,52 @@
       Promise.all(promises).finally(() => {
         this.fetchLoading = false
       })
+    },
+    showAddPhyNetModal () {
+      this.addPhyNetModal = true
+    },
+    initAddPhyNetForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({
+        name: [{ required: true, message: this.$t('label.required') }]
+      })
+    },
+    handleAddPhyNet () {
+      this.formRef.value.validate().then(() => {
+        const values = toRaw(this.form)
+        values.zoneid = this.resource.id
+        if (values.tags) {
+          values.tags = values.tags.join()
+        }
+        console.log(values)
+        api('createPhysicalNetwork', values).then(response => {
+          this.$pollJob({
+            jobId: response.createphysicalnetworkresponse.jobid,
+            successMessage: this.$t('message.success.add.physical.network'),
+            successMethod: () => {
+              this.fetchData()
+              this.closeModals()
+            },
+            errorMessage: this.$t('message.add.physical.network.failed'),
+            errorMethod: () => {
+              this.fetchData()
+              this.closeModals()
+            },
+            loadingMessage: this.$t('message.add.physical.network.processing'),
+            catchMessage: this.$t('error.fetching.async.job.result'),
+            catchMethod: () => {
+              this.fetchData()
+              this.closeModals()
+            }
+          })
+        }).catch(error => {
+          this.$notifyError(error)
+        })
+      })
+    },
+    closeModals () {
+      this.addPhyNetModal = false
     }
   }
 }
diff --git a/ui/src/views/infra/zone/StaticInputsForm.vue b/ui/src/views/infra/zone/StaticInputsForm.vue
index e917ad9..77f509a 100644
--- a/ui/src/views/infra/zone/StaticInputsForm.vue
+++ b/ui/src/views/infra/zone/StaticInputsForm.vue
@@ -46,12 +46,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="option in field.options"
               :key="option.id"
               :value="option.id"
+              :label="option.name || option.description"
             >
               {{ option.name || option.description }}
             </a-select-option>
diff --git a/ui/src/views/infra/zone/ZoneWizardAddResources.vue b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
index 4c919af..24ddd26 100644
--- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue
+++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
@@ -108,6 +108,7 @@
 import { api } from '@/api'
 import { mixinDevice } from '@/utils/mixin.js'
 import StaticInputsForm from '@views/infra/zone/StaticInputsForm'
+import store from '@/store'
 
 export default {
   components: {
@@ -283,7 +284,7 @@
           placeHolder: 'message.error.host.name',
           required: true,
           display: {
-            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
+            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator', store.getters.customHypervisorName]
           }
         },
         {
@@ -292,7 +293,7 @@
           placeHolder: 'message.error.host.username',
           required: true,
           display: {
-            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
+            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator', store.getters.customHypervisorName]
           }
         },
         {
@@ -329,7 +330,7 @@
           required: true,
           password: true,
           display: {
-            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator'],
+            hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator', store.getters.customHypervisorName],
             authmethod: 'password'
           }
         },
diff --git a/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue b/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue
index a9d9847..55bf1eb 100644
--- a/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue
+++ b/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue
@@ -29,128 +29,130 @@
       :columns="columns"
       :pagination="false"
       style="margin-bottom: 24px; width: 100%">
-      <template #name="{ text, record, index }">
-        <a-input
-          :disabled="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
-          :value="text"
-          @change="e => onCellChange(record.key, 'name', e.target.value)"
-          v-focus="true">
-          <template #suffix>
-            <a-tooltip
-              v-if="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
-              :title="$t('message.no.support.tungsten.fabric')">
-              <warning-outlined style="color: #f5222d" />
-            </a-tooltip>
-          </template>
-        </a-input>
-      </template>
-      <template #isolationMethod="{ text, record, index }">
-        <a-select
-          :disabled="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
-          style="width: 100%"
-          :defaultValue="text"
-          @change="value => onCellChange(record.key, 'isolationMethod', value)"
-          showSearch
-          optionFilterProp="label"
-          :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-          }" >
-          <a-select-option value="VLAN"> VLAN </a-select-option>
-          <a-select-option value="VXLAN"> VXLAN </a-select-option>
-          <a-select-option value="GRE"> GRE </a-select-option>
-          <a-select-option value="STT"> STT </a-select-option>
-          <a-select-option value="BCF_SEGMENT"> BCF_SEGMENT </a-select-option>
-          <a-select-option value="ODL"> ODL </a-select-option>
-          <a-select-option value="L3VPN"> L3VPN </a-select-option>
-          <a-select-option value="VSP"> VSP </a-select-option>
-          <a-select-option value="VCS"> VCS </a-select-option>
-          <a-select-option value="TF"> TF </a-select-option>
-
-          <template #suffixIcon>
-            <a-tooltip
-              v-if="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
-              :title="$t('message.no.support.tungsten.fabric')">
-              <warning-outlined style="color: #f5222d" />
-            </a-tooltip>
-          </template>
-        </a-select>
-      </template>
-      <template #traffics="{ record, index }">
-        <div v-for="traffic in record.traffics" :key="traffic.type">
-          <a-tooltip :title="traffic.type.toUpperCase() + ' (' + traffic.label + ')'">
-            <a-tag
-              :color="trafficColors[traffic.type]"
-              style="margin:2px"
-            >
-            {{ (traffic.type.toUpperCase() + ' (' + traffic.label + ')').slice(0, 20) }}
-            {{ (traffic.type.toUpperCase() + ' (' + traffic.label + ')').length > 20 ? '...' : '' }}
-            <edit-outlined class="traffic-type-action" @click="editTraffic(record.key, traffic, $event)"/>
-            <delete-outlined class="traffic-type-action" @click="deleteTraffic(record.key, traffic, $event)"/>
-          </a-tag>
-          </a-tooltip>
-        </div>
-        <div v-if="isShowAddTraffic(record.traffics, index)">
-          <div class="traffic-select-item" v-if="addingTrafficForKey === record.key">
-            <a-select
-              :defaultValue="trafficLabelSelected"
-              @change="val => { trafficLabelSelected = val }"
-              style="min-width: 120px;"
-              showSearch
-              optionFilterProp="label"
-              :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }" >
-              <a-select-option
-                v-for="(traffic, idx) in availableTrafficToAdd"
-                :value="traffic"
-                :key="idx"
-                :disabled="isDisabledTraffic(record.traffics, traffic)"
+      <template #bodyCell="{ column, text, record, index }">
+        <template v-if="column.key === 'name'">
+          <a-input
+            :disabled="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
+            :value="text"
+            @change="e => onCellChange(record.key, 'name', e.target.value)"
+            v-focus="true">
+            <template #suffix>
+              <a-tooltip
+                v-if="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
+                :title="$t('message.no.support.tungsten.fabric')">
+                <warning-outlined style="color: #f5222d" />
+              </a-tooltip>
+            </template>
+          </a-input>
+        </template>
+        <template v-if="column.key === 'isolationMethod'">
+          <a-select
+            :disabled="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
+            style="width: 100%"
+            :defaultValue="text"
+            @change="value => onCellChange(record.key, 'isolationMethod', value)"
+            showSearch
+            optionFilterProp="value"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+            <a-select-option value="VLAN"> VLAN </a-select-option>
+            <a-select-option value="VXLAN"> VXLAN </a-select-option>
+            <a-select-option value="GRE"> GRE </a-select-option>
+            <a-select-option value="STT"> STT </a-select-option>
+            <a-select-option value="BCF_SEGMENT"> BCF_SEGMENT </a-select-option>
+            <a-select-option value="ODL"> ODL </a-select-option>
+            <a-select-option value="L3VPN"> L3VPN </a-select-option>
+            <a-select-option value="VSP"> VSP </a-select-option>
+            <a-select-option value="VCS"> VCS </a-select-option>
+            <a-select-option value="TF"> TF </a-select-option>
+            <template #suffixIcon>
+              <a-tooltip
+                v-if="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
+                :title="$t('message.no.support.tungsten.fabric')">
+                <warning-outlined style="color: #f5222d" />
+              </a-tooltip>
+            </template>
+          </a-select>
+        </template>
+        <template v-if="column.key === 'traffics'">
+          <div v-for="traffic in record.traffics" :key="traffic.type">
+            <a-tooltip :title="traffic.type.toUpperCase() + ' (' + traffic.label + ')'">
+              <a-tag
+                :color="trafficColors[traffic.type]"
+                style="margin:2px"
               >
-                {{ traffic.toUpperCase() }}
-              </a-select-option>
-            </a-select>
-            <tooltip-button
-              :tooltip="$t('label.add')"
-              buttonClass="icon-button"
-              icon="plus-outlined"
-              size="small"
-              @onClick="trafficAdded" />
-            <tooltip-button
-              :tooltip="$t('label.cancel')"
-              buttonClass="icon-button"
-              type="primary"
-              :danger="true"
-              icon="close-outlined"
-              size="small"
-              @onClick="() => { addingTrafficForKey = null }" />
+
+                {{ (traffic.type.toUpperCase() + ' (' + traffic.label + ')').slice(0, 20) }}
+                {{ (traffic.type.toUpperCase() + ' (' + traffic.label + ')').length > 20 ? '...' : '' }}
+                <edit-outlined class="traffic-type-action" @click="editTraffic(record.key, traffic, $event)"/>
+                <delete-outlined class="traffic-type-action" @click="deleteTraffic(record.key, traffic, $event)"/>
+              </a-tag>
+            </a-tooltip>
           </div>
-          <a-tag
-            key="addingTraffic"
-            style="margin:2px;"
-            v-else
-          >
-            <a @click="addingTraffic(record.key, record.traffics)">
-              <plus-outlined />
-              {{ $t('label.add.traffic') }}
-            </a>
-          </a-tag>
-        </div>
-      </template>
-      <template #tags="{ text, record, index }">
-        <a-input
+          <div v-if="isShowAddTraffic(record.traffics, index)">
+            <div class="traffic-select-item" v-if="addingTrafficForKey === record.key">
+              <a-select
+                :defaultValue="trafficLabelSelected"
+                @change="val => { trafficLabelSelected = val }"
+                style="min-width: 120px;"
+                showSearch
+                optionFilterProp="value"
+                :filterOption="(input, option) => {
+                  return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }" >
+                <a-select-option
+                  v-for="(traffic, index) in availableTrafficToAdd"
+                  :value="traffic"
+                  :key="index"
+                  :disabled="isDisabledTraffic(record.traffics, traffic)"
+                >
+                  {{ traffic.toUpperCase() }}
+                </a-select-option>
+              </a-select>
+              <tooltip-button
+                :tooltip="$t('label.add')"
+                buttonClass="icon-button"
+                icon="plus-outlined"
+                size="small"
+                @onClick="trafficAdded" />
+              <tooltip-button
+                :tooltip="$t('label.cancel')"
+                buttonClass="icon-button"
+                type="primary"
+                :danger="true"
+                icon="close-outlined"
+                size="small"
+                @onClick="() => { addingTrafficForKey = null }" />
+            </div>
+            <a-tag
+              key="addingTraffic"
+              style="margin:2px;"
+              v-else
+            >
+              <a @click="addingTraffic(record.key, record.traffics)">
+                <plus-outlined />
+                {{ $t('label.add.traffic') }}
+              </a>
+            </a-tag>
+          </div>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            :tooltip="$t('label.delete')"
+            v-if="tungstenNetworkIndex === -1 ? index > 0 : tungstenNetworkIndex !== index"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="onDelete(record)" />
+        </template>
+        <template v-if="column.key === 'tags'">
+          <a-input
           :disabled="tungstenNetworkIndex > -1 && tungstenNetworkIndex !== index"
           :value="text"
           @change="e => onCellChange(record.key, 'tags', e.target.value)"
            />
       </template>
-      <template #actions="{ record, index }">
-        <tooltip-button
-          :tooltip="$t('label.delete')"
-          v-if="tungstenNetworkIndex === -1 ? index > 0 : tungstenNetworkIndex !== index"
-          type="primary"
-          :danger="true"
-          icon="delete-outlined"
-          @onClick="onDelete(record)" />
       </template>
       <template #footer v-if="isAdvancedZone">
         <a-button
@@ -237,11 +239,17 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option value="nexusdvs">{{ $t('label.vswitch.type.nexusdvs') }}</a-select-option>
-              <a-select-option value="vmwaresvs">{{ $t('label.vswitch.type.vmwaresvs') }}</a-select-option>
-              <a-select-option value="vmwaredvs">{{ $t('label.vswitch.type.vmwaredvs') }}</a-select-option>
+              <a-select-option
+                value="nexusdvs"
+                :label="$t('label.vswitch.type.nexusdvs')">{{ $t('label.vswitch.type.nexusdvs') }}</a-select-option>
+              <a-select-option
+                value="vmwaresvs"
+                :label="$t('label.vswitch.type.vmwaresvs')">{{ $t('label.vswitch.type.vmwaresvs') }}</a-select-option>
+              <a-select-option
+                value="vmwaredvs"
+                :label="$t('label.vswitch.type.vmwaredvs')">{{ $t('label.vswitch.type.vmwaredvs') }}</a-select-option>
             </a-select>
           </a-form-item>
         </span>
@@ -309,36 +317,34 @@
     columns () {
       const columns = []
       columns.push({
+        key: 'name',
         title: this.$t('label.network.name'),
         dataIndex: 'name',
-        width: 175,
-        slots: { customRender: 'name' }
+        width: 175
       })
       columns.push({
+        key: 'isolationMethod',
         title: this.$t('label.isolation.method'),
         dataIndex: 'isolationMethod',
-        width: 125,
-        slots: { customRender: 'isolationMethod' }
+        width: 125
       })
       columns.push({
-        title: this.$t('label.traffic.types'),
         key: 'traffics',
+        title: this.$t('label.traffic.types'),
         dataIndex: 'traffics',
-        width: 250,
-        slots: { customRender: 'traffics' }
+        width: 250
       })
       columns.push({
         title: this.$t('label.tags'),
         key: 'tags',
         dataIndex: 'tags',
-        width: 175,
-        slots: { customRender: 'tags' }
+        width: 175
       })
       if (this.isAdvancedZone) {
         columns.push({
+          key: 'actions',
           title: '',
           dataIndex: 'actions',
-          slots: { customRender: 'actions' },
           width: 70
         })
       }
diff --git a/ui/src/views/infra/zone/ZoneWizardZoneDetailsStep.vue b/ui/src/views/infra/zone/ZoneWizardZoneDetailsStep.vue
index 42e30a7..8aea3ff 100644
--- a/ui/src/views/infra/zone/ZoneWizardZoneDetailsStep.vue
+++ b/ui/src/views/infra/zone/ZoneWizardZoneDetailsStep.vue
@@ -121,9 +121,9 @@
           :loading="hypervisors === null"
           :disabled="this.isEdgeZone"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option v-for="hypervisor in hypervisors" :key="hypervisor.name">
             {{ hypervisor.name }}
@@ -145,11 +145,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="networkOffering in availableNetworkOfferings"
-              :key="networkOffering.id">
+              :key="networkOffering.id"
+              :label="networkOffering.displaytext || networkOffering.name || networkOffering.description">
               {{ networkOffering.displaytext || networkOffering.name || networkOffering.description }}
             </a-select-option>
           </a-select>
@@ -193,9 +194,9 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option v-for="dom in domains" :key="dom.id">
+          <a-select-option v-for="dom in domains" :key="dom.id" :label="dom.path">
             {{ dom.path }}
           </a-select-option>
         </a-select>
diff --git a/ui/src/views/network/AclListRulesTab.vue b/ui/src/views/network/AclListRulesTab.vue
index a7fbdf4..4207a46 100644
--- a/ui/src/views/network/AclListRulesTab.vue
+++ b/ui/src/views/network/AclListRulesTab.vue
@@ -40,7 +40,6 @@
         handle=".drag-handle"
         animation="200"
         ghostClass="drag-ghost"
-        tag="transition-group"
         :component-data="{type: 'transition'}"
         item-key="id">
         <template #item="{element}">
@@ -182,10 +181,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="allow">{{ $t('label.allow') }}</a-select-option>
-            <a-select-option value="deny">{{ $t('label.deny') }}</a-select-option>
+            <a-select-option value="allow" :label="$t('label.allow')">{{ $t('label.allow') }}</a-select-option>
+            <a-select-option value="deny" :label="$t('label.deny')">{{ $t('label.deny') }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item :label="$t('label.protocol')" ref="protocol" name="protocol">
@@ -194,13 +193,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp">{{ capitalise($t('label.tcp')) }}</a-select-option>
-            <a-select-option value="udp">{{ capitalise($t('label.udp')) }}</a-select-option>
-            <a-select-option value="icmp">{{ capitalise($t('label.icmp')) }}</a-select-option>
-            <a-select-option value="all">{{ $t('label.all') }}</a-select-option>
-            <a-select-option value="protocolnumber">{{ $t('label.protocol.number') }}</a-select-option>
+            <a-select-option value="tcp" :label="$t('label.tcp')">{{ capitalise($t('label.tcp')) }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ capitalise($t('label.udp')) }}</a-select-option>
+            <a-select-option value="icmp" :label="$t('label.icmp')">{{ capitalise($t('label.icmp')) }}</a-select-option>
+            <a-select-option value="all" :label="$t('label.all')">{{ $t('label.all') }}</a-select-option>
+            <a-select-option value="protocolnumber" :label="$t('label.protocol.number')">{{ $t('label.protocol.number') }}</a-select-option>
           </a-select>
         </a-form-item>
 
@@ -236,10 +235,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="ingress">{{ $t('label.ingress') }}</a-select-option>
-            <a-select-option value="egress">{{ $t('label.egress') }}</a-select-option>
+            <a-select-option value="ingress" :label="$t('label.ingress')">{{ $t('label.ingress') }}</a-select-option>
+            <a-select-option value="egress" :label="$t('label.egress')">{{ $t('label.egress') }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item :label="$t('label.description')" ref="reason" name="reason">
diff --git a/ui/src/views/network/CreateIsolatedNetworkForm.vue b/ui/src/views/network/CreateIsolatedNetworkForm.vue
index 21ab5e3..67b5ed4 100644
--- a/ui/src/views/network/CreateIsolatedNetworkForm.vue
+++ b/ui/src/views/network/CreateIsolatedNetworkForm.vue
@@ -75,12 +75,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
-              :loading="domainLoading"
+              :loading="domain.loading"
               :placeholder="apiParams.domainid.description"
               @change="val => { handleDomainChange(domains[val]) }">
-              <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
                 {{ opt.path || opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -124,12 +124,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="networkOfferingLoading"
               :placeholder="apiParams.networkofferingid.description"
               @change="val => { handleNetworkOfferingChange(networkOfferings[val]) }">
-              <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex" :label="opt.displaytext || opt.name || opt.description">
                 {{ opt.displaytext || opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -189,12 +189,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="vpcLoading"
               :placeholder="apiParams.vpcid.description"
               @change="val => { selectedVpc = vpcs[val] }">
-              <a-select-option v-for="(opt, optIndex) in vpcs" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in vpcs" :key="optIndex" :label="opt.name || opt.description">
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -291,6 +291,36 @@
               </a-col>
             </a-row>
           </div>
+          <a-form-item v-if="selectedNetworkOfferingSupportsSourceNat" name="sourcenatipaddress" ref="sourcenatipaddress">
+            <template #label>
+              <tooltip-label :title="$t('label.routerip')" :tooltip="apiParams.sourcenatipaddress?.description"/>
+            </template>
+            <a-input
+              v-model:value="form.sourcenatipaddress"
+              :placeholder="apiParams.sourcenatipaddress?.description"/>
+          </a-form-item>
+          <a-form-item
+            ref="networkdomain"
+            name="networkdomain"
+            v-if="!isObjectEmpty(selectedNetworkOffering) && !selectedNetworkOffering.forvpc">
+            <template #label>
+              <tooltip-label :title="$t('label.networkdomain')" :tooltip="apiParams.networkdomain.description"/>
+            </template>
+            <a-input
+             v-model:value="form.networkdomain"
+              :placeholder="apiParams.networkdomain.description"/>
+          </a-form-item>
+          <a-form-item
+            ref="account"
+            name="account"
+            v-if="accountVisible">
+            <template #label>
+              <tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
+            </template>
+            <a-input
+             v-model:value="form.account"
+              :placeholder="apiParams.account.description"/>
+          </a-form-item>
           <div :span="24" class="action-button">
             <a-button
               :loading="actionLoading"
@@ -345,7 +375,7 @@
     return {
       actionLoading: false,
       domains: [],
-      domainLoading: false,
+      domain: { loading: false },
       selectedDomain: {},
       accountVisible: isAdminOrDomainAdmin(),
       accounts: [],
@@ -397,6 +427,14 @@
         return dnsServices && dnsServices.length === 1
       }
       return false
+    },
+    selectedNetworkOfferingSupportsSourceNat () {
+      if (this.selectedNetworkOffering) {
+        const services = this.selectedNetworkOffering?.service || []
+        const sourcenatService = services.filter(service => service.name === 'SourceNat')
+        return sourcenatService && sourcenatService.length === 1
+      }
+      return false
     }
   },
   methods: {
@@ -459,15 +497,28 @@
       this.updateVPCCheckAndFetchNetworkOfferingData()
     },
     fetchDomainData () {
+      if ('listDomains' in this.$store.getters.apis) {
+        this.domain.loading = true
+        this.loadMore('listDomains', 1, this.domain)
+      }
+    },
+    loadMore (apiToCall, page, sema) {
       const params = {}
       params.listAll = true
       params.details = 'min'
-      this.domainLoading = true
-      api('listDomains', params).then(json => {
+      params.pagesize = 100
+      params.page = page
+      var count
+      api(apiToCall, params).then(json => {
         const listDomains = json.listdomainsresponse.domain
+        count = json.listdomainsresponse.count
         this.domains = this.domains.concat(listDomains)
       }).finally(() => {
-        this.domainLoading = false
+        if (count <= this.domains.length) {
+          sema.loading = false
+        } else {
+          this.loadMore(apiToCall, page + 1, sema)
+        }
         this.form.domainid = 0
         this.handleDomainChange(this.domains[0])
       })
@@ -596,7 +647,7 @@
           displayText: values.displaytext,
           networkOfferingId: this.selectedNetworkOffering.id
         }
-        var usefulFields = ['gateway', 'netmask', 'startip', 'endip', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'externalid', 'vlan', 'networkdomain']
+        var usefulFields = ['gateway', 'netmask', 'startip', 'startipv4', 'endip', 'endipv4', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'sourcenatipaddress', 'externalid', 'vpcid', 'vlan', 'networkdomain']
         for (var field of usefulFields) {
           if (this.isValidTextValueForKey(values, field)) {
             params[field] = values[field]
diff --git a/ui/src/views/network/CreateL2NetworkForm.vue b/ui/src/views/network/CreateL2NetworkForm.vue
index b5f5763..76695eb 100644
--- a/ui/src/views/network/CreateL2NetworkForm.vue
+++ b/ui/src/views/network/CreateL2NetworkForm.vue
@@ -117,12 +117,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="networkOfferingLoading"
               :placeholder="apiParams.networkofferingid.description"
               @change="val => { handleNetworkOfferingChange(networkOfferings[val]) }">
-              <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex" :label="opt.displaytext || opt.name || opt.description">
                 {{ opt.displaytext || opt.name || opt.description }}
               </a-select-option>
             </a-select>
diff --git a/ui/src/views/network/CreateNetwork.vue b/ui/src/views/network/CreateNetwork.vue
index 4ead6ba..44921f4 100644
--- a/ui/src/views/network/CreateNetwork.vue
+++ b/ui/src/views/network/CreateNetwork.vue
@@ -106,7 +106,8 @@
     fetchActionZoneData () {
       this.loading = true
       const params = {}
-      if (this.resource.zoneid && this.$route.name === 'deployVirtualMachine') {
+      console.log(this.resource)
+      if (this.$route.name === 'deployVirtualMachine' && this.resource.zoneid) {
         params.id = this.resource.zoneid
       }
       this.actionZoneLoading = true
diff --git a/ui/src/views/network/CreateNetworkPermission.vue b/ui/src/views/network/CreateNetworkPermission.vue
index 8872346..037e91e 100644
--- a/ui/src/views/network/CreateNetworkPermission.vue
+++ b/ui/src/views/network/CreateNetworkPermission.vue
@@ -35,9 +35,9 @@
               :loading="accountLoading"
               :placeholder="apiParams.accountids.description"
               showSearch
-              optionFilterProp="children"
+              optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option v-for="(opt, optIndex) in accounts" :key="optIndex" :label="opt.name || opt.description">
                 <span>
@@ -58,9 +58,9 @@
               :loading="projectLoading"
               :placeholder="apiParams.projectids.description"
               showSearch
-              optionFilterProp="children"
+              optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option v-for="(opt, optIndex) in projects" :key="optIndex" :label="opt.name || opt.description">
                 <span>
@@ -131,6 +131,7 @@
   created () {
     this.formRef = ref()
     this.form = reactive({})
+    this.rules = reactive({})
     this.apiParams = this.$getApiParams('createNetworkPermissions')
     this.fetchData()
   },
diff --git a/ui/src/views/network/CreateSharedNetworkForm.vue b/ui/src/views/network/CreateSharedNetworkForm.vue
index 380ced7..93bf693 100644
--- a/ui/src/views/network/CreateSharedNetworkForm.vue
+++ b/ui/src/views/network/CreateSharedNetworkForm.vue
@@ -80,7 +80,7 @@
               :loading="formPhysicalNetworkLoading"
               :placeholder="apiParams.physicalnetworkid.description"
               @change="val => { handlePhysicalNetworkChange(formPhysicalNetworks[val]) }">
-              <a-select-option v-for="(opt, optIndex) in formPhysicalNetworks" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in formPhysicalNetworks" :key="optIndex" :label="opt.name || opt.description">
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -164,7 +164,7 @@
               :loading="domainLoading"
               :placeholder="apiParams.domainid.description"
               @change="val => { handleDomainChange(domains[val]) }">
-              <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
                 <span>
                   <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
                   <block-outlined v-else style="margin-right: 5px" />
@@ -193,7 +193,7 @@
               :loading="accountLoading"
               :placeholder="apiParams.account.description"
               @change="val => { handleAccountChange(accounts[val]) }">
-              <a-select-option v-for="(opt, optIndex) in accounts" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in accounts" :key="optIndex" :label="opt.name || opt.description">
                 <span>
                   <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
                   <user-outlined v-else style="margin-right: 5px" />
@@ -216,7 +216,7 @@
               :loading="projectLoading"
               :placeholder="apiParams.projectid.description"
               @change="val => { handleProjectChange(projects[val]) }">
-              <a-select-option v-for="(opt, optIndex) in projects" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in projects" :key="optIndex" :label="opt.name || opt.description">
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -235,7 +235,7 @@
               :loading="networkOfferingLoading"
               :placeholder="apiParams.networkofferingid.description"
               @change="val => { handleNetworkOfferingChange(networkOfferings[val]) }">
-              <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex">
+              <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex" :label="opt.displaytext || opt.name || opt.description">
                 {{ opt.displaytext || opt.name || opt.description }}
               </a-select-option>
             </a-select>
diff --git a/ui/src/views/network/CreateVlanIpRange.vue b/ui/src/views/network/CreateVlanIpRange.vue
index d69c145..fdc1e9e 100644
--- a/ui/src/views/network/CreateVlanIpRange.vue
+++ b/ui/src/views/network/CreateVlanIpRange.vue
@@ -35,9 +35,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id">{{ pod.name }}</a-select-option>
+              <a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id" :label="pod.name">{{ pod.name }}</a-select-option>
             </a-select>
           </a-form-item>
           <a-form-item name="gateway" ref="gateway">
diff --git a/ui/src/views/network/CreateVpc.vue b/ui/src/views/network/CreateVpc.vue
index fb2d110..64e5cdd 100644
--- a/ui/src/views/network/CreateVpc.vue
+++ b/ui/src/views/network/CreateVpc.vue
@@ -88,10 +88,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             @change="handleVpcOfferingChange" >
-            <a-select-option :value="offering.id" v-for="offering in vpcOfferings" :key="offering.id">
+            <a-select-option :value="offering.id" v-for="offering in vpcOfferings" :key="offering.id" :label="offering.name">
               {{ offering.name }}
             </a-select-option>
           </a-select>
@@ -155,6 +155,14 @@
             </a-form-item>
           </a-col>
         </a-row>
+        <a-form-item v-if="selectedNetworkOfferingSupportsSourceNat" name="sourcenatipaddress" ref="sourcenatipaddress">
+          <template #label>
+            <tooltip-label :title="$t('label.routerip')" :tooltip="apiParams.sourcenatipaddress?.description"/>
+          </template>
+          <a-input
+            v-model:value="form.sourcenatipaddress"
+            :placeholder="apiParams.sourcenatipaddress?.description"/>
+        </a-form-item>
         <a-form-item name="start" ref="start">
           <template #label>
             <tooltip-label :title="$t('label.start')" :tooltip="apiParams.start.description"/>
@@ -212,6 +220,14 @@
         return dnsServices && dnsServices.length === 1
       }
       return false
+    },
+    selectedNetworkOfferingSupportsSourceNat () {
+      if (this.selectedVpcOffering) {
+        const services = this.selectedVpcOffering?.service || []
+        const sourcenatService = services.filter(service => service.name === 'SourceNat')
+        return sourcenatService && sourcenatService.length === 1
+      }
+      return false
     }
   },
   methods: {
@@ -222,7 +238,6 @@
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.required.input') }],
-        displaytext: [{ required: true, message: this.$t('message.error.required.input') }],
         zoneid: [{ required: true, message: this.$t('label.required') }],
         cidr: [{ required: true, message: this.$t('message.error.required.input') }],
         vpcofferingid: [{ required: true, message: this.$t('label.required') }]
@@ -275,6 +290,10 @@
         this.selectedVpcOffering = this.vpcOfferings[0] || {}
       }).finally(() => {
         this.loadingOffering = false
+        if (this.vpcOfferings.length > 0) {
+          this.form.vpcofferingid = 0
+          this.handleVpcOfferingChange(this.vpcOfferings[0].id)
+        }
       })
     },
     handleVpcOfferingChange (value) {
@@ -285,6 +304,7 @@
       for (var offering of this.vpcOfferings) {
         if (offering.id === value) {
           this.selectedVpcOffering = offering
+          this.form.vpcofferingid = offering.id
           return
         }
       }
diff --git a/ui/src/views/network/CreateVpnCustomerGateway.vue b/ui/src/views/network/CreateVpnCustomerGateway.vue
index b890e29..00b9f3b 100644
--- a/ui/src/views/network/CreateVpnCustomerGateway.vue
+++ b/ui/src/views/network/CreateVpnCustomerGateway.vue
@@ -62,9 +62,9 @@
         <a-select
           v-model:value="form.ikeEncryption"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option :value="algo" v-for="(algo, idx) in encryptionAlgo" :key="idx">
             {{ algo }}
@@ -75,9 +75,9 @@
         <a-select
           v-model:value="form.ikeHash"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option :value="h" v-for="(h, idx) in hash" :key="idx">
             {{ h }}
@@ -91,9 +91,9 @@
         <a-select
           v-model:value="form.ikeversion"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option :value="vers" v-for="(vers, idx) in ikeVersions" :key="idx">
             {{ vers }}
@@ -106,9 +106,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option :value="DHGroups[group]" v-for="(group, idx) in Object.keys(DHGroups)" :key="idx">
+          <a-select-option
+            :value="DHGroups[group]"
+            v-for="(group, idx) in Object.keys(DHGroups)"
+            :key="idx"
+            :label="group + '(' + DHGroups[group] + ')'">
             <div v-if="group !== ''">
               {{ group+"("+DHGroups[group]+")" }}
             </div>
@@ -119,9 +123,9 @@
         <a-select
           v-model:value="form.espEncryption"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option :value="algo" v-for="(algo, idx) in encryptionAlgo" :key="idx">
             {{ algo }}
@@ -132,9 +136,9 @@
         <a-select
           v-model:value="form.espHash"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option :value="h" v-for="(h, idx) in hash" :key="idx">
             {{ h }}
@@ -147,9 +151,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option :value="DHGroups[group]" v-for="(group, idx) in Object.keys(DHGroups)" :key="idx">
+          <a-select-option
+            :value="DHGroups[group]"
+            v-for="(group, idx) in Object.keys(DHGroups)"
+            :key="idx"
+            :label="group === '' ? DHGroups[group] : group + '(' + DHGroups[group] + ')'">
             <div v-if="group === ''">
               {{ DHGroups[group] }}
             </div>
diff --git a/ui/src/views/network/EgressRulesTab.vue b/ui/src/views/network/EgressRulesTab.vue
index dcd8054..a0fb708 100644
--- a/ui/src/views/network/EgressRulesTab.vue
+++ b/ui/src/views/network/EgressRulesTab.vue
@@ -45,12 +45,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp">{{ capitalise($t('label.tcp'))  }}</a-select-option>
-            <a-select-option value="udp">{{ capitalise($t('label.udp')) }}</a-select-option>
-            <a-select-option value="icmp">{{ capitalise($t('label.icmp')) }}</a-select-option>
-            <a-select-option value="all">{{ $t('label.all') }}</a-select-option>
+            <a-select-option value="tcp" :label="$t('label.tcp')">{{ capitalise($t('label.tcp'))  }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ capitalise($t('label.udp')) }}</a-select-option>
+            <a-select-option value="icmp" :label="$t('label.icmp')">{{ capitalise($t('label.icmp')) }}</a-select-option>
+            <a-select-option value="all" :label="$t('label.all')">{{ $t('label.all') }}</a-select-option>
           </a-select>
         </div>
         <div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
@@ -97,23 +97,25 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.id">
-      <template #protocol="{ record }">
-        {{ getCapitalise(record.protocol) }}
-      </template>
-      <template #startport="{ record }">
-        {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : 'All' }}
-      </template>
-      <template #endport="{ record }">
-        {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : 'All' }}
-      </template>
-      <template #actions="{ record }">
-        <tooltip-button
-          :tooltip="$t('label.delete')"
-          :disabled="!('deleteEgressFirewallRule' in $store.getters.apis)"
-          type="primary"
-          :danger="true"
-          icon="delete-outlined"
-          @onClick="deleteRule(record)" />
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'protocol'">
+          {{ getCapitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'startport'">
+          {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : 'All' }}
+        </template>
+        <template v-if="column.key === 'endport'">
+          {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : 'All' }}
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            :tooltip="$t('label.delete')"
+            :disabled="!('deleteEgressFirewallRule' in $store.getters.apis)"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="deleteRule(record)" />
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -176,7 +178,7 @@
       showGroupActionModal: false,
       selectedItems: [],
       selectedColumns: [],
-      filterColumns: ['Action'],
+      filterColumns: ['Actions'],
       showConfirmationAction: false,
       message: {
         title: this.$t('label.action.bulk.delete.egress.firewall.rules'),
@@ -207,20 +209,20 @@
           dataIndex: 'destcidrlist'
         },
         {
-          title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol',
+          title: this.$t('label.protocol')
         },
         {
-          title: this.$t('label.icmptype.start.port'),
-          slots: { customRender: 'startport' }
+          key: 'startport',
+          title: this.$t('label.icmptype.start.port')
         },
         {
-          title: this.$t('label.icmpcode.end.port'),
-          slots: { customRender: 'endport' }
+          key: 'endport',
+          title: this.$t('label.icmpcode.end.port')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ]
     }
@@ -291,9 +293,9 @@
     deleteRules (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
diff --git a/ui/src/views/network/EnableStaticNat.vue b/ui/src/views/network/EnableStaticNat.vue
index 8def80f..51d3e8f 100644
--- a/ui/src/views/network/EnableStaticNat.vue
+++ b/ui/src/views/network/EnableStaticNat.vue
@@ -27,9 +27,9 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option v-for="network in networksList" :key="network.id" :value="network.id">
+          <a-select-option v-for="network in networksList" :key="network.id" :value="network.id" :label="network.name">
             {{ network.name }}
           </a-select-option>
         </a-select>
@@ -52,36 +52,39 @@
       :dataSource="vmsList"
       :pagination="false"
       :rowKey="record => record.id || record.account">
-      <template #name="{ record }">
-        <div>
-          {{ record.name }}
-        </div>
-        <a-select
-          v-if="nicsList.length && selectedVm && selectedVm === record.id"
-          class="nic-select"
-          :defaultValue="selectedNic.ipaddress"
-          showSearch
-          optionFilterProp="label"
-          :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-          }">
-          <a-select-option
-            @click="selectedNic = item"
-            v-for="item in nicsList"
-            :key="item.id">
-            {{ item.ipaddress }}
-          </a-select-option>
-        </a-select>
-      </template>
-      <template #state="{ text }">
-        <status :text="text ? text : ''" displayText />
-      </template>
-      <template #radio="{ text }">
-        <a-radio
-          class="list__radio"
-          :value="text"
-          :checked="selectedVm && selectedVm === text"
-          @change="fetchNics"></a-radio>
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'name'">
+          <div>
+            {{ record.name }}
+          </div>
+          <a-select
+            v-if="nicsList.length && selectedVm && selectedVm === record.id"
+            class="nic-select"
+            :defaultValue="selectedNic.ipaddress"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }">
+            <a-select-option
+              @click="selectedNic = item"
+              v-for="item in nicsList"
+              :key="item.id"
+              :label="item.ipaddress">
+              {{ item.ipaddress }}
+            </a-select-option>
+          </a-select>
+        </template>
+        <template v-if="column.key === 'state'">
+          <status :text="text ? text : ''" displayText />
+        </template>
+        <template v-if="column.key === 'radio'">
+          <a-radio
+            class="list__radio"
+            :value="text"
+            :checked="selectedVm && selectedVm === text"
+            @change="fetchNics"></a-radio>
+        </template>
       </template>
     </a-table>
 
@@ -134,14 +137,14 @@
       selectedNic: null,
       columns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          slots: { customRender: 'name' },
           width: 200
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.displayname'),
@@ -156,9 +159,9 @@
           dataIndex: 'zonename'
         },
         {
+          key: 'radio',
           title: this.$t('label.select'),
           dataIndex: 'id',
-          slots: { customRender: 'radio' },
           width: 70
         }
       ],
@@ -188,8 +191,6 @@
         pageSize: this.pageSize,
         listAll: true,
         networkid: this.resource.associatednetworkid,
-        account: this.resource.account,
-        domainid: this.resource.domainid,
         keyword: this.searchQuery
       }).then(response => {
         this.vmCount = response.listvirtualmachinesresponse.count
@@ -207,8 +208,6 @@
         pageSize: this.pageSize,
         listAll: true,
         networkid: e,
-        account: this.resource.account,
-        domainid: this.resource.domainid,
         vpcid: this.resource.vpcid,
         keyword: this.searchQuery
       }).then(response => {
@@ -247,8 +246,7 @@
       this.loading = true
       api('listNetworks', {
         vpcid: this.resource.vpcid,
-        domainid: this.resource.domainid,
-        account: this.resource.account,
+        isrecursive: true,
         supportedservices: 'StaticNat'
       }).then(response => {
         this.networksList = response.listnetworksresponse.network
diff --git a/ui/src/views/network/FirewallRules.vue b/ui/src/views/network/FirewallRules.vue
index 523a632..787f5c2 100644
--- a/ui/src/views/network/FirewallRules.vue
+++ b/ui/src/views/network/FirewallRules.vue
@@ -32,11 +32,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
-            <a-select-option value="udp">{{ $t('label.udp') }}</a-select-option>
-            <a-select-option value="icmp">{{ $t('label.icmp') }}</a-select-option>
+            <a-select-option value="tcp" :label="$t('label.tcp')">{{ $t('label.tcp') }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ $t('label.udp') }}</a-select-option>
+            <a-select-option value="icmp" :label="$t('label.icmp')">{{ $t('label.icmp') }}</a-select-option>
           </a-select>
         </div>
         <div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
@@ -80,27 +80,29 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.id">
-      <template #protocol="{ record }">
-        {{ getCapitalise(record.protocol) }}
-      </template>
-      <template #startport="{ record }">
-        {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
-      </template>
-      <template #endport="{ record }">
-        {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
-      </template>
-      <template #actions="{ record }">
-        <div class="actions">
-          <tooltip-button :tooltip="$t('label.edit.tags')" icon="tag-outlined" buttonClass="rule-action" @onClick="() => openTagsModal(record.id)" />
-          <tooltip-button
-            :tooltip="$t('label.delete')"
-            type="primary"
-            :danger="true"
-            icon="delete-outlined"
-            buttonClass="rule-action"
-            :disabled="!('deleteFirewallRule' in $store.getters.apis)"
-            @onClick="deleteRule(record)" />
-        </div>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'protocol'">
+          {{ getCapitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'startport'">
+          {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : $t('label.all') }}
+        </template>
+        <template v-if="column.key === 'endport'">
+          {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : $t('label.all') }}
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="actions">
+            <tooltip-button :tooltip="$t('label.edit.tags')" icon="tag-outlined" buttonClass="rule-action" @onClick="() => openTagsModal(record.id)" />
+            <tooltip-button
+              :tooltip="$t('label.delete')"
+              type="primary"
+              :danger="true"
+              icon="delete-outlined"
+              buttonClass="rule-action"
+              :disabled="!('deleteFirewallRule' in $store.getters.apis)"
+              @onClick="deleteRule(record)" />
+          </div>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -218,7 +220,7 @@
       showGroupActionModal: false,
       selectedItems: [],
       selectedColumns: [],
-      filterColumns: ['State', 'Action'],
+      filterColumns: ['State', 'Actions'],
       showConfirmationAction: false,
       message: {
         title: this.$t('label.action.bulk.delete.firewall.rules'),
@@ -248,24 +250,24 @@
           dataIndex: 'cidrlist'
         },
         {
-          title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol',
+          title: this.$t('label.protocol')
         },
         {
-          title: `${this.$t('label.startport')}/${this.$t('label.icmptype')}`,
-          slots: { customRender: 'startport' }
+          key: 'startport',
+          title: `${this.$t('label.startport')}/${this.$t('label.icmptype')}`
         },
         {
-          title: `${this.$t('label.endport')}/${this.$t('label.icmpcode')}`,
-          slots: { customRender: 'endport' }
+          key: 'endport',
+          title: `${this.$t('label.endport')}/${this.$t('label.icmpcode')}`
         },
         {
           title: this.$t('label.state'),
           dataIndex: 'state'
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ]
     }
@@ -346,9 +348,9 @@
     deleteRules (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
diff --git a/ui/src/views/network/GuestIpRanges.vue b/ui/src/views/network/GuestIpRanges.vue
index b2e56de..815b3d9 100644
--- a/ui/src/views/network/GuestIpRanges.vue
+++ b/ui/src/views/network/GuestIpRanges.vue
@@ -36,14 +36,16 @@
         :rowKey="item => item.id"
         :pagination="false" >
 
-        <template #action="{ record }">
+        <template #bodyCell="{ column, record }">
           <tooltip-button
+            v-if="column.key === 'actions'"
             tooltipPlacement="bottom"
             :tooltip="$t('label.edit')"
             type="primary"
             @click="() => { handleUpdateIpRangeModal(record) }"
             icon="swap-outlined" />
           <a-popconfirm
+            v-if="column.key === 'actions'"
             :title="$t('message.confirm.remove.ip.range')"
             @confirm="removeIpRange(record.id)"
             :okText="$t('label.yes')"
@@ -56,9 +58,10 @@
               icon="delete-outlined" />
           </a-popconfirm>
         </template>
-
       </a-table>
+
       <a-divider/>
+
       <a-pagination
         class="row-element pagination"
         size="small"
@@ -187,8 +190,8 @@
           dataIndex: 'netmask'
         },
         {
-          title: '',
-          slots: { customRender: 'action' }
+          key: 'actions',
+          title: ''
         }
       ]
     }
@@ -225,6 +228,10 @@
     },
     removeIpRange (id) {
       api('deleteVlanIpRange', { id: id }).then(json => {
+        const message = `${this.$t('message.success.delete')} ${this.$t('label.ip.range')}`
+        this.$message.success(message)
+      }).catch((error) => {
+        this.$notifyError(error)
       }).finally(() => {
         this.fetchData()
       })
diff --git a/ui/src/views/network/GuestVlanNetworksTab.vue b/ui/src/views/network/GuestVlanNetworksTab.vue
index 2258eed..657880b 100644
--- a/ui/src/views/network/GuestVlanNetworksTab.vue
+++ b/ui/src/views/network/GuestVlanNetworksTab.vue
@@ -25,13 +25,15 @@
     :pagination="false"
     :loading="fetchLoading"
   >
-    <template #name="{ text, record }">
-      <router-link v-if="record.issystem === false && !record.projectid" :to="{ path: '/guestnetwork/' + record.id }" >{{ text }}</router-link>
-      <router-link v-else-if="record.issystem === false && record.projectid" :to="{ path: '/guestnetwork/' + record.id, query: { projectid: record.projectid}}" >{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
-    </template>
-    <template #state="{ record }">
-      <status :text="record.state" displayText></status>
+    <template #bodyCell="{ column, text, record }">
+      <template v-if="column.key === 'name'">
+        <router-link v-if="record.issystem === false && !record.projectid" :to="{ path: '/guestnetwork/' + record.id }" >{{ text }}</router-link>
+        <router-link v-else-if="record.issystem === false && record.projectid" :to="{ path: '/guestnetwork/' + record.id, query: { projectid: record.projectid}}" >{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
+      <template v-if="column.key === 'state'">
+        <status :text="record.state" displayText></status>
+      </template>
     </template>
   </a-table>
 </template>
@@ -61,17 +63,17 @@
       networks: [],
       columns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
           title: this.$t('label.type'),
           dataIndex: 'type'
         },
         {
-          title: this.$t('label.state'),
-          slots: { customRender: 'state' }
+          key: 'state',
+          title: this.$t('label.state')
         },
         {
           title: this.$t('label.broadcasturi'),
diff --git a/ui/src/views/network/IngressEgressRuleConfigure.vue b/ui/src/views/network/IngressEgressRuleConfigure.vue
index c1e82a8..adf013c 100644
--- a/ui/src/views/network/IngressEgressRuleConfigure.vue
+++ b/ui/src/views/network/IngressEgressRuleConfigure.vue
@@ -38,13 +38,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp">{{ capitalise($t('label.tcp')) }}</a-select-option>
-            <a-select-option value="udp">{{ capitalise($t('label.udp')) }}</a-select-option>
-            <a-select-option value="icmp">{{ capitalise($t('label.icmp')) }}</a-select-option>
-            <a-select-option value="all">{{ capitalise($t('label.all')) }}</a-select-option>
-            <a-select-option value="protocolnumber">{{ capitalise($t('label.protocol.number')) }}</a-select-option>
+            <a-select-option value="tcp" :label="$t('label.tcp')">{{ capitalise($t('label.tcp')) }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ capitalise($t('label.udp')) }}</a-select-option>
+            <a-select-option value="icmp" :label="$t('label.icmp')">{{ capitalise($t('label.icmp')) }}</a-select-option>
+            <a-select-option value="all" :label="$t('label.all')">{{ capitalise($t('label.all')) }}</a-select-option>
+            <a-select-option value="protocolnumber" :label="$t('label.protocol.number')">{{ capitalise($t('label.protocol.number')) }}</a-select-option>
           </a-select>
         </div>
         <div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
@@ -94,38 +94,40 @@
       :dataSource="rules"
       :pagination="{ pageSizeOptions: ['10', '20', '40', '80', '100', '200'], showSizeChanger: true}"
       :rowKey="record => record.ruleid">
-      <template #protocol="{ record }">
-        {{ getCapitalise(record.protocol) }}
-      </template>
-      <template #account="{ record }">
-        <div v-if="record.account && record.securitygroupname">
-          {{ record.account }} - {{ record.securitygroupname }}
-        </div>
-      </template>
-      <template #startport="{text, record}">
-        <div v-if="!['tcp', 'udp', 'icmp'].includes(record.protocol)">{{ $t('label.all') }}</div>
-        <div v-else>{{ text }}</div>
-      </template>
-      <template #endport="{text, record}">
-        <div v-if="!['tcp', 'udp', 'icmp'].includes(record.protocol)">{{ $t('label.all') }}</div>
-        <div v-else>{{ text }}</div>
-      </template>
-      <template #actions="{ record }">
-        <tooltip-button :tooltip="$t('label.edit.tags')" icon="tag-outlined" buttonClass="rule-action" @onClick="() => openTagsModal(record)" />
-        <a-popconfirm
-          :title="$t('label.delete') + '?'"
-          @confirm="handleDeleteRule(record)"
-          :okText="$t('label.yes')"
-          :cancelText="$t('label.no')"
-        >
-          <tooltip-button
-            :disabled="!('revokeSecurityGroupIngress' in $store.getters.apis) && !('revokeSecurityGroupEgress' in $store.getters.apis)"
-            :tooltip="$t('label.delete')"
-            type="primary"
-            :danger="true"
-            icon="delete-outlined"
-            buttonClass="rule-action" />
-        </a-popconfirm>
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'protocol'">
+          {{ getCapitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'account'">
+          <div v-if="record.account && record.securitygroupname">
+            {{ record.account }} - {{ record.securitygroupname }}
+          </div>
+        </template>
+        <template v-if="column.key === 'startport'">
+          <div v-if="!['tcp', 'udp', 'icmp'].includes(record.protocol)">{{ $t('label.all') }}</div>
+          <div v-else>{{ text }}</div>
+        </template>
+        <template v-if="column.key === 'endport'">
+          <div v-if="!['tcp', 'udp', 'icmp'].includes(record.protocol)">{{ $t('label.all') }}</div>
+          <div v-else>{{ text }}</div>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button :tooltip="$t('label.edit.tags')" icon="tag-outlined" buttonClass="rule-action" @onClick="() => openTagsModal(record)" />
+          <a-popconfirm
+            :title="$t('label.delete') + '?'"
+            @confirm="handleDeleteRule(record)"
+            :okText="$t('label.yes')"
+            :cancelText="$t('label.no')"
+          >
+            <tooltip-button
+              :disabled="!('revokeSecurityGroupIngress' in $store.getters.apis) && !('revokeSecurityGroupEgress' in $store.getters.apis)"
+              :tooltip="$t('label.delete')"
+              type="primary"
+              :danger="true"
+              icon="delete-outlined"
+              buttonClass="rule-action" />
+          </a-popconfirm>
+        </template>
       </template>
     </a-table>
 
@@ -223,18 +225,18 @@
       pagesize: 10,
       columns: [
         {
-          title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol',
+          title: this.$t('label.protocol')
         },
         {
+          key: 'startport',
           title: this.$t('label.startport'),
-          dataIndex: 'startport',
-          slots: { customRender: 'startport' }
+          dataIndex: 'startport'
         },
         {
+          key: 'endport',
           title: this.$t('label.endport'),
-          dataIndex: 'endport',
-          slots: { customRender: 'endport' }
+          dataIndex: 'endport'
         },
         {
           title: this.$t('label.icmptype'),
@@ -249,12 +251,12 @@
           dataIndex: 'cidr'
         },
         {
-          title: this.$t('label.account.and.security.group'),
-          slots: { customRender: 'account' }
+          key: 'account',
+          title: this.$t('label.account.and.security.group')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ],
       isSubmitted: false
@@ -464,7 +466,6 @@
     },
     openTagsModal (rule) {
       this.selectedRule = rule
-      this.initForm()
       this.fetchTags(this.selectedRule)
       this.tagsModalVisible = true
     },
diff --git a/ui/src/views/network/InternalLBAssignVmForm.vue b/ui/src/views/network/InternalLBAssignVmForm.vue
index 2e48689..8eb030c 100644
--- a/ui/src/views/network/InternalLBAssignVmForm.vue
+++ b/ui/src/views/network/InternalLBAssignVmForm.vue
@@ -40,14 +40,19 @@
               v-focus="!addVmModalNicLoading && iLb.virtualmachineid[index] === vm.id && index === 0"
               v-else-if="!addVmModalNicLoading && iLb.virtualmachineid[index] === vm.id"
               mode="multiple"
+              style="min-width: 200px;"
               v-model:value="iLb.vmguestip[index]"
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="(nic, nicIndex) in nics[index]" :key="nic" :value="nic">
-                {{ nic }}{{ nicIndex === 0 ? ` (${this.$t('label.primary')})` : null }}
+              <a-select-option
+                v-for="(nic, nicIndex) in nics[index]"
+                :key="nic"
+                :value="nic"
+                :label="nic">
+                {{ nic }}{{ nicIndex === 0 ? ` (${this.$t('label.primary')})` : '' }}
               </a-select-option>
             </a-select>
           </span>
diff --git a/ui/src/views/network/InternalLBAssignedVmTab.vue b/ui/src/views/network/InternalLBAssignedVmTab.vue
index 67211f0..035af0e 100644
--- a/ui/src/views/network/InternalLBAssignedVmTab.vue
+++ b/ui/src/views/network/InternalLBAssignedVmTab.vue
@@ -25,23 +25,25 @@
       :rowKey="item => item.id"
       :pagination="false"
     >
-      <template #displayname="{ record }">
-        <router-link :to="{ path: '/vm/' + record.id }">{{ record.displayname || record.name }}</router-link>
-      </template>
-      <template #ipaddress="{ record }">
-        <span v-for="nic in record.nic" :key="nic.id">
-          <span v-if="nic.networkid === resource.networkid">
-            {{ nic.ipaddress }} <br/>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'displayname'">
+          <router-link :to="{ path: '/vm/' + record.id }">{{ record.displayname || record.name }}</router-link>
+        </template>
+        <template v-if="column.key === 'ipaddress'">
+          <span v-for="nic in record.nic" :key="nic.id">
+            <span v-if="nic.networkid === resource.networkid">
+              {{ nic.ipaddress }} <br/>
+            </span>
           </span>
-        </span>
-      </template>
-      <template #remove="{ record }">
-        <tooltip-button
-          :tooltip="$t('label.remove.vm.from.lb')"
-          type="primary"
-          :danger="true"
-          icon="delete-outlined"
-          @onClick="removeVmFromLB(record)" />
+        </template>
+        <template v-if="column.key === 'remove'">
+          <tooltip-button
+            :tooltip="$t('label.remove.vm.from.lb')"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="removeVmFromLB(record)" />
+        </template>
       </template>
       <a-divider />
     </a-table>
@@ -86,18 +88,18 @@
       totalInstances: 0,
       columns: [
         {
+          key: 'displayname',
           title: this.$t('label.name'),
-          dataIndex: 'displayname',
-          slots: { customRender: 'displayname' }
+          dataIndex: 'displayname'
         },
         {
+          key: 'ipaddress',
           title: this.$t('label.ipaddress'),
-          dataIndex: 'ipaddress',
-          slots: { customRender: 'ipaddress' }
+          dataIndex: 'ipaddress'
         },
         {
-          title: '',
-          slots: { customRender: 'remove' }
+          key: 'remove',
+          title: ''
         }
       ]
     }
diff --git a/ui/src/views/network/IpAddressesTab.vue b/ui/src/views/network/IpAddressesTab.vue
index bc61d65..37b5b21 100644
--- a/ui/src/views/network/IpAddressesTab.vue
+++ b/ui/src/views/network/IpAddressesTab.vue
@@ -47,12 +47,12 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option key="all" value="">
             {{ $t('label.view.all') }}
           </a-select-option>
-          <a-select-option v-for="network in networksList" :key="network.id" :value="network.id">
+          <a-select-option v-for="network in networksList" :key="network.id" :value="network.id" :label="network.name">
             {{ network.name }}
           </a-select-option>
         </a-select>
@@ -65,35 +65,57 @@
         :rowKey="item => item.id"
         :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
         :pagination="false" >
-        <template #ipaddress="{ text, record }">
-          <router-link v-if="record.forvirtualnetwork === true" :to="{ path: '/publicip/' + record.id }" >{{ text }} </router-link>
-          <div v-else>{{ text }}</div>
-          <a-tag v-if="record.issourcenat === true">source-nat</a-tag>
-        </template>
+        <template #bodyCell="{ column, text, record }">
+          <template v-if="column.key === 'ipaddress'">
+            <router-link v-if="record.forvirtualnetwork === true" :to="{ path: '/publicip/' + record.id }" >{{ text }}&nbsp;</router-link>
+            <div v-else>{{ text }}</div>
+            <template v-if="record.issourcenat === true">
+              <a-tag>{{ $t('label.sourcenat') }}</a-tag>
+            </template>
+            <template v-else-if="record.isstaticnat === true">
+              <a-tag>{{ $t('label.staticnat') }}</a-tag>
+            </template>
+            <template v-else-if="record.hasrules === false">
+              <tooltip-button
+                v-if="record.forvirtualnetwork === true"
+                :tooltip="$t('label.action.set.as.source.nat.ip')"
+                type="primary"
+                :danger="false"
+                icon="aim-outlined"
+                :disabled="!('updateNetwork' in $store.getters.apis)"
+                @onClick="showChangeSourceNat(record)"></tooltip-button>
+            </template>
+            <template v-else><!-- -if="record.hasrules === true" -->
+              <Tooltip placement="topLeft" :title="$t('message.sourcenatip.change.inhibited')" >
+                <a-tag>{{ $t('label.hasrules') }}</a-tag>
+              </Tooltip>
+            </template>
+          </template>
 
-        <template #state="{ record }">
-          <status :text="record.state" displayText />
-        </template>
+          <template v-if="column.key === 'state'">
+            <status :text="record.state" displayText />
+          </template>
 
-        <template #virtualmachineid="{ record }">
-          <desktop-outlined v-if="record.virtualmachineid" />
-          <router-link :to="{ path: '/vm/' + record.virtualmachineid }" > {{ record.virtualmachinename || record.virtualmachineid }} </router-link>
-        </template>
+          <template v-if="column.key === 'virtualmachineid'">
+            <desktop-outlined v-if="record.virtualmachineid" />
+            <router-link :to="{ path: getVmRouteUsingType(record) + record.virtualmachineid }" > {{ record.virtualmachinename || record.virtualmachineid }} </router-link>
+          </template>
 
-        <template #associatednetworkname="{ record }">
-          <router-link v-if="record.forvirtualnetwork === true" :to="{ path: '/guestnetwork/' + record.associatednetworkid }" > {{ record.associatednetworkname || record.associatednetworkid }} </router-link>
-          <div v-else>{{ record.networkname }}</div>
-        </template>
+          <template v-if="column.key === 'associatednetworkname'">
+            <router-link v-if="record.forvirtualnetwork === true" :to="{ path: '/guestnetwork/' + record.associatednetworkid }" > {{ record.associatednetworkname || record.associatednetworkid }} </router-link>
+            <div v-else>{{ record.networkname }}</div>
+          </template>
 
-        <template #action="{ record }">
-          <tooltip-button
-            v-if="record.issourcenat !== true && record.forvirtualnetwork === true"
-            :tooltip="$t('label.action.release.ip')"
-            type="primary"
-            :danger="true"
-            icon="delete-outlined"
-            :disabled="!('disassociateIpAddress' in $store.getters.apis)"
-            @onClick="releaseIpAddress(record)" />
+          <template v-if="column.key === 'actions'">
+            <tooltip-button
+              v-if="record.issourcenat !== true && record.forvirtualnetwork === true"
+              :tooltip="$t('label.action.release.ip')"
+              type="primary"
+              :danger="true"
+              icon="delete-outlined"
+              :disabled="!('disassociateIpAddress' in $store.getters.apis)"
+              @onClick="releaseIpAddress(record)" />
+          </template>
         </template>
       </a-table>
       <a-divider/>
@@ -133,11 +155,12 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option
                 v-for="ip in listPublicIpAddress"
-                :key="ip.ipaddress">{{ ip.ipaddress }} ({{ ip.state }})</a-select-option>
+                :key="ip.ipaddress"
+                :label="ip.ipaddress + '(' + ip.state + ')'">{{ ip.ipaddress }} ({{ ip.state }})</a-select-option>
             </a-select>
           </a-form-item>
           <div :span="24" class="action-button">
@@ -147,6 +170,24 @@
         </a-form>
       </a-spin>
     </a-modal>
+    <a-modal
+      v-if="changeSourceNat"
+      :title="$t('message.sourcenatip.change.warning')"
+      :visible="changeSourceNat"
+      :closable="true"
+      :footer="null"
+      @cancel="cancelChangeSourceNat"
+      centered
+      :disabled="!('updateNetwork' in $store.getters.apis)"
+      width="450px">
+      <template>
+        <a-alert :message="$t('message.sourcenatip.change.warning')" type="warning" />
+      </template>
+      <div :span="24" class="action-button">
+        <a-button @click="cancelChangeSourceNat">{{ $t('label.cancel') }}</a-button>
+        <a-button ref="submit" type="primary" @click="setSourceNatIp(record)">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-modal>
     <bulk-action-view
       v-if="showConfirmationAction || showGroupActionModal"
       :showConfirmationAction="showConfirmationAction"
@@ -164,6 +205,7 @@
       @close-modal="closeModal" />
   </div>
 </template>
+
 <script>
 import { api } from '@/api'
 import Status from '@/components/widgets/Status'
@@ -204,7 +246,7 @@
       showGroupActionModal: false,
       selectedItems: [],
       selectedColumns: [],
-      filterColumns: ['Action'],
+      filterColumns: ['Actions'],
       showConfirmationAction: false,
       message: {
         title: this.$t('label.action.bulk.release.public.ip.address'),
@@ -212,34 +254,35 @@
       },
       columns: [
         {
+          key: 'ipaddress',
           title: this.$t('label.ipaddress'),
-          dataIndex: 'ipaddress',
-          slots: { customRender: 'ipaddress' }
+          dataIndex: 'ipaddress'
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
+          key: 'virtualmachineid',
           title: this.$t('label.vm'),
-          dataIndex: 'virtualmachineid',
-          slots: { customRender: 'virtualmachineid' }
+          dataIndex: 'virtualmachineid'
         },
         {
+          key: 'associatednetworkname',
           title: this.$t('label.network'),
-          dataIndex: 'associatednetworkname',
-          slots: { customRender: 'associatednetworkname' }
+          dataIndex: 'associatednetworkname'
         },
         {
-          title: '',
-          slots: { customRender: 'action' }
+          key: 'actions',
+          title: ''
         }
       ],
       showAcquireIp: false,
       acquireLoading: false,
       acquireIp: null,
-      listPublicIpAddress: []
+      listPublicIpAddress: [],
+      changeSourceNat: false
     }
   },
   created () {
@@ -312,6 +355,50 @@
         return selection.indexOf(item.id) !== -1
       }))
     },
+    setSourceNatIp (ipaddress) {
+      if (this.settingsourcenat) return
+      if (this.$route.path.startsWith('/vpc')) {
+        this.updateVpc(ipaddress)
+      } else {
+        this.updateNetwork(ipaddress)
+      }
+    },
+    updateNetwork (ipaddress) {
+      const params = {}
+      params.sourcenatipaddress = this.sourceNatIp.ipaddress
+      params.id = this.resource.id
+      this.settingsourcenat = true
+      api('updateNetwork', params).then(response => {
+        this.fetchData()
+      }).catch(error => {
+        this.$notification.error({
+          message: `${this.$t('label.error')} ${error.response.status}`,
+          description: error.response.data.updatenetworkresponse.errortext || error.response.data.errorresponse.errortext,
+          duration: 0
+        })
+      }).finally(() => {
+        this.settingsourcenat = false
+        this.cancelChangeSourceNat()
+      })
+    },
+    updateVpc (ipaddress) {
+      const params = {}
+      params.sourcenatipaddress = this.sourceNatIp.ipaddress
+      params.id = this.resource.id
+      this.settingsourcenat = true
+      api('updateVPC', params).then(response => {
+        this.fetchData()
+      }).catch(error => {
+        this.$notification.error({
+          message: `${this.$t('label.error')} ${error.response.status}`,
+          description: error.response.data.updatevpcresponse.errortext || error.response.data.errorresponse.errortext,
+          duration: 0
+        })
+      }).finally(() => {
+        this.settingsourcenat = false
+        this.cancelChangeSourceNat()
+      })
+    },
     resetSelection () {
       this.setSelection([])
     },
@@ -366,8 +453,8 @@
         this.onCloseModal()
       }).catch(error => {
         this.$notification.error({
-          message: `${this.$t('label.error')} ${error.response.status}`,
-          description: error.response.data.associateipaddressresponse.errortext || error.response.data.errorresponse.errortext,
+          message: this.$t('message.request.failed'),
+          description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
           duration: 0
         })
       }).finally(() => {
@@ -385,9 +472,9 @@
     releaseIpAddresses (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
@@ -439,6 +526,14 @@
         })
       })
     },
+    getVmRouteUsingType (record) {
+      switch (record.virtualmachinetype) {
+        case 'DomainRouter' : return '/router/'
+        case 'ConsoleProxy' :
+        case 'SecondaryStorageVm': return '/systemvm/'
+        default: return '/vm/'
+      }
+    },
     async onShowAcquireIp () {
       this.showAcquireIp = true
       this.acquireLoading = true
@@ -471,6 +566,13 @@
     },
     closeModal () {
       this.showConfirmationAction = false
+    },
+    showChangeSourceNat (ipaddress) {
+      this.changeSourceNat = true
+      this.sourceNatIp = ipaddress
+    },
+    cancelChangeSourceNat () {
+      this.changeSourceNat = false
     }
   }
 }
diff --git a/ui/src/views/network/Ipv6FirewallRulesTab.vue b/ui/src/views/network/Ipv6FirewallRulesTab.vue
index cacaaf2..ffdcc9d 100644
--- a/ui/src/views/network/Ipv6FirewallRulesTab.vue
+++ b/ui/src/views/network/Ipv6FirewallRulesTab.vue
@@ -41,13 +41,13 @@
           <a-select
             v-model:value="newRule.traffictype"
             showSearch
-            optionFilterProp="children"
+            optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             @change="val => { handleTrafficTypeChange(val) }" >
-            <a-select-option value="ingress">{{ $t('label.ingress') }}</a-select-option>
-            <a-select-option value="egress">{{ $t('label.egress') }}</a-select-option>
+            <a-select-option value="ingress" :label="$t('label.ingress')">{{ $t('label.ingress') }}</a-select-option>
+            <a-select-option value="egress" :label="$t('label.egress')">{{ $t('label.egress') }}</a-select-option>
           </a-select>
         </div>
         <div class="form__item">
@@ -57,11 +57,11 @@
             style="width: 100%;"
             @change="resetRulePorts"
             showSearch
-            optionFilterProp="children"
+            optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="opt in protocols" :key="opt">
+            <a-select-option v-for="opt in protocols" :key="opt" :label="$t('label.' + opt)">
               {{ $t('label.' + opt) }}
             </a-select-option>
           </a-select>
@@ -107,27 +107,29 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.id">
-      <template #traffictype="{record}">
-        {{ record.traffictype }}
-      </template>
-      <template #protocol="{record}">
-        {{ capitalise(record.protocol) }}
-      </template>
-      <template #startport="{record}">
-        {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : 'All' }}
-      </template>
-      <template #endport="{record}">
-        {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : 'All' }}
-      </template>
-      <template #actions="{record}">
-        <tooltip-button
-          :tooltip="$t('label.delete')"
-          :disabled="!('deleteIpv6FirewallRule' in $store.getters.apis)"
-          type="primary"
-          :danger="true"
-          icon="delete-outlined"
-          buttonClass="rule-action"
-          @click="deleteRule(record)" />
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'traffictype'">
+          {{ record.traffictype }}
+        </template>
+        <template v-if="column.key === 'protocol'">
+          {{ capitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'startport'">
+          {{ record.icmptype || record.startport >= 0 ? record.icmptype || record.startport : 'All' }}
+        </template>
+        <template v-if="column.key === 'endport'">
+          {{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : 'All' }}
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            :tooltip="$t('label.delete')"
+            :disabled="!('deleteIpv6FirewallRule' in $store.getters.apis)"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            buttonClass="rule-action"
+            @click="deleteRule(record)" />
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -190,7 +192,7 @@
       showGroupActionModal: false,
       selectedItems: [],
       selectedColumns: [],
-      filterColumns: ['Action'],
+      filterColumns: ['Actions'],
       showConfirmationAction: false,
       message: {
         title: this.$t('label.action.bulk.delete.ip.v6.firewall.rules'),
@@ -222,24 +224,24 @@
           dataIndex: 'destcidrlist'
         },
         {
-          title: this.$t('label.traffictype'),
-          slots: { customRender: 'traffictype' }
+          key: 'traffictype',
+          title: this.$t('label.traffictype')
         },
         {
-          title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol',
+          title: this.$t('label.protocol')
         },
         {
-          title: this.$t('label.icmptype.start.port'),
-          slots: { customRender: 'startport' }
+          key: 'startport',
+          title: this.$t('label.icmptype.start.port')
         },
         {
-          title: this.$t('label.icmpcode.end.port'),
-          slots: { customRender: 'endport' }
+          key: 'endport',
+          title: this.$t('label.icmpcode.end.port')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ],
       protocols: [
@@ -312,9 +314,9 @@
     deleteRules (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
diff --git a/ui/src/views/network/LoadBalancing.vue b/ui/src/views/network/LoadBalancing.vue
index 90ed4c0..eeec232 100644
--- a/ui/src/views/network/LoadBalancing.vue
+++ b/ui/src/views/network/LoadBalancing.vue
@@ -47,11 +47,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="roundrobin">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
-            <a-select-option value="leastconn">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
-            <a-select-option value="source">{{ $t('label.lb.algorithm.source') }}</a-select-option>
+            <a-select-option value="roundrobin" :label="$t('label.lb.algorithm.roundrobin')">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
+            <a-select-option value="leastconn" :label="$t('label.lb.algorithm.leastconn')">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
+            <a-select-option value="source" :label="$t('label.lb.algorithm.source')">{{ $t('label.lb.algorithm.source') }}</a-select-option>
           </a-select>
         </div>
         <div class="form__item">
@@ -62,11 +62,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp-proxy">{{ $t('label.tcp.proxy') }}</a-select-option>
-            <a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
-            <a-select-option value="udp">{{ $t('label.udp') }}</a-select-option>
+            <a-select-option value="tcp-proxy" :label="$t('label.tcp.proxy')">{{ $t('label.tcp.proxy') }}</a-select-option>
+            <a-select-option value="tcp" :label="$t('label.tcp')">{{ $t('label.tcp') }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ $t('label.udp') }}</a-select-option>
           </a-select>
         </div>
         <div class="form__item">
@@ -76,20 +76,26 @@
             defaultValue="no"
             style="min-width: 100px"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option value="yes">{{ $t('label.yes') }}</a-select-option>
             <a-select-option value="no">{{ $t('label.no') }}</a-select-option>
           </a-select>
         </div>
-        <div class="form__item" v-if="!newRule.autoscale || newRule.autoscale === 'no' || ('vpcid' in this.resource && !('associatednetworkid' in this.resource))">
+        <div class="form__item" v-if="!newRule.autoscale || newRule.autoscale === 'no'">
           <div class="form__label" style="white-space: nowrap;">{{ $t('label.add.vms') }}</div>
           <a-button :disabled="!('createLoadBalancerRule' in $store.getters.apis)" type="primary" @click="handleOpenAddVMModal">
             {{ $t('label.add') }}
           </a-button>
         </div>
+        <div class="form__item" v-else-if="newRule.autoscale === 'yes' && ('vpcid' in this.resource && !this.associatednetworkid)">
+          <div class="form__label" style="white-space: nowrap;">{{ $t('label.select.tier') }}</div>
+          <a-button :disabled="!('createLoadBalancerRule' in $store.getters.apis)" type="primary" @click="handleOpenAddNetworkModal">
+            {{ $t('label.add') }}
+          </a-button>
+        </div>
         <div class="form__item" v-else-if="newRule.autoscale === 'yes'">
           <div class="form__label" style="white-space: nowrap;">{{ $t('label.add') }}</div>
           <a-button :disabled="!('createLoadBalancerRule' in $store.getters.apis)" type="primary" @click="handleAddNewRule">
@@ -118,42 +124,61 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.id">
-      <template #cidrlist="{ record }">
-        <span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
-      </template>
-      <template #algorithm="{ record }">
-        {{ returnAlgorithmName(record.algorithm) }}
-      </template>
-      <template #protocol="{record}">
-        {{ getCapitalise(record.protocol) }}
-      </template>
-      <template #stickiness="{record}">
-        <a-button @click="() => openStickinessModal(record.id)">
-          {{ returnStickinessLabel(record.id) }}
-        </a-button>
-      </template>
-      <template #autoscale="{record}">
-        <div>
-          <router-link :to="{ path: '/autoscalevmgroup/' + record.autoscalevmgroup.id }" v-if='record.autoscalevmgroup'>
-            <a-button>{{ $t('label.view') }}</a-button>
-          </router-link>
-          <router-link :to="{ path: '/action/createAutoScaleVmGroup', query: { networkid: record.networkid, lbruleid : record.id } }" v-else-if='!record.ruleInstances'>
-            <a-button>{{ $t('label.new') }}</a-button>
-          </router-link>
-        </div>
-      </template>
-      <template #healthmonitor="{ record }">
-        <a-button @click="() => openHealthMonitorModal(record.id)">
-          {{ returnHealthMonitorLabel(record.id) }}
-        </a-button>
-      </template>
-      <template #add="{record}">
-        <a-button type="primary" @click="() => { selectedRule = record; handleOpenAddVMModal() }" v-if='!record.autoscalevmgroup'>
-          <template #icon>
-            <plus-outlined />
-          </template>
-          {{ $t('label.add') }}
-        </a-button>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'cidrlist'">
+          <span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
+        </template>
+        <template v-if="column.key === 'algorithm'">
+          {{ returnAlgorithmName(record.algorithm) }}
+        </template>
+        <template v-if="column.key === 'protocol'">
+          {{ getCapitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'stickiness'">
+          <a-button @click="() => openStickinessModal(record.id)">
+            {{ returnStickinessLabel(record.id) }}
+          </a-button>
+        </template>
+        <template v-if="column.key === 'autoscale'">
+          <div>
+            <router-link :to="{ path: '/autoscalevmgroup/' + record.autoscalevmgroup.id }" v-if='record.autoscalevmgroup'>
+              <a-button>{{ $t('label.view') }}</a-button>
+            </router-link>
+            <router-link :to="{ path: '/action/createAutoScaleVmGroup', query: { networkid: record.networkid, lbruleid : record.id } }" v-else-if='!record.ruleInstances'>
+              <a-button>{{ $t('label.new') }}</a-button>
+            </router-link>
+          </div>
+        </template>
+        <template v-if="column.key === 'healthmonitor'">
+          <a-button @click="() => openHealthMonitorModal(record.id)">
+            {{ returnHealthMonitorLabel(record.id) }}
+          </a-button>
+        </template>
+        <template v-if="column.key === 'add'">
+          <a-button v-if="!record.autoscalevmgroup" type="primary" @click="() => { selectedRule = record; handleOpenAddVMModal() }">
+            <template #icon><plus-outlined /></template>
+              {{ $t('label.add') }}
+          </a-button>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="actions">
+            <tooltip-button :tooltip="$t('label.edit')" icon="edit-outlined" @onClick="() => openEditRuleModal(record)" />
+            <tooltip-button :tooltip="$t('label.edit.tags')" :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" icon="tag-outlined" @onClick="() => openTagsModal(record.id)" />
+            <a-popconfirm
+              :title="$t('label.delete') + '?'"
+              @confirm="handleDeleteRule(record)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')"
+            >
+              <tooltip-button
+                :tooltip="$t('label.delete')"
+                :disabled="!('deleteLoadBalancerRule' in $store.getters.apis)"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined" />
+            </a-popconfirm>
+          </div>
+        </template>
       </template>
       <template #expandedRowRender="{ record }">
         <div class="rule-instance-list">
@@ -178,25 +203,6 @@
           </div>
         </div>
       </template>
-      <template #actions="{record}">
-        <div class="actions">
-          <tooltip-button :tooltip="$t('label.edit')" icon="edit-outlined" @onClick="() => openEditRuleModal(record)" />
-          <tooltip-button :tooltip="$t('label.edit.tags')" :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" icon="tag-outlined" @onClick="() => openTagsModal(record.id)" />
-          <a-popconfirm
-            :title="$t('label.delete') + '?'"
-            @confirm="handleDeleteRule(record)"
-            :okText="$t('label.yes')"
-            :cancelText="$t('label.no')"
-          >
-            <tooltip-button
-              :tooltip="$t('label.delete')"
-              :disabled="!('deleteLoadBalancerRule' in $store.getters.apis) || record.autoscalevmgroup"
-              type="primary"
-              :danger="true"
-              icon="delete-outlined" />
-          </a-popconfirm>
-        </div>
-      </template>
     </a-table>
     <a-pagination
       class="pagination"
@@ -267,7 +273,6 @@
     </a-modal>
 
     <a-modal
-      :title="$t('label.configure.sticky.policy')"
       :visible="stickinessModalVisible"
       :footer="null"
       :afterClose="closeModal"
@@ -276,6 +281,16 @@
       :okButtonProps="{ props: {htmlType: 'submit'}}"
       @cancel="stickinessModalVisible = false">
 
+      <template #title>
+        <span>{{ $t('label.configure.sticky.policy') }}</span>
+        <a
+          style="margin-left: 5px"
+          :href="$config.docBase + '/adminguide/networking/external_firewalls_and_load_balancers.html#sticky-session-policies-for-load-balancer-rules'"
+          target="_blank">
+          <question-circle-outlined />
+        </a>
+      </template>
+
       <span v-show="stickinessModalLoading" class="modal-loading">
         <loading-outlined />
       </span>
@@ -288,7 +303,10 @@
         v-ctrl-enter="handleSubmitStickinessForm"
         class="custom-ant-form"
        >
-        <a-form-item name="methodname" ref="methodname" :label="$t('label.stickiness.method')">
+        <a-form-item name="methodname" ref="methodname">
+          <template #label>
+            <tooltip-label :title="$t('label.stickiness.method')" :tooltip="createLoadBalancerStickinessPolicyParams.methodname.description" :tooltip-placement="'right'"/>
+          </template>
           <a-select
             v-focus="true"
             v-model:value="form.methodname"
@@ -296,69 +314,77 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="LbCookie">{{ $t('label.lb.cookie') }}</a-select-option>
-            <a-select-option value="AppCookie">{{ $t('label.app.cookie') }}</a-select-option>
-            <a-select-option value="SourceBased">{{ $t('label.source.based') }}</a-select-option>
-            <a-select-option value="none">{{ $t('label.none') }}</a-select-option>
+            <a-select-option value="LbCookie" :label="$t('label.lb.cookie')">{{ $t('label.lb.cookie') }}</a-select-option>
+            <a-select-option value="AppCookie" :label="$t('label.app.cookie')">{{ $t('label.app.cookie') }}</a-select-option>
+            <a-select-option value="SourceBased" :label="$t('label.source.based')">{{ $t('label.source.based') }}</a-select-option>
+            <a-select-option value="none" :label="$t('label.none')">{{ $t('label.none') }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item
           name="name"
           ref="name"
-          :label="$t('label.sticky.name')"
           v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
             'AppCookie' || stickinessPolicyMethod === 'SourceBased'">
           <a-input v-model:value="form.name" />
+          <template #label>
+            <tooltip-label :title="$t('label.sticky.name')" :tooltip="createLoadBalancerStickinessPolicyParams.name.description" :tooltip-placement="'right'"/>
+          </template>
         </a-form-item>
-        <a-form-item
-          name="cookieName"
-          ref="cookieName"
-          :label="$t('label.sticky.cookie-name')"
-          v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
-            'AppCookie'">
-          <a-input v-model:value="form.cookieName" />
-        </a-form-item>
-        <a-form-item
-          name="mode"
-          ref="mode"
-          :label="$t('label.sticky.mode')"
-          v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
-            'AppCookie'">
-          <a-input v-model:value="form.mode" />
-        </a-form-item>
-        <a-form-item name="nocache" ref="nocache" :label="$t('label.sticky.nocache')" v-show="stickinessPolicyMethod === 'LbCookie'">
-          <a-checkbox v-model:checked="form.nocache"></a-checkbox>
-        </a-form-item>
-        <a-form-item name="indirect" ref="indirect" :label="$t('label.sticky.indirect')" v-show="stickinessPolicyMethod === 'LbCookie'">
-          <a-checkbox v-model:checked="form.indirect"></a-checkbox>
-        </a-form-item>
-        <a-form-item name="postonly" ref="postonly" :label="$t('label.sticky.postonly')" v-show="stickinessPolicyMethod === 'LbCookie'">
-          <a-checkbox v-model:checked="form.postonly"></a-checkbox>
-        </a-form-item>
-        <a-form-item name="domain" ref="domain" :label="$t('label.domain')" v-show="stickinessPolicyMethod === 'LbCookie'">
-          <a-input v-model:value="form.domain" />
-        </a-form-item>
-        <a-form-item name="length" ref="length" :label="$t('label.sticky.length')" v-show="stickinessPolicyMethod === 'AppCookie'">
-          <a-input v-model:value="form.length" type="number" />
-        </a-form-item>
-        <a-form-item name="holdtime" ref="holdtime" :label="$t('label.sticky.holdtime')" v-show="stickinessPolicyMethod === 'AppCookie'">
-          <a-input v-model:value="form.holdtime" type="number" />
-        </a-form-item>
-        <a-form-item name="requestLearn" ref="requestLearn" :label="$t('label.sticky.request-learn')" v-show="stickinessPolicyMethod === 'AppCookie'">
-          <a-checkbox v-model:checked="form.requestLearn"></a-checkbox>
-        </a-form-item>
-        <a-form-item name="prefix" ref="prefix" :label="$t('label.sticky.prefix')" v-show="stickinessPolicyMethod === 'AppCookie'">
-          <a-checkbox v-model:checked="form.prefix"></a-checkbox>
-        </a-form-item>
-        <a-form-item name="tablesize" ref="tablesize" :label="$t('label.sticky.tablesize')" v-show="stickinessPolicyMethod === 'SourceBased'">
-          <a-input v-model:value="form.tablesize" />
-        </a-form-item>
-        <a-form-item name="expire" ref="expire" :label="$t('label.sticky.expire')" v-show="stickinessPolicyMethod === 'SourceBased'">
-          <a-input v-model:value="form.expire" />
-        </a-form-item>
-
+        <div v-if="stickinessPolicyMethod !== 'none'">
+          <br/>
+          {{ $t('message.loadbalancer.stickypolicy.configuration') }}
+          <br/>
+          <a-card>
+            <a-form-item
+              name="cookieName"
+              ref="cookieName"
+              :label="$t('label.sticky.cookie-name')"
+              v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
+                'AppCookie'">
+              <a-input v-model:value="form.cookieName" />
+            </a-form-item>
+            <a-form-item
+              name="mode"
+              ref="mode"
+              :label="$t('label.sticky.mode')"
+              v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
+                'AppCookie'">
+              <a-input v-model:value="form.mode" />
+            </a-form-item>
+            <a-form-item name="nocache" ref="nocache" :label="$t('label.sticky.nocache')" v-show="stickinessPolicyMethod === 'LbCookie'">
+              <a-checkbox v-model:checked="form.nocache"></a-checkbox>
+            </a-form-item>
+            <a-form-item name="indirect" ref="indirect" :label="$t('label.sticky.indirect')" v-show="stickinessPolicyMethod === 'LbCookie'">
+              <a-checkbox v-model:checked="form.indirect"></a-checkbox>
+            </a-form-item>
+            <a-form-item name="postonly" ref="postonly" :label="$t('label.sticky.postonly')" v-show="stickinessPolicyMethod === 'LbCookie'">
+              <a-checkbox v-model:checked="form.postonly"></a-checkbox>
+            </a-form-item>
+            <a-form-item name="domain" ref="domain" :label="$t('label.domain')" v-show="stickinessPolicyMethod === 'LbCookie'">
+              <a-input v-model:value="form.domain" />
+            </a-form-item>
+            <a-form-item name="length" ref="length" :label="$t('label.sticky.length')" v-show="stickinessPolicyMethod === 'AppCookie'">
+              <a-input v-model:value="form.length" type="number" />
+            </a-form-item>
+            <a-form-item name="holdtime" ref="holdtime" :label="$t('label.sticky.holdtime')" v-show="stickinessPolicyMethod === 'AppCookie'">
+              <a-input v-model:value="form.holdtime" type="number" />
+            </a-form-item>
+            <a-form-item name="requestLearn" ref="requestLearn" :label="$t('label.sticky.request-learn')" v-show="stickinessPolicyMethod === 'AppCookie'">
+              <a-checkbox v-model:checked="form.requestLearn"></a-checkbox>
+            </a-form-item>
+            <a-form-item name="prefix" ref="prefix" :label="$t('label.sticky.prefix')" v-show="stickinessPolicyMethod === 'AppCookie'">
+              <a-checkbox v-model:checked="form.prefix"></a-checkbox>
+            </a-form-item>
+            <a-form-item name="tablesize" ref="tablesize" :label="$t('label.sticky.tablesize')" v-show="stickinessPolicyMethod === 'SourceBased'">
+              <a-input v-model:value="form.tablesize" />
+            </a-form-item>
+            <a-form-item name="expire" ref="expire" :label="$t('label.sticky.expire')" v-show="stickinessPolicyMethod === 'SourceBased'">
+              <a-input v-model:value="form.expire" />
+            </a-form-item>
+          </a-card>
+        </div>
         <div :span="24" class="action-button">
           <a-button @click="stickinessModalVisible = false">{{ $t('label.cancel') }}</a-button>
           <a-button type="primary" ref="submit" @click="handleSubmitStickinessForm">{{ $t('label.ok') }}</a-button>
@@ -390,11 +416,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="roundrobin">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
-            <a-select-option value="leastconn">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
-            <a-select-option value="source">{{ $t('label.lb.algorithm.source') }}</a-select-option>
+            <a-select-option value="roundrobin" :label="$t('label.lb.algorithm.roundrobin')">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
+            <a-select-option value="leastconn" :label="$t('label.lb.algorithm.leastconn')">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
+            <a-select-option value="source" :label="$t('label.lb.algorithm.source')">{{ $t('label.lb.algorithm.source') }}</a-select-option>
           </a-select>
         </div>
         <div class="edit-rule__item">
@@ -404,11 +430,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp-proxy">{{ $t('label.tcp.proxy') }}</a-select-option>
-            <a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
-            <a-select-option value="udp">{{ $t('label.udp') }}</a-select-option>
+            <a-select-option value="tcp-proxy" :label="$t('label.tcp.proxy')">{{ $t('label.tcp.proxy') }}</a-select-option>
+            <a-select-option value="tcp" :label="$t('label.tcp')">{{ $t('label.tcp') }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ $t('label.udp') }}</a-select-option>
           </a-select>
         </div>
         <div :span="24" class="action-button">
@@ -441,12 +467,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="tier in tiers.data"
               :loading="tiers.loading"
-              :key="tier.id">
+              :key="tier.id"
+              :label="tier.displaytext">
               {{ tier.displaytext }}
             </a-select-option>
           </a-select>
@@ -467,33 +494,39 @@
           :pagination="false"
           :rowKey="record => record.id"
           :scroll="{ y: 300 }">
-          <template #name="{text, record, index}">
-            <span>
-              {{ text }}
-            </span>
-            <loading-outlined v-if="addVmModalNicLoading" />
-            <a-select
-              style="display: block"
-              v-else-if="!addVmModalNicLoading && newRule.virtualmachineid[index] === record.id"
-              mode="multiple"
-              v-model:value="newRule.vmguestip[index]"
-              showSearch
-              optionFilterProp="label"
-              :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }" >
-              <a-select-option v-for="(nic, nicIndex) in nics[index]" :key="nic" :value="nic">
-                {{ nic }}{{ nicIndex === 0 ? ` (${$t('label.primary')})` : null }}
-              </a-select-option>
-            </a-select>
-          </template>
+          <template #bodyCell="{ column, text, record, index }">
+            <template v-if="column.key === 'name'">
+              <span>
+                {{ text }}
+              </span>
+              <loading-outlined v-if="addVmModalNicLoading" />
+              <a-select
+                style="display: block"
+                v-else-if="!addVmModalNicLoading && newRule.virtualmachineid[index] === record.id"
+                mode="multiple"
+                v-model:value="newRule.vmguestip[index]"
+                showSearch
+                optionFilterProp="label"
+                :filterOption="(input, option) => {
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }" >
+                <a-select-option
+                  v-for="(nic, nicIndex) in nics[index]"
+                  :key="nic"
+                  :value="nic"
+                  :label="nic + nicIndex === 0 ? ` (${$t('label.primary')})` : null">
+                  {{ nic }}{{ nicIndex === 0 ? ` (${$t('label.primary')})` : null }}
+                </a-select-option>
+              </a-select>
+            </template>
 
-          <template #state="{text}">
-            <status :text="text ? text : ''" displayText></status>
-          </template>
+            <template v-if="column.key === 'state'">
+              <status :text="text ? text : ''" displayText></status>
+            </template>
 
-          <template #action="{text, record, index}" style="text-align: center" :text="text">
-            <a-checkbox v-model:value="record.id" @change="e => fetchNics(e, index)" :disabled="newRule.autoscale" />
+            <template v-if="column.key === 'actions'" style="text-align: center" :text="text">
+              <a-checkbox v-model:value="record.id" @change="e => fetchNics(e, index)" />
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -520,6 +553,71 @@
     </a-modal>
 
     <a-modal
+      :title="$t('label.select.tier')"
+      :maskClosable="false"
+      :closable="true"
+      v-if="addNetworkModalVisible"
+      :visible="addNetworkModalVisible"
+      class="network-modal"
+      width="60vw"
+      :footer="null"
+      @cancel="closeModal"
+    >
+      <div @keyup.ctrl.enter="handleAddNewRule">
+        <a-input-search
+          v-focus="!('vpcid' in resource && !('associatednetworkid' in resource))"
+          class="input-search"
+          :placeholder="$t('label.search')"
+          v-model:value="searchQuery"
+          allowClear
+          @search="onNetworkSearch" />
+        <a-table
+          size="small"
+          class="list-view"
+          :loading="addNetworkModalLoading"
+          :columns="networkColumns"
+          :dataSource="networks"
+          :pagination="false"
+          :rowKey="record => record.id"
+          :scroll="{ y: 300 }">
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.key === 'actions'">
+              <div style="text-align: center">
+                <a-radio-group
+                  class="radio-group"
+                  :key="record.id"
+                  v-model:value="this.selectedTierForAutoScaling"
+                  @change="($event) => this.selectedTierForAutoScaling = $event.target.value">
+                  <a-radio :value="record.id" />
+                </a-radio-group>
+              </div>
+            </template>
+          </template>
+        </a-table>
+        <a-pagination
+          class="pagination"
+          size="small"
+          :current="networkPage"
+          :pageSize="networkPageSize"
+          :total="networkCount"
+          :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
+          :pageSizeOptions="['10', '20', '40', '80', '100']"
+          @change="handleChangeNetworkPage"
+          @showSizeChange="handleChangeNetworkPageSize"
+          showSizeChanger>
+          <template #buildOptionText="props">
+            <span>{{ props.value }} / {{ $t('label.page') }}</span>
+          </template>
+        </a-pagination>
+
+        <div :span="24" class="action-button">
+          <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+          <a-button :disabled="this.selectedTierForAutoScaling === null" type="primary" ref="submit" @click="handleAddNewRule">{{ $t('label.ok') }}</a-button>
+        </div>
+      </div>
+    </a-modal>
+
+    <a-modal
       v-if="healthMonitorModal"
       :title="$t('label.configure.health.monitor')"
       :visible="healthMonitorModal"
@@ -540,9 +638,9 @@
             v-model:value="monitorForm.type"
             @change="(value) => { healthMonitorParams.type = value }"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
             <a-select-option value="PING">PING</a-select-option>
             <a-select-option value="TCP">TCP</a-select-option>
@@ -567,9 +665,9 @@
             v-focus="true"
             v-model:value="monitorForm.httpmethodtype"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }">
             <a-select-option value="GET">GET</a-select-option>
             <a-select-option value="HEAD">HEAD</a-select-option>
@@ -648,7 +746,7 @@
       showGroupActionModal: false,
       selectedItems: [],
       selectedColumns: [],
-      filterColumns: ['State', 'Action', 'Add VMs', 'Stickiness'],
+      filterColumns: ['State', 'Actions', 'Add VMs', 'Stickiness'],
       showConfirmationAction: false,
       message: {
         title: this.$t('label.action.bulk.delete.load.balancer.rules'),
@@ -705,36 +803,36 @@
           dataIndex: 'privateport'
         },
         {
-          title: this.$t('label.cidrlist'),
-          slots: { customRender: 'cidrlist' }
+          key: 'algorithm',
+          title: this.$t('label.algorithm')
         },
         {
-          title: this.$t('label.algorithm'),
-          slots: { customRender: 'algorithm' }
+          key: 'cidrlist',
+          title: this.$t('label.cidrlist')
         },
         {
-          title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol',
+          title: this.$t('label.protocol')
         },
         {
           title: this.$t('label.state'),
           dataIndex: 'state'
         },
         {
-          title: this.$t('label.action.configure.stickiness'),
-          slots: { customRender: 'stickiness' }
+          key: 'stickiness',
+          title: this.$t('label.action.configure.stickiness')
         },
         {
-          title: this.$t('label.autoscale'),
-          slots: { customRender: 'autoscale' }
+          key: 'add',
+          title: this.$t('label.add.vms')
         },
         {
-          title: this.$t('label.add.vms'),
-          slots: { customRender: 'add' }
+          key: 'autoscale',
+          title: this.$t('label.autoscale')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ],
       tiers: {
@@ -743,15 +841,15 @@
       },
       vmColumns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
           dataIndex: 'name',
-          slots: { customRender: 'name' },
           width: 220
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.displayname'),
@@ -766,15 +864,50 @@
           dataIndex: 'zonename'
         },
         {
+          key: 'actions',
           title: this.$t('label.select'),
-          dataIndex: 'action',
-          slots: { customRender: 'action' },
+          dataIndex: 'actions',
           width: 80
         }
       ],
       vmPage: 1,
       vmPageSize: 10,
       vmCount: 0,
+      addNetworkModalVisible: false,
+      addNetworkModalLoading: false,
+      networks: [],
+      associatednetworkid: null,
+      selectedTierForAutoScaling: null,
+      networkColumns: [
+        {
+          key: 'name',
+          title: this.$t('label.name'),
+          dataIndex: 'name',
+          width: 220
+        },
+        {
+          key: 'state',
+          title: this.$t('label.state'),
+          dataIndex: 'state'
+        },
+        {
+          title: this.$t('label.gateway'),
+          dataIndex: 'gateway'
+        },
+        {
+          title: this.$t('label.netmask'),
+          dataIndex: 'netmask'
+        },
+        {
+          key: 'actions',
+          title: this.$t('label.select'),
+          dataIndex: 'actions',
+          width: 80
+        }
+      ],
+      networkPage: 1,
+      networkPageSize: 10,
+      networkCount: 0,
       searchQuery: null,
       tungstenHealthMonitors: [],
       healthMonitorModal: false,
@@ -797,6 +930,10 @@
   },
   beforeCreate () {
     this.createLoadBalancerRuleParams = this.$getApiParams('createLoadBalancerRule')
+    this.createLoadBalancerStickinessPolicyParams = this.$getApiParams('createLBStickinessPolicy')
+    if ('associatednetworkid' in this.resource) {
+      this.associatednetworkid = this.resource.associatednetworkid
+    }
   },
   created () {
     this.initForm()
@@ -847,9 +984,8 @@
       this.tiers.loading = true
 
       api('listNetworks', {
-        account: this.resource.account,
-        domainid: this.resource.domainid,
         supportedservices: 'Lb',
+        isrecursive: true,
         vpcid: this.resource.vpcid
       }).then(json => {
         this.tiers.data = json.listnetworksresponse.network || []
@@ -857,7 +993,7 @@
         if (this.tiers.data?.[0]?.broadcasturi === 'tf://tf') {
           this.columns.splice(8, 0, {
             title: this.$t('label.action.health.monitor'),
-            slots: { customRender: 'healthmonitor' }
+            key: 'healthmonitor'
           })
         }
       }).catch(error => {
@@ -1311,9 +1447,9 @@
     deleteRules (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
@@ -1447,9 +1583,7 @@
         keyword: this.searchQuery,
         page: this.vmPage,
         pagesize: this.vmPageSize,
-        networkid: networkId,
-        account: this.resource.account,
-        domainid: this.resource.domainid
+        networkid: networkId
       }).then(response => {
         this.vmCount = response.listvirtualmachinesresponse.count || 0
         this.vms = response.listvirtualmachinesresponse.virtualmachine || []
@@ -1464,6 +1598,55 @@
         this.addVmModalLoading = false
       })
     },
+    handleOpenAddNetworkModal () {
+      if (this.addNetworkModalLoading) return
+      if (!this.checkNewRule()) {
+        return
+      }
+      this.addNetworkModalVisible = true
+      this.fetchNetworks()
+    },
+    fetchNetworks () {
+      this.networkCount = 0
+      this.networks = []
+      this.addNetworkModalLoading = true
+      const vpcid = this.resource.vpcid
+      if (!vpcid) {
+        this.addNetworkModalLoading = false
+        return
+      }
+      api('listNetworks', {
+        listAll: true,
+        keyword: this.searchQuery,
+        page: this.networkPage,
+        pagesize: this.networkPageSize,
+        supportedservices: 'Lb',
+        isrecursive: true,
+        vpcid: vpcid
+      }).then(response => {
+        this.networkCount = response.listnetworksresponse.count || 0
+        this.networks = response.listnetworksresponse.network || []
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.addNetworkModalLoading = false
+      })
+      this.selectedTierForAutoScaling = null
+    },
+    onNetworkSearch (value) {
+      this.searchQuery = value
+      this.fetchNetworks()
+    },
+    handleChangeNetworkPage (page, pageSize) {
+      this.networkPage = page
+      this.networkPageSize = pageSize
+      this.fetchNetworks()
+    },
+    handleChangeNetworkPageSize (currentPage, pageSize) {
+      this.networkPage = currentPage
+      this.networkPageSize = pageSize
+      this.fetchNetworks()
+    },
     handleAssignToLBRule (data) {
       const vmIDIpMap = {}
 
@@ -1535,7 +1718,8 @@
         return
       }
 
-      const networkId = ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
+      const networkId = this.selectedTierForAutoScaling != null ? this.selectedTierForAutoScaling
+        : ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
       api('createLoadBalancerRule', {
         openfirewall: false,
         networkid: networkId,
@@ -1548,7 +1732,9 @@
         cidrlist: this.newRule.cidrlist
       }).then(response => {
         this.addVmModalVisible = false
+        this.addNetworkModalVisible = false
         this.handleAssignToLBRule(response.createloadbalancerruleresponse.id)
+        this.associatednetworkid = networkId
       }).catch(error => {
         this.$notifyError(error)
         this.loading = false
@@ -1572,6 +1758,9 @@
       this.nics = []
       this.addVmModalVisible = false
       this.newRule.virtualmachineid = []
+      this.addNetworkModalLoading = false
+      this.addNetworkModalVisible = false
+      this.selectedTierForAutoScaling = null
     },
     handleChangePage (page, pageSize) {
       this.page = page
diff --git a/ui/src/views/network/NetworkPermissions.vue b/ui/src/views/network/NetworkPermissions.vue
index 8961b7e..a5434e5 100644
--- a/ui/src/views/network/NetworkPermissions.vue
+++ b/ui/src/views/network/NetworkPermissions.vue
@@ -44,19 +44,21 @@
         :rowKey="item => item.id"
         :pagination="false" >
 
-        <template #action="{ record }">
-          <a-popconfirm
-            :title="$t('message.confirm.remove.network.permission')"
-            @confirm="removeNetworkPermission(record.accountid, record.projectid)"
-            :okText="$t('label.yes')"
-            :cancelText="$t('label.no')" >
-            <tooltip-button
-              tooltipPlacement="bottom"
-              :tooltip="$t('label.action.delete.network.permission')"
-              type="primary"
-              :danger="true"
-              icon="delete-outlined" />
-          </a-popconfirm>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'actions'">
+            <a-popconfirm
+              :title="$t('message.confirm.remove.network.permission')"
+              @confirm="removeNetworkPermission(record.accountid, record.projectid)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')" >
+              <tooltip-button
+                tooltipPlacement="bottom"
+                :tooltip="$t('label.action.delete.network.permission')"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined" />
+            </a-popconfirm>
+          </template>
         </template>
 
       </a-table>
@@ -85,15 +87,12 @@
       :footer="null"
       @cancel="showResetPermissionModal = false"
       centered
-      width="auto"
-      v-ctrl-enter="resetNetworkPermission">
+      width="auto">
       {{ $t('message.confirm.reset.network.permissions') }}
-      <a-form @submit="resetNetworkPermission">
-        <div :span="24" class="action-button">
-          <a-button @click="showResetPermissionModal = false">{{ $t('label.cancel') }}</a-button>
-          <a-button type="primary" ref="submit" @click="resetNetworkPermission">{{ $t('label.ok') }}</a-button>
-        </div>
-      </a-form>
+      <div :span="24" class="action-button">
+        <a-button @click="showResetPermissionModal = false">{{ $t('label.cancel') }}</a-button>
+        <a-button type="primary" ref="submit" @click="resetNetworkPermission">{{ $t('label.ok') }}</a-button>
+      </div>
     </a-modal>
   </div>
 </template>
@@ -140,8 +139,8 @@
           dataIndex: 'project'
         },
         {
-          title: '',
-          slots: { customRender: 'action' }
+          key: 'actions',
+          title: ''
         }
       ]
     }
diff --git a/ui/src/views/network/NicsTable.vue b/ui/src/views/network/NicsTable.vue
index d254990..f791e12 100644
--- a/ui/src/views/network/NicsTable.vue
+++ b/ui/src/views/network/NicsTable.vue
@@ -60,15 +60,17 @@
         </template>
       </a-descriptions>
     </template>
-    <template #networkname="{ text, record }">
-      <resource-icon v-if="!networkIconLoading && networkicon[record.id]" :image="networkicon[record.id]" size="1x" style="margin-right: 5px"/>
-      <apartment-outlined v-else style="margin-right: 5px" />
-      <router-link :to="{ path: '/guestnetwork/' + record.networkid }">
-        {{ text }}
-      </router-link>
-      <a-tag v-if="record.isdefault">
-        {{ $t('label.default') }}
-      </a-tag>
+    <template #bodyCell="{ column, text, record }">
+      <template v-if="column.key === 'networkname'">
+        <resource-icon v-if="!networkIconLoading && networkicon[record.id]" :image="networkicon[record.id]" size="1x" style="margin-right: 5px"/>
+        <apartment-outlined v-else style="margin-right: 5px" />
+        <router-link :to="{ path: '/guestnetwork/' + record.networkid }">
+          {{ text }}
+        </router-link>
+        <a-tag v-if="record.isdefault">
+          {{ $t('label.default') }}
+        </a-tag>
+      </template>
     </template>
   </a-table>
 </template>
@@ -97,14 +99,14 @@
     return {
       nicColumns: [
         {
+          key: 'deviceid',
           title: this.$t('label.deviceid'),
-          dataIndex: 'deviceid',
-          slots: { customRender: 'deviceid' }
+          dataIndex: 'deviceid'
         },
         {
+          key: 'networkname',
           title: this.$t('label.networkname'),
-          dataIndex: 'networkname',
-          slots: { customRender: 'networkname' }
+          dataIndex: 'networkname'
         },
         {
           title: this.$t('label.macaddress'),
diff --git a/ui/src/views/network/PortForwarding.vue b/ui/src/views/network/PortForwarding.vue
index fc773c0..81843a1 100644
--- a/ui/src/views/network/PortForwarding.vue
+++ b/ui/src/views/network/PortForwarding.vue
@@ -66,10 +66,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
-            <a-select-option value="udp">{{ $t('label.udp') }}</a-select-option>
+            <a-select-option value="tcp" label="$t('label.tcp')">{{ $t('label.tcp') }}</a-select-option>
+            <a-select-option value="udp" :label="$t('label.udp')">{{ $t('label.udp') }}</a-select-option>
           </a-select>
         </div>
         <div class="form__item" style="margin-left: auto;">
@@ -98,33 +98,35 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.id">
-      <template #privateport="{record}">
-        {{ record.privateport }} - {{ record.privateendport }}
-      </template>
-      <template #publicport="{record}">
-        {{ record.publicport }} - {{ record.publicendport }}
-      </template>
-      <template #protocol="{record}">
-        {{ getCapitalise(record.protocol) }}
-      </template>
-      <template #vm="{record}">
-        <div><desktop-outlined/>
-          <router-link
-            :to="{ path: '/vm/' + record.virtualmachineid }">
-            {{ record.virtualmachinename }}</router-link> ({{ record.vmguestip }})</div>
-      </template>
-      <template #actions="{record}">
-        <div class="actions">
-          <tooltip-button :tooltip="$t('label.tags')" icon="tag-outlined" buttonClass="rule-action" @onClick="() => openTagsModal(record.id)" />
-          <tooltip-button
-            :tooltip="$t('label.remove.rule')"
-            type="primary"
-            :danger="true"
-            icon="delete-outlined"
-            buttonClass="rule-action"
-            :disabled="!('deletePortForwardingRule' in $store.getters.apis)"
-            @onClick="deleteRule(record)" />
-        </div>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'privateport'">
+          {{ record.privateport }} - {{ record.privateendport }}
+        </template>
+        <template v-if="column.key === 'publicport'">
+          {{ record.publicport }} - {{ record.publicendport }}
+        </template>
+        <template v-if="column.key === 'protocol'">
+          {{ getCapitalise(record.protocol) }}
+        </template>
+        <template v-if="column.key === 'vm'">
+          <div><desktop-outlined/>
+            <router-link
+              :to="{ path: '/vm/' + record.virtualmachineid }">
+              {{ record.virtualmachinename }}</router-link> ({{ record.vmguestip }})</div>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="actions">
+            <tooltip-button :tooltip="$t('label.tags')" icon="tag-outlined" buttonClass="rule-action" @onClick="() => openTagsModal(record.id)" />
+            <tooltip-button
+              :tooltip="$t('label.remove.rule')"
+              type="primary"
+              :danger="true"
+              icon="delete-outlined"
+              buttonClass="rule-action"
+              :disabled="!('deletePortForwardingRule' in $store.getters.apis)"
+              @onClick="deleteRule(record)" />
+          </div>
+        </template>
       </template>
     </a-table>
     <a-pagination
@@ -215,12 +217,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="tier in tiers.data"
               :loading="tiers.loading"
-              :key="tier.id">
+              :key="tier.id"
+              :label="tier.displaytext || ''">
               {{ tier.displaytext }}
             </a-select-option>
           </a-select>
@@ -241,40 +244,46 @@
           :pagination="false"
           :rowKey="record => record.id"
           :scroll="{ y: 300 }">
-          <template #name="{text, record}">
-            <span>
-              {{ text }}
-            </span>
-            <loading-outlined v-if="addVmModalNicLoading"></loading-outlined>
-            <a-select
-              style="display: block"
-              v-else-if="!addVmModalNicLoading && newRule.virtualmachineid === record.id"
-              v-model:value="newRule.vmguestip"
-              showSearch
-              optionFilterProp="label"
-              :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }" >
-              <a-select-option v-for="(nic, nicIndex) in nics" :key="nic" :value="nic">
-                {{ nic }}{{ nicIndex === 0 ? ` (${$t('label.primary')})` : null }}
-              </a-select-option>
-            </a-select>
-          </template>
+          <template #bodyCell="{ column, text, record }">
+            <template v-if="column.key === 'name'">
+              <span>
+                {{ text }}
+              </span>
+              <loading-outlined v-if="addVmModalNicLoading"></loading-outlined>
+              <a-select
+                style="display: block"
+                v-else-if="!addVmModalNicLoading && newRule.virtualmachineid === record.id"
+                v-model:value="newRule.vmguestip"
+                showSearch
+                optionFilterProp="label"
+                :filterOption="(input, option) => {
+                  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }" >
+                <a-select-option
+                  v-for="(nic, nicIndex) in nics"
+                  :key="nic"
+                  :value="nic"
+                  :label="nic">
+                  {{ nic }}{{ nicIndex === 0 ? ` (${$t('label.primary')})` : null }}
+                </a-select-option>
+              </a-select>
+            </template>
 
-          <template #state="{text}">
-            <status :text="text ? text : ''" displayText></status>
-          </template>
+            <template v-if="column.key === 'state'">
+              <status :text="text ? text : ''" displayText></status>
+            </template>
 
-          <template #action="{record}">
-            <div style="text-align: center">
-              <a-radio-group
-                class="radio-group"
-                :key="record.id"
-                v-model:value="checked"
-                @change="($event) => checked = $event.target.value">
-                <a-radio :value="record.id" @change="e => fetchNics(e)" />
-              </a-radio-group>
-            </div>
+            <template v-if="column.key === 'actions'">
+              <div style="text-align: center">
+                <a-radio-group
+                  class="radio-group"
+                  :key="record.id"
+                  v-model:value="checked"
+                  @change="($event) => checked = $event.target.value">
+                  <a-radio :value="record.id" @change="e => fetchNics(e)" />
+                </a-radio-group>
+              </div>
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -346,7 +355,7 @@
       showGroupActionModal: false,
       selectedItems: [],
       selectedColumns: [],
-      filterColumns: ['State', 'Action'],
+      filterColumns: ['State', 'Actions'],
       showConfirmationAction: false,
       message: {
         title: this.$t('label.action.bulk.delete.portforward.rules'),
@@ -379,28 +388,28 @@
       pageSize: 10,
       columns: [
         {
-          title: this.$t('label.privateport'),
-          slots: { customRender: 'privateport' }
+          key: 'privateport',
+          title: this.$t('label.privateport')
         },
         {
-          title: this.$t('label.publicport'),
-          slots: { customRender: 'publicport' }
+          key: 'publicport',
+          title: this.$t('label.publicport')
         },
         {
-          title: this.$t('label.protocol'),
-          slots: { customRender: 'protocol' }
+          key: 'protocol',
+          title: this.$t('label.protocol')
         },
         {
           title: this.$t('label.state'),
           dataIndex: 'state'
         },
         {
-          title: this.$t('label.vm'),
-          slots: { customRender: 'vm' }
+          key: 'vm',
+          title: this.$t('label.vm')
         },
         {
-          title: this.$t('label.action'),
-          slots: { customRender: 'actions' }
+          key: 'actions',
+          title: this.$t('label.actions')
         }
       ],
       tiers: {
@@ -409,15 +418,15 @@
       },
       vmColumns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
           dataIndex: 'name',
-          slots: { customRender: 'name' },
           width: 210
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.displayname'),
@@ -432,9 +441,9 @@
           dataIndex: 'zonename'
         },
         {
+          key: 'actions',
           title: this.$t('label.select'),
-          dataIndex: 'action',
-          slots: { customRender: 'action' },
+          dataIndex: 'actions',
           width: 80
         }
       ],
@@ -485,10 +494,9 @@
       this.selectedTier = null
       this.tiers.loading = true
       api('listNetworks', {
-        account: this.resource.account,
-        domainid: this.resource.domainid,
         supportedservices: 'PortForwarding',
-        vpcid: this.resource.vpcid
+        vpcid: this.resource.vpcid,
+        listall: this.resource.vpcid !== null
       }).then(json => {
         this.tiers.data = json.listnetworksresponse.network || []
         if (this.tiers.data && this.tiers.data.length > 0) {
@@ -545,9 +553,9 @@
     deleteRules (e) {
       this.showConfirmationAction = false
       this.selectedColumns.splice(0, 0, {
+        key: 'status',
         dataIndex: 'status',
         title: this.$t('label.operation.status'),
-        slots: { customRender: 'status' },
         filters: [
           { text: 'In Progress', value: 'InProgress' },
           { text: 'Success', value: 'success' },
@@ -790,9 +798,7 @@
         keyword: this.searchQuery,
         page: this.vmPage,
         pagesize: this.vmPageSize,
-        networkid: networkId,
-        account: this.resource.account,
-        domainid: this.resource.domainid
+        networkid: networkId
       }).then(response => {
         this.vmCount = response.listvirtualmachinesresponse.count || 0
         this.vms = response.listvirtualmachinesresponse.virtualmachine
diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue
index fdbd96a..18bc003 100644
--- a/ui/src/views/network/PublicIpResource.vue
+++ b/ui/src/views/network/PublicIpResource.vue
@@ -66,10 +66,22 @@
       tabs: [{
         name: 'details',
         component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+      },
+      {
+        name: 'events',
+        resourceType: 'IpAddress',
+        component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+        show: () => { return 'listEvents' in this.$store.getters.apis }
       }],
       defaultTabs: [{
         name: 'details',
         component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
+      },
+      {
+        name: 'events',
+        resourceType: 'IpAddress',
+        component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
+        show: () => { return 'listEvents' in this.$store.getters.apis }
       }],
       activeTab: ''
     }
diff --git a/ui/src/views/network/ReservePublicIP.vue b/ui/src/views/network/ReservePublicIP.vue
index dc5486e..3fffc5e 100644
--- a/ui/src/views/network/ReservePublicIP.vue
+++ b/ui/src/views/network/ReservePublicIP.vue
@@ -35,9 +35,9 @@
           v-model:value="selectedAccountType"
           v-focus="true"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }">
           <a-select-option :value="$t('label.account')">{{ $t('label.account') }}</a-select-option>
           <a-select-option :value="$t('label.project')">{{ $t('label.project') }}</a-select-option>
@@ -71,7 +71,7 @@
             @change="changeAccount"
             v-model:value="selectedAccount"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
               return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
diff --git a/ui/src/views/network/RoutersTab.vue b/ui/src/views/network/RoutersTab.vue
index 73f7bf6..a2e9323 100644
--- a/ui/src/views/network/RoutersTab.vue
+++ b/ui/src/views/network/RoutersTab.vue
@@ -25,20 +25,22 @@
     :pagination="false"
     :loading="fetchLoading"
   >
-    <template #name="{ text, record }">
-      <router-link :to="{ path: '/router/' + record.id }" >{{ text }}</router-link>
-    </template>
-    <template #status="{ record }">
-      <status class="status" :text="record.state" displayText />
-    </template>
-    <template #requiresupgrade="{ record }">
-      {{ record.requiresupgrade ? $t('label.yes') : $t('label.no') }}
-    </template>
-    <template #isredundantrouter="{ record }">
-      {{ record.isredundantrouter ? record.redundantstate : record.isredundantrouter }}
-    </template>
-    <template #hostname="{ record }">
-      <router-link :to="{ path: '/host/' + record.hostid }" >{{ record.hostname || record.hostid }}</router-link>
+    <template #bodyCell="{ column, text, record }">
+      <template v-if="column.key === 'name'">
+        <router-link :to="{ path: '/router/' + record.id }" >{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'status'">
+        <status class="status" :text="record.state" displayText />
+      </template>
+      <template v-if="column.key === 'requiresupgrade'">
+        {{ record.requiresupgrade ? $t('label.yes') : $t('label.no') }}
+      </template>
+      <template v-if="column.key === 'isredundantrouter'">
+        {{ record.isredundantrouter ? record.redundantstate : record.isredundantrouter }}
+      </template>
+      <template v-if="column.key === 'hostname'">
+        <router-link :to="{ path: '/host/' + record.hostid }" >{{ record.hostname || record.hostid }}</router-link>
+      </template>
     </template>
   </a-table>
 </template>
@@ -68,14 +70,14 @@
       routers: [],
       columns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
+          key: 'status',
           title: this.$t('label.status'),
-          dataIndex: 'state',
-          slots: { customRender: 'status' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.ip'),
@@ -86,19 +88,19 @@
           dataIndex: 'version'
         },
         {
+          key: 'requiresupgrade',
           title: this.$t('label.requiresupgrade'),
-          dataIndex: 'requiresupgrade',
-          slots: { customRender: 'requiresupgrade' }
+          dataIndex: 'requiresupgrade'
         },
         {
+          key: 'isredundantrouter',
           title: this.$t('label.isredundantrouter'),
-          dataIndex: 'isredundantrouter',
-          slots: { customRender: 'isredundantrouter' }
+          dataIndex: 'isredundantrouter'
         },
         {
+          key: 'hostname',
           title: this.$t('label.hostname'),
-          dataIndex: 'hostname',
-          slots: { customRender: 'hostname' }
+          dataIndex: 'hostname'
         }
       ]
     }
diff --git a/ui/src/views/network/UpdateNetwork.vue b/ui/src/views/network/UpdateNetwork.vue
index 02b826c..42c9107 100644
--- a/ui/src/views/network/UpdateNetwork.vue
+++ b/ui/src/views/network/UpdateNetwork.vue
@@ -80,6 +80,24 @@
             </a-col>
           </a-row>
         </div>
+        <a-form-item name="sourcenatipaddress" ref="sourcenatipaddress">
+          <template #label>
+            <tooltip-label :title="$t('label.sourcenatipaddress')" :tooltip="apiParams.sourcenatipaddress.description"/>
+          </template>
+          <span v-if="sourcenatchange">
+            <a-alert type="warning">
+              <template #message>
+                <span v-html="$t('message.sourcenatip.change.warning')" />
+              </template>
+            </a-alert>
+            <br/>
+          </span>
+          <a-input
+            v-model:value="form.sourcenatipaddress"
+            :placeholder="apiParams.sourcenatipaddress.description"
+            v-focus="true"
+            @change="sourcenatchange = form.sourcenatipaddress.length > 0"/>
+        </a-form-item>
         <a-form-item name="networkofferingid" ref="networkofferingid" v-if="isUpdatingIsolatedNetwork">
           <template #label>
             <tooltip-label :title="$t('label.networkofferingid')" :tooltip="apiParams.networkofferingid.description"/>
@@ -98,12 +116,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="networkOfferingLoading"
             :placeholder="apiParams.networkofferingid.description"
             @change="val => { networkOffering = networkOfferings[val] }">
-            <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in networkOfferings" :key="optIndex" :label="opt.displaytext || opt.name">
               {{ opt.displaytext || opt.name }}
             </a-select-option>
           </a-select>
@@ -238,7 +256,8 @@
       minMTU: 68,
       errorPrivateMtu: '',
       errorPublicMtu: '',
-      setMTU: false
+      setMTU: false,
+      sourcenatchange: false
     }
   },
   beforeCreate () {
diff --git a/ui/src/views/network/VnfAppliancesTab.vue b/ui/src/views/network/VnfAppliancesTab.vue
new file mode 100644
index 0000000..fa763d6
--- /dev/null
+++ b/ui/src/views/network/VnfAppliancesTab.vue
@@ -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.
+
+<template>
+  <a-table
+    size="small"
+    style="overflow-y: auto"
+    :columns="columns"
+    :dataSource="virtualmachines"
+    :rowKey="item => item.id"
+    :pagination="false"
+    :loading="fetchLoading"
+  >
+    <template #bodyCell="{ column, text, record }">
+      <template v-if="column.key === 'name'">
+        <router-link :to="{ path: '/vnfapp/' + record.id }" >{{ text }}</router-link>
+      </template>
+      <template v-if="column.key === 'status'">
+        <status class="status" :text="record.state" displayText />
+      </template>
+      <template v-if="column.key === 'vmip'">
+        <status class="status" :text="record.vmip" displayText />
+      </template>
+      <template v-if="column.key === 'templatename'">
+        <router-link :to="{ path: '/template/' + record.templateid }" >{{ record.templatename || record.templateid }}</router-link>
+      </template>
+      <template v-if="column.key === 'osdisplayname'">
+        <status class="status" :text="record.osdisplayname" displayText />
+      </template>
+      <template v-if="column.key === 'hostname'">
+        <router-link :to="{ path: '/host/' + record.hostid }" >{{ record.hostname || record.hostid }}</router-link>
+      </template>
+    </template>
+  </a-table>
+</template>
+
+<script>
+import { api } from '@/api'
+import Status from '@/components/widgets/Status'
+
+export default {
+  name: 'VnfAppliancesTab',
+  components: {
+    Status
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      fetchLoading: false,
+      virtualmachines: [],
+      columns: [
+        {
+          key: 'name',
+          title: this.$t('label.name'),
+          dataIndex: 'name'
+        },
+        {
+          key: 'status',
+          title: this.$t('label.status'),
+          dataIndex: 'state'
+        },
+        {
+          title: this.$t('label.ipaddress'),
+          dataIndex: 'vmip'
+        },
+        {
+          key: 'templatename',
+          title: this.$t('label.templatename'),
+          dataIndex: 'templatename'
+        },
+        {
+          title: this.$t('label.osdisplayname'),
+          dataIndex: 'osdisplayname'
+        },
+        {
+          key: 'hostname',
+          title: this.$t('label.hostname'),
+          dataIndex: 'hostname'
+        }
+      ]
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  watch: {
+    resource: {
+      deep: true,
+      handler (newItem) {
+        if (!newItem || !newItem.id) {
+          return
+        }
+        this.fetchData()
+      }
+    }
+  },
+  methods: {
+    fetchData () {
+      var params = {
+        details: 'servoff,tmpl,nics',
+        isVnf: true,
+        listAll: true
+      }
+      if (this.$route.fullPath.startsWith('/vpc')) {
+        params.vpcid = this.resource.id
+      } else {
+        params.networkid = this.resource.id
+      }
+      this.fetchLoading = true
+      api('listVirtualMachines', params).then(json => {
+        this.virtualmachines = json.listvirtualmachinesresponse.virtualmachine || []
+        for (const vm of this.virtualmachines) {
+          for (const vmnic of vm.nic) {
+            if (vmnic.networkid === this.resource.id) {
+              vm.vmip = vmnic.ipaddress
+            }
+          }
+        }
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.fetchLoading = false
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.status {
+  margin-top: -5px;
+
+  &--end {
+    margin-left: 5px;
+  }
+}
+</style>
diff --git a/ui/src/views/network/VpcTab.vue b/ui/src/views/network/VpcTab.vue
index 0183764..4603e0a 100644
--- a/ui/src/views/network/VpcTab.vue
+++ b/ui/src/views/network/VpcTab.vue
@@ -48,10 +48,12 @@
           :rowKey="item => item.id"
           :pagination="false"
         >
-          <template #name="{ text, record }">
-            <router-link :to="{ path: '/acllist/' + record.id }">
-              {{ text }}
-            </router-link>
+          <template #bodyCell="{ column, text, record }">
+            <template v-if="column.key === 'name'">
+              <router-link :to="{ path: '/acllist/' + record.id }">
+                {{ text }}
+              </router-link>
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -75,7 +77,7 @@
           :footer="null"
           :maskClosable="false"
           :closable="true"
-          @cancel="modals.networkAcl = fetchAclList">
+          @cancel="modals.networkAcl = false">
           <a-form
             layout="vertical"
             :ref="formRef"
@@ -117,11 +119,13 @@
           :rowKey="item => item.id"
           :pagination="false"
         >
-          <template #ipaddress="{ text, record }">
-            <router-link :to="{ path: '/privategw/' + record.id }">{{ text }}</router-link>
-          </template>
-          <template #state="{ record }">
-            <status :text="record.state" displayText></status>
+          <template #bodyCell="{ column, text, record }">
+            <template v-if="column.key === 'ipaddress'">
+              <router-link :to="{ path: '/privategw/' + record.id }">{{ text }}</router-link>
+            </template>
+            <template v-if="column.key === 'state'">
+              <status :text="record.state" displayText></status>
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -163,9 +167,9 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }" >
-                  <a-select-option v-for="item in physicalnetworks" :key="item.id" :value="item.id">
+                  <a-select-option v-for="item in physicalnetworks" :key="item.id" :value="item.id" :label="item.name">
                     {{ item.name }}
                   </a-select-option>
                 </a-select>
@@ -286,13 +290,15 @@
           :dataSource="vpnConnections"
           :pagination="false"
           :rowKey="record => record.id">
-          <template #publicip="{text, record}">
-            <router-link :to="{ path: '/s2svpnconn/' + record.id }">
-              {{ text }}
-            </router-link>
-          </template>
-          <template #state="{text}">
-            <status :text="text ? text : ''" displayText />
+          <template #bodyCell="{ column, text, record }">
+            <template v-if="column.key === 'publicip'">
+              <router-link :to="{ path: '/s2svpnconn/' + record.id }">
+                {{ text }}
+              </router-link>
+            </template>
+            <template v-if="column.key === 'state'">
+              <status :text="text ? text : ''" displayText />
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -332,9 +338,9 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }" >
-                  <a-select-option v-for="item in vpncustomergateways" :key="item.id" :value="item.id">
+                  <a-select-option v-for="item in vpncustomergateways" :key="item.id" :value="item.id" :label="item.name">
                     {{ item.name }}
                   </a-select-option>
                 </a-select>
@@ -354,6 +360,9 @@
       <a-tab-pane :tab="$t('label.virtual.routers')" key="vr" v-if="$store.getters.userInfo.roletype === 'Admin'">
         <RoutersTab :resource="resource" :loading="loading" />
       </a-tab-pane>
+      <a-tab-pane :tab="$t('label.vnf.appliances')" key="vnf" v-if="'deployVnfAppliance' in $store.getters.apis">
+        <VnfAppliancesTab :resource="resource" :loading="loading" />
+      </a-tab-pane>
       <a-tab-pane :tab="$t('label.events')" key="events" v-if="'listEvents' in $store.getters.apis">
         <events-tab :resource="resource" resourceType="Vpc" :loading="loading" />
       </a-tab-pane>
@@ -376,8 +385,10 @@
 import IpAddressesTab from './IpAddressesTab'
 import RoutersTab from './RoutersTab'
 import VpcTiersTab from './VpcTiersTab'
+import VnfAppliancesTab from './VnfAppliancesTab'
 import EventsTab from '@/components/view/EventsTab'
 import AnnotationsTab from '@/components/view/AnnotationsTab'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'VpcTab',
@@ -387,8 +398,10 @@
     IpAddressesTab,
     RoutersTab,
     VpcTiersTab,
+    VnfAppliancesTab,
     EventsTab,
-    AnnotationsTab
+    AnnotationsTab,
+    ResourceIcon
   },
   mixins: [mixinDevice],
   props: {
@@ -426,14 +439,14 @@
       vpncustomergateways: [],
       privateGatewaysColumns: [
         {
+          key: 'ipaddress',
           title: this.$t('label.ip'),
-          dataIndex: 'ipaddress',
-          slots: { customRender: 'ipaddress' }
+          dataIndex: 'ipaddress'
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.gateway'),
@@ -450,14 +463,14 @@
       ],
       vpnConnectionsColumns: [
         {
+          key: 'publicip',
           title: this.$t('label.ip'),
-          dataIndex: 'publicip',
-          slots: { customRender: 'publicip' }
+          dataIndex: 'publicip'
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
           title: this.$t('label.gateway'),
@@ -470,9 +483,9 @@
       ],
       networkAclsColumns: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
           title: this.$t('label.description'),
diff --git a/ui/src/views/network/VpcTiersTab.vue b/ui/src/views/network/VpcTiersTab.vue
index bd37001..214ea1a 100644
--- a/ui/src/views/network/VpcTiersTab.vue
+++ b/ui/src/views/network/VpcTiersTab.vue
@@ -80,17 +80,19 @@
                 :rowKey="item => item.id"
                 :pagination="false"
                 :loading="fetchLoading">
-                <template #name="{ record }">
-                  <router-link :to="{ path: '/vm/' + record.id}">{{ record.name }}
-                  </router-link>
-                </template>
-                <template #state="{ record }">
-                  <status :text="record.state" displayText></status>
-                </template>
-                <template #ip="{ record }">
-                  <div v-for="nic in record.nic" :key="nic.id">
-                    {{ nic.networkid === network.id ? nic.ipaddress : '' }}
-                  </div>
+                <template #bodyCell="{ column, record }">
+                  <template v-if="column.key === 'name'">
+                    <router-link :to="{ path: '/vm/' + record.id}">{{ record.name }}
+                    </router-link>
+                  </template>
+                  <template v-if="column.key === 'state'">
+                    <status :text="record.state" displayText></status>
+                  </template>
+                  <template v-if="column.key === 'ip'">
+                    <div v-for="nic in record.nic" :key="nic.id">
+                      {{ nic.networkid === network.id ? nic.ipaddress : '' }}
+                    </div>
+                  </template>
                 </template>
               </a-table>
               <a-divider/>
@@ -110,7 +112,7 @@
                 </template>
               </a-pagination>
             </a-collapse-panel>
-            <a-collapse-panel :header="$t('label.internal.lb')" key="ilb" :style="customStyle" :disabled="!showIlb(network)" >
+            <a-collapse-panel :header="$t('label.internal.lb')" key="ilb" :style="customStyle" :collapsible="!showIlb(network) ? 'disabled' : null" >
               <a-button
                 type="dashed"
                 style="margin-bottom: 15px; width: 100%"
@@ -127,9 +129,11 @@
                 :rowKey="item => item.id"
                 :pagination="false"
                 :loading="fetchLoading">
-                <template #name="{ record }">
-                  <router-link :to="{ path: '/ilb/'+ record.id}">{{ record.name }}
-                  </router-link>
+                <template #bodyCell="{ column, record }">
+                  <template v-if="column.key === 'name'">
+                    <router-link :to="{ path: '/ilb/'+ record.id}">{{ record.name }}
+                    </router-link>
+                  </template>
                 </template>
               </a-table>
               <a-divider/>
@@ -188,9 +192,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="item in networkOfferings" :key="item.id" :value="item.id">
+              <a-select-option v-for="item in networkOfferings" :key="item.id" :value="item.id" :label="item.displaytext || item.name || item.description">
                 {{ item.displaytext || item.name || item.description }}
               </a-select-option>
             </a-select>
@@ -252,9 +256,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="item in networkAclList" :key="item.id" :value="item.id">
+              <a-select-option v-for="item in networkAclList" :key="item.id" :value="item.id" :label="`${item.name}(${item.description})`">
                 <strong>{{ item.name }}</strong> ({{ item.description }})
               </a-select-option>
             </a-select>
@@ -316,9 +320,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="(key, idx) in Object.keys(algorithms)" :key="idx" :value="algorithms[key]">
+              <a-select-option v-for="(key, idx) in Object.keys(algorithms)" :key="idx" :value="algorithms[key]" :label="key">
                 {{ key }}
               </a-select-option>
             </a-select>
@@ -384,9 +388,9 @@
       },
       internalLbCols: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
           title: this.$t('label.sourceipaddress'),
@@ -401,58 +405,20 @@
           dataIndex: 'account'
         }
       ],
-      LBPublicIPCols: [
-        {
-          title: this.$t('label.ip'),
-          dataIndex: 'ipaddress',
-          slots: { customRender: 'ipaddress' }
-        },
-        {
-          title: this.$t('label.state'),
-          dataIndex: 'state'
-        },
-        {
-          title: this.$t('label.networkid'),
-          dataIndex: 'associatedNetworkName'
-        },
-        {
-          title: this.$t('label.vm'),
-          dataIndex: 'virtualmachinename'
-        }
-      ],
-      StaticNatCols: [
-        {
-          title: this.$t('label.ips'),
-          dataIndex: 'ipaddress',
-          slots: { customRender: 'ipaddress' }
-        },
-        {
-          title: this.$t('label.zoneid'),
-          dataIndex: 'zonename'
-        },
-        {
-          title: this.$t('label.networkid'),
-          dataIndex: 'associatedNetworkName'
-        },
-        {
-          title: this.$t('label.state'),
-          dataIndex: 'state'
-        }
-      ],
       vmCols: [
         {
+          key: 'name',
           title: this.$t('label.name'),
-          dataIndex: 'name',
-          slots: { customRender: 'name' }
+          dataIndex: 'name'
         },
         {
+          key: 'state',
           title: this.$t('label.state'),
-          dataIndex: 'state',
-          slots: { customRender: 'state' }
+          dataIndex: 'state'
         },
         {
-          title: this.$t('label.ip'),
-          slots: { customRender: 'ip' }
+          key: 'ip',
+          title: this.$t('label.ip')
         }
       ],
       customStyle: 'margin-bottom: 0; border: none',
diff --git a/ui/src/views/network/tungsten/FirewallPolicyTab.vue b/ui/src/views/network/tungsten/FirewallPolicyTab.vue
index f25997b..9be36fd 100644
--- a/ui/src/views/network/tungsten/FirewallPolicyTab.vue
+++ b/ui/src/views/network/tungsten/FirewallPolicyTab.vue
@@ -34,26 +34,28 @@
         :dataSource="dataSource"
         :rowKey="item => item.uuid"
         :pagination="false">
-        <template #name="{ text, record }">
+        <template #bodyCell="{ column, text, record }">
+          <template v-if="column.key === 'name'">
           <router-link :to="{ path: '/tungstenfirewallpolicy/' + record.uuid, query: { zoneid: zoneId } }">{{ text }}</router-link>
-        </template>
-        <template #firewallrule="{ record }">
-          <span v-if="record.firewallrule.length > 0">{{ record.firewallrule[0].name }}</span>
-        </template>
-        <template #action="{ record }">
-          <a-popconfirm
-            :title="$t('label.confirm.delete.tungsten.firewall.policy')"
-            @confirm="removeFirewallRule(record.uuid)"
-            :okText="$t('label.yes')"
-            :cancelText="$t('label.no')">
-            <tooltip-button
-              tooltipPlacement="bottom"
-              :tooltip="$t('label.delete.tungsten.firewall.policy')"
-              danger
-              type="primary"
-              icon="delete-outlined"
-              :loading="deleteLoading" />
-          </a-popconfirm>
+          </template>
+          <template v-if="column.key === 'firewallrule'">
+            <span v-if="record.firewallrule.length > 0">{{ record.firewallrule[0].name }}</span>
+          </template>
+          <template v-if="column.key === 'actions'">
+            <a-popconfirm
+              :title="$t('label.confirm.delete.tungsten.firewall.policy')"
+              @confirm="removeFirewallRule(record.uuid)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')">
+              <tooltip-button
+                tooltipPlacement="bottom"
+                :tooltip="$t('label.delete.tungsten.firewall.policy')"
+                danger
+                type="primary"
+                icon="delete-outlined"
+                :loading="deleteLoading" />
+            </a-popconfirm>
+          </template>
         </template>
       </a-table>
       <div style="display: block; text-align: right; margin-top: 10px;">
@@ -146,14 +148,14 @@
       columns: [{
         title: this.$t('label.name'),
         dataIndex: 'name',
-        slots: { customRender: 'name' }
+        key: 'name'
       }, {
         title: this.$t('label.firewallrule'),
         dataIndex: 'firewallrule',
-        slots: { customRender: 'firewallrule' }
+        key: 'firewallrule'
       }, {
-        title: this.$t('label.action'),
-        slots: { customRender: 'action' },
+        title: this.$t('label.actions'),
+        key: 'actions',
         width: 80
       }],
       dataSource: [],
diff --git a/ui/src/views/network/tungsten/FirewallRuleTab.vue b/ui/src/views/network/tungsten/FirewallRuleTab.vue
index 7bfaa71..c19cdda 100644
--- a/ui/src/views/network/tungsten/FirewallRuleTab.vue
+++ b/ui/src/views/network/tungsten/FirewallRuleTab.vue
@@ -34,20 +34,22 @@
         :dataSource="dataSource"
         :rowKey="item => item.uuid"
         :pagination="false">
-        <template #actions="{ record }">
-          <a-popconfirm
-            :title="$t('message.confirm.remove.firewall.rule')"
-            @confirm="removeFirewallRule(record.uuid)"
-            :okText="$t('label.yes')"
-            :cancelText="$t('label.no')">
-            <tooltip-button
-              tooltipPlacement="bottom"
-              :tooltip="$t('label.remove.firewall.rule')"
-              danger
-              type="primary"
-              icon="delete-outlined"
-              :loading="deleteLoading" />
-          </a-popconfirm>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'actions'">
+            <a-popconfirm
+              :title="$t('message.confirm.remove.firewall.rule')"
+              @confirm="removeFirewallRule(record.uuid)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')">
+              <tooltip-button
+                tooltipPlacement="bottom"
+                :tooltip="$t('label.remove.firewall.rule')"
+                danger
+                type="primary"
+                icon="delete-outlined"
+                :loading="deleteLoading" />
+            </a-popconfirm>
+          </template>
         </template>
       </a-table>
       <div style="display: block; text-align: right; margin-top: 10px;">
@@ -96,9 +98,9 @@
             <a-select
               v-model:value="form.action"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :placeholder="apiParams.action.description">
               <a-select-option value="pass">PASS</a-select-option>
@@ -114,11 +116,14 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="serviceGroup.loading"
               :placeholder="apiParams.servicegroupuuid.description">
-              <a-select-option v-for="group in serviceGroup.opts" :key="group.uuid">
+              <a-select-option
+                v-for="group in serviceGroup.opts"
+                :key="group.uuid"
+                :label="group.name || group.description || group.displaytext">
                 {{ group.name || group.description || group.displaytext }}
               </a-select-option>
             </a-select>
@@ -142,11 +147,14 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="srcTag.loading"
               :placeholder="apiParams.srctaguuid.description">
-              <a-select-option v-for="tag in srcTag.opts" :key="tag.uuid">
+              <a-select-option
+                v-for="tag in srcTag.opts"
+                :key="tag.uuid"
+                :label="tag.name || tag.description || tag.displaytext">
                 {{ tag.name || tag.description || tag.displaytext }}
               </a-select-option>
             </a-select>
@@ -160,11 +168,14 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="srcAddress.loading"
               :placeholder="apiParams.srcaddressgroupuuid.description">
-              <a-select-option v-for="address in srcAddress.opts" :key="address.uuid">
+              <a-select-option
+                v-for="address in srcAddress.opts"
+                :key="address.uuid"
+                :label="address.name || address.description || address.displaytext">
                 {{ address.name || address.description || address.displaytext }}
               </a-select-option>
             </a-select>
@@ -178,11 +189,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="networks.loading"
               :placeholder="apiParams.srcnetworkuuid.description">
-              <a-select-option v-for="network in networks.opts" :key="network.uuid">
+              <a-select-option v-for="network in networks.opts" :key="network.uuid" :label="network.name || network.description || network.displaytext">
                 {{ network.name || network.description || network.displaytext }}
               </a-select-option>
             </a-select>
@@ -194,9 +205,9 @@
             <a-select
               v-model:value="form.direction"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :placeholder="apiParams.direction.description">
               <a-select-option value="oneway">ONE WAY</a-select-option>
@@ -222,11 +233,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="srcTag.loading"
               :placeholder="apiParams.desttaguuid.description">
-              <a-select-option v-for="tag in srcTag.opts" :key="tag.uuid">
+              <a-select-option v-for="tag in srcTag.opts" :key="tag.uuid" :label="tag.name || tag.description || tag.displaytext">
                 {{ tag.name || tag.description || tag.displaytext }}
               </a-select-option>
             </a-select>
@@ -240,11 +251,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="srcAddress.loading"
               :placeholder="apiParams.destaddressgroupuuid.description">
-              <a-select-option v-for="address in srcAddress.opts" :key="address.uuid">
+              <a-select-option v-for="address in srcAddress.opts" :key="address.uuid" :label="address.name || address.description || address.displaytext">
                 {{ address.name || address.description || address.displaytext }}
               </a-select-option>
             </a-select>
@@ -258,11 +269,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="networks.loading"
               :placeholder="apiParams.destnetworkuuid.description">
-              <a-select-option v-for="network in networks.opts" :key="network.uuid">
+              <a-select-option v-for="network in networks.opts" :key="network.uuid" :label="network.name || network.description || network.displaytext">
                 {{ network.name || network.description || network.displaytext }}
               </a-select-option>
             </a-select>
@@ -276,11 +287,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               :loading="tagType.loading"
               :placeholder="apiParams.tagtypeuuid.description">
-              <a-select-option v-for="tag in tagType.opts" :key="tag.uuid">
+              <a-select-option v-for="tag in tagType.opts" :key="tag.uuid" :label="tag.name || tag.description || tag.displaytext">
                 {{ tag.name || tag.description || tag.displaytext }}
               </a-select-option>
             </a-select>
@@ -357,7 +368,7 @@
         dataIndex: 'tagtype'
       }, {
         title: this.$t('label.actions'),
-        slots: { customRender: 'actions' },
+        key: 'actions',
         width: 80
       }],
       dataSource: [],
diff --git a/ui/src/views/network/tungsten/FirewallTagTab.vue b/ui/src/views/network/tungsten/FirewallTagTab.vue
index 8373130..b1e08e9 100644
--- a/ui/src/views/network/tungsten/FirewallTagTab.vue
+++ b/ui/src/views/network/tungsten/FirewallTagTab.vue
@@ -34,20 +34,22 @@
         :dataSource="dataSource"
         :rowKey="item => item.uuid"
         :pagination="false">
-        <template #action="{ record }">
-          <a-popconfirm
-            :title="$t('message.delete.tungsten.tag')"
-            @confirm="removeTag(record.uuid)"
-            :okText="$t('label.yes')"
-            :cancelText="$t('label.no')">
-            <tooltip-button
-              tooltipPlacement="bottom"
-              :tooltip="$t('label.remove.tag')"
-              danger
-              type="primary"
-              icon="delete-outlined"
-              :loading="deleteLoading" />
-          </a-popconfirm>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'actions'">
+            <a-popconfirm
+              :title="$t('message.delete.tungsten.tag')"
+              @confirm="removeTag(record.uuid)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')">
+              <tooltip-button
+                tooltipPlacement="bottom"
+                :tooltip="$t('label.remove.tag')"
+                danger
+                type="primary"
+                icon="delete-outlined"
+                :loading="deleteLoading" />
+            </a-popconfirm>
+          </template>
         </template>
       </a-table>
       <a-divider/>
@@ -89,11 +91,11 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }"
               v-model:value="form.taguuid"
               :placeholder="apiParams.taguuid.description">
-              <a-select-option v-for="opt in tagSrc.opts" :key="opt.uuid">{{ opt.name }}</a-select-option>
+              <a-select-option v-for="opt in tagSrc.opts" :key="opt.uuid" :label="opt.name">{{ opt.name }}</a-select-option>
             </a-select>
           </a-form-item>
 
@@ -141,8 +143,8 @@
         title: this.$t('label.name'),
         dataIndex: 'name'
       }, {
-        title: this.$t('label.action'),
-        slots: { customRender: 'action' },
+        title: this.$t('label.actions'),
+        key: 'actions',
         width: 80
       }],
       page: 1,
diff --git a/ui/src/views/network/tungsten/LogicalRouterTab.vue b/ui/src/views/network/tungsten/LogicalRouterTab.vue
index 60b3cfc..5334d5b 100644
--- a/ui/src/views/network/tungsten/LogicalRouterTab.vue
+++ b/ui/src/views/network/tungsten/LogicalRouterTab.vue
@@ -110,9 +110,9 @@
         title: this.$t('label.name'),
         dataIndex: 'name'
       }, {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        slots: { customRender: 'action' },
+        title: this.$t('label.actions'),
+        dataIndex: 'actions',
+        key: 'actions',
         width: 80
       }],
       dataSource: [],
diff --git a/ui/src/views/network/tungsten/NetworkPolicyTab.vue b/ui/src/views/network/tungsten/NetworkPolicyTab.vue
index 91e27e7..b1970d0 100644
--- a/ui/src/views/network/tungsten/NetworkPolicyTab.vue
+++ b/ui/src/views/network/tungsten/NetworkPolicyTab.vue
@@ -32,22 +32,24 @@
       :dataSource="dataSource"
       :rowKey="(item, index) => index"
       :pagination="false">
-      <template #action="{ record }">
-        <a-popconfirm
-          v-if="'removeTungstenFabricPolicy' in $store.getters.apis"
-          placement="topRight"
-          :title="$t('message.confirm.remove.network.policy')"
-          :ok-text="$t('label.yes')"
-          :cancel-text="$t('label.no')"
-          :loading="deleteLoading"
-          @confirm="removeNetworkPolicy(record)"
-        >
-          <tooltip-button
-            :tooltip="$t('label.action.remove.network.policy')"
-            danger
-            type="primary"
-            icon="delete-outlined" />
-        </a-popconfirm>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'actions'">
+          <a-popconfirm
+            v-if="'removeTungstenFabricPolicy' in $store.getters.apis"
+            placement="topRight"
+            :title="$t('message.confirm.remove.network.policy')"
+            :ok-text="$t('label.yes')"
+            :cancel-text="$t('label.no')"
+            :loading="deleteLoading"
+            @confirm="removeNetworkPolicy(record)"
+          >
+            <tooltip-button
+              :tooltip="$t('label.action.remove.network.policy')"
+              danger
+              type="primary"
+              icon="delete-outlined" />
+          </a-popconfirm>
+        </template>
       </template>
     </a-table>
     <div style="display: block; text-align: right; margin-top: 10px;">
@@ -143,9 +145,9 @@
         title: this.$t('label.name'),
         dataIndex: 'name'
       }, {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        slots: { customRender: 'action' },
+        title: this.$t('label.actions'),
+        dataIndex: 'actions',
+        key: 'actions',
         width: 80
       }],
       dataSource: [],
diff --git a/ui/src/views/network/tungsten/TungstenFabric.vue b/ui/src/views/network/tungsten/TungstenFabric.vue
index 066e32b..25ebf85 100644
--- a/ui/src/views/network/tungsten/TungstenFabric.vue
+++ b/ui/src/views/network/tungsten/TungstenFabric.vue
@@ -87,16 +87,16 @@
             {
               dataIndex: 'name',
               title: this.$t('label.name'),
-              slots: { customRender: 'name' }
+              key: 'name'
             },
             {
               dataIndex: 'network',
               title: this.$t('label.network'),
-              slots: { customRender: 'network' }
+              key: 'network'
             },
             {
-              title: this.$t('label.action'),
-              slots: { customRender: 'action' },
+              title: this.$t('label.actions'),
+              key: 'actions',
               width: 150
             }
           ]
@@ -135,21 +135,21 @@
             {
               dataIndex: 'name',
               title: this.$t('label.name'),
-              slots: { customRender: 'name' }
+              key: 'name'
             },
             {
               dataIndex: 'firewallpolicy',
               title: this.$t('label.firewallpolicy'),
-              slots: { customRender: 'firewallpolicy' }
+              key: 'firewallpolicy'
             },
             {
               dataIndex: 'tag',
               title: this.$t('label.tag'),
-              slots: { customRender: 'tag' }
+              key: 'tag'
             },
             {
-              title: this.$t('label.action'),
-              slots: { customRender: 'action' },
+              title: this.$t('label.actions'),
+              key: 'actions',
               width: 150
             }
           ]
@@ -206,14 +206,14 @@
           columns: [{
             dataIndex: 'name',
             title: this.$t('label.name'),
-            slots: { customRender: 'name' }
+            key: 'name'
           }, {
             dataIndex: 'network',
             title: this.$t('label.network'),
-            slots: { customRender: 'network' }
+            key: 'network'
           }, {
-            title: this.$t('label.action'),
-            slots: { customRender: 'action' },
+            title: this.$t('label.actions'),
+            key: 'actions',
             width: 150
           }]
         },
@@ -298,11 +298,11 @@
             {
               dataIndex: 'name',
               title: this.$t('label.name'),
-              slots: { customRender: 'name' }
+              key: 'name'
             },
             {
-              title: this.$t('label.action'),
-              slots: { customRender: 'action' },
+              title: this.$t('label.actions'),
+              key: 'actions',
               width: 150
             }
           ]
@@ -338,10 +338,10 @@
           columns: [{
             dataIndex: 'name',
             title: this.$t('label.name'),
-            slots: { customRender: 'name' }
+            key: 'name'
           }, {
-            title: this.$t('label.action'),
-            slots: { customRender: 'action' },
+            title: this.$t('label.actions'),
+            key: 'actions',
             width: 150
           }]
         },
@@ -411,26 +411,26 @@
             {
               dataIndex: 'name',
               title: this.$t('label.name'),
-              slots: { customRender: 'name' }
+              key: 'name'
             },
             {
               dataIndex: 'protocol',
               title: this.$t('label.protocol'),
-              slots: { customRender: 'protocol' }
+              key: 'protocol'
             },
             {
               dataIndex: 'startport',
               title: this.$t('label.startport'),
-              slots: { customRender: 'startport' }
+              key: 'startport'
             },
             {
               dataIndex: 'endport',
               title: this.$t('label.endport'),
-              slots: { customRender: 'endport' }
+              key: 'endport'
             },
             {
-              title: this.$t('label.action'),
-              slots: { customRender: 'action' },
+              title: this.$t('label.actions'),
+              key: 'actions',
               width: 150
             }
           ]
@@ -476,21 +476,21 @@
             {
               dataIndex: 'name',
               title: this.$t('label.name'),
-              slots: { customRender: 'name' }
+              key: 'name'
             },
             {
               dataIndex: 'ipprefix',
               title: this.$t('label.ipprefix'),
-              slots: { customRender: 'ipprefix' }
+              key: 'ipprefix'
             },
             {
               dataIndex: 'ipprefixlen',
               title: this.$t('label.ipprefixlen'),
-              slots: { customRender: 'ipprefixlen' }
+              key: 'ipprefixlen'
             },
             {
-              title: this.$t('label.action'),
-              slots: { customRender: 'action' },
+              title: this.$t('label.actions'),
+              key: 'actions',
               width: 150
             }
           ]
diff --git a/ui/src/views/network/tungsten/TungstenFabricPolicyRule.vue b/ui/src/views/network/tungsten/TungstenFabricPolicyRule.vue
index 0b0b3d5..c842224 100644
--- a/ui/src/views/network/tungsten/TungstenFabricPolicyRule.vue
+++ b/ui/src/views/network/tungsten/TungstenFabricPolicyRule.vue
@@ -32,28 +32,30 @@
       :dataSource="dataSource"
       :rowKey="(item, index) => index"
       :pagination="false">
-      <template #sourceport="{ record }">
-        <span>{{ record.srcstartport + ':' + record.srcendport }}</span>
-      </template>
-      <template #destport="{ record }">
-        <span>{{ record.deststartport + ':' + record.destendport }}</span>
-      </template>
-      <template #ruleAction="{ record }">
-        <a-popconfirm
-          v-if="'removeTungstenFabricPolicyRule' in $store.getters.apis"
-          placement="topRight"
-          :title="$t('message.delete.tungsten.policy.rule')"
-          :ok-text="$t('label.yes')"
-          :cancel-text="$t('label.no')"
-          :loading="deleteLoading"
-          @confirm="deleteRule(record)"
-        >
-          <tooltip-button
-            :tooltip="$t('label.delete.rule')"
-            danger
-            type="primary"
-            icon="delete-outlined" />
-        </a-popconfirm>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'sourceport'">
+          <span>{{ record.srcstartport + ':' + record.srcendport }}</span>
+        </template>
+        <template v-if="column.key === 'destport'">
+          <span>{{ record.deststartport + ':' + record.destendport }}</span>
+        </template>
+        <template v-if="column.key === 'ruleAction'">
+          <a-popconfirm
+            v-if="'removeTungstenFabricPolicyRule' in $store.getters.apis"
+            placement="topRight"
+            :title="$t('message.delete.tungsten.policy.rule')"
+            :ok-text="$t('label.yes')"
+            :cancel-text="$t('label.no')"
+            :loading="deleteLoading"
+            @confirm="deleteRule(record)"
+          >
+            <tooltip-button
+              :tooltip="$t('label.delete.rule')"
+              danger
+              type="primary"
+              icon="delete-outlined" />
+          </a-popconfirm>
+        </template>
       </template>
     </a-table>
 
@@ -247,43 +249,43 @@
       pageSize: this.$store.getters.defaultListViewPageSize,
       columns: [
         {
-          title: this.$t('label.action'),
-          dataIndex: 'action',
-          slots: { customRender: 'action' }
+          title: this.$t('label.actions'),
+          dataIndex: 'actions',
+          key: 'actions'
         },
         {
           title: this.$t('label.direction'),
           dataIndex: 'direction',
-          slots: { customRender: 'direction' }
+          key: 'direction'
         },
         {
           title: this.$t('label.protocol'),
           dataIndex: 'protocol',
-          slots: { customRender: 'protocol' }
+          key: 'protocol'
         },
         {
           title: this.$t('label.srcnetwork'),
           dataIndex: 'srcnetwork',
-          slots: { customRender: 'srcnetwork' }
+          key: 'srcnetwork'
         },
         {
           title: this.$t('label.sourceport'),
           dataIndex: 'sourceport',
-          slots: { customRender: 'sourceport' }
+          key: 'sourceport'
         },
         {
           title: this.$t('label.destnetwork'),
           dataIndex: 'destnetwork',
-          slots: { customRender: 'destnetwork' }
+          key: 'destnetwork'
         },
         {
           title: this.$t('label.destport'),
           dataIndex: 'destport',
-          slots: { customRender: 'destport' }
+          key: 'destport'
         },
         {
           dataIndex: 'ruleAction',
-          slots: { customRender: 'ruleAction' },
+          key: 'ruleAction',
           width: 50
         }
       ]
diff --git a/ui/src/views/network/tungsten/TungstenFabricPolicyTag.vue b/ui/src/views/network/tungsten/TungstenFabricPolicyTag.vue
index d4e14cf..502279a 100644
--- a/ui/src/views/network/tungsten/TungstenFabricPolicyTag.vue
+++ b/ui/src/views/network/tungsten/TungstenFabricPolicyTag.vue
@@ -32,25 +32,27 @@
       :dataSource="dataSource"
       :rowKey="(item, index) => index"
       :pagination="false">
-      <template #policy="{ record }">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'policy'">
         <span v-if="record.policy.length > 0">{{ record.policy[0].name }}</span>
-      </template>
-      <template #action="{ record }">
-        <a-popconfirm
-          v-if="'removeTungstenFabricTag' in $store.getters.apis"
-          placement="topRight"
-          :title="$t('message.delete.tungsten.tag')"
-          :ok-text="$t('label.yes')"
-          :cancel-text="$t('label.no')"
-          :loading="deleteLoading"
-          @confirm="deleteRule(record)"
-        >
-          <tooltip-button
-            :tooltip="$t('label.delete.tag')"
-            danger
-            type="primary"
-            icon="delete-outlined" />
-        </a-popconfirm>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <a-popconfirm
+            v-if="'removeTungstenFabricTag' in $store.getters.apis"
+            placement="topRight"
+            :title="$t('message.delete.tungsten.tag')"
+            :ok-text="$t('label.yes')"
+            :cancel-text="$t('label.no')"
+            :loading="deleteLoading"
+            @confirm="deleteRule(record)"
+          >
+            <tooltip-button
+              :tooltip="$t('label.delete.tag')"
+              danger
+              type="primary"
+              icon="delete-outlined" />
+          </a-popconfirm>
+        </template>
       </template>
     </a-table>
 
@@ -147,17 +149,17 @@
         {
           title: this.$t('label.name'),
           dataIndex: 'name',
-          slots: { customRender: 'name' }
+          key: 'name'
         },
         {
           title: this.$t('label.policy'),
           dataIndex: 'policy',
-          slots: { customRender: 'policy' }
+          key: 'policy'
         },
         {
-          title: this.$t('label.action'),
-          dataIndex: 'action',
-          slots: { customRender: 'action' },
+          title: this.$t('label.actions'),
+          dataIndex: 'actions',
+          key: 'actions',
           width: 70
         }
       ]
diff --git a/ui/src/views/network/tungsten/TungstenNetworkTable.vue b/ui/src/views/network/tungsten/TungstenNetworkTable.vue
index 7bb0e90..bed85f8 100644
--- a/ui/src/views/network/tungsten/TungstenNetworkTable.vue
+++ b/ui/src/views/network/tungsten/TungstenNetworkTable.vue
@@ -25,52 +25,49 @@
       :pagination="false"
       :rowKey="(record, idx) => record.id || record.name || idx + '-' + Math.random()"
       :scroll="{ y: 350 }">
-      <template #name="{ text, record }">
-        <!-- <QuickView
-          :actions="actions"
-          :enabled="true"
-          :resource="record"
-          @exec-action="(action) => execAction(action, record)"/> -->
+      <template #bodyCell="{ column, text, record }">
+        <template v-if="column.key === 'name'">
         <router-link v-if="apiName === 'listTungstenFabricPolicy'" :to="{ path: '/tungstenpolicy/' + record.uuid, query: { zoneid: resource.zoneid } }" >{{ text }}</router-link>
         <router-link v-else-if="apiName === 'listTungstenFabricApplicationPolicySet'" :to="{ path: '/tungstenpolicyset/' + record.uuid, query: { zoneid: resource.zoneid } }" >{{ text }}</router-link>
         <span v-else>{{ text }}</span>
-      </template>
-      <template #tungstenvms="{ record }">
-        <ul v-if="record.tungstenvms.length > 0"><li v-for="item in record.tungstenvms" :key="item.uuid">{{ item.name }}</li></ul>
-      </template>
-      <template #network="{ record }">
-        <ul v-if="record.network.length > 0"><li v-for="item in record.network" :key="item.uuid"><span v-if="item.name">{{ item.name }}</span></li></ul>
-      </template>
-      <template #firewallpolicy="{ record }">
-        <span v-if="record.firewallpolicy.length > 0">{{ record.firewallpolicy.map(item => item.name).join(',') }}</span>
-      </template>
-      <template #firewallrule="{ record }">
-        <span v-if="record.firewallrule.length > 0">{{ record.firewallrule[0].name }}</span>
-      </template>
-      <template #tungstenroutingpolicyterm="{ record }">
-        <span v-if="record.tungstenroutingpolicyterm.length > 0">{{ record.tungstenroutingpolicyterm[0].name }}</span>
-      </template>
-      <template #vm="{ record }">
-        <ul v-if="record.vm.length > 0"><li v-for="item in record.vm" :key="item.uuid">{{ item.name }}</li></ul>
-      </template>
-      <template #nic="{ record }">
-        <ul v-if="record.nic.length > 0"><li v-for="item in record.nic" :key="item.uuid">{{ item.name }}</li></ul>
-      </template>
-      <template #tag="{ record }">
-        <div class="tags" v-for="tag in record.tag" :key="tag.uuid">
-          <a-tag :key="tag.uuid">{{ tag.name }}</a-tag>
-        </div>
-      </template>
-      <template #action="{ record }">
-        <span v-for="(action, index) in actions" :key="index" style="margin-right: 5px">
-          <tooltip-button
-            v-if="action.dataView && ('show' in action ? action.show(record, $store.getters) : true)"
-            :tooltip="$t(action.label)"
-            :danger="['delete-outlined', 'DeleteOutlined'].includes(action.icon)"
-            :type="(['DeleteOutlined', 'delete-outlined'].includes(action.icon) ? 'primary' : 'default')"
-            :icon="action.icon"
-            @click="() => execAction(action, record)" />
-        </span>
+        </template>
+        <template v-if="column.key === 'tungstenvms'">
+          <ul v-if="record.tungstenvms.length > 0"><li v-for="item in record.tungstenvms" :key="item.uuid">{{ item.name }}</li></ul>
+        </template>
+        <template v-if="column.key === 'network'">
+          <ul v-if="record.network.length > 0"><li v-for="item in record.network" :key="item.uuid"><span v-if="item.name">{{ item.name }}</span></li></ul>
+        </template>
+        <template v-if="column.key === 'firewallpolicy'">
+          <span v-if="record.firewallpolicy.length > 0">{{ record.firewallpolicy.map(item => item.name).join(',') }}</span>
+        </template>
+        <template v-if="column.key === 'firewallrule'">
+          <span v-if="record.firewallrule.length > 0">{{ record.firewallrule[0].name }}</span>
+        </template>
+        <template v-if="column.key === 'tungstenroutingpolicyterm'">
+          <span v-if="record.tungstenroutingpolicyterm.length > 0">{{ record.tungstenroutingpolicyterm[0].name }}</span>
+        </template>
+        <template v-if="column.key === 'vm'">
+          <ul v-if="record.vm.length > 0"><li v-for="item in record.vm" :key="item.uuid">{{ item.name }}</li></ul>
+        </template>
+        <template v-if="column.key === 'nic'">
+          <ul v-if="record.nic.length > 0"><li v-for="item in record.nic" :key="item.uuid">{{ item.name }}</li></ul>
+        </template>
+        <template v-if="column.key === 'tag'">
+          <div class="tags" v-for="tag in record.tag" :key="tag.uuid">
+            <a-tag :key="tag.uuid">{{ tag.name }}</a-tag>
+          </div>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <span v-for="(action, index) in actions" :key="index" style="margin-right: 5px">
+            <tooltip-button
+              v-if="action.dataView && ('show' in action ? action.show(record, $store.getters) : true)"
+              :tooltip="$t(action.label)"
+              :danger="['delete-outlined', 'DeleteOutlined'].includes(action.icon)"
+              :type="(['DeleteOutlined', 'delete-outlined'].includes(action.icon) ? 'primary' : 'default')"
+              :icon="action.icon"
+              @click="() => execAction(action, record)" />
+          </span>
+        </template>
       </template>
     </a-table>
     <a-pagination
diff --git a/ui/src/views/offering/AddComputeOffering.vue b/ui/src/views/offering/AddComputeOffering.vue
index ae8e07e..80d200f 100644
--- a/ui/src/views/offering/AddComputeOffering.vue
+++ b/ui/src/views/offering/AddComputeOffering.vue
@@ -51,12 +51,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="apiParams.systemvmtype.description">
-            <a-select-option key="domainrouter">{{ $t('label.domain.router') }}</a-select-option>
-            <a-select-option key="consoleproxy">{{ $t('label.console.proxy') }}</a-select-option>
-            <a-select-option key="secondarystoragevm">{{ $t('label.secondary.storage.vm') }}</a-select-option>
+            <a-select-option key="domainrouter" :label="$t('label.domain.router')">{{ $t('label.domain.router') }}</a-select-option>
+            <a-select-option key="consoleproxy" :label="$t('label.console.proxy')">{{ $t('label.console.proxy') }}</a-select-option>
+            <a-select-option key="secondarystoragevm" :label="$t('label.secondary.storage.vm')">{{ $t('label.secondary.storage.vm') }}</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item name="offeringtype" ref="offeringtype" :label="$t('label.offeringtype')" v-show="!isSystem">
@@ -218,12 +218,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="deploymentPlannerLoading"
             :placeholder="apiParams.deploymentplanner.description"
             @change="val => { handleDeploymentPlannerChange(val) }">
-            <a-select-option v-for="(opt) in deploymentPlanners" :key="opt.name">
+            <a-select-option v-for="(opt) in deploymentPlanners" :key="opt.name" :label="opt.name || opt.description || ''">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -244,14 +244,19 @@
           </a-radio-group>
         </a-form-item>
         <a-form-item name="pcidevice" ref="pcidevice" :label="$t('label.gpu')" v-if="!isSystem">
-          <a-radio-group
+          <a-select
             v-model:value="form.pcidevice"
-            buttonStyle="solid"
-            @change="selected => { handleGpuChange(selected.target.value) }">
-            <a-radio-button v-for="(opt, optIndex) in gpuTypes" :key="optIndex" :value="opt.value">
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :placeholder="$t('label.gpu')"
+            @change="handleGpuChange">
+            <a-select-option v-for="(opt, optIndex) in gpuTypes" :key="optIndex" :value="opt.value">
               {{ opt.title }}
-            </a-radio-button>
-          </a-radio-group>
+            </a-select-option>
+          </a-select>
         </a-form-item>
         <a-form-item name="vgputype" ref="vgputype" :label="$t('label.vgputype')" v-if="vGpuVisible">
           <a-select
@@ -259,10 +264,10 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="$t('label.vgputype')">
-            <a-select-option v-for="(opt, optIndex) in vGpuTypes" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in vGpuTypes" :key="optIndex" :label="opt">
               {{ opt }}
             </a-select-option>
           </a-select>
@@ -331,9 +336,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="policy in storagePolicies" :key="policy.id">
+            <a-select-option v-for="policy in storagePolicies" :key="policy.id" :label="policy.name || policy.id || ''">
               {{ policy.name || policy.id }}
             </a-select-option>
           </a-select>
@@ -516,9 +521,9 @@
                     mode="tags"
                     v-model:value="form.storagetags"
                     showSearch
-                    optionFilterProp="label"
+                    optionFilterProp="value"
                     :filterOption="(input, option) => {
-                      return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                      return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }"
                     :loading="storageTagLoading"
                     :placeholder="apiParams.tags.description"
@@ -643,14 +648,34 @@
           vgpu: []
         },
         {
-          value: 'Group of NVIDIA Corporation GK107GL [GRID K1] GPUs',
-          title: 'NVIDIA GRID K1',
-          vgpu: ['', 'passthrough', 'GRID K100', 'GRID K120Q', 'GRID K140Q', 'GRID K160Q', 'GRID K180Q']
+          value: 'Group of NVIDIA Corporation GV100GL [TESLA V100] GPUs',
+          title: 'NVIDIA TESLA V100',
+          vgpu: ['', 'passthrough', 'GRID V100D-1A', 'GRID V100D-1B', 'GRID V100D-1Q', 'GRID V100D-2A', 'GRID V100D-2B', 'GRID V100D-2B4', 'GRID V100D-2Q', 'GRID V100D-4A', 'GRID V100D-4Q', 'GRID V100D-8A', 'GRID V100D-8Q', 'GRID V100D-16A', 'GRID V100D-16Q', 'GRID V100D-32A', 'GRID V100D-32Q']
         },
         {
-          value: 'Group of NVIDIA Corporation GK104GL [GRID K2] GPUs',
-          title: 'NVIDIA GRID K2',
-          vgpu: ['', 'passthrough', 'GRID K200', 'GRID K220Q', 'GRID K240Q', 'GRID K260Q', 'GRID K280Q']
+          value: 'Group of Nvidia Corporation TU104GL [Tesla T4] GPUs',
+          title: 'NVIDIA TESLA T4',
+          vgpu: ['', 'passthrough', 'GRID T4-1A', 'GRID T4-1B', 'GRID T4-1Q', 'GRID T4-2A', 'GRID T4-2B', 'GRID T4-2B4', 'GRID T4-2Q', 'GRID T4-4A', 'GRID T4-4Q', 'GRID T4-8A', 'GRID T4-8Q', 'GRID T4-16A', 'GRID T4-16Q']
+        },
+        {
+          value: 'Group of Nvidia Corporation GA102 [RTX A5500] GPUs',
+          title: 'NVIDIA RTX A5500',
+          vgpu: ['', 'passthrough', 'NVIDIA RTXA5500-1A', 'NVIDIA RTXA5500-1B', 'NVIDIA RTXA5500-1Q', 'NVIDIA RTXA5500-2A', 'NVIDIA RTXA5500-2B', 'NVIDIA RTXA5500-2Q', 'NVIDIA RTXA5500-3A', 'NVIDIA RTXA5500-3Q', 'NVIDIA RTXA5500-4A', 'NVIDIA RTXA5500-4Q', 'NVIDIA RTXA5500-6A', 'NVIDIA RTXA5500-6Q', 'NVIDIA RTXA5500-8A', 'NVIDIA RTXA5500-8Q', 'NVIDIA RTXA5500-12A', 'NVIDIA RTXA5500-12Q', 'NVIDIA RTXA5500-24A', 'NVIDIA RTXA5500-24Q']
+        },
+        {
+          value: 'Group of NVIDIA Corporation GA102GL [A40] GPUs',
+          title: 'NVIDIA RTX A40',
+          vgpu: ['', 'passthrough', 'NVIDIA A40-1A', 'NVIDIA A40-1B', 'NVIDIA A40-1Q', 'NVIDIA A40-2A', 'NVIDIA A40-2B', 'NVIDIA A40-2Q', 'NVIDIA A40-3A', 'NVIDIA A40-3Q', 'NVIDIA A40-4A', 'NVIDIA A40-4Q', 'NVIDIA A40-6A', 'NVIDIA A40-6Q', 'NVIDIA A40-8A', 'NVIDIA A40-8Q', 'NVIDIA A40-12A', 'NVIDIA A40-12Q', 'NVIDIA A40-16A', 'NVIDIA A40-16Q', 'NVIDIA A40-24A', 'NVIDIA A40-24Q', 'NVIDIA A40-48A', 'NVIDIA A40-48Q']
+        },
+        {
+          value: 'Group of NVIDIA Corporation GA107 [NVIDIA A16/NVIDIA A2] GPUs',
+          title: 'NVIDIA RTX A2',
+          vgpu: ['', 'passthrough', 'NVIDIA A2-1A', 'NVIDIA A2-1B', 'NVIDIA A2-1Q', 'NVIDIA A2-2A', 'NVIDIA A2-2B', 'NVIDIA A2-2Q', 'NVIDIA A2-4A', 'NVIDIA A2-4Q', 'NVIDIA A2-8A', 'NVIDIA A2-8Q', 'NVIDIA A2-16A', 'NVIDIA A2-16Q']
+        },
+        {
+          value: 'Group of NVIDIA Corporation GA102GL [A10] GPUs',
+          title: 'NVIDIA RTX A10',
+          vgpu: ['', 'passthrough', 'NVIDIA A10-1A', 'NVIDIA A10-1B', 'NVIDIA A10-1Q', 'NVIDIA A10-2A', 'NVIDIA A10-2B', 'NVIDIA A10-2Q', 'NVIDIA A10-3A', 'NVIDIA A10-3Q', 'NVIDIA A10-4A', 'NVIDIA A10-4Q', 'NVIDIA A10-6A', 'NVIDIA A10-6Q', 'NVIDIA A10-8A', 'NVIDIA A10-8Q', 'NVIDIA A10-12A', 'NVIDIA A10-12Q', 'NVIDIA A10-24A', 'NVIDIA A10-24Q']
         }
       ],
       vGpuVisible: false,
@@ -707,7 +732,6 @@
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.required.input') }],
-        displaytext: [{ required: true, message: this.$t('message.error.required.input') }],
         cpunumber: [
           { required: true, message: this.$t('message.error.required.input') },
           this.naturalNumberRule
diff --git a/ui/src/views/offering/AddDiskOffering.vue b/ui/src/views/offering/AddDiskOffering.vue
index 880eae5..576721d 100644
--- a/ui/src/views/offering/AddDiskOffering.vue
+++ b/ui/src/views/offering/AddDiskOffering.vue
@@ -205,9 +205,9 @@
             mode="tags"
             v-model:value="form.tags"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children?.[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="storageTagLoading"
             :placeholder="apiParams.tags.description"
@@ -278,9 +278,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="policy in storagePolicies" :key="policy.id">
+            <a-select-option v-for="policy in storagePolicies" :key="policy.id" :label="policy.name || policy.id || ''">
               {{ policy.name || policy.id }}
             </a-select-option>
           </a-select>
@@ -357,7 +357,6 @@
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.required.input') }],
-        displaytext: [{ required: true, message: this.$t('message.error.required.input') }],
         disksize: [
           { required: true, message: this.$t('message.error.required.input') },
           { type: 'number', validator: this.validateNumber }
diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue
index abdb9da..17359e4 100644
--- a/ui/src/views/offering/AddNetworkOffering.vue
+++ b/ui/src/views/offering/AddNetworkOffering.vue
@@ -252,11 +252,11 @@
             optionFilterProp="label"
             v-model:value="form.serviceofferingid"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="serviceOfferingLoading"
             :placeholder="apiParams.serviceofferingid.description">
-            <a-select-option v-for="(opt) in serviceOfferings" :key="opt.id">
+            <a-select-option v-for="(opt) in serviceOfferings" :key="opt.id" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -320,11 +320,11 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="registeredServicePackageLoading"
             :placeholder="$t('label.service.lb.netscaler.servicepackages')">
-            <a-select-option v-for="(opt, optIndex) in registeredServicePackages" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in registeredServicePackages" :key="optIndex" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -577,7 +577,6 @@
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.name') }],
-        displaytext: [{ required: true, message: this.$t('message.error.description') }],
         networkrate: [{ type: 'number', validator: this.validateNumber }],
         serviceofferingid: [{ required: true, message: this.$t('message.error.select') }],
         domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }],
diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue
index 9b9d576..738a03a 100644
--- a/ui/src/views/offering/AddVpcOffering.vue
+++ b/ui/src/views/offering/AddVpcOffering.vue
@@ -108,11 +108,11 @@
             optionFilterProp="label"
             v-model:value="form.serviceofferingid"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="serviceOfferingLoading"
             :placeholder="apiParams.serviceofferingid.description">
-            <a-select-option v-for="(opt) in serviceOfferings" :key="opt.id">
+            <a-select-option v-for="(opt) in serviceOfferings" :key="opt.id" :label="opt.name || opt.description">
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -245,7 +245,6 @@
       })
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.name') }],
-        displaytext: [{ required: true, message: this.$t('message.error.description') }],
         domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }],
         zoneid: [{
           type: 'array',
diff --git a/ui/src/views/offering/ImportBackupOffering.vue b/ui/src/views/offering/ImportBackupOffering.vue
index a8becc9..9b8e9d6 100644
--- a/ui/src/views/offering/ImportBackupOffering.vue
+++ b/ui/src/views/offering/ImportBackupOffering.vue
@@ -72,9 +72,9 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option v-for="opt in externals.opts" :key="opt.id">
+          <a-select-option v-for="opt in externals.opts" :key="opt.id" :label="opt.name">
             {{ opt.name }}
           </a-select-option>
         </a-select>
diff --git a/ui/src/views/plugins/quota/QuotaBalance.vue b/ui/src/views/plugins/quota/QuotaBalance.vue
index ce8f28d..ab81615 100644
--- a/ui/src/views/plugins/quota/QuotaBalance.vue
+++ b/ui/src/views/plugins/quota/QuotaBalance.vue
@@ -26,11 +26,13 @@
       :pagination="false"
       :scroll="{ y: '55vh' }"
     >
-      <template #quota="{ text }">
-        <span v-if="text!==null">{{ `${currency} ${text}` }}</span>
-      </template>
-      <template #credit="{ text }">
-        <span v-if="text!==null">{{ `${currency} ${text}` }}</span>
+      <template #bodyCell="{ column, text }">
+        <template v-if="column.key === 'quota'">
+          <span v-if="text!==null">{{ `${currency} ${text}` }}</span>
+        </template>
+        <template v-if="column.key === 'credit'">
+          <span v-if="text!==null">{{ `${currency} ${text}` }}</span>
+        </template>
       </template>
     </a-table>
   </div>
@@ -65,22 +67,22 @@
     columns () {
       return [
         {
+          key: 'date',
           title: this.$t('label.date'),
           dataIndex: 'date',
-          width: 'calc(100% / 3)',
-          slots: { customRender: 'date' }
+          width: 'calc(100% / 3)'
         },
         {
+          key: 'quota',
           title: this.$t('label.quota.value'),
           dataIndex: 'quota',
-          width: 'calc(100% / 3)',
-          slots: { customRender: 'quota' }
+          width: 'calc(100% / 3)'
         },
         {
+          key: 'credit',
           title: this.$t('label.credit'),
           dataIndex: 'credit',
-          width: 'calc(100% / 3)',
-          slots: { customRender: 'credit' }
+          width: 'calc(100% / 3)'
         }
       ]
     }
diff --git a/ui/src/views/plugins/quota/QuotaUsage.vue b/ui/src/views/plugins/quota/QuotaUsage.vue
index f8e339f..04a5f4f 100644
--- a/ui/src/views/plugins/quota/QuotaUsage.vue
+++ b/ui/src/views/plugins/quota/QuotaUsage.vue
@@ -26,8 +26,10 @@
       :pagination="false"
       :scroll="{ y: '55vh' }"
     >
-      <template #quota="{ text }">
-        <span v-if="text!==undefined">{{ `${currency} ${text}` }}</span>
+      <template #bodyCell="{ column, text }">
+        <template v-if="column.key === 'quota'">
+          <span v-if="text!==undefined">{{ `${currency} ${text}` }}</span>
+        </template>
       </template>
     </a-table>
   </div>
@@ -63,22 +65,22 @@
     columns () {
       return [
         {
+          key: 'name',
           title: this.$t('label.quota.type.name'),
           dataIndex: 'name',
-          width: 'calc(100% / 3)',
-          slots: { customRender: 'name' }
+          width: 'calc(100% / 3)'
         },
         {
+          key: 'unit',
           title: this.$t('label.quota.type.unit'),
           dataIndex: 'unit',
-          width: 'calc(100% / 3)',
-          slots: { customRender: 'unit' }
+          width: 'calc(100% / 3)'
         },
         {
+          key: 'quota',
           title: this.$t('label.quota.usage'),
           dataIndex: 'quota',
-          width: 'calc(100% / 3)',
-          slots: { customRender: 'quota' }
+          width: 'calc(100% / 3)'
         }
       ]
     }
diff --git a/ui/src/views/project/AccountsTab.vue b/ui/src/views/project/AccountsTab.vue
index fd3408b..8c7282b 100644
--- a/ui/src/views/project/AccountsTab.vue
+++ b/ui/src/views/project/AccountsTab.vue
@@ -26,42 +26,44 @@
           :dataSource="dataSource"
           :pagination="false"
           :rowKey="rowItem => rowItem.userid ? rowItem.userid : (rowItem.accountid || rowItem.account)">
-          <template #user="{ record }">
-            <span v-if="record.userid">{{ record.username }}</span>
-          </template>
-          <template #projectrole="{ record }">
-            <span v-if="record.projectroleid">{{ getProjectRole(record) }}</span>
-          </template>
-          <template #action="{ record }">
-            <div>
-              <span v-if="imProjectAdmin && dataSource.length > 1" class="account-button-action">
-                <tooltip-button
-                  tooltipPlacement="top"
-                  :tooltip="record.userid ? $t('label.make.user.project.owner') : $t('label.make.project.owner')"
-                  v-if="record.role !== owner"
-                  type="default"
-                  icon="arrow-up-outlined"
-                  size="small"
-                  @onClick="promoteAccount(record)" />
-                <tooltip-button
-                  tooltipPlacement="top"
-                  :tooltip="record.userid ? $t('label.demote.project.owner.user') : $t('label.demote.project.owner')"
-                  v-if="updateProjectApi.params.filter(x => x.name === 'swapowner').length > 0 && record.role === owner"
-                  type="default"
-                  icon="arrow-down-outlined"
-                  size="small"
-                  @onClick="demoteAccount(record)" />
-                <tooltip-button
-                  tooltipPlacement="top"
-                  :tooltip="record.userid ? $t('label.remove.project.user') : $t('label.remove.project.account')"
-                  type="primary"
-                  :danger="true"
-                  icon="delete-outlined"
-                  size="small"
-                  :disabled="!('deleteAccountFromProject' in $store.getters.apis)"
-                  @onClick="onShowConfirmDelete(record)" />
-              </span>
-            </div>
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.key === 'user'">
+              <span v-if="record.userid">{{ record.username }}</span>
+            </template>
+            <template v-if="column.key === 'projectrole'">
+              <span v-if="record.projectroleid">{{ getProjectRole(record) }}</span>
+            </template>
+            <template v-if="column.key === 'actions'">
+              <div>
+                <span v-if="imProjectAdmin && dataSource.length > 1" class="account-button-action">
+                  <tooltip-button
+                    tooltipPlacement="top"
+                    :tooltip="record.userid ? $t('label.make.user.project.owner') : $t('label.make.project.owner')"
+                    v-if="record.role !== owner"
+                    type="default"
+                    icon="arrow-up-outlined"
+                    size="small"
+                    @onClick="promoteAccount(record)" />
+                  <tooltip-button
+                    tooltipPlacement="top"
+                    :tooltip="record.userid ? $t('label.demote.project.owner.user') : $t('label.demote.project.owner')"
+                    v-if="updateProjectApi.params.filter(x => x.name === 'swapowner').length > 0 && record.role === owner"
+                    type="default"
+                    icon="arrow-down-outlined"
+                    size="small"
+                    @onClick="demoteAccount(record)" />
+                  <tooltip-button
+                    tooltipPlacement="top"
+                    :tooltip="record.userid ? $t('label.remove.project.user') : $t('label.remove.project.account')"
+                    type="primary"
+                    :danger="true"
+                    icon="delete-outlined"
+                    size="small"
+                    :disabled="!('deleteAccountFromProject' in $store.getters.apis)"
+                    @onClick="onShowConfirmDelete(record)" />
+                </span>
+              </div>
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -122,31 +124,31 @@
   created () {
     this.columns = [
       {
+        key: 'account',
         title: this.$t('label.account'),
-        dataIndex: 'account',
-        slots: { customRender: 'account' }
+        dataIndex: 'account'
       },
       {
+        key: 'role',
         title: this.$t('label.roletype'),
-        dataIndex: 'role',
-        slots: { customRender: 'role' }
+        dataIndex: 'role'
       },
       {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        slots: { customRender: 'action' }
+        key: 'actions',
+        title: this.$t('label.actions'),
+        dataIndex: 'actions'
       }
     ]
     if (this.isProjectRolesSupported()) {
       this.columns.splice(1, 0, {
+        key: 'user',
         title: this.$t('label.user'),
-        dataIndex: 'userid',
-        slots: { customRender: 'user' }
+        dataIndex: 'userid'
       })
       this.columns.splice(this.columns.length - 1, 0, {
+        key: 'projectrole',
         title: this.$t('label.project.role'),
-        dataIndex: 'projectroleid',
-        slots: { customRender: 'projectrole' }
+        dataIndex: 'projectroleid'
       })
     }
     this.page = 1
diff --git a/ui/src/views/project/AddAccountOrUserToProject.vue b/ui/src/views/project/AddAccountOrUserToProject.vue
index 31af9b0..e65c6a7 100644
--- a/ui/src/views/project/AddAccountOrUserToProject.vue
+++ b/ui/src/views/project/AddAccountOrUserToProject.vue
@@ -55,9 +55,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="role in projectRoles" :key="role.id">
+              <a-select-option v-for="role in projectRoles" :key="role.id" :label="role.name">
                 {{ role.name }}
               </a-select-option>
             </a-select>
@@ -70,9 +70,9 @@
               v-model:value="form.roletype"
               :placeholder="apiParams.addAccountToProject.roletype.description"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option value="Admin">Admin</a-select-option>
               <a-select-option value="Regular">Regular</a-select-option>
@@ -128,9 +128,9 @@
               showSearch
               optionFilterProp="label"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="role in projectRoles" :key="role.id">
+              <a-select-option v-for="role in projectRoles" :key="role.id" :label="role.name">
                 {{ role.name }}
               </a-select-option>
             </a-select>
@@ -143,9 +143,9 @@
               v-model:value="form.roletype"
               :placeholder="apiParams.addUserToProject.roletype.description"
               showSearch
-              optionFilterProp="label"
+              optionFilterProp="value"
               :filterOption="(input, option) => {
-                return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option value="Admin">Admin</a-select-option>
               <a-select-option value="Regular">Regular</a-select-option>
diff --git a/ui/src/views/project/InvitationsTemplate.vue b/ui/src/views/project/InvitationsTemplate.vue
index af13412..88159fc 100644
--- a/ui/src/views/project/InvitationsTemplate.vue
+++ b/ui/src/views/project/InvitationsTemplate.vue
@@ -36,26 +36,28 @@
           :pagination="false"
           :rowKey="record => record.id || record.account"
           @change="onChangeTable">
-          <template #state="{ text }">
-            <status :text="text ? text : ''" displayText />
-          </template>
-          <template #action="{ record }">
-            <div v-if="record.state===stateAllow" class="account-button-action">
-              <tooltip-button
-                tooltipPlacement="top"
-                :tooltip="$t('label.accept.project.invitation')"
-                icon="check-outlined"
-                size="small"
-                @onClick="onShowConfirmAcceptInvitation(record)"/>
-              <tooltip-button
-                tooltipPlacement="top"
-                :tooltip="$t('label.decline.invitation')"
-                type="primary"
-                :danger="true"
-                icon="close-outlined"
-                size="small"
-                @onClick="onShowConfirmRevokeInvitation(record)"/>
-            </div>
+          <template #bodyCell="{ column, text, record }">
+            <template v-if="column.key === 'state'">
+              <status :text="text ? text : ''" displayText />
+            </template>
+            <template v-if="column.key === 'actions'">
+              <div v-if="record.state===stateAllow" class="account-button-action">
+                <tooltip-button
+                  tooltipPlacement="top"
+                  :tooltip="$t('label.accept.project.invitation')"
+                  icon="check-outlined"
+                  size="small"
+                  @onClick="onShowConfirmAcceptInvitation(record)"/>
+                <tooltip-button
+                  tooltipPlacement="top"
+                  :tooltip="$t('label.decline.invitation')"
+                  type="primary"
+                  :danger="true"
+                  icon="close-outlined"
+                  size="small"
+                  @onClick="onShowConfirmRevokeInvitation(record)"/>
+              </div>
+            </template>
           </template>
         </a-table>
         <a-pagination
@@ -108,25 +110,25 @@
   created () {
     this.columns = [
       {
+        key: 'project',
         title: this.$t('label.project'),
-        dataIndex: 'project',
-        slots: { customRender: 'project' }
+        dataIndex: 'project'
       },
       {
+        key: 'account',
         title: this.$t('label.account'),
-        dataIndex: 'account',
-        slots: { customRender: 'account' }
+        dataIndex: 'account'
       },
       {
+        key: 'domain',
         title: this.$t('label.domain'),
-        dataIndex: 'domain',
-        slots: { customRender: 'domain' }
+        dataIndex: 'domain'
       },
       {
+        key: 'state',
         title: this.$t('label.state'),
         dataIndex: 'state',
         width: 130,
-        slots: { customRender: 'state' },
         filters: [
           {
             text: this.$t('state.pending'),
@@ -148,10 +150,10 @@
         filterMultiple: false
       },
       {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        width: 80,
-        slots: { customRender: 'action' }
+        key: 'actions',
+        title: this.$t('label.actions'),
+        dataIndex: 'actions',
+        width: 80
       }
     ]
 
@@ -161,9 +163,9 @@
     this.apiParams = this.$getApiParams('listProjectInvitations')
     if (this.apiParams.userid) {
       this.columns.splice(2, 0, {
+        key: 'user',
         title: this.$t('label.user'),
-        dataIndex: 'userid',
-        slots: { customRender: 'user' }
+        dataIndex: 'userid'
       })
     }
     this.fetchData()
diff --git a/ui/src/views/project/iam/ProjectRolePermissionTab.vue b/ui/src/views/project/iam/ProjectRolePermissionTab.vue
index df82d21..7b24098a 100644
--- a/ui/src/views/project/iam/ProjectRolePermissionTab.vue
+++ b/ui/src/views/project/iam/ProjectRolePermissionTab.vue
@@ -61,7 +61,6 @@
         handle=".drag-handle"
         animation="200"
         ghostClass="drag-ghost"
-        tag="transition-group"
         :component-data="{type: 'transition'}"
         item-key="id">
         <template #item="{element}">
diff --git a/ui/src/views/project/iam/ProjectRoleTab.vue b/ui/src/views/project/iam/ProjectRoleTab.vue
index d8d8b44..a0768db 100644
--- a/ui/src/views/project/iam/ProjectRoleTab.vue
+++ b/ui/src/views/project/iam/ProjectRoleTab.vue
@@ -27,31 +27,33 @@
           :loading="loading"
           :columns="columns"
           :dataSource="dataSource"
-          :rowKey="(record,idx) => record.projectid + '-' + idx"
+          :rowKey="(record, index) => record.projectid + '-' + index"
           :pagination="false">
           <template #expandedRowRender="{ record }">
             <ProjectRolePermissionTab class="table" :resource="resource" :role="record"/>
           </template>
-          <template #name="{ record }"> {{ record.name }} </template>
-          <template #description="{ record }">
-            {{ record.description }}
-          </template>
-          <template #action="{ record }">
-            <tooltip-button
-              tooltipPlacement="top"
-              :tooltip="$t('label.update.project.role')"
-              icon="edit-outlined"
-              size="small"
-              style="margin:10px"
-              @onClick="openUpdateModal(record)" />
-            <tooltip-button
-              tooltipPlacement="top"
-              :tooltip="$t('label.remove.project.role')"
-              type="primary"
-              :danger="true"
-              icon="delete-outlined"
-              size="small"
-              @onClick="deleteProjectRole(record)" />
+          <template #bodyCell="{ column, record }">
+            <template v-if="column.key === 'name'"> {{ record.name }} </template>
+            <template v-if="column.key === 'description'">
+              {{ record.description }}
+            </template>
+            <template v-if="column.key === 'actions'">
+              <tooltip-button
+                tooltipPlacement="top"
+                :tooltip="$t('label.update.project.role')"
+                icon="edit-outlined"
+                size="small"
+                style="margin:10px"
+                @onClick="openUpdateModal(record)" />
+              <tooltip-button
+                tooltipPlacement="top"
+                :tooltip="$t('label.remove.project.role')"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined"
+                size="small"
+                @onClick="deleteProjectRole(record)" />
+            </template>
           </template>
         </a-table>
         <a-modal
@@ -149,20 +151,21 @@
   created () {
     this.columns = [
       {
+        key: 'name',
         title: this.$t('label.name'),
         dataIndex: 'name',
-        width: '35%',
-        slots: { customRender: 'name' }
+        width: '35%'
       },
       {
+        key: 'description',
         title: this.$t('label.description'),
         dataIndex: 'description'
       },
       {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        width: 100,
-        slots: { customRender: 'action' }
+        key: 'actions',
+        title: this.$t('label.actions'),
+        dataIndex: 'actions',
+        width: 100
       }
     ]
     this.initForm()
diff --git a/ui/src/views/setting/ConfigurationHierarchy.vue b/ui/src/views/setting/ConfigurationHierarchy.vue
index c3cfddb..45060ef 100644
--- a/ui/src/views/setting/ConfigurationHierarchy.vue
+++ b/ui/src/views/setting/ConfigurationHierarchy.vue
@@ -26,18 +26,18 @@
     :rowClassName="getRowClassName"
     style="overflow-y: auto; margin-left: 10px" >
 
-    <template #name="{ record }">
-      <span :style="hierarchyExists ? 'padding-left: 0px;' : 'padding-left: 25px;'">
-        <b><span v-if="record.parent">└─ &nbsp;</span>{{record.displaytext }} </b> {{ ' (' + record.name + ')' }}
-      </span>
-      <br/>
-      <span :style="record.parent ? 'padding-left: 50px; display:block' : 'padding-left: 25px; display:block'">{{ record.description }}</span>
+    <template #bodyCell="{ column, record }">
+      <template v-if="column.key === 'name'">
+    <span :style="hierarchyExists ? 'padding-left: 0px;' : 'padding-left: 25px;'">
+      <b><span v-if="record.parent">└─ &nbsp;</span>{{record.displaytext }} </b> {{ ' (' + record.name + ')' }}
+    </span>
+    <br/>
+    <span :style="record.parent ? 'padding-left: 50px; display:block' : 'padding-left: 25px; display:block'">{{ record.description }}</span>
+      </template>
+      <template v-if="column.key === 'value'">
+        <ConfigurationValue :configrecord="record" />
+      </template>
     </template>
-
-    <template #value="{ record }">
-      <ConfigurationValue :configrecord="record" />
-    </template>
-
   </a-table>
 </template>
 
diff --git a/ui/src/views/setting/ConfigurationTab.vue b/ui/src/views/setting/ConfigurationTab.vue
index 0948a13..11c0b04 100644
--- a/ui/src/views/setting/ConfigurationTab.vue
+++ b/ui/src/views/setting/ConfigurationTab.vue
@@ -138,12 +138,12 @@
         {
           title: 'name',
           dataIndex: 'name',
-          slots: { customRender: 'name' }
+          key: 'name'
         },
         {
           title: 'value',
           dataIndex: 'value',
-          slots: { customRender: 'value' },
+          key: 'value',
           width: '29%'
         }
       ]
diff --git a/ui/src/views/setting/ConfigurationTable.vue b/ui/src/views/setting/ConfigurationTable.vue
index d818251..6854852 100644
--- a/ui/src/views/setting/ConfigurationTable.vue
+++ b/ui/src/views/setting/ConfigurationTable.vue
@@ -27,11 +27,13 @@
       :rowKey="record => record.name"
       :rowClassName="getRowClassName" >
 
-      <template #name="{ record }">
-        <b> {{record.displaytext }} </b> {{ ' (' + record.name + ')' }} <br/> {{ record.description }}
-      </template>
-      <template #value="{ record }">
-        <ConfigurationValue :configrecord="record" />
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'name'">
+          <b> {{record.displaytext }} </b> {{ ' (' + record.name + ')' }} <br/> {{ record.description }}
+        </template>
+        <template v-if="column.key === 'value'">
+          <ConfigurationValue :configrecord="record" />
+        </template>
       </template>
     </a-table>
     <a-pagination
diff --git a/ui/src/views/storage/AttachVolume.vue b/ui/src/views/storage/AttachVolume.vue
index 96e81ba3..de0a2f7 100644
--- a/ui/src/views/storage/AttachVolume.vue
+++ b/ui/src/views/storage/AttachVolume.vue
@@ -41,9 +41,9 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
-          <a-select-option v-for="vm in virtualmachines" :key="vm.id">
+          <a-select-option v-for="vm in virtualmachines" :key="vm.id" :label="vm.name || vm.displayname">
             {{ vm.name || vm.displayname }}
           </a-select-option>
         </a-select>
diff --git a/ui/src/views/storage/ChangeOfferingForVolume.vue b/ui/src/views/storage/ChangeOfferingForVolume.vue
index 203a2da..5ab8039 100644
--- a/ui/src/views/storage/ChangeOfferingForVolume.vue
+++ b/ui/src/views/storage/ChangeOfferingForVolume.vue
@@ -41,13 +41,14 @@
         showSearch
         optionFilterProp="label"
         :filterOption="(input, option) => {
-          return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
         }"
         @change="id => onChangeDiskOffering(id)">
         <a-select-option
           v-for="(offering, index) in diskOfferings"
           :value="offering.id"
-          :key="index">
+          :key="index"
+          :label="offering.displaytext || offering.name">
           {{ offering.displaytext || offering.name }}
         </a-select-option>
       </a-select>
diff --git a/ui/src/views/storage/CreateBucket.vue b/ui/src/views/storage/CreateBucket.vue
new file mode 100644
index 0000000..f95b736
--- /dev/null
+++ b/ui/src/views/storage/CreateBucket.vue
@@ -0,0 +1,196 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div class="form-layout" v-ctrl-enter="handleSubmit">
+    <a-spin :spinning="loading">
+      <a-form
+        :ref="formRef"
+        :model="form"
+        :rules="rules"
+        layout="vertical"
+        @finish="handleSubmit"
+       >
+        <a-form-item name="name" ref="name" :label="$t('label.name')">
+          <a-input v-model:value="form.name" v-focus="true" />
+        </a-form-item>
+        <a-form-item name="objectstore" ref="objectstore" :label="$t('label.object.storage')">
+          <a-select
+            v-model:value="form.objectstore"
+            @change="val => { form.objectstore = val }"
+            showSearch
+            optionFilterProp="value"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+            <a-select-option :value="objectstore.id" v-for="objectstore in objectstores" :key="objectstore.id" :label="objectstore.name">
+              {{ objectstore.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="quota" ref="quota" :label="$t('label.quotagb')">
+          <a-input
+            v-model:value="form.quota"
+            :placeholder="$t('label.quota')"/>
+        </a-form-item>
+        <a-form-item name="encryption" ref="encryption" :label="$t('label.encryption')">
+          <a-switch
+            v-model:checked="form.encryption"
+            :checked="encryption"
+            @change="val => { encryption = val }"/>
+        </a-form-item>
+        <a-form-item name="versioning" ref="versioning" :label="$t('label.versioning')">
+          <a-switch
+            v-model:checked="form.versioning"
+            :checked="versioning"
+            @change="val => { versioning = val }"/>
+        </a-form-item>
+        <a-form-item name="objectlocking" ref="objectlocking" :label="$t('label.objectlocking')">
+          <a-switch
+            v-model:checked="form.objectlocking"
+            :checked="objectlocking"
+            @change="val => { objectlocking = val }"/>
+        </a-form-item>
+        <a-form-item name="Bucket Policy" ref="policy" :label="$t('label.bucket.policy')">
+          <a-select
+            v-model:value="form.policy"
+            @change="val => { form.policy = val }"
+            showSearch
+            optionFilterProp="value"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+            <a-select-option
+              :value="policy"
+              v-for="(policy,idx) in policyList"
+              :key="idx"
+            >{{ policy }}</a-select-option>
+          </a-select>
+        </a-form-item>
+        <div :span="24" class="action-button">
+          <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+          <a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+        </div>
+      </a-form>
+    </a-spin>
+  </div>
+</template>
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+import { mixinForm } from '@/utils/mixin'
+import ResourceIcon from '@/components/view/ResourceIcon'
+
+export default {
+  name: 'CreateBucket',
+  mixins: [mixinForm],
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  components: {
+    ResourceIcon
+  },
+  inject: ['parentFetchData'],
+  data () {
+    return {
+      loading: false
+    }
+  },
+  created () {
+    this.initForm()
+    this.policyList = ['Public', 'Private']
+    this.fetchData()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({
+      })
+      this.rules = reactive({
+        name: [{ required: true, message: this.$t('label.required') }],
+        objectstore: [{ required: true, message: this.$t('label.required') }]
+      })
+    },
+    fetchData () {
+      this.listObjectStores()
+    },
+    listObjectStores () {
+      this.loading = true
+      api('listObjectStoragePools').then(json => {
+        this.objectstores = json.listobjectstoragepoolsresponse.objectstore || []
+        if (this.objectstores.length > 0) {
+          this.form.objectstore = this.objectstores[0].id
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    closeModal () {
+      this.$emit('close-action')
+    },
+    handleSubmit (e) {
+      e.preventDefault()
+      if (this.loading) return
+      this.formRef.value.validate().then(async () => {
+        const formRaw = toRaw(this.form)
+        const values = this.handleRemoveFields(formRaw)
+
+        var data = {
+          name: values.name,
+          objectstorageid: values.objectstore,
+          quota: values.quota,
+          encryption: values.encryption,
+          versioning: values.versioning,
+          objectlocking: values.objectlocking,
+          policy: values.policy
+        }
+        this.loading = true
+        api('createBucket', data).then(response => {
+          this.$pollJob({
+            jobId: response.createbucketresponse.jobid,
+            title: this.$t('label.create.bucket'),
+            description: values.name,
+            successMessage: this.$t('message.success.create.bucket'),
+            errorMessage: this.$t('message.create.bucket.failed'),
+            loadingMessage: this.$t('message.create.bucket.processing'),
+            catchMessage: this.$t('error.fetching.async.job.result')
+          })
+          this.closeModal()
+        }).catch(error => {
+          this.$notifyError(error)
+        }).finally(() => {
+          this.loading = false
+        })
+      }).catch((error) => {
+        this.formRef.value.scrollToField(error.errorFields[0].name)
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.form-layout {
+  width: 85vw;
+
+  @media (min-width: 1000px) {
+    width: 35vw;
+  }
+}
+</style>
diff --git a/ui/src/views/storage/CreateSnapshotFromVMSnapshot.vue b/ui/src/views/storage/CreateSnapshotFromVMSnapshot.vue
index 5702bc4..409e931 100644
--- a/ui/src/views/storage/CreateSnapshotFromVMSnapshot.vue
+++ b/ui/src/views/storage/CreateSnapshotFromVMSnapshot.vue
@@ -40,12 +40,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option
             v-for="(volume, index) in volumes"
             :value="volume.id"
-            :key="index">
+            :key="index"
+            :label="volume.displaytext || volume.name">
             {{ volume.displaytext || volume.name }}
           </a-select-option>
         </a-select>
diff --git a/ui/src/views/storage/CreateTemplate.vue b/ui/src/views/storage/CreateTemplate.vue
new file mode 100644
index 0000000..13ce757
--- /dev/null
+++ b/ui/src/views/storage/CreateTemplate.vue
@@ -0,0 +1,384 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-spin :spinning="loading">
+    <a-form
+      class="form"
+      layout="vertical"
+      :ref="formRef"
+      :model="form"
+      :rules="rules"
+      @finish="handleSubmit"
+      v-ctrl-enter="handleSubmit"
+     >
+      <a-form-item ref="name" name="name">
+        <template #label>
+          <tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
+        </template>
+        <a-input
+          v-focus="true"
+          v-model:value="form.name"
+          :placeholder="apiParams.name.description" />
+      </a-form-item>
+      <a-form-item ref="displaytext" name="displaytext">
+        <template #label>
+          <tooltip-label :title="$t('label.displaytext')" :tooltip="apiParams.displaytext.description"/>
+        </template>
+        <a-input
+          v-model:value="form.displaytext"
+          :placeholder="apiParams.displaytext.description" />
+      </a-form-item>
+      <a-form-item ref="zoneid" name="zoneid">
+        <template #label>
+          <tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
+        </template>
+        <a-select
+          v-model:value="form.zoneid"
+          :loading="loading"
+          :placeholder="apiParams.zoneid.description"
+          showSearch
+          optionFilterProp="label"
+          :filterOption="(input, option) => {
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          }" >
+          <a-select-option
+            v-for="(zone, index) in zones"
+            :value="zone.id"
+            :key="index"
+            :label="zone.name">
+            <span>
+              <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <global-outlined v-else style="margin-right: 5px"/>
+              {{ zone.name }}
+            </span>
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item name="domainid" ref="domainid" v-if="'listDomains' in $store.getters.apis">
+          <template #label>
+            <tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
+          </template>
+          <a-select
+            v-model:value="form.domainid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="domainLoading"
+            :placeholder="apiParams.domainid.description"
+            @change="val => { handleDomainChange(val) }">
+            <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex" :label="opt.path || opt.name || opt.description" :value="opt.id">
+              <span>
+                <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <block-outlined v-else style="margin-right: 5px" />
+                {{ opt.path || opt.name || opt.description }}
+              </span>
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item name="account" ref="account" v-if="domainid">
+          <template #label>
+            <tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
+          </template>
+          <a-select
+            v-model:value="form.account"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :placeholder="apiParams.account.description"
+            @change="val => { handleAccountChange(val) }">
+            <a-select-option v-for="(acc, index) in accounts" :value="acc.name" :key="index">
+              {{ acc.name }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+
+      <a-form-item
+          name="ostypeid"
+          ref="ostypeid"
+          :label="$t('label.ostypeid')">
+          <a-select
+          showSearch
+          optionFilterProp="label"
+          :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+          }"
+          v-model:value="form.ostypeid"
+          :loading="osTypes.loading"
+          :placeholder="apiParams.ostypeid.description">
+          <a-select-option v-for="opt in osTypes.opts" :key="opt.id" :label="opt.name || opt.description">
+              {{ opt.name || opt.description }}
+          </a-select-option>
+          </a-select>
+      </a-form-item>
+      <a-row :gutter="12">
+        <a-col :md="24" :lg="24">
+          <a-form-item ref="groupenabled" name="groupenabled">
+            <a-checkbox-group
+              v-model:value="form.groupenabled"
+              style="width: 100%;"
+            >
+              <a-row>
+                <a-col :span="12">
+                  <a-checkbox value="passwordenabled">
+                    {{ $t('label.passwordenabled') }}
+                  </a-checkbox>
+                </a-col>
+                <a-col :span="12">
+                  <a-checkbox value="isdynamicallyscalable">
+                    {{ $t('label.isdynamicallyscalable') }}
+                  </a-checkbox>
+                </a-col>
+                <a-col :span="12">
+                  <a-checkbox value="requireshvm">
+                    {{ $t('label.requireshvm') }}
+                  </a-checkbox>
+                </a-col>
+                <a-col :span="12" v-if="isAdminRole">
+                  <a-checkbox value="isfeatured">
+                    {{ $t('label.isfeatured') }}
+                  </a-checkbox>
+                </a-col>
+                <a-col :span="12" v-if="isAdminRole || $store.getters.features.userpublictemplateenabled">
+                  <a-checkbox value="ispublic">
+                    {{ $t('label.ispublic') }}
+                  </a-checkbox>
+                </a-col>
+              </a-row>
+            </a-checkbox-group>
+          </a-form-item>
+        </a-col>
+      </a-row>
+      <div :span="24" class="action-button">
+        <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+        <a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-form>
+  </a-spin>
+</template>
+
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+import { mixinForm } from '@/utils/mixin'
+import ResourceIcon from '@/components/view/ResourceIcon'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+
+export default {
+  name: 'CreateTemplate',
+  mixins: [mixinForm],
+  components: {
+    ResourceIcon,
+    TooltipLabel
+  },
+  props: {
+    resource: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data () {
+    return {
+      snapshotZoneIds: [],
+      zones: [],
+      osTypes: {},
+      loading: false,
+      domains: [],
+      accounts: [],
+      domainLoading: false,
+      domainid: null,
+      account: null
+    }
+  },
+  computed: {
+    isAdminRole () {
+      return this.$store.getters.userInfo.roletype === 'Admin'
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('createTemplate')
+  },
+  created () {
+    this.initForm()
+    this.fetchData()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({
+        groupenabled: ['requireshvm']
+      })
+      this.rules = reactive({
+        name: [{ required: true, message: this.$t('message.error.required.input') }],
+        ostypeid: [{ required: true, message: this.$t('message.error.select') }],
+        groupenabled: [{ type: 'array' }]
+      })
+    },
+    fetchData () {
+      this.fetchOsTypes()
+      this.fetchSnapshotZones()
+      if ('listDomains' in this.$store.getters.apis) {
+        this.fetchDomains()
+      }
+    },
+    fetchOsTypes () {
+      this.osTypes.opts = []
+      this.osTypes.loading = true
+
+      api('listOsTypes').then(json => {
+        const listOsTypes = json.listostypesresponse.ostype
+        this.osTypes.opts = listOsTypes
+        this.defaultOsType = this.osTypes.opts[1].description
+        this.defaultOsId = this.osTypes.opts[1].name
+      }).finally(() => {
+        this.osTypes.loading = false
+      })
+    },
+    fetchZones (id) {
+      this.loading = true
+      const params = { showicon: true }
+      if (Array.isArray(id)) {
+        params.ids = id.join()
+      } else {
+        params.id = id
+      }
+      api('listZones', params).then(json => {
+        this.zones = json.listzonesresponse.zone || []
+        this.form.zoneid = this.zones[0].id || ''
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    fetchSnapshotZones () {
+      this.loading = true
+      this.snapshotZoneIds = []
+      const params = {
+        showunique: false,
+        id: this.resource.id
+      }
+      api('listSnapshots', params).then(json => {
+        const snapshots = json.listsnapshotsresponse.snapshot || []
+        for (const snapshot of snapshots) {
+          if (!this.snapshotZoneIds.includes(snapshot.zoneid)) {
+            this.snapshotZoneIds.push(snapshot.zoneid)
+          }
+        }
+      }).finally(() => {
+        if (this.snapshotZoneIds && this.snapshotZoneIds.length > 0) {
+          this.fetchZones(this.snapshotZoneIds)
+        }
+      })
+    },
+    fetchDomains () {
+      const params = {}
+      params.listAll = true
+      params.showicon = true
+      params.details = 'min'
+      this.domainLoading = true
+      api('listDomains', params).then(json => {
+        this.domains = json.listdomainsresponse.domain
+      }).finally(() => {
+        this.domainLoading = false
+        this.handleDomainChange(null)
+      })
+    },
+    handleDomainChange (domain) {
+      this.domainid = domain
+      this.form.account = null
+      this.account = null
+      if ('listAccounts' in this.$store.getters.apis) {
+        this.fetchAccounts()
+      }
+    },
+    fetchAccounts () {
+      api('listAccounts', {
+        domainid: this.domainid
+      }).then(response => {
+        this.accounts = response.listaccountsresponse.account || []
+      }).catch(error => {
+        this.$notifyError(error)
+      })
+    },
+    handleAccountChange (acc) {
+      if (acc) {
+        this.account = acc.name
+      } else {
+        this.account = acc
+      }
+    },
+    handleSubmit (e) {
+      if (this.loading) return
+      this.formRef.value.validate().then(() => {
+        const formRaw = toRaw(this.form)
+        const values = this.handleRemoveFields(formRaw)
+        values.snapshotid = this.resource.id
+        if (values.groupenabled) {
+          const input = values.groupenabled
+          for (const index in input) {
+            const name = input[index]
+            values[name] = true
+          }
+          delete values.groupenabled
+        }
+        this.loading = true
+        api('createTemplate', values).then(response => {
+          this.$pollJob({
+            jobId: response.createtemplateresponse.jobid,
+            title: this.$t('message.success.create.template'),
+            description: values.name,
+            successMessage: this.$t('message.success.create.template'),
+            successMethod: (result) => {
+              this.closeModal()
+            },
+            errorMessage: this.$t('message.create.template.failed'),
+            loadingMessage: this.$t('message.create.template.processing'),
+            catchMessage: this.$t('error.fetching.async.job.result')
+          })
+          this.closeModal()
+        }).catch(error => {
+          this.$notifyError(error)
+        }).finally(() => {
+          this.loading = false
+        })
+      }).catch((error) => {
+        this.formRef.value.scrollToField(error.errorFields[0].name)
+      })
+    },
+    closeModal () {
+      this.$emit('close-action')
+    },
+    isAdminOrDomainAdmin () {
+      return ['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.form {
+  width: 80vw;
+
+  @media (min-width: 500px) {
+    min-width: 400px;
+    width: 100%;
+  }
+}
+</style>
diff --git a/ui/src/views/storage/CreateVolume.vue b/ui/src/views/storage/CreateVolume.vue
index e068e4a..3efe31a 100644
--- a/ui/src/views/storage/CreateVolume.vue
+++ b/ui/src/views/storage/CreateVolume.vue
@@ -35,7 +35,7 @@
           v-model:value="form.name"
           :placeholder="apiParams.name.description" />
       </a-form-item>
-      <a-form-item ref="zoneid" name="zoneid" v-if="!createVolumeFromVM && !createVolumeFromSnapshot">
+      <a-form-item ref="zoneid" name="zoneid" v-if="!createVolumeFromVM">
         <template #label>
           <tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
         </template>
@@ -74,12 +74,13 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option
             v-for="(offering, index) in offerings"
             :value="offering.id"
-            :key="index">
+            :key="index"
+            :label="offering.displaytext || offering.name">
             {{ offering.displaytext || offering.name }}
           </a-select-option>
         </a-select>
@@ -142,6 +143,7 @@
   },
   data () {
     return {
+      snapshotZoneIds: [],
       zones: [],
       offerings: [],
       customDiskOffering: false,
@@ -194,10 +196,23 @@
       }
     },
     fetchData () {
+      if (this.createVolumeFromSnapshot) {
+        this.fetchSnapshotZones()
+        return
+      }
+      let zoneId = null
+      if (this.createVolumeFromVM) {
+        zoneId = this.resource.zoneid
+      }
+      this.fetchZones(zoneId)
+    },
+    fetchZones (id) {
       this.loading = true
       const params = { showicon: true }
-      if (this.createVolumeFromVM) {
-        params.id = this.resource.zoneid
+      if (Array.isArray(id)) {
+        params.ids = id.join()
+      } else if (id !== null) {
+        params.id = id
       }
       api('listZones', params).then(json => {
         this.zones = json.listzonesresponse.zone || []
@@ -207,6 +222,26 @@
         this.loading = false
       })
     },
+    fetchSnapshotZones () {
+      this.loading = true
+      this.snapshotZoneIds = []
+      const params = {
+        showunique: false,
+        id: this.resource.id
+      }
+      api('listSnapshots', params).then(json => {
+        const snapshots = json.listsnapshotsresponse.snapshot || []
+        for (const snapshot of snapshots) {
+          if (!this.snapshotZoneIds.includes(snapshot.zoneid)) {
+            this.snapshotZoneIds.push(snapshot.zoneid)
+          }
+        }
+      }).finally(() => {
+        if (this.snapshotZoneIds && this.snapshotZoneIds.length > 0) {
+          this.fetchZones(this.snapshotZoneIds)
+        }
+      })
+    },
     fetchDiskOfferings (zoneId) {
       this.loading = true
       api('listDiskOfferings', {
diff --git a/ui/src/views/storage/FormSchedule.vue b/ui/src/views/storage/FormSchedule.vue
index 235be2e..ffd8908 100644
--- a/ui/src/views/storage/FormSchedule.vue
+++ b/ui/src/views/storage/FormSchedule.vue
@@ -74,8 +74,10 @@
                 name="timeSelect"
                 ref="timeSelect">
                 <a-time-picker
-                  format="HH:mm"
-                  v-model:value="form.timeSelect" />
+                  use12Hours
+                  format="h:mm A"
+                  v-model:value="form.timeSelect"
+                  style="width: 100%;" />
               </a-form-item>
             </a-col>
             <a-col :md="24" :lg="12" v-if="form.intervaltype==='weekly'">
@@ -85,9 +87,9 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }" >
-                  <a-select-option v-for="(opt, optIndex) in dayOfWeek" :key="optIndex">
+                  <a-select-option v-for="(opt, optIndex) in dayOfWeek" :key="optIndex" :label="opt.name || opt.description">
                     {{ opt.name || opt.description }}
                   </a-select-option>
                 </a-select>
@@ -98,9 +100,9 @@
                 <a-select
                   v-model:value="form['day-of-month']"
                   showSearch
-                  optionFilterProp="label"
+                  optionFilterProp="value"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }" >
                   <a-select-option v-for="opt in dayOfMonth" :key="opt.name">
                     {{ opt.name }}
@@ -128,14 +130,45 @@
                   showSearch
                   optionFilterProp="label"
                   :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }" >
-                  <a-select-option v-for="opt in timeZoneMap" :key="opt.id">
+                  <a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
                     {{ opt.name || opt.description }}
                   </a-select-option>
                 </a-select>
               </a-form-item>
             </a-col>
+            <a-col :md="24" :lg="24" v-if="resourceType === 'Volume'">
+              <a-form-item ref="zoneids" name="zoneids">
+                <template #label>
+                  <tooltip-label :title="$t('label.zones')" :tooltip="''"/>
+                </template>
+                <a-alert type="info" style="margin-bottom: 2%">
+                  <template #message>
+                    <div v-html="formattedAdditionalZoneMessage"/>
+                  </template>
+                </a-alert>
+                <a-select
+                  id="zone-selection"
+                  v-model:value="form.zoneids"
+                  mode="multiple"
+                  showSearch
+                  optionFilterProp="label"
+                  :filterOption="(input, option) => {
+                    return  option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  }"
+                  :loading="zoneLoading"
+                  :placeholder="''">
+                  <a-select-option v-for="opt in this.zones" :key="opt.id" :label="opt.name || opt.description">
+                    <span>
+                      <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                      <global-outlined v-else style="margin-right: 5px"/>
+                      {{ opt.name || opt.description }}
+                    </span>
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
+            </a-col>
           </a-row>
           <a-divider/>
           <div class="tagsTitle">{{ $t('label.tags') }}</div>
@@ -192,6 +225,7 @@
 import { ref, reactive, toRaw } from 'vue'
 import { api } from '@/api'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
 import { timeZone } from '@/utils/timezone'
 import { mixinForm } from '@/utils/mixin'
 import debounce from 'lodash/debounce'
@@ -200,7 +234,8 @@
   name: 'FormSchedule',
   mixins: [mixinForm],
   components: {
-    TooltipButton
+    TooltipButton,
+    TooltipLabel
   },
   props: {
     loading: {
@@ -214,6 +249,10 @@
     resource: {
       type: Object,
       required: true
+    },
+    resourceType: {
+      type: String,
+      default: null
     }
   },
   data () {
@@ -232,7 +271,8 @@
       dayOfMonth: [],
       timeZoneMap: [],
       fetching: false,
-      listDayOfWeek: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']
+      listDayOfWeek: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'],
+      zones: []
     }
   },
   created () {
@@ -240,6 +280,11 @@
     this.volumeId = this.resource.id
     this.fetchTimeZone()
   },
+  computed: {
+    formattedAdditionalZoneMessage () {
+      return `${this.$t('message.snapshot.additional.zones').replace('%x', this.resource.zonename)}`
+    }
+  },
   methods: {
     initForm () {
       this.formRef = ref()
@@ -260,6 +305,23 @@
         maxsnaps: [{ required: true, message: this.$t('message.error.required.input') }],
         timezone: [{ required: true, message: `${this.$t('message.error.select')}` }]
       })
+      if (this.resourceType === 'Volume') {
+        this.fetchZoneData()
+      }
+    },
+    fetchZoneData () {
+      const params = {}
+      params.showicon = true
+      this.zoneLoading = true
+      api('listZones', params).then(json => {
+        const listZones = json.listzonesresponse.zone
+        if (listZones) {
+          this.zones = listZones
+          this.zones = this.zones.filter(zone => zone.type !== 'Edge' && zone.id !== this.resource.zoneid)
+        }
+      }).finally(() => {
+        this.zoneLoading = false
+      })
     },
     fetchTimeZone (value) {
       this.timeZoneMap = []
@@ -357,6 +419,9 @@
         params.intervaltype = values.intervaltype
         params.timezone = values.timezone
         params.maxsnaps = values.maxsnaps
+        if (values.zoneids && values.zoneids.length > 0) {
+          params.zoneids = values.zoneids.join()
+        }
         switch (values.intervaltype) {
           case 'hourly':
             params.schedule = values.time
diff --git a/ui/src/views/storage/MigrateImageStoreResource.vue b/ui/src/views/storage/MigrateImageStoreResource.vue
new file mode 100644
index 0000000..927ef42
--- /dev/null
+++ b/ui/src/views/storage/MigrateImageStoreResource.vue
@@ -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.
+
+<template>
+  <div v-ctrl-enter="submitForm">
+    <a-alert type="error">
+      <template #message>
+        <span v-html="$t('message.migrate.resource.to.ss')" />
+      </template>
+    </a-alert>
+    <image-store-selector
+      :zoneid="zoneid"
+      :srcImageStoreId="srcImageStoreId"
+      @select="handleImageStoreChange" />
+    <a-divider />
+
+    <div class="actions">
+      <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+      <a-button type="primary" @click="submitForm">{{ $t('label.ok') }}</a-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { api } from '@/api'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import ImageStoreSelector from '@/components/view/ImageStoreSelectView'
+
+export default {
+  name: 'MigrateImageStoreResource',
+  components: {
+    TooltipLabel,
+    ImageStoreSelector
+  },
+  props: {
+    snapshotIdsToMigrate: {
+      type: Array,
+      default: () => [],
+      required: false
+    },
+    templateIdsToMigrate: {
+      type: Array,
+      default: () => [],
+      required: false
+    },
+    sourceImageStore: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      selectedStore: null,
+      destinationStoreList: []
+    }
+  },
+  beforeCreate () {
+    this.zoneid = this.sourceImageStore.zoneid
+    this.srcImageStoreId = this.sourceImageStore.id
+  },
+  computed: {},
+  methods: {
+    fetchDestinationStores () {
+      api('listImageStores', {
+        zoneid: this.zoneid
+      }).then(response => {
+        this.destinationStoreList = response.listimagestoresresponse.imagestore
+      }).catch(error => {
+        console.error(error)
+      })
+    },
+    handleImageStoreChange (value) {
+      this.selectedStore = value
+    },
+    isValidValueForKey (obj, key) {
+      return key in obj && obj[key] != null
+    },
+    arrayHasItems (array) {
+      return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
+    },
+    isObjectEmpty (obj) {
+      return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
+    },
+    handleStoragePoolChange (storagePool) {
+      this.selectedStore = storagePool
+    },
+    handleVolumeToPoolChange (volumeToPool) {
+      this.volumeToPoolSelection = volumeToPool
+    },
+    submitForm () {
+      this.migrateResources(this.selectedStore.id)
+    },
+    migrateResources (destStoreId) {
+      var params = {
+        srcpool: this.sourceImageStore.id,
+        destpool: destStoreId
+      }
+      params.templates = this.templateIdsToMigrate.join(',')
+      params.snapshots = this.snapshotIdsToMigrate.join(',')
+
+      api('migrateResourceToAnotherSecondaryStorage', params).then(response => {
+        const jobId = response.migrateresourcetoanothersecondarystorageresponse.jobid
+        this.$pollJob({
+          title: this.$t('label.migrating.data'),
+          description: '',
+          jobId: jobId,
+          successMessage: this.$t('message.success.migration'),
+          successMethod: () => {
+            this.closeModal()
+          },
+          errorMessage: this.$t('message.migrating.failed'),
+          errorMethod: () => {
+            this.closeModal()
+          },
+          loadingMessage: this.$t('label.migrating'),
+          catchMessage: this.$t('error.fetching.async.job.result'),
+          catchMethod: () => {
+            this.closeModal()
+          }
+        })
+        this.closeModal()
+      }).catch(error => {
+        console.error(error)
+        this.$message.error(`${this.$t('message.migrating.vm.to.storage.failed')} ${this.selectedStore.id}`)
+      })
+    },
+    closeModal () {
+      this.$emit('close-action')
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+  .form-layout {
+    width: 80vw;
+
+    @media (min-width: 900px) {
+      width: 850px;
+    }
+  }
+
+  .top-spaced {
+    margin-top: 20px;
+  }
+
+  .radio-style {
+    display: block;
+    margin-left: 10px;
+    height: 40px;
+    line-height: 40px;
+  }
+
+  .actions {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 20px;
+
+    button {
+      &:not(:last-child) {
+        margin-right: 10px;
+      }
+    }
+  }
+</style>
diff --git a/ui/src/views/storage/MigrateVolume.vue b/ui/src/views/storage/MigrateVolume.vue
index 62bed70..761fe02 100644
--- a/ui/src/views/storage/MigrateVolume.vue
+++ b/ui/src/views/storage/MigrateVolume.vue
@@ -50,9 +50,9 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="(diskOffering, index) in diskOfferings" :value="diskOffering.id" :key="index">
+            <a-select-option v-for="(diskOffering, index) in diskOfferings" :value="diskOffering.id" :key="index" :label="diskOffering.displaytext">
               {{ diskOffering.displaytext }}
             </a-select-option>
           </a-select>
diff --git a/ui/src/views/storage/RecurringSnapshotVolume.vue b/ui/src/views/storage/RecurringSnapshotVolume.vue
index 7f4bd5d..ce92984 100644
--- a/ui/src/views/storage/RecurringSnapshotVolume.vue
+++ b/ui/src/views/storage/RecurringSnapshotVolume.vue
@@ -23,6 +23,7 @@
           :loading="loading"
           :resource="resource"
           :dataSource="dataSource"
+          :resourceType="'Volume'"
           @close-action="closeAction"
           @refresh="handleRefresh"/>
       </a-tab-pane>
diff --git a/ui/src/views/storage/RestoreAttachBackupVolume.vue b/ui/src/views/storage/RestoreAttachBackupVolume.vue
index 41a8f76..72c38ac 100644
--- a/ui/src/views/storage/RestoreAttachBackupVolume.vue
+++ b/ui/src/views/storage/RestoreAttachBackupVolume.vue
@@ -34,11 +34,12 @@
           showSearch
           optionFilterProp="label"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option
             v-for="(opt) in volumeOptions.opts"
-            :key="opt.id">
+            :key="opt.id"
+            :label="opt.name">
             {{ opt.name }}
           </a-select-option>
         </a-select>
@@ -49,9 +50,9 @@
           v-model:value="form.virtualmachineid"
           :loading="virtualMachineOptions.loading"
           showSearch
-          optionFilterProp="label"
+          optionFilterProp="value"
           :filterOption="(input, option) => {
-            return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option
             v-for="(opt) in virtualMachineOptions.opts"
diff --git a/ui/src/views/storage/ScheduledSnapshots.vue b/ui/src/views/storage/ScheduledSnapshots.vue
index 0c0f6d1..6792dc3 100644
--- a/ui/src/views/storage/ScheduledSnapshots.vue
+++ b/ui/src/views/storage/ScheduledSnapshots.vue
@@ -24,54 +24,61 @@
       :rowKey="record => record.id"
       :pagination="false"
       :loading="loading">
-      <template #icon="{ record }">
-        <label class="interval-icon">
-          <span v-if="record.intervaltype===0">
-            <clock-circle-outlined />
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'icon'">
+          <label class="interval-icon">
+            <span v-if="record.intervaltype===0">
+              <clock-circle-outlined />
+            </span>
+            <span class="custom-icon icon-daily" v-else-if="record.intervaltype===1">
+              <calendar-outlined />
+            </span>
+            <span class="custom-icon icon-weekly" v-else-if="record.intervaltype===2">
+              <calendar-outlined />
+            </span>
+            <span class="custom-icon icon-monthly" v-else-if="record.intervaltype===3">
+              <calendar-outlined />
+            </span>
+          </label>
+        </template>
+        <template v-if="column.key === 'time'">
+          <label class="interval-content">
+            <span v-if="record.intervaltype===0">{{ record.schedule + $t('label.min.past.hour') }}</span>
+            <span v-else>{{ record.schedule.split(':')[1] + ':' + record.schedule.split(':')[0] }}</span>
+          </label>
+        </template>
+        <template v-if="column.key === 'interval'">
+          <span v-if="record.intervaltype===2">
+            {{ `${$t('label.every')} ${$t(listDayOfWeek[record.schedule.split(':')[2] - 1])}` }}
           </span>
-          <span class="custom-icon icon-daily" v-else-if="record.intervaltype===1">
-            <calendar-outlined />
+          <span v-else-if="record.intervaltype===3">
+            {{ `${$t('label.day')} ${record.schedule.split(':')[2]} ${$t('label.of.month')}` }}
           </span>
-          <span class="custom-icon icon-weekly" v-else-if="record.intervaltype===2">
-            <calendar-outlined />
-          </span>
-          <span class="custom-icon icon-monthly" v-else-if="record.intervaltype===3">
-            <calendar-outlined />
-          </span>
-        </label>
-      </template>
-      <template #time="{ record }">
-        <label class="interval-content">
-          <span v-if="record.intervaltype===0">{{ record.schedule + $t('label.min.past.hour') }}</span>
-          <span v-else>{{ record.schedule.split(':')[1] + ':' + record.schedule.split(':')[0] }}</span>
-        </label>
-      </template>
-      <template #interval="{ record }">
-        <span v-if="record.intervaltype===2">
-          {{ `${$t('label.every')} ${$t(listDayOfWeek[record.schedule.split(':')[2] - 1])}` }}
-        </span>
-        <span v-else-if="record.intervaltype===3">
-          {{ `${$t('label.day')} ${record.schedule.split(':')[2]} ${$t('label.of.month')}` }}
-        </span>
-      </template>
-      <template #timezone="{ record }">
-        <label>{{ getTimeZone(record.timezone) }}</label>
-      </template>
-      <template #tags="{ record }">
-        <a-tag v-for="(tag, index) in record.tags" :key="index">{{ tag.key + '=' + tag.value }}</a-tag>
-      </template>
-      <template #action="{ record }">
-        <div class="account-button-action">
-          <tooltip-button
-            tooltipPlacement="top"
-            :tooltip="$t('label.delete')"
-            type="primary"
-            :danger="true"
-            icon="close-outlined"
-            size="small"
-            :loading="actionLoading"
-            @onClick="handleClickDelete(record)" />
-        </div>
+        </template>
+        <template v-if="column.key === 'timezone'">
+          <label>{{ getTimeZone(record.timezone) }}</label>
+        </template>
+        <template v-if="column.key === 'tags'">
+          <a-tag v-for="(tag, index) in record.tags" :key="index">{{ tag.key + '=' + tag.value }}</a-tag>
+        </template>
+        <template v-if="column.key === 'zones'">
+          <div v-for="zone in record.zone" :key="zone.id">
+            <router-link :to="{ path: '/zones/' + zone.id }">{{ zone.name }}</router-link>
+          </div>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <div class="account-button-action">
+            <tooltip-button
+              tooltipPlacement="top"
+              :tooltip="$t('label.delete')"
+              type="primary"
+              :danger="true"
+              icon="close-outlined"
+              size="small"
+              :loading="actionLoading"
+              @onClick="handleClickDelete(record)" />
+          </div>
+        </template>
       </template>
     </a-table>
   </div>
@@ -112,41 +119,46 @@
   created () {
     this.columns = [
       {
+        key: 'icon',
         title: '',
         dataIndex: 'icon',
-        width: 30,
-        slots: { customRender: 'icon' }
+        width: 30
       },
       {
+        key: 'time',
         title: this.$t('label.time'),
-        dataIndex: 'schedule',
-        slots: { customRender: 'time' }
+        dataIndex: 'schedule'
       },
       {
+        key: 'interval',
         title: '',
-        dataIndex: 'interval',
-        slots: { customRender: 'interval' }
+        dataIndex: 'interval'
       },
       {
+        key: 'timezone',
         title: this.$t('label.timezone'),
-        dataIndex: 'timezone',
-        slots: { customRender: 'timezone' }
+        dataIndex: 'timezone'
       },
       {
+        key: 'keep',
         title: this.$t('label.keep'),
-        dataIndex: 'maxsnaps',
-        slots: { customRender: 'keep' }
+        dataIndex: 'maxsnaps'
       },
       {
+        key: 'tags',
         title: this.$t('label.tags'),
-        dataIndex: 'tags',
-        slots: { customRender: 'tags' }
+        dataIndex: 'tags'
       },
       {
-        title: this.$t('label.action'),
-        dataIndex: 'action',
-        width: 50,
-        slots: { customRender: 'action' }
+        key: 'zones',
+        title: this.$t('label.zones'),
+        dataIndex: 'zone'
+      },
+      {
+        key: 'actions',
+        title: this.$t('label.actions'),
+        dataIndex: 'actions',
+        width: 50
       }
     ]
   },
@@ -183,6 +195,9 @@
     },
     getTimeZone (timeZone) {
       return timeZoneName(timeZone)
+    },
+    getZones (record) {
+
     }
   }
 }
diff --git a/ui/src/views/storage/SnapshotZones.vue b/ui/src/views/storage/SnapshotZones.vue
new file mode 100644
index 0000000..66bd0f3
--- /dev/null
+++ b/ui/src/views/storage/SnapshotZones.vue
@@ -0,0 +1,562 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div>
+    <a-button
+      v-if="((deleteApi in $store.getters.apis) && this.selectedRowKeys.length > 0)"
+      type="primary"
+      danger
+      style="width: 100%; margin-bottom: 15px"
+      @click="bulkActionConfirmation()">
+      <template #icon><delete-outlined /></template>
+      {{ $t('label.action.bulk.delete.snapshots') }}
+    </a-button>
+    <a-table
+      size="small"
+      style="overflow-y: auto"
+      :loading="loading || fetchLoading"
+      :columns="columns"
+      :dataSource="dataSource"
+      :pagination="false"
+      :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
+      :rowKey="record => record.zoneid">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'zonename'">
+          <span v-if="record.datastoreid">
+            <router-link :to="{ path: (record.datastoretype === 'Primary' ? '/storagepool/' : '/imagestore/') + record.datastoreid }">
+              <span v-if="fetchZoneIcon(record.zoneid)">
+                <resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/>
+              </span>
+              <global-outlined v-else style="margin-right: 5px" />
+              <span> {{ record.zonename }} </span>
+            </router-link>
+          </span>
+          <span v-else>
+            <span v-if="fetchZoneIcon(record.zoneid)">
+              <resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/>
+            </span>
+            <global-outlined v-else style="margin-right: 5px" />
+            <span> {{ record.zonename }} </span>
+          </span>
+        </template>
+        <template v-if="column.key === 'isready'">
+          <span v-if="record.datastorestate==='Ready'">{{ $t('label.yes') }}</span>
+          <span v-else>{{ $t('label.no') }}</span>
+        </template>
+        <template v-if="column.key === 'actions'">
+          <tooltip-button
+            v-if="record.state==='BackedUp'"
+            style="margin-right: 5px"
+            :disabled="!(copyApi in $store.getters.apis)"
+            :title="$t('label.action.copy.snapshot')"
+            icon="copy-outlined"
+            :loading="copyLoading"
+            @onClick="showCopySnapshot(record)" />
+          <tooltip-button
+            v-if="record.state==='BackedUp'"
+            style="margin-right: 5px"
+            :disabled="!(deleteApi in $store.getters.apis)"
+            :title="$t('label.action.delete.snapshot')"
+            type="primary"
+            :danger="true"
+            icon="delete-outlined"
+            @onClick="onShowDeleteModal(record)"/>
+        </template>
+      </template>
+    </a-table>
+    <a-pagination
+      class="row-element"
+      size="small"
+      :current="page"
+      :pageSize="pageSize"
+      :total="itemCount"
+      :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
+      :pageSizeOptions="['10', '20', '40', '80', '100']"
+      @change="handleChangePage"
+      @showSizeChange="handleChangePageSize"
+      showSizeChanger>
+      <template #buildOptionText="props">
+        <span>{{ props.value }} / {{ $t('label.page') }}</span>
+      </template>
+    </a-pagination>
+
+    <a-modal
+      v-if="copyApi in $store.getters.apis"
+      style="top: 20px;"
+      :title="$t('label.action.copy.snapshot')"
+      :visible="showCopyActionForm"
+      :closable="true"
+      :maskClosable="false"
+      :footer="null"
+      :confirmLoading="copyLoading"
+      @cancel="onCloseModal"
+      centered>
+      <a-spin :spinning="copyLoading" v-ctrl-enter="handleCopySnapshotSubmit">
+        <a-form
+          :ref="formRef"
+          :model="form"
+          :rules="rules"
+          layout="vertical"
+          @finish="handleCopySnapshotSubmit">
+          <a-form-item ref="zoneid" name="zoneid" :label="$t('label.zoneid')">
+            <a-select
+              id="zone-selection"
+              mode="multiple"
+              :placeholder="$t('label.select.zones')"
+              v-model:value="form.zoneid"
+              showSearch
+              optionFilterProp="label"
+              :filterOption="(input, option) => {
+                return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              }"
+              :loading="zoneLoading"
+              v-focus="true">
+              <a-select-option v-for="zone in zones" :key="zone.id" :label="zone.name">
+                <div>
+                  <span v-if="zone.icon && zone.icon.base64image">
+                    <resource-icon :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+                  </span>
+                  <global-outlined v-else style="margin-right: 5px" />
+                  {{ zone.name }}
+                </div>
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+
+          <div :span="24" class="action-button">
+            <a-button @click="onCloseModal">{{ $t('label.cancel') }}</a-button>
+            <a-button type="primary" ref="submit" @click="handleCopySnapshotSubmit">{{ $t('label.ok') }}</a-button>
+          </div>
+        </a-form>
+      </a-spin>
+    </a-modal>
+
+    <a-modal
+      :title="selectedItems.length > 0 && showTable ? $t(message.title) : $t('label.action.delete.snapshot')"
+      :visible="showDeleteSnapshot"
+      :closable="true"
+      :maskClosable="false"
+      :footer="null"
+      :width="showTable ? modalWidth : '30vw'"
+      @ok="selectedItems.length > 0 ? deleteSnapshots() : deleteSnapshot(currentRecord)"
+      @cancel="onCloseModal"
+      :ok-button-props="getOkProps()"
+      :cancel-button-props="getCancelProps()"
+      :confirmLoading="deleteLoading"
+      centered>
+      <div v-ctrl-enter="deleteSnapshot">
+        <div v-if="selectedRowKeys.length > 0">
+          <a-alert type="error">
+            <template #message>
+              <exclamation-circle-outlined style="color: red; fontSize: 30px; display: inline-flex" />
+              <span style="padding-left: 5px" v-html="`<b>${selectedRowKeys.length} ` + $t('label.items.selected') + `. </b>`" />
+              <span v-html="$t(message.confirmMessage)" />
+            </template>
+          </a-alert>
+        </div>
+        <a-alert v-else :message="$t('message.action.delete.snapshot')" type="warning" />
+        <br />
+        <a-table
+          v-if="selectedRowKeys.length > 0 && showTable"
+          size="middle"
+          :columns="selectedColumns"
+          :dataSource="selectedItems"
+          :rowKey="record => record.zoneid || record.name"
+          :pagination="true"
+          style="overflow-y: auto">
+        </a-table>
+        <a-spin :spinning="deleteLoading">
+          <div :span="24" class="action-button">
+            <a-button @click="onCloseModal">{{ $t('label.cancel') }}</a-button>
+            <a-button type="primary" ref="submit" @click="deleteSnapshot">{{ $t('label.ok') }}</a-button>
+          </div>
+        </a-spin>
+      </div>
+    </a-modal>
+    <bulk-action-progress
+      :showGroupActionModal="showGroupActionModal"
+      :selectedItems="selectedItems"
+      :selectedColumns="selectedColumns"
+      :message="message"
+      @handle-cancel="handleCancel" />
+  </div>
+</template>
+
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+import OsLogo from '@/components/widgets/OsLogo'
+import ResourceIcon from '@/components/view/ResourceIcon'
+import TooltipButton from '@/components/widgets/TooltipButton'
+import BulkActionProgress from '@/components/view/BulkActionProgress'
+import Status from '@/components/widgets/Status'
+import eventBus from '@/config/eventBus'
+
+export default {
+  name: 'SnapshotZones',
+  components: {
+    TooltipButton,
+    OsLogo,
+    ResourceIcon,
+    BulkActionProgress,
+    Status
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      columns: [],
+      dataSource: [],
+      page: 1,
+      pageSize: 10,
+      itemCount: 0,
+      fetchLoading: false,
+      showCopyActionForm: false,
+      currentRecord: {},
+      zones: [],
+      zoneLoading: false,
+      copyLoading: false,
+      deleteLoading: false,
+      showDeleteSnapshot: false,
+      forcedDelete: false,
+      selectedRowKeys: [],
+      showGroupActionModal: false,
+      selectedItems: [],
+      selectedColumns: [],
+      filterColumns: ['Status', 'Ready'],
+      showConfirmationAction: false,
+      message: {
+        title: this.$t('label.action.bulk.delete.snapshots'),
+        confirmMessage: this.$t('label.confirm.delete.snapshot.zones')
+      },
+      modalWidth: '30vw',
+      showTable: false
+    }
+  },
+  beforeCreate () {
+    this.deleteApi = 'deleteSnapshot'
+    this.copyApi = 'copySnapshot'
+    this.apiParams = this.$getApiParams(this.copyApi)
+  },
+  created () {
+    this.columns = [
+      {
+        key: 'zonename',
+        title: this.$t('label.zonename'),
+        dataIndex: 'zonename'
+      },
+      {
+        title: this.$t('label.status'),
+        dataIndex: 'status'
+      },
+      {
+        key: 'isready',
+        title: this.$t('label.isready'),
+        dataIndex: 'isready'
+      }
+    ]
+    if (this.isActionPermitted()) {
+      this.columns.push({
+        key: 'actions',
+        title: '',
+        dataIndex: 'actions',
+        width: 100
+      })
+    }
+
+    this.initForm()
+    this.fetchData()
+  },
+  watch: {
+    loading (newData, oldData) {
+      if (!newData && !this.showGroupActionModal) {
+        this.fetchData()
+      }
+    }
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({
+        zoneid: [{ type: 'array', required: true, message: this.$t('message.error.select') }]
+      })
+    },
+    fetchData () {
+      const params = {}
+      params.id = this.resource.id
+      params.showunique = false
+      params.locationtype = 'Secondary'
+      params.listall = true
+      params.page = this.page
+      params.pagesize = this.pageSize
+
+      this.dataSource = []
+      this.itemCount = 0
+      this.fetchLoading = true
+      api('listSnapshots', params).then(json => {
+        this.dataSource = json.listsnapshotsresponse.snapshot || []
+        this.itemCount = json.listsnapshotsresponse.count || 0
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.fetchLoading = false
+      })
+      this.fetchZoneData()
+    },
+    fetchZoneIcon (zoneid) {
+      const zoneItem = this.zones.filter(zone => zone.id === zoneid)
+      if (zoneItem?.[0]?.icon?.base64image) {
+        this.zoneIcon = zoneItem[0].icon.base64image
+        return true
+      }
+      return false
+    },
+    handleChangePage (page, pageSize) {
+      this.page = page
+      this.pageSize = pageSize
+      this.fetchData()
+    },
+    handleChangePageSize (currentPage, pageSize) {
+      this.page = currentPage
+      this.pageSize = pageSize
+      this.fetchData()
+    },
+    isActionPermitted () {
+      return this.resource.state && this.resource.state === 'BackedUp'
+    },
+    setSelection (selection) {
+      this.selectedRowKeys = selection
+      if (selection?.length > 0) {
+        this.modalWidth = '50vw'
+        this.$emit('selection-change', this.selectedRowKeys)
+        this.selectedItems = (this.dataSource.filter(function (item) {
+          return selection.indexOf(item.zoneid) !== -1
+        }))
+      } else {
+        this.modalWidth = '30vw'
+        this.selectedItems = []
+      }
+    },
+    resetSelection () {
+      this.setSelection([])
+    },
+    onSelectChange (selectedRowKeys, selectedRows) {
+      this.setSelection(selectedRowKeys)
+    },
+    bulkActionConfirmation () {
+      this.showConfirmationAction = true
+      this.selectedColumns = this.columns.filter(column => {
+        return !this.filterColumns.includes(column.title)
+      })
+      this.selectedItems = this.selectedItems.map(v => ({ ...v, status: 'InProgress' }))
+      this.onShowDeleteModal(this.selectedItems[0])
+    },
+    handleCancel () {
+      eventBus.emit('update-bulk-job-status', { items: this.selectedItems, action: false })
+      this.showGroupActionModal = false
+      this.selectedItems = []
+      this.selectedColumns = []
+      this.selectedRowKeys = []
+      this.showTable = false
+      this.fetchData()
+      if (this.dataSource.length === 0) {
+        this.$router.go(-1)
+      }
+    },
+    getOkProps () {
+      if (this.selectedRowKeys.length > 0) {
+        return { props: { type: 'default' } }
+      } else {
+        return { props: { type: 'primary' } }
+      }
+    },
+    getCancelProps () {
+      if (this.selectedRowKeys.length > 0) {
+        return { props: { type: 'primary' } }
+      } else {
+        return { props: { type: 'default' } }
+      }
+    },
+    deleteSnapshots (e) {
+      this.showConfirmationAction = false
+      this.selectedColumns.splice(0, 0, {
+        key: 'status',
+        dataIndex: 'status',
+        title: this.$t('label.operation.status'),
+        filters: [
+          { text: 'In Progress', value: 'InProgress' },
+          { text: 'Success', value: 'success' },
+          { text: 'Failed', value: 'failed' }
+        ]
+      })
+      if (this.selectedRowKeys.length > 0 && this.showTable) {
+        this.showGroupActionModal = true
+      }
+      for (const snapshot of this.selectedItems) {
+        this.deleteSnapshot(snapshot)
+      }
+    },
+    deleteSnapshot (snapshot) {
+      if (!snapshot.id) {
+        snapshot = this.currentRecord
+      }
+      const params = {
+        id: snapshot.id,
+        zoneid: snapshot.zoneid
+      }
+      this.deleteLoading = true
+      api(this.deleteApi, params).then(json => {
+        const jobId = json.deletesnapshotresponse.jobid
+        eventBus.emit('update-job-details', { jobId, resourceId: null })
+        const singleZone = (this.dataSource.length === 1)
+        this.$pollJob({
+          jobId,
+          title: this.$t('label.action.delete.snapshot'),
+          description: this.resource.name,
+          successMethod: result => {
+            if (singleZone) {
+              const isResourcePage = (this.$route.params && this.$route.params.id)
+              const isSameResource = isResourcePage && this.$route.params.id === result.jobinstanceid
+              if (isResourcePage && isSameResource && this.selectedItems.length === 0 && !this.showGroupActionModal) {
+                this.$router.push({ path: '/snapshot' })
+              }
+            } else {
+              if (this.selectedItems.length === 0) {
+                this.fetchData()
+              }
+            }
+            if (this.selectedItems.length > 0) {
+              eventBus.emit('update-resource-state', { selectedItems: this.selectedItems, resource: snapshot.zoneid, state: 'success' })
+            }
+          },
+          errorMethod: () => {
+            if (this.selectedItems.length === 0) {
+              this.fetchData()
+            }
+            if (this.selectedItems.length > 0) {
+              eventBus.emit('update-resource-state', { selectedItems: this.selectedItems, resource: snapshot.zoneid, state: 'failed' })
+            }
+          },
+          showLoading: !(this.selectedItems.length > 0 && this.showGroupActionModal),
+          loadingMessage: `${this.$t('label.deleting.snapshot')} ${this.resource.name} ${this.$t('label.in.progress')}`,
+          catchMessage: this.$t('error.fetching.async.job.result'),
+          bulkAction: this.selectedItems.length > 0 && this.showGroupActionModal
+        })
+        this.onCloseModal()
+        if (this.selectedItems.length === 0) {
+          this.fetchData()
+        }
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.deleteLoading = false
+      })
+    },
+    fetchZoneData () {
+      this.zones = []
+      this.zoneLoading = true
+      api('listZones', { showicon: true }).then(json => {
+        const zones = json.listzonesresponse.zone || []
+        this.zones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)]
+      }).finally(() => {
+        this.zoneLoading = false
+      })
+    },
+    showCopySnapshot (record) {
+      this.currentRecord = record
+      this.form.zoneid = []
+      this.fetchZoneData()
+      this.showCopyActionForm = true
+    },
+    onShowDeleteModal (record) {
+      this.forcedDelete = false
+      this.currentRecord = record
+      this.showDeleteSnapshot = true
+      if (this.showConfirmationAction) {
+        this.showTable = true
+      } else {
+        this.selectedItems = []
+      }
+    },
+    onCloseModal () {
+      this.currentRecord = {}
+      this.showCopyActionForm = false
+      this.showDeleteSnapshot = false
+      this.showConfirmationAction = false
+      this.showTable = false
+      this.selectedRowKeys = []
+    },
+    handleCopySnapshotSubmit (e) {
+      e.preventDefault()
+      if (this.copyLoading) return
+      this.formRef.value.validate().then(() => {
+        const values = toRaw(this.form)
+        const params = {
+          id: this.currentRecord.id,
+          sourcezoneid: this.currentRecord.zoneid,
+          destzoneids: values.zoneid.join()
+        }
+        this.copyLoading = true
+        api(this.copyApi, params).then(json => {
+          const jobId = json.copysnapshotresponse.jobid
+          eventBus.emit('update-job-details', { jobId, resourceId: null })
+          this.$pollJob({
+            jobId,
+            title: this.$t('label.action.copy.snapshot'),
+            description: this.resource.name,
+            successMethod: result => {
+              this.fetchData()
+            },
+            errorMethod: () => this.fetchData(),
+            loadingMessage: `${this.$t('label.action.copy.snapshot')} ${this.resource.name} ${this.$t('label.in.progress')}`,
+            catchMessage: this.$t('error.fetching.async.job.result')
+          })
+        }).catch(error => {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
+          })
+        }).finally(() => {
+          this.copyLoading = false
+          this.$emit('refresh-data')
+          this.onCloseModal()
+          this.fetchData()
+        })
+      }).catch(error => {
+        this.formRef.value.scrollToField(error.errorFields[0].name)
+      })
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.row-element {
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+</style>
diff --git a/ui/src/views/storage/TakeSnapshot.vue b/ui/src/views/storage/TakeSnapshot.vue
index 7e450b5..ee0eafb 100644
--- a/ui/src/views/storage/TakeSnapshot.vue
+++ b/ui/src/views/storage/TakeSnapshot.vue
@@ -31,26 +31,47 @@
         layout="vertical"
         @finish="handleSubmit"
        >
-        <a-row :gutter="12">
-          <a-col :md="24" :lg="24">
-            <a-form-item :label="$t('label.name')" name="name" ref="name">
-              <a-input
-                v-model:value="form.name"
-                :placeholder="apiParams.name.description"
-                v-focus="true" />
-            </a-form-item>
-          </a-col>
-          <a-col :md="24" :lg="24" v-if="!supportsStorageSnapshot">
-            <a-form-item :label="$t('label.asyncbackup')" name="asyncbackup" ref="asyncbackup">
-              <a-switch v-model:checked="form.asyncbackup" />
-            </a-form-item>
-          </a-col>
-          <a-col :md="24" :lg="24" v-if="quiescevm" name="quiescevm" ref="quiescevm">
-            <a-form-item :label="$t('label.quiescevm')">
-              <a-switch v-model:checked="form.quiescevm" />
-            </a-form-item>
-          </a-col>
-        </a-row>
+        <a-form-item :label="$t('label.name')" name="name" ref="name">
+          <a-input
+            v-model:value="form.name"
+            :placeholder="apiParams.name.description"
+            v-focus="true" />
+        </a-form-item>
+        <a-form-item ref="zoneids" name="zoneids">
+          <template #label>
+            <tooltip-label :title="$t('label.zones')" :tooltip="''"/>
+          </template>
+          <a-alert type="info" style="margin-bottom: 2%">
+            <template #message>
+              <div v-html="formattedAdditionalZoneMessage"/>
+            </template>
+          </a-alert>
+          <a-select
+            id="zone-selection"
+            v-model:value="form.zoneids"
+            mode="multiple"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return  option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            :loading="zoneLoading"
+            :placeholder="''">
+            <a-select-option v-for="opt in zones" :key="opt.id" :label="opt.name || opt.description">
+              <span>
+                <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <global-outlined v-else style="margin-right: 5px" />
+                {{ opt.name || opt.description }}
+              </span>
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item :label="$t('label.asyncbackup')" name="asyncbackup" ref="asyncbackup">
+          <a-switch v-model:checked="form.asyncbackup" />
+        </a-form-item>
+        <a-form-item :label="$t('label.quiescevm')">
+          <a-switch v-model:checked="form.quiescevm" />
+        </a-form-item>
         <a-divider/>
         <div class="tagsTitle">{{ $t('label.tags') }}</div>
         <div>
@@ -106,12 +127,16 @@
 import { api } from '@/api'
 import { mixinForm } from '@/utils/mixin'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'TakeSnapshot',
   mixins: [mixinForm],
   components: {
-    TooltipButton
+    TooltipButton,
+    TooltipLabel,
+    ResourceIcon
   },
   props: {
     loading: {
@@ -131,6 +156,8 @@
       inputValue: '',
       inputKey: '',
       inputVisible: '',
+      zones: [],
+      zoneLoading: false,
       tags: [],
       dataSource: []
     }
@@ -142,6 +169,12 @@
     this.initForm()
     this.quiescevm = this.resource.quiescevm
     this.supportsStorageSnapshot = this.resource.supportsstoragesnapshot
+    this.fetchZoneData()
+  },
+  computed: {
+    formattedAdditionalZoneMessage () {
+      return `${this.$t('message.snapshot.additional.zones').replace('%x', this.resource.zonename)}`
+    }
   },
   methods: {
     initForm () {
@@ -153,6 +186,20 @@
       })
       this.rules = reactive({})
     },
+    fetchZoneData () {
+      const params = {}
+      params.showicon = true
+      this.zoneLoading = true
+      api('listZones', params).then(json => {
+        const listZones = json.listzonesresponse.zone
+        if (listZones) {
+          this.zones = listZones
+          this.zones = this.zones.filter(zone => zone.type !== 'Edge' && zone.id !== this.resource.zoneid)
+        }
+      }).finally(() => {
+        this.zoneLoading = false
+      })
+    },
     handleSubmit (e) {
       e.preventDefault()
       if (this.actionLoading) return
@@ -173,6 +220,9 @@
         if (values.quiescevm) {
           params.quiescevm = values.quiescevm
         }
+        if (values.zoneids && values.zoneids.length > 0) {
+          params.zoneids = values.zoneids.join()
+        }
         for (let i = 0; i < this.tags.length; i++) {
           const formattedTagData = {}
           const tag = this.tags[i]
diff --git a/ui/src/views/storage/UpdateBucket.vue b/ui/src/views/storage/UpdateBucket.vue
new file mode 100644
index 0000000..5d68c90
--- /dev/null
+++ b/ui/src/views/storage/UpdateBucket.vue
@@ -0,0 +1,180 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div class="form-layout" v-ctrl-enter="handleSubmit">
+    <a-form
+      :ref="formRef"
+      :model="form"
+      :rules="rules"
+      layout="vertical"
+      @finish="handleSubmit"
+    >
+      <a-form-item name="quota" ref="quota" :label="$t('label.quotagb')">
+        <a-input
+          v-model:value="form.quota"
+          :placeholder="$t('label.quota')"/>
+      </a-form-item>
+      <a-form-item name="encryption" ref="encryption" :label="$t('label.encryption')">
+        <a-switch
+          v-model:checked="form.encryption"
+          :checked="encryption"
+          @change="val => { encryption = val }"/>
+      </a-form-item>
+      <a-form-item name="versioning" ref="versioning" :label="$t('label.versioning')">
+        <a-switch
+          v-model:checked="form.versioning"
+          :checked="versioning"
+          @change="val => { versioning = val }"/>
+      </a-form-item>
+      <a-form-item name="Bucket Policy" ref="policy" :label="$t('label.bucket.policy')">
+        <a-select
+          v-model:value="form.policy"
+          @change="val => { form.policy = val }"
+          showSearch
+          optionFilterProp="value"
+          :filterOption="(input, option) => {
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }" >
+          <a-select-option
+            :value="policy"
+            v-for="(policy,idx) in policyList"
+            :key="idx"
+          >{{ policy }}</a-select-option>
+        </a-select>
+      </a-form-item>
+      <div :span="24" class="action-button">
+        <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+        <a-button :loading="loading" type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-form>
+  </div>
+</template>
+<script>
+import { ref, reactive, toRaw } from 'vue'
+import { api } from '@/api'
+import { mixinForm } from '@/utils/mixin'
+
+export default {
+  name: 'updateBucket',
+  mixins: [mixinForm],
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      offerings: [],
+      customDiskOffering: false,
+      loading: false,
+      customDiskOfferingIops: false
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('updateBucket')
+  },
+  created () {
+    this.initForm()
+    this.policyList = ['Public', 'Private']
+    this.fetchData()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({})
+      this.rules = reactive({
+      })
+    },
+    fetchData () {
+      this.loading = false
+      this.fillEditFormFieldValues()
+    },
+    fillEditFormFieldValues () {
+      const form = this.form
+      this.loading = true
+      Object.keys(this.apiParams).forEach(item => {
+        const field = this.apiParams[item]
+        let fieldValue = null
+        let fieldName = null
+
+        if (field.type === 'list' || field.name === 'account') {
+          fieldName = field.name.replace('ids', 'name').replace('id', 'name')
+        } else {
+          fieldName = field.name
+        }
+        fieldValue = this.resource[fieldName] ? this.resource[fieldName] : null
+        if (fieldValue) {
+          form[field.name] = fieldValue
+        }
+      })
+      this.loading = false
+    },
+    handleSubmit (e) {
+      if (this.loading) return
+      this.formRef.value.validate().then(() => {
+        const formRaw = toRaw(this.form)
+        const values = this.handleRemoveFields(formRaw)
+
+        var data = {
+          id: this.resource.id,
+          quota: values.quota,
+          encryption: values.encryption,
+          versioning: values.versioning,
+          objectlocking: values.objectlocking,
+          policy: values.policy
+        }
+
+        this.loading = true
+        api('updateBucket', data).then(response => {
+          this.$emit('refresh-data')
+          this.$notification.success({
+            message: this.$t('label.bucket.update'),
+            description: `${this.$t('message.success.update.bucket')}`
+          })
+          this.closeModal()
+        }).catch(error => {
+          console.log(error)
+          this.$notification.error({
+            message: `${this.$t('label.bucket.update')} ${this.$t('label.error')}`,
+            description: error.response.data.updatebucketresponse.errortext,
+            duration: 0
+          })
+        }).finally(() => {
+          this.loading = false
+        })
+      }).catch((error) => {
+        this.formRef.value.scrollToField(error.errorFields[0].name)
+      })
+    },
+    closeModal () {
+      this.$emit('refresh-data')
+      this.$emit('close-action')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.form-layout {
+  width: 85vw;
+
+  @media (min-width: 760px) {
+    width: 500px;
+  }
+}
+</style>
diff --git a/ui/src/views/storage/UploadLocalVolume.vue b/ui/src/views/storage/UploadLocalVolume.vue
index 3548e02..75775c9 100644
--- a/ui/src/views/storage/UploadLocalVolume.vue
+++ b/ui/src/views/storage/UploadLocalVolume.vue
@@ -33,7 +33,7 @@
           <a-upload-dragger
             :multiple="false"
             :fileList="fileList"
-            :remove="handleRemove"
+            @remove="handleRemove"
             :beforeUpload="beforeUpload"
             v-model:value="form.file">
             <p class="ant-upload-drag-icon">
@@ -84,10 +84,14 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
-            <a-select-option v-for="opt in offerings" :key="opt.id">
-              {{ opt.name || opt.displaytext }}
+            <a-select-option
+              v-for="(offering, index) in offerings"
+              :value="offering.id"
+              :key="index"
+              :label="offering.displaytext || offering.name">
+              {{ offering.displaytext || offering.name }}
             </a-select-option>
           </a-select>
         </a-form-item>
@@ -98,9 +102,9 @@
           <a-select
             v-model:value="form.format"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="format in formats" :key="format">
               {{ format }}
@@ -125,12 +129,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="domainLoading"
             :placeholder="$t('label.domainid')"
             @change="val => { handleDomainChange(domainList[val].id) }">
-            <a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex" :label="opt.path || opt.name || opt.description">
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -142,9 +146,9 @@
           <a-select
             v-model:value="form.account"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="$t('label.account')"
             @change="val => { handleAccountChange(val) }">
diff --git a/ui/src/views/storage/UploadVolume.vue b/ui/src/views/storage/UploadVolume.vue
index 5bfdcfa..937c3ad 100644
--- a/ui/src/views/storage/UploadVolume.vue
+++ b/ui/src/views/storage/UploadVolume.vue
@@ -70,9 +70,9 @@
           <a-select
             v-model:value="form.format"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="format in formats" :key="format">
               {{ format }}
@@ -90,12 +90,13 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option
               v-for="(offering, index) in offerings"
               :value="offering.id"
-              :key="index">
+              :key="index"
+              :label="offering.displaytext || offering.name">
               {{ offering.displaytext || offering.name }}
             </a-select-option>
           </a-select>
@@ -118,12 +119,12 @@
             showSearch
             optionFilterProp="label"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :loading="domainLoading"
             :placeholder="$t('label.domainid')"
             @change="val => { handleDomainChange(domainList[val].id) }">
-            <a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex">
+            <a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex" :label="opt.path || opt.name || opt.description">
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -135,9 +136,9 @@
           <a-select
             v-model:value="form.account"
             showSearch
-            optionFilterProp="label"
+            optionFilterProp="value"
             :filterOption="(input, option) => {
-              return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }"
             :placeholder="$t('label.account')"
             @change="val => { handleAccountChange(val) }">
diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue
index 6d90f72..9eb7487 100644
--- a/ui/src/views/tools/ImportUnmanagedInstance.vue
+++ b/ui/src/views/tools/ImportUnmanagedInstance.vue
@@ -19,7 +19,7 @@
   <div>
     <a-spin :spinning="loading" v-ctrl-enter="handleSubmit">
       <a-row :gutter="12">
-        <a-col :md="24" :lg="7">
+        <a-col :md="24" :lg="7" v-if="!isDiskImport">
           <info-card
             class="vm-info-card"
             :isStatic="true"
@@ -34,6 +34,12 @@
               :rules="rules"
               @finish="handleSubmit"
               layout="vertical">
+              <a-alert
+                v-if="selectedVmwareVcenter && isVmRunning"
+                type="warning"
+                :showIcon="true"
+                :message="$t('message.import.running.instance.warning')"
+              />
               <a-form-item name="displayname" ref="displayname">
                 <template #label>
                   <tooltip-label :title="$t('label.displayname')" :tooltip="apiParams.displayname.description"/>
@@ -105,7 +111,7 @@
                   </a-select-option>
                 </a-select>
               </a-form-item>
-              <a-form-item name="templateid" ref="templateid">
+              <a-form-item name="templateid" ref="templateid" v-if="cluster.hypervisortype === 'VMware' || (cluster.hypervisortype === 'KVM' && !selectedVmwareVcenter && !isDiskImport && !isExternalImport)">
                 <template #label>
                   <tooltip-label :title="$t('label.templatename')" :tooltip="apiParams.templateid.description + '. ' + $t('message.template.import.vm.temporary')"/>
                 </template>
@@ -114,7 +120,7 @@
                   :value="templateType"
                   @change="changeTemplateType">
                   <a-row :gutter="12">
-                    <a-col :md="24" :lg="12">
+                    <a-col :md="24" :lg="12" v-if="this.cluster.hypervisortype === 'VMware'">
                       <a-radio value="auto">
                         {{ $t('label.template.temporary.import') }}
                       </a-radio>
@@ -146,62 +152,118 @@
                   </a-row>
                 </a-radio-group>
               </a-form-item>
+              <a-form-item name="converthostid" ref="converthostid">
+                <check-box-select-pair
+                  layout="vertical"
+                  v-if="cluster.hypervisortype === 'KVM' && selectedVmwareVcenter"
+                  :resourceKey="cluster.id"
+                  :selectOptions="kvmHostsForConversion"
+                  :checkBoxLabel="'(Optional) Select a KVM host in the cluster to perform the instance conversion through virt-v2v'"
+                  :defaultCheckBoxValue="false"
+                  :reversed="false"
+                  @handle-checkselectpair-change="updateSelectedKvmHostForConversion"
+                />
+              </a-form-item>
+              <a-form-item name="convertstorageoption" ref="convertstorageoption">
+                <check-box-select-pair
+                  layout="vertical"
+                  style="margin-bottom: 20px"
+                  v-if="cluster.hypervisortype === 'KVM' && selectedVmwareVcenter"
+                  :resourceKey="cluster.id"
+                  :selectOptions="storageOptionsForConversion"
+                  :checkBoxLabel="'(Optional) Select a Storage temporary destination for the converted disks through virt-v2v'"
+                  :defaultCheckBoxValue="false"
+                  :reversed="false"
+                  @handle-checkselectpair-change="updateSelectedStorageOptionForConversion"
+                />
+              </a-form-item>
+              <a-form-item v-if="showStoragePoolsForConversion" name="convertstoragepool" ref="convertstoragepool" :label="$t('label.storagepool')">
+                <a-select
+                  v-model:value="form.convertstoragepoolid"
+                  defaultActiveFirstOption
+                  showSearch
+                  optionFilterProp="label"
+                  :filterOption="(input, option) => {
+                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                  }"
+                  @change="val => { selectedStoragePoolForConversion = val }">
+                  <a-select-option v-for="(pool) in storagePoolsForConversion" :key="pool.id" :label="pool.name">
+                    {{ pool.name }}
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
               <a-form-item name="serviceofferingid" ref="serviceofferingid">
                 <template #label>
                   <tooltip-label :title="$t('label.serviceofferingid')" :tooltip="apiParams.serviceofferingid.description"/>
                 </template>
+                <compute-offering-selection
+                  :compute-items="computeOfferings"
+                  :loading="computeOfferingLoading"
+                  :rowCount="totalComputeOfferings"
+                  :value="computeOffering ? computeOffering.id : ''"
+                  :minimumCpunumber="isVmRunning ? resource.cpunumber : null"
+                  :minimumCpuspeed="isVmRunning ? resource.cpuspeed : null"
+                  :minimumMemory="isVmRunning ? resource.memory : null"
+                  :allowAllOfferings="selectedVmwareVcenter ? true : false"
+                  size="small"
+                  @select-compute-item="($event) => updateComputeOffering($event)"
+                  @handle-search-filter="($event) => fetchComputeOfferings($event)" />
+                <compute-selection
+                  class="row-element"
+                  v-if="computeOffering && (computeOffering.iscustomized || computeOffering.iscustomizediops)"
+                  :isCustomized="computeOffering.iscustomized"
+                  :isCustomizedIOps="'iscustomizediops' in computeOffering && computeOffering.iscustomizediops"
+                  :cpuNumberInputDecorator="cpuNumberKey"
+                  :cpuSpeedInputDecorator="cpuSpeedKey"
+                  :memoryInputDecorator="memoryKey"
+                  :computeOfferingId="computeOffering.id"
+                  :preFillContent="resource"
+                  :isConstrained="'serviceofferingdetails' in computeOffering"
+                  :minCpu="getMinCpu()"
+                  :maxCpu="getMaxCpu()"
+                  :minMemory="getMinMemory()"
+                  :maxMemory="getMaxMemory()"
+                  :cpuSpeed="getCPUSpeed()"
+                  @update-iops-value="updateFieldValue"
+                  @update-compute-cpunumber="updateFieldValue"
+                  @update-compute-cpuspeed="updateCpuSpeed"
+                  @update-compute-memory="updateFieldValue" />
               </a-form-item>
-              <compute-offering-selection
-                :compute-items="computeOfferings"
-                :loading="computeOfferingLoading"
-                :rowCount="totalComputeOfferings"
-                :value="computeOffering ? computeOffering.id : ''"
-                :minimumCpunumber="isVmRunning ? resource.cpunumber : null"
-                :minimumCpuspeed="isVmRunning ? resource.cpuspeed : null"
-                :minimumMemory="isVmRunning ? resource.memory : null"
-                size="small"
-                @select-compute-item="($event) => updateComputeOffering($event)"
-                @handle-search-filter="($event) => fetchComputeOfferings($event)" />
-              <compute-selection
-                class="row-element"
-                v-if="computeOffering && (computeOffering.iscustomized || computeOffering.iscustomizediops)"
-                :isCustomized="computeOffering.iscustomized"
-                :isCustomizedIOps="'iscustomizediops' in computeOffering && computeOffering.iscustomizediops"
-                :cpuNumberInputDecorator="cpuNumberKey"
-                :cpuSpeedInputDecorator="cpuSpeedKey"
-                :memoryInputDecorator="memoryKey"
-                :computeOfferingId="computeOffering.id"
-                :preFillContent="resource"
-                :isConstrained="'serviceofferingdetails' in computeOffering"
-                :minCpu="getMinCpu()"
-                :maxCpu="getMaxCpu()"
-                :minMemory="getMinMemory()"
-                :maxMemory="getMaxMemory()"
-                :cpuSpeed="getCPUSpeed()"
-                @update-iops-value="updateFieldValue"
-                @update-compute-cpunumber="updateFieldValue"
-                @update-compute-cpuspeed="updateFieldValue"
-                @update-compute-memory="updateFieldValue" />
               <div v-if="resource.disk && resource.disk.length > 1">
                 <a-form-item name="selection" ref="selection">
                   <template #label>
                     <tooltip-label :title="$t('label.disk.selection')" :tooltip="apiParams.datadiskofferinglist.description"/>
                   </template>
                 </a-form-item>
-                <a-form-item name="rootdiskid" ref="rootdiskid" :label="$t('label.rootdisk')">
+                <a-form-item name="rootdiskid" ref="rootdiskid" :label="$t('label.select.root.disk')">
                   <a-select
                     v-model:value="form.rootdiskid"
                     defaultActiveFirstOption
                     showSearch
                     optionFilterProp="label"
                     :filterOption="(input, option) => {
-                      return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }"
-                    @change="val => { selectedRootDiskIndex = val }">
-                    <a-select-option v-for="(opt, optIndex) in resource.disk" :key="optIndex">
+                    @change="onSelectRootDisk">
+                    <a-select-option v-for="(opt, optIndex) in resource.disk" :key="optIndex" :label="opt.label || opt.id">
                       {{ opt.label || opt.id }}
                     </a-select-option>
                   </a-select>
+                  <a-table
+                    :columns="selectedRootDiskColumns"
+                    :dataSource="selectedRootDiskSources"
+                    :pagination="false">
+                    <template #bodyCell="{ column, record }">
+                      <template v-if="column.key === 'name'">
+                        <span>{{ record.displaytext || record.name }}</span>
+                        <div v-if="record.meta">
+                          <div v-for="meta in record.meta" :key="meta.key">
+                            <a-tag style="margin-top: 5px" :key="meta.key">{{ meta.key + ': ' + meta.value }}</a-tag>
+                          </div>
+                        </div>
+                      </template>
+                    </template>
+                  </a-table>
                 </a-form-item>
                 <multi-disk-selection
                   :items="dataDisks"
@@ -209,6 +271,7 @@
                   :selectionEnabled="false"
                   :customOfferingsAllowed="true"
                   :autoSelectCustomOffering="true"
+                  :isKVMUnmanage="isKVMUnmanage"
                   :autoSelectLabel="$t('label.auto.assign.diskoffering.disk.size')"
                   @select-multi-disk-offering="updateMultiDiskOffering" />
               </div>
@@ -219,22 +282,58 @@
                   </template>
                   <span>{{ $t('message.ip.address.changes.effect.after.vm.restart') }}</span>
                 </a-form-item>
+                <a-row v-if="selectedVmwareVcenter" :gutter="12" justify="end">
+                  <a-col style="text-align: right">
+                    <a-form-item name="forced" ref="forced">
+                      <template #label>
+                        <tooltip-label
+                          :title="$t('label.allow.duplicate.macaddresses')"
+                          :tooltip="apiParams.forced.description"/>
+                      </template>
+                      <a-switch v-model:checked="form.forced" @change="val => { switches.forced = val }" />
+                    </a-form-item>
+                  </a-col>
+                </a-row>
                 <multi-network-selection
                   :items="nics"
                   :zoneId="cluster.zoneid"
                   :selectionEnabled="false"
                   :filterUnimplementedNetworks="true"
-                  filterMatchKey="broadcasturi"
+                  :hypervisor="this.cluster.hypervisortype"
+                  :filterMatchKey="isKVMUnmanage ? undefined : 'broadcasturi'"
                   @select-multi-network="updateMultiNetworkOffering" />
               </div>
-              <a-row v-else style="margin: 12px 0">
-                <a-alert type="warning">
-                  <template #message>
-                    <div v-html="$t('message.warn.importing.instance.without.nic')"></div>
-                  </template>
-                </a-alert>
+              <a-row v-else style="margin: 12px 0" >
+                <div v-if="!isExternalImport && !isDiskImport">
+                  <a-alert type="warning">
+                    <template #message>
+                      <div v-html="$t('message.warn.importing.instance.without.nic')"></div>
+                    </template>
+                  </a-alert>
+                </div>
               </a-row>
-              <a-row :gutter="12">
+              <div v-if="isDiskImport">
+                <a-form-item name="networkid" ref="networkid">
+                  <template #label>
+                    <tooltip-label :title="$t('label.network')"/>
+                  </template>
+                  <a-select
+                    v-model:value="form.networkid"
+                    showSearch
+                    optionFilterProp="label"
+                    :filterOption="(input, option) => {
+                      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }"
+                    :loading="optionsLoading.networks">
+                    <a-select-option v-for="network in networkSelectOptions" :key="network.value" :label="network.label">
+                      <span>
+                        {{ network.label }}
+                      </span>
+                    </a-select-option>
+                  </a-select>
+                </a-form-item>
+              </div>
+              <a-row v-if="!selectedVmwareVcenter" :gutter="12">
                 <a-col :md="24" :lg="12">
                   <a-form-item name="migrateallowed" ref="migrateallowed">
                     <template #label>
@@ -243,10 +342,12 @@
                     <a-switch v-model:checked="form.migrateallowed" @change="val => { switches.migrateAllowed = val }" />
                   </a-form-item>
                 </a-col>
-                <a-col :md="24" :lg="12">
+                <a-col>
                   <a-form-item name="forced" ref="forced">
                     <template #label>
-                      <tooltip-label :title="$t('label.forced')" :tooltip="apiParams.forced.description"/>
+                      <tooltip-label
+                        :title="$t('label.forced')"
+                        :tooltip="apiParams.forced.description"/>
                     </template>
                     <a-switch v-model:checked="form.forced" @change="val => { switches.forced = val }" />
                   </a-form-item>
@@ -276,6 +377,7 @@
 import MultiNetworkSelection from '@views/compute/wizard/MultiNetworkSelection'
 import OsLogo from '@/components/widgets/OsLogo'
 import ResourceIcon from '@/components/view/ResourceIcon'
+import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
 
 export default {
   name: 'ImportUnmanagedInstances',
@@ -287,13 +389,22 @@
     MultiDiskSelection,
     MultiNetworkSelection,
     OsLogo,
-    ResourceIcon
+    ResourceIcon,
+    CheckBoxSelectPair
   },
   props: {
     cluster: {
       type: Object,
       required: true
     },
+    host: {
+      type: Object,
+      required: true
+    },
+    pool: {
+      type: Object,
+      required: true
+    },
     resource: {
       type: Object,
       required: true
@@ -301,6 +412,42 @@
     isOpen: {
       type: Boolean,
       required: false
+    },
+    zoneid: {
+      type: String,
+      required: false
+    },
+    importsource: {
+      type: String,
+      required: false
+    },
+    hypervisor: {
+      type: String,
+      required: false
+    },
+    exthost: {
+      type: String,
+      required: false
+    },
+    username: {
+      type: String,
+      required: false
+    },
+    password: {
+      type: String,
+      required: false
+    },
+    tmppath: {
+      type: String,
+      required: false
+    },
+    diskpath: {
+      type: String,
+      required: false
+    },
+    selectedVmwareVcenter: {
+      type: Array,
+      required: false
     }
   },
   data () {
@@ -308,12 +455,14 @@
       options: {
         domains: [],
         projects: [],
+        networks: [],
         templates: []
       },
       rowCount: {},
       optionsLoading: {
         domains: false,
         projects: false,
+        networks: false,
         templates: false
       },
       domains: [],
@@ -321,7 +470,7 @@
       selectedDomainId: null,
       templates: [],
       templateLoading: false,
-      templateType: 'auto',
+      templateType: this.defaultTemplateType(),
       totalComputeOfferings: 0,
       computeOfferings: [],
       computeOfferingLoading: false,
@@ -335,7 +484,30 @@
       minIopsKey: 'minIops',
       maxIopsKey: 'maxIops',
       switches: {},
-      loading: false
+      loading: false,
+      kvmHostsForConversion: [],
+      selectedKvmHostForConversion: null,
+      storageOptionsForConversion: [
+        {
+          id: 'secondary',
+          name: 'Secondary Storage'
+        }, {
+          id: 'primary',
+          name: 'Primary Storage'
+        }
+      ],
+      storagePoolsForConversion: [],
+      selectedStorageOptionForConversion: null,
+      selectedStoragePoolForConversion: null,
+      showStoragePoolsForConversion: false,
+      selectedRootDiskColumns: [
+        {
+          key: 'name',
+          dataIndex: 'name',
+          title: this.$t('label.rootdisk')
+        }
+      ],
+      selectedRootDiskSources: []
     }
   },
   beforeCreate () {
@@ -370,6 +542,15 @@
             showicon: true
           }
         },
+        networks: {
+          list: 'listNetworks',
+          isLoad: true,
+          field: 'networkid',
+          options: {
+            zoneid: this.zoneid,
+            details: 'min'
+          }
+        },
         templates: {
           list: 'listTemplates',
           isLoad: true,
@@ -388,6 +569,21 @@
       }
       return false
     },
+    isDiskImport () {
+      if (this.importsource === 'local' || this.importsource === 'shared') {
+        return true
+      }
+      return false
+    },
+    isExternalImport () {
+      if (this.importsource === 'external') {
+        return true
+      }
+      return false
+    },
+    isKVMUnmanage () {
+      return this.hypervisor && this.hypervisor === 'kvm' && (this.importsource === 'unmanaged' || this.importsource === 'external')
+    },
     domainSelectOptions () {
       var domains = this.options.domains.map((domain) => {
         return {
@@ -416,6 +612,19 @@
       })
       return projects
     },
+    networkSelectOptions () {
+      var networks = this.options.networks.map((network) => {
+        return {
+          label: network.name + ' (' + network.displaytext + ')',
+          value: network.id
+        }
+      })
+      networks.unshift({
+        label: '',
+        value: null
+      })
+      return networks
+    },
     templateSelectOptions () {
       return this.options.templates.map((template) => {
         return {
@@ -449,13 +658,20 @@
           var nic = { ...nicEntry }
           nic.name = nic.name || nic.id
           nic.displaytext = nic.name
+          if (this.isExternalImport && nic.vlanid === -1) {
+            delete nic.vlanid
+          }
           if (nic.vlanid) {
             nic.broadcasturi = 'vlan://' + nic.vlanid
             if (nic.isolatedpvlan) {
               nic.broadcasturi = 'pvlan://' + nic.vlanid + '-i' + nic.isolatedpvlan
             }
           }
-          nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan', networkname: 'network' })
+          if (this.cluster.hypervisortype === 'VMware') {
+            nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan', networkname: 'network' })
+          } else {
+            nic.meta = this.getMeta(nic, { macaddress: 'mac', vlanid: 'vlan' })
+          }
           nics.push(nic)
         }
       }
@@ -496,6 +712,10 @@
         pageSize: 10,
         page: 1
       })
+      this.fetchKvmHostsForConversion()
+      if (this.resource?.disk?.length > 1) {
+        this.updateSelectedRootDisk()
+      }
     },
     getMeta (obj, metaKeys) {
       var meta = []
@@ -602,6 +822,15 @@
         this.selectMatchingComputeOffering()
       })
     },
+    updateCpuSpeed (name, value) {
+      if (this.computeOffering.iscustomized) {
+        if (this.computeOffering.serviceofferingdetails) {
+          this.updateFieldValue(this.cpuSpeedKey, this.computeOffering.cpuspeed)
+        } else {
+          this.updateFieldValue(this.cpuSpeedKey, value)
+        }
+      }
+    },
     updateFieldValue (name, value) {
       this.form[name] = value
     },
@@ -619,6 +848,12 @@
     updateMultiNetworkOffering (data) {
       this.nicsNetworksMapping = data
     },
+    defaultTemplateType () {
+      if (this.cluster.hypervisortype === 'VMware') {
+        return 'auto'
+      }
+      return 'custom'
+    },
     changeTemplateType (e) {
       this.templateType = e.target.value
       if (this.templateType === 'auto') {
@@ -661,6 +896,85 @@
         }
       }
     },
+    fetchKvmHostsForConversion () {
+      api('listHosts', {
+        clusterid: this.cluster.id,
+        hypervisor: this.cluster.hypervisortype,
+        type: 'Routing',
+        state: 'Up',
+        resourcestate: 'Enabled'
+      }).then(json => {
+        this.kvmHostsForConversion = json.listhostsresponse.host || []
+      })
+    },
+    fetchStoragePoolsForConversion () {
+      if (this.selectedStorageOptionForConversion === 'primary') {
+        api('listStoragePools', {
+          zoneid: this.cluster.zoneid,
+          state: 'Up'
+        }).then(json => {
+          this.storagePoolsForConversion = json.liststoragepoolsresponse.storagepool || []
+        })
+      } else if (this.selectedStorageOptionForConversion === 'local') {
+        const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0]
+        api('listStoragePools', {
+          scope: 'HOST',
+          ipaddress: kvmHost.ipaddress,
+          state: 'Up'
+        }).then(json => {
+          this.storagePoolsForConversion = json.liststoragepoolsresponse.storagepool || []
+        })
+      }
+    },
+    updateSelectedKvmHostForConversion (clusterid, checked, value) {
+      if (checked) {
+        this.selectedKvmHostForConversion = value
+        const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0]
+        if (kvmHost.islocalstorageactive) {
+          this.storageOptionsForConversion.push({
+            id: 'local',
+            name: 'Host Local Storage'
+          })
+        } else {
+          this.resetStorageOptionsForConversion()
+        }
+      } else {
+        this.selectedKvmHostForConversion = null
+        this.resetStorageOptionsForConversion()
+      }
+    },
+    updateSelectedStorageOptionForConversion (clusterid, checked, value) {
+      if (checked) {
+        this.selectedStorageOptionForConversion = value
+        this.fetchStoragePoolsForConversion()
+        this.showStoragePoolsForConversion = value !== 'secondary'
+      } else {
+        this.showStoragePoolsForConversion = false
+        this.selectedStoragePoolForConversion = null
+      }
+    },
+    resetStorageOptionsForConversion () {
+      this.storageOptionsForConversion = [
+        {
+          id: 'secondary',
+          name: 'Secondary Storage'
+        }, {
+          id: 'primary',
+          name: 'Primary Storage'
+        }
+      ]
+    },
+    onSelectRootDisk (val) {
+      this.selectedRootDiskIndex = val
+      this.updateSelectedRootDisk()
+    },
+    updateSelectedRootDisk () {
+      var rootDisk = this.resource.disk[this.selectedRootDiskIndex]
+      rootDisk.size = rootDisk.capacity / (1024 * 1024 * 1024)
+      rootDisk.name = `${rootDisk.label} (${rootDisk.size} GB)`
+      rootDisk.meta = this.getMeta(rootDisk, { controller: 'controller', datastorename: 'datastore', position: 'position' })
+      this.selectedRootDiskSources = [rootDisk]
+    },
     handleSubmit (e) {
       e.preventDefault()
       if (this.loading) return
@@ -669,7 +983,33 @@
         const params = {
           name: this.resource.name,
           clusterid: this.cluster.id,
-          displayname: values.displayname
+          displayname: values.displayname,
+          zoneid: this.zoneid,
+          importsource: this.importsource,
+          hypervisor: this.hypervisor,
+          host: this.exthost,
+          hostname: values.hostname,
+          username: this.username,
+          password: this.password,
+          hostid: this.host.id,
+          storageid: this.pool.id,
+          diskpath: this.diskpath,
+          temppath: this.tmppath
+        }
+        var importapi = 'importUnmanagedInstance'
+        if (this.isExternalImport || this.isDiskImport || this.selectedVmwareVcenter) {
+          importapi = 'importVm'
+          if (this.isDiskImport) {
+            if (!values.networkid) {
+              this.$notification.error({
+                message: this.$t('message.request.failed'),
+                description: this.$t('message.please.enter.valid.value') + ': ' + this.$t('label.network')
+              })
+              return
+            }
+            params.name = values.displayname
+            params.networkid = values.networkid
+          }
         }
         if (!this.computeOffering || !this.computeOffering.id) {
           this.$notification.error({
@@ -713,6 +1053,34 @@
             })
           }
         }
+        if (this.isDiskImport) {
+          var storageType = this.computeOffering.storagetype
+          if (this.importsource !== storageType) {
+            this.$notification.error({
+              message: this.$t('message.request.failed'),
+              description: 'Incompatible Storage. Import Source is: ' + this.importsource + '. Storage Type in service offering is: ' + storageType
+            })
+            return
+          }
+        }
+        if (this.selectedVmwareVcenter) {
+          if (this.selectedVmwareVcenter.existingvcenterid) {
+            params.existingvcenterid = this.selectedVmwareVcenter.existingvcenterid
+          } else {
+            params.vcenter = this.selectedVmwareVcenter.vcenter
+            params.datacentername = this.selectedVmwareVcenter.datacentername
+            params.username = this.selectedVmwareVcenter.username
+            params.password = this.selectedVmwareVcenter.password
+          }
+          params.hostip = this.resource.hostname
+          params.clustername = this.resource.clustername
+          if (this.selectedKvmHostForConversion) {
+            params.convertinstancehostid = this.selectedKvmHostForConversion
+          }
+          if (this.selectedStoragePoolForConversion) {
+            params.convertinstancepoolid = this.selectedStoragePoolForConversion
+          }
+        }
         var keys = ['hostname', 'domainid', 'projectid', 'account', 'migrateallowed', 'forced']
         if (this.templateType !== 'auto') {
           keys.push('templateid')
@@ -737,6 +1105,7 @@
         }
         var nicNetworkIndex = 0
         var nicIpIndex = 0
+        var networkcheck = new Set()
         for (var nicId in this.nicsNetworksMapping) {
           if (!this.nicsNetworksMapping[nicId].network) {
             this.$notification.error({
@@ -747,6 +1116,16 @@
           }
           params['nicnetworklist[' + nicNetworkIndex + '].nic'] = nicId
           params['nicnetworklist[' + nicNetworkIndex + '].network'] = this.nicsNetworksMapping[nicId].network
+          var netId = this.nicsNetworksMapping[nicId].network
+          if (!networkcheck.has(netId)) {
+            networkcheck.add(netId)
+          } else {
+            this.$notification.error({
+              message: this.$t('message.request.failed'),
+              description: 'Same network cannot be assigned to multiple Nics'
+            })
+            return
+          }
           nicNetworkIndex++
           if ('ipAddress' in this.nicsNetworksMapping[nicId]) {
             if (!this.nicsNetworksMapping[nicId].ipAddress) {
@@ -762,28 +1141,46 @@
           }
         }
         this.updateLoading(true)
-        const name = this.resource.name
-        api('importUnmanagedInstance', params).then(json => {
-          const jobId = json.importunmanagedinstanceresponse.jobid
-          this.$pollJob({
-            jobId,
-            title: this.$t('label.import.instance'),
-            description: name,
-            loadingMessage: `${this.$t('label.import.instance')} ${name} ${this.$t('label.in.progress')}`,
-            catchMessage: this.$t('error.fetching.async.job.result'),
-            successMessage: this.$t('message.success.import.instance') + ' ' + name,
-            successMethod: result => {
-              this.$emit('refresh-data')
+        const name = params.name
+        return new Promise((resolve, reject) => {
+          api(importapi, params).then(response => {
+            var jobId
+            if (this.isDiskImport || this.isExternalImport || this.selectedVmwareVcenter) {
+              jobId = response.importvmresponse.jobid
+            } else {
+              jobId = response.importunmanagedinstanceresponse.jobid
             }
+            let msgLoading = this.$t('label.import.instance') + ' ' + name + ' ' + this.$t('label.in.progress')
+            if (this.selectedKvmHostForConversion) {
+              const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0]
+              msgLoading += ' on host ' + kvmHost.name
+            }
+            this.$pollJob({
+              jobId,
+              title: this.$t('label.import.instance'),
+              description: name,
+              loadingMessage: msgLoading,
+              catchMessage: this.$t('error.fetching.async.job.result'),
+              successMessage: this.$t('message.success.import.instance') + ' ' + name,
+              successMethod: result => {
+                this.$emit('refresh-data')
+                resolve(result)
+              },
+              errorMethod: (result) => {
+                this.updateLoading(false)
+                reject(result.jobresult.errortext)
+              }
+            })
+          }).catch(error => {
+            this.updateLoading(false)
+            this.$notifyError(error)
+          }).finally(() => {
+            this.closeAction()
+            this.updateLoading(false)
           })
-          this.closeAction()
-        }).catch(error => {
-          this.$notifyError(error)
-        }).finally(() => {
-          this.updateLoading(false)
         })
-      }).catch((error) => {
-        this.formRef.value.scrollToField(error.errorFields[0].name)
+      }).catch(() => {
+        this.$emit('loading-changed', false)
       })
     },
     updateLoading (value) {
@@ -795,7 +1192,7 @@
       for (var field of fields) {
         this.updateFieldValue(field, undefined)
       }
-      this.templateType = 'auto'
+      this.templateType = this.defaultTemplateType()
       this.updateComputeOffering(undefined)
       this.switches = {}
     },
@@ -807,33 +1204,33 @@
 </script>
 
 <style lang="less">
-  @import url('../../style/index');
-  .ant-table-selection-column {
-    // Fix for the table header if the row selection use radio buttons instead of checkboxes
-    > div:empty {
-      width: 16px;
-    }
+@import url('../../style/index');
+.ant-table-selection-column {
+  // Fix for the table header if the row selection use radio buttons instead of checkboxes
+  > div:empty {
+    width: 16px;
   }
+}
 
-  .ant-collapse-borderless > .ant-collapse-item {
-    border: 1px solid @border-color-split;
-    border-radius: @border-radius-base !important;
-    margin: 0 0 1.2rem;
+.ant-collapse-borderless > .ant-collapse-item {
+  border: 1px solid @border-color-split;
+  border-radius: @border-radius-base !important;
+  margin: 0 0 1.2rem;
+}
+
+.form-layout {
+  width: 120vw;
+
+  @media (min-width: 1000px) {
+    width: 550px;
   }
+}
 
-  .form-layout {
-    width: 120vw;
+.action-button {
+  text-align: right;
 
-    @media (min-width: 1000px) {
-      width: 550px;
-    }
+  button {
+    margin-right: 5px;
   }
-
-  .action-button {
-    text-align: right;
-
-    button {
-      margin-right: 5px;
-    }
-  }
+}
 </style>
diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue
index 1e6cf3a..b61c8a9 100644
--- a/ui/src/views/tools/ManageInstances.vue
+++ b/ui/src/views/tools/ManageInstances.vue
@@ -37,78 +37,294 @@
       :md="24">
       <div>
         <a-card>
-          <a-alert type="info" :showIcon="true" :message="$t('label.desc.importexportinstancewizard')" :description="$t('message.desc.importexportinstancewizard')" />
+          <a-alert
+            type="info"
+            :showIcon="true"
+            :message="wizardTitle"
+          >
+            <template #description>
+              <span v-html="wizardDescription" />
+            </template>
+          </a-alert>
           <br />
-          <a-form
-            style="min-width: 170px"
-            :ref="formRef"
-            :model="form"
-            :rules="rules"
-            layout="vertical"
-           >
-            <a-col :md="24" :lg="8">
-              <a-form-item name="zoneid" ref="zoneid" :label="$t('label.zoneid')">
-                <a-select
-                  v-model:value="form.zoneid"
-                  showSearch
-                  optionFilterProp="label"
-                  :filterOption="(input, option) => {
-                    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                  }"
-                  @change="onSelectZoneId"
-                  :loading="optionLoading.zones"
-                  v-focus="true"
+          <a-row :gutter="12">
+            <a-card class="source-dest-card">
+              <a-col :md="24" :lg="48">
+                <a-form
+                  style="min-width: 170px"
+                  :ref="formRef"
+                  :model="form"
+                  :rules="rules"
+                  layout="vertical"
                 >
-                  <a-select-option v-for="zoneitem in zoneSelectOptions" :key="zoneitem.value" :label="zoneitem.label">
-                    <span>
-                      <resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
-                      <global-outlined v-else style="margin-right: 5px" />
-                      {{ zoneitem.label }}
-                    </span>
-                  </a-select-option>
-                </a-select>
-              </a-form-item>
+                  <a-col :md="24" :lg="24">
+                    <a-form-item name="sourcehypervisor" ref="sourcehypervisor" :label="$t('label.source')">
+                      <a-radio-group
+                        style="text-align: center; width: 100%"
+                        v-model:value="form.sourceHypervisor"
+                        @change="selected => { onSelectHypervisor(selected.target.value) }"
+                        buttonStyle="solid">
+                        <a-radio-button value="vmware" style="width: 50%; text-align: center">
+                          VMware
+                        </a-radio-button>
+                        <a-radio-button value="kvm" style="width: 50%; text-align: center">
+                          KVM
+                        </a-radio-button>
+                      </a-radio-group>
+                    </a-form-item>
+                    <a-form-item name="sourceaction" ref="sourceaction" :label="$t('label.action')" v-if="sourceActions">
+                      <a-select
+                        v-model:value="form.sourceAction"
+                        showSearch
+                        optionFilterProp="label"
+                        :filterOption="(input, option) => {
+                          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }"
+                        @change="onSelectSourceAction"
+                        :loading="optionLoading.sourcehypervisor"
+                        v-focus="true"
+                      >
+                        <a-select-option v-for="opt in sourceActions" :key="opt.name" :label="opt.label">
+                          <span>
+                            {{ opt.label }}
+                          </span>
+                        </a-select-option>
+                      </a-select>
+                    </a-form-item>
+                  </a-col>
+                    <a-col v-if="showExtHost" :md="24" :lg="12">
+                        <a-form-item
+                                name="hostname"
+                                ref="hostname">
+                          <template #label>
+                            <tooltip-label
+                              :title="$t('label.hostname')"
+                              :tooltip="$t('label.ext.hostname.tooltip')"/>
+                          </template>
+                            <a-input
+                                    v-model:value="form.hostname"
+                            ></a-input>
+                        </a-form-item>
+                    </a-col>
+                    <a-col v-if="showExtHost" :md="24" :lg="12">
+                        <a-form-item
+                                name="username"
+                                ref="username">
+                          <template #label>
+                            <tooltip-label
+                              :title="$t('label.username')"
+                              :tooltip="$t('label.username.tooltip')"/>
+                          </template>
+                            <a-input
+                                    v-model:value="form.username"
+                            ></a-input>
+                        </a-form-item>
+                    </a-col>
+                    <a-col v-if="showExtHost" :md="24" :lg="12">
+                        <a-form-item
+                                name="password"
+                                ref="password">
+                          <template #label>
+                            <tooltip-label
+                              :title="$t('label.password')"
+                              :tooltip="$t('label.password.tooltip')"/>
+                          </template>
+                            <a-input-password
+                                    v-model:value="form.password"
+                            ></a-input-password>
+                        </a-form-item>
+                    </a-col>
+                    <a-col v-if="showExtHost" :md="24" :lg="12">
+                        <a-form-item
+                                name="tmppath"
+                                ref="tmppath">
+                          <template #label>
+                            <tooltip-label
+                              :title="$t('label.tmppath')"
+                              :tooltip="$t('label.tmppath.tooltip')"/>
+                          </template>
+                            <a-input
+                                    v-model:value="form.tmppath"
+                            ></a-input>
+                        </a-form-item>
+                    </a-col>
+                </a-form>
+              </a-col>
+            </a-card>
+            <!-- ------------ -->
+            <!-- RIGHT COLUMN -->
+            <!-- ------------ -->
+            <a-card class="source-dest-card">
+              <template #title>
+                Destination
+              </template>
+              <a-col :md="24" :lg="48">
+                <a-form
+                  style="min-width: 170px"
+                  :ref="formRef"
+                  :model="form"
+                  :rules="rules"
+                  layout="vertical"
+                >
+                <a-form-item v-if="showPool" name="scope" ref="scope">
+                  <template #label>
+                    <tooltip-label :title="$t('label.scope')" :tooltip="$t('label.scope.tooltip')"/>
+                  </template>
+                  <a-select
+                    v-model:value="this.poolscope"
+                    @change="onSelectPoolScope"
+                    showSearch
+                    optionFilterProp="label"
+                    :filterOption="(input, option) => {
+                      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }" >
+                    <a-select-option :value="'cluster'" :label="$t('label.clusterid')"> {{ $t('label.clusterid') }} </a-select-option>
+                    <a-select-option :value="'zone'" :label="$t('label.zoneid')"> {{ $t('label.zoneid') }} </a-select-option>
+                  </a-select>
+                </a-form-item>
+                  <a-form-item
+                    name="zoneid"
+                    ref="zoneid"
+                    :label="isMigrateFromVmware ? $t('label.destination.zone') : $t('label.zoneid')"
+                  >
+                    <a-select
+                      v-model:value="form.zoneid"
+                      showSearch
+                      optionFilterProp="label"
+                      :filterOption="(input, option) => {
+                        return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                      }"
+                      @change="onSelectZoneId"
+                      :loading="optionLoading.zones"
+                    >
+                      <a-select-option v-for="zoneitem in zoneSelectOptions" :key="zoneitem.value" :label="zoneitem.label">
+                        <span>
+                          <resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
+                          <global-outlined v-else style="margin-right: 5px" />
+                          {{ zoneitem.label }}
+                        </span>
+                      </a-select-option>
+                    </a-select>
+                  </a-form-item>
+                  <a-form-item
+                    v-if="showPod"
+                    name="podid"
+                    ref="podid"
+                    :label="isMigrateFromVmware ? $t('label.destination.pod') : $t('label.podid')">
+                    <a-select
+                      v-model:value="form.podid"
+                      showSearch
+                      optionFilterProp="label"
+                      :filterOption="filterOption"
+                      :options="podSelectOptions"
+                      :loading="optionLoading.pods"
+                      @change="onSelectPodId"
+                    ></a-select>
+                  </a-form-item>
+                  <a-form-item
+                    v-if="showCluster"
+                    name="clusterid"
+                    ref="clusterid"
+                    :label="isMigrateFromVmware ? $t('label.destination.cluster') : $t('label.clusterid')">
+                    <a-select
+                      v-model:value="form.clusterid"
+                      showSearch
+                      optionFilterProp="label"
+                      :filterOption="filterOption"
+                      :options="clusterSelectOptions"
+                      :loading="optionLoading.clusters"
+                      @change="onSelectClusterId"
+                    ></a-select>
+                  </a-form-item>
+                  <a-form-item v-if="isDestinationKVM && isMigrateFromVmware && clusterId != undefined">
+                    <SelectVmwareVcenter
+                      @onVcenterTypeChanged="updateVmwareVcenterType"
+                      @loadingVmwareUnmanagedInstances="() => this.unmanagedInstancesLoading = true"
+                      @listedVmwareUnmanagedInstances="($e) => onListUnmanagedInstancesFromVmware($e)"
+                    />
+                  </a-form-item>
+                  <a-form-item
+                    v-if="showHost"
+                    name="hostid"
+                    ref="hostid">
+                    <template #label>
+                      <tooltip-label
+                        :title="$t('label.hostname')"
+                        :tooltip="$t('label.hostname.tooltip')"/>
+                    </template>
+                      <a-select
+                        v-model:value="form.hostid"
+                        showSearch
+                        optionFilterProp="label"
+                        :filterOption="filterOption"
+                        :options="hostSelectOptions"
+                        :loading="optionLoading.hosts"
+                        @change="onSelectHostId"
+                      ></a-select>
+                  </a-form-item>
+                  <a-form-item
+                    v-if="isDiskImport"
+                    name="poolid"
+                    ref="poolid">
+                    <template #label>
+                      <tooltip-label
+                        :title="$t('label.storagepool')"
+                        :tooltip="$t('label.storagepool.tooltip')"/>
+                    </template>
+                      <a-select
+                        v-model:value="form.poolid"
+                        showSearch
+                        optionFilterProp="label"
+                        :filterOption="filterOption"
+                        :options="poolSelectOptions"
+                        :loading="optionLoading.pools"
+                        @change="onSelectPoolId"
+                      ></a-select>
+                  </a-form-item>
+                  <a-form-item
+                    v-if="showDiskPath"
+                    name="diskpath"
+                    ref="diskpath">
+                    <template #label>
+                          <tooltip-label
+                            :title="$t('label.disk')"
+                            :tooltip="$t('label.disk.tooltip')"/>
+                    </template>
+                    <a-input
+                      v-model:value="form.diskpath"
+                    ></a-input>
+                  </a-form-item>
+                  <a-col v-if="showDiskPath" :md="24" :lg="8">
+                    <a-button
+                        type="primary"
+                        @click="onImportInstanceAction">
+                      <template #icon><import-outlined /></template>
+                      {{ $t('label.import.instance') }}
+                    </a-button>
+                  </a-col>
+                </a-form>
+              </a-col>
+            </a-card>
+          </a-row>
+          <a-row v-if="showExtHost">
+            <a-col class="fetch-instances-column">
+              <div>
+                <a-button
+                  shape="round"
+                  type="primary"
+                  @click="() => { fetchExtKVMInstances() }">
+                  {{ $t('label.fetch.instances') }}
+                </a-button>
+              </div>
             </a-col>
-            <a-col :md="24" :lg="8">
-              <a-form-item
-                name="podid"
-                ref="podid"
-                :label="$t('label.podid')">
-                <a-select
-                  v-model:value="form.podid"
-                  showSearch
-                  optionFilterProp="label"
-                  :filterOption="filterOption"
-                  :options="podSelectOptions"
-                  :loading="optionLoading.pods"
-                  @change="onSelectPodId"
-                ></a-select>
-              </a-form-item>
-            </a-col>
-            <a-col :md="24" :lg="8">
-              <a-form-item
-                name="clusterid"
-                ref="clusterid"
-                :label="$t('label.clusterid')">
-                <a-select
-                  v-model:value="form.clusterid"
-                  showSearch
-                  optionFilterProp="label"
-                  :filterOption="filterOption"
-                  :options="clusterSelectOptions"
-                  :loading="optionLoading.clusters"
-                  @change="onSelectClusterId"
-                ></a-select>
-              </a-form-item>
-            </a-col>
-          </a-form>
+          </a-row>
           <a-divider />
           <a-row :gutter="12">
-            <a-col :md="24" :lg="12">
+            <a-col v-if="!isDiskImport" :md="24" :lg="(!isMigrateFromVmware && showManagedInstances) ? 12 : 24">
               <a-card class="instances-card">
                 <template #title>
-                  {{ $t('label.unmanaged.instances') }}
-                  <a-tooltip :title="$t('message.instances.unmanaged')">
+                  {{ (isMigrateFromVmware && vmwareVcenterType === 'existing') ? $t('label.instances') : $t('label.unmanaged.instances') }}
+                  <a-tooltip :title="(isMigrateFromVmware && vmwareVcenterType === 'existing') ? $t('message.instances.migrate.vmware') : $t('message.instances.unmanaged')">
                     <info-circle-outlined />
                   </a-tooltip>
                   <a-button
@@ -129,6 +345,7 @@
                   </span>
                 </template>
                 <a-table
+                  v-if="!isExternal"
                   class="instances-card-table"
                   :loading="unmanagedInstancesLoading"
                   :rowSelection="unmanagedInstanceSelection"
@@ -139,8 +356,28 @@
                   size="middle"
                   :rowClassName="getRowClassName"
                 >
-                  <template #state="{text}">
-                    <status :text="text ? text : ''" displayText />
+                  <template #bodyCell="{ column, text }">
+                    <template v-if="column.key === 'state'">
+                      <status :text="text ? text : ''" displayText />
+                    </template>
+                  </template>
+                </a-table>
+                <a-table
+                  v-if="isExternal"
+                  class="instances-card-table"
+                  :loading="unmanagedInstancesLoading"
+                  :rowSelection="unmanagedInstanceSelection"
+                  :rowKey="(record, index) => index"
+                  :columns="externalInstancesColumns"
+                  :data-source="unmanagedInstances"
+                  :pagination="false"
+                  size="middle"
+                  :rowClassName="getRowClassName"
+                >
+                  <template #bodyCell="{ column, text }">
+                      <template v-if="column.key === 'state'">
+                          <status :text="text ? text : ''" displayText />
+                      </template>
                   </template>
                 </a-table>
                 <div class="instances-card-footer">
@@ -170,7 +407,7 @@
                 </div>
               </a-card>
             </a-col>
-            <a-col :md="24" :lg="12">
+            <a-col :md="24" :lg="12" v-if="!isMigrateFromVmware && showManagedInstances">
               <a-card class="instances-card">
                 <template #title>
                   {{ $t('label.managed.instances') }}
@@ -205,11 +442,13 @@
                   size="middle"
                   :rowClassName="getRowClassName"
                 >
-                  <template #name="{text, record}" href="javascript:;">
-                    <router-link :to="{ path: '/vm/' + record.id }">{{ text }}</router-link>
-                  </template>
-                  <template #state="{text}">
-                    <status :text="text ? text : ''" displayText />
+                  <template #bodyCell="{ column, text, record }">
+                    <template v-if="column.key === 'name'">
+                      <router-link :to="{ path: '/vm/' + record.id }">{{ text }}</router-link>
+                    </template>
+                    <template v-if="column.key === 'state'">
+                      <status :text="text ? text : ''" displayText />
+                    </template>
                   </template>
                 </a-table>
                 <div class="instances-card-footer">
@@ -228,6 +467,7 @@
                   </a-pagination>
                   <div :span="24" class="action-button-right">
                     <a-button
+
                       :disabled="!(('unmanageVirtualMachine' in $store.getters.apis) && managedInstancesSelectedRowKeys.length > 0)"
                       type="primary"
                       @click="onUnmanageInstanceAction">
@@ -257,10 +497,22 @@
             class="importform"
             :resource="selectedUnmanagedInstance"
             :cluster="selectedCluster"
+            :host="selectedHost"
+            :pool="selectedPool"
+            :importsource="selectedSourceAction"
+            :zoneid="this.zoneId"
+            :hypervisor="this.destinationHypervisor"
+            :exthost="this.values?.hostname || ''"
+            :username="this.values?.username || ''"
+            :password="this.values?.password || ''"
+            :tmppath="this.values?.tmppath || ''"
+            :diskpath="this.values?.diskpath || ''"
             :isOpen="showUnmanageForm"
+            :selectedVmwareVcenter="selectedVmwareVcenter"
             @refresh-data="fetchInstances"
             @close-action="closeImportUnmanagedInstanceForm"
             @loading-changed="updateManageInstanceActionLoading"
+            @track-import-jobid="trackImportJobId"
           />
         </a-modal>
       </div>
@@ -269,7 +521,8 @@
 </template>
 
 <script>
-import { ref, reactive } from 'vue'
+import { message } from 'ant-design-vue'
+import { ref, reactive, toRaw } from 'vue'
 import { api } from '@/api'
 import _ from 'lodash'
 import Breadcrumb from '@/components/widgets/Breadcrumb'
@@ -277,17 +530,69 @@
 import SearchView from '@/components/view/SearchView'
 import ImportUnmanagedInstances from '@/views/tools/ImportUnmanagedInstance'
 import ResourceIcon from '@/components/view/ResourceIcon'
+import SelectVmwareVcenter from '@/views/tools/SelectVmwareVcenter'
+import TooltipLabel from '@/components/widgets/TooltipLabel.vue'
 
 export default {
   components: {
+    TooltipLabel,
     Breadcrumb,
     Status,
     SearchView,
     ImportUnmanagedInstances,
-    ResourceIcon
+    ResourceIcon,
+    SelectVmwareVcenter
   },
   name: 'ManageVms',
   data () {
+    const AllSourceActions = [
+      {
+        name: 'unmanaged',
+        label: 'Manage/Unmanage existing instances',
+        sourceDestHypervisors: {
+          vmware: 'vmware',
+          kvm: 'kvm'
+        },
+        wizardTitle: this.$t('label.desc.importexportinstancewizard'),
+        wizardDescription: this.$t('message.desc.importexportinstancewizard')
+      },
+      {
+        name: 'vmware',
+        label: 'Migrate existing instances to KVM',
+        sourceDestHypervisors: {
+          vmware: 'kvm'
+        },
+        wizardTitle: this.$t('label.desc.importmigratefromvmwarewizard'),
+        wizardDescription: this.$t('message.desc.importmigratefromvmwarewizard')
+      },
+      {
+        name: 'external',
+        label: 'Import Instance from remote KVM host',
+        sourceDestHypervisors: {
+          kvm: 'kvm'
+        },
+        wizardTitle: this.$t('label.desc.import.ext.kvm.wizard'),
+        wizardDescription: this.$t('message.desc.import.ext.kvm.wizard')
+      },
+      {
+        name: 'local',
+        label: 'Import QCOW2 image from Local Storage',
+        sourceDestHypervisors: {
+          kvm: 'kvm'
+        },
+        wizardTitle: this.$t('label.desc.import.local.kvm.wizard'),
+        wizardDescription: this.$t('message.desc.import.local.kvm.wizard')
+      },
+      {
+        name: 'shared',
+        label: 'Import QCOW2 image from Shared Storage',
+        sourceDestHypervisors: {
+          kvm: 'kvm'
+        },
+        wizardTitle: this.$t('label.desc.import.shared.kvm.wizard'),
+        wizardDescription: this.$t('message.desc.import.shared.kvm.wizard')
+      }
+    ]
     const unmanagedInstancesColumns = [
       {
         title: this.$t('label.name'),
@@ -295,34 +600,50 @@
         width: 100
       },
       {
+        key: 'state',
         title: this.$t('label.state'),
-        dataIndex: 'powerstate',
-        slots: { customRender: 'state' }
+        dataIndex: 'powerstate'
       },
       {
         title: this.$t('label.hostname'),
         dataIndex: 'hostname'
       },
       {
+        title: this.$t('label.clustername'),
+        dataIndex: 'clustername'
+      },
+      {
         title: this.$t('label.ostypename'),
         dataIndex: 'osdisplayname'
       }
     ]
-    const managedInstancesColumns = [
+    const externalInstancesColumns = [
       {
         title: this.$t('label.name'),
         dataIndex: 'name',
-        width: 100,
-        slots: { customRender: 'name' }
+        width: 200
+      },
+      {
+        key: 'state',
+        title: this.$t('label.state'),
+        dataIndex: 'powerstate'
+      }
+    ]
+    const managedInstancesColumns = [
+      {
+        key: 'name',
+        title: this.$t('label.name'),
+        dataIndex: 'name',
+        width: 100
       },
       {
         title: this.$t('label.instancename'),
         dataIndex: 'instancename'
       },
       {
+        key: 'state',
         title: this.$t('label.state'),
-        dataIndex: 'state',
-        slots: { customRender: 'state' }
+        dataIndex: 'state'
       },
       {
         title: this.$t('label.hostname'),
@@ -335,15 +656,22 @@
     ]
     return {
       options: {
+        hypervisors: [],
         zones: [],
         pods: [],
-        clusters: []
+        clusters: [],
+        hosts: [],
+        pools: []
       },
       rowCount: {},
       optionLoading: {
+        sourceaction: false,
+        hypervisors: false,
         zones: false,
         pods: false,
-        clusters: false
+        clusters: false,
+        hosts: false,
+        pools: false
       },
       page: {
         unmanaged: 1,
@@ -362,15 +690,37 @@
         managed: {}
       },
       itemCount: {},
+      hypervisors: [],
+      sourceHypervisor: 'vmware',
+      destinationHypervisor: 'vmware',
+      sourceActions: undefined,
+      selectedSourceAction: undefined,
+      wizardTitle: this.$t('label.desc.importexportinstancewizard'),
+      wizardDescription: this.$t('message.desc.importexportinstancewizard'),
       zone: {},
+      pod: {},
+      cluster: {},
+      values: undefined,
       zoneId: undefined,
       podId: undefined,
       clusterId: undefined,
+      hostname: undefined,
+      username: undefined,
+      password: undefined,
+      hostId: undefined,
+      poolId: undefined,
+      diskpath: undefined,
+      tmppath: undefined,
+      poolscope: 'cluster',
       listInstancesApi: {
         unmanaged: 'listUnmanagedInstances',
-        managed: 'listVirtualMachines'
+        managed: 'listVirtualMachines',
+        migratefromvmware: 'listVmwareDcVms',
+        external: 'listVmsForImport'
       },
       unmanagedInstancesColumns,
+      externalInstancesColumns,
+      AllSourceActions,
       unmanagedInstancesLoading: false,
       unmanagedInstances: [],
       unmanagedInstancesSelectedRowKeys: [],
@@ -381,7 +731,9 @@
       managedInstancesSelectedRowKeys: [],
       showUnmanageForm: false,
       selectedUnmanagedInstance: {},
-      query: {}
+      query: {},
+      vmwareVcenterType: undefined,
+      selectedVmwareVcenter: undefined
     }
   },
   created () {
@@ -401,11 +753,60 @@
       }
       return true
     },
+    isUnmanaged () {
+      return this.selectedSourceAction === 'unmanaged'
+    },
+    isExternal () {
+      return this.selectedSourceAction === 'external'
+    },
+    isMigrateFromVmware () {
+      return this.selectedSourceAction === 'vmware'
+    },
+    isDestinationKVM () {
+      return this.destinationHypervisor === 'kvm'
+    },
+    showPod () {
+      if (this.selectedSourceAction === 'shared') {
+        return this.poolscope !== 'zone'
+      }
+      return (this.selectedSourceAction !== 'external')
+    },
+    showCluster () {
+      if (this.selectedSourceAction === 'shared') {
+        return this.poolscope !== 'zone'
+      }
+      return (this.selectedSourceAction !== 'external')
+    },
+    showHost () {
+      return (this.selectedSourceAction === 'local')
+    },
+    showPool () {
+      return (this.selectedSourceAction === 'shared')
+    },
+    showExtHost () {
+      return (this.selectedSourceAction === 'external')
+    },
+    showDiskPath () {
+      return ((this.selectedSourceAction === 'local') || (this.selectedSourceAction === 'shared'))
+    },
+    showManagedInstances () {
+      return ((this.selectedSourceAction !== 'local') && (this.selectedSourceAction !== 'shared') && (this.selectedSourceAction !== 'external'))
+    },
+    isDiskImport () {
+      return ((this.selectedSourceAction === 'local') || (this.selectedSourceAction === 'shared'))
+    },
+    getPoolScope () {
+      if (this.selectedSourceAction === 'local') {
+        return 'host'
+      } else {
+        return this.poolscope
+      }
+    },
     params () {
       return {
         zones: {
           list: 'listZones',
-          isLoad: true,
+          isLoad: false,
           field: 'zoneid',
           options: {
             showicon: true
@@ -424,9 +825,32 @@
           isLoad: false,
           options: {
             zoneid: _.get(this.zone, 'id'),
-            podid: this.podId
+            podid: this.podId,
+            hypervisor: this.destinationHypervisor
           },
           field: 'clusterid'
+        },
+        hosts: {
+          list: 'listHosts',
+          isLoad: false,
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            podid: this.podId,
+            clusterid: this.clusterId
+          },
+          field: 'hostid'
+        },
+        pools: {
+          list: 'listStoragePools',
+          isLoad: false,
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            podid: this.podId,
+            clusterid: this.clusterId,
+            hostid: this.hostId,
+            scope: this.getPoolScope
+          },
+          field: 'poolid'
         }
       }
     },
@@ -465,6 +889,24 @@
       })
       return options
     },
+    hostSelectOptions () {
+      const options = this.options.hosts.map((host) => {
+        return {
+          label: host.name,
+          value: host.id
+        }
+      })
+      return options
+    },
+    poolSelectOptions () {
+      const options = this.options.pools.map((pool) => {
+        return {
+          label: pool.name,
+          value: pool.id
+        }
+      })
+      return options
+    },
     unmanagedInstanceSelection () {
       return {
         type: 'radio',
@@ -486,17 +928,40 @@
         return _.find(this.options.clusters, (option) => option.id === this.clusterId)
       }
       return {}
+    },
+    selectedHost () {
+      if (this.options.hosts &&
+          this.options.hosts.length > 0 &&
+          this.hostId) {
+        return _.find(this.options.hosts, (option) => option.id === this.hostId)
+      }
+      return {}
+    },
+    selectedPool () {
+      if (this.options.pools &&
+          this.options.pools.length > 0 &&
+          this.poolId) {
+        return _.find(this.options.pools, (option) => option.id === this.poolId)
+      }
+      return {}
     }
   },
   methods: {
     initForm () {
       this.formRef = ref()
-      this.form = reactive({})
-      this.rules = reactive({})
+      this.form = reactive({
+        sourceHypervisor: this.sourceHypervisor
+      })
+      this.rules = reactive({
+        hostname: [{ required: true, message: this.$t('message.error.input.value') }],
+        username: [{ required: true, message: this.$t('message.error.input.value') }],
+        password: [{ required: true, message: this.$t('message.error.input.value') }]
+      })
     },
     fetchData () {
       this.unmanagedInstances = []
       this.managedInstances = []
+      this.onSelectHypervisor(this.sourceHypervisor)
       _.each(this.params, (param, name) => {
         if (param.isLoad) {
           this.fetchOptions(param, name)
@@ -505,7 +970,7 @@
     },
     filterOption (input, option) {
       return (
-        option.children[0].children.toUpperCase().indexOf(input.toUpperCase()) >= 0
+        option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0
       )
     },
     fetchOptions (param, name, exclude) {
@@ -518,7 +983,7 @@
       param.loading = true
       param.opts = []
       const options = param.options || {}
-      if (!('listall' in options) && !['zones', 'pods', 'clusters'].includes(name)) {
+      if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'pools'].includes(name)) {
         options.listall = true
       }
       api(param.list, options).then((response) => {
@@ -556,13 +1021,13 @@
       return 'dark-row'
     },
     handleFetchOptionsSuccess (name, param) {
-      if (['zones', 'pods', 'clusters'].includes(name)) {
+      if (['zones', 'pods', 'clusters', 'hosts', 'pools'].includes(name)) {
         let paramid = ''
         const query = Object.assign({}, this.$route.query)
         if (query[param.field] && _.find(this.options[name], (option) => option.id === query[param.field])) {
           paramid = query[param.field]
         }
-        if (!paramid && this.options[name].length === 1) {
+        if (!paramid && this.options[name].length > 0) {
           paramid = (this.options[name])[0].id
         }
         if (paramid) {
@@ -575,6 +1040,12 @@
           } else if (name === 'clusters') {
             this.form.clusterid = paramid
             this.onSelectClusterId(paramid)
+          } else if (name === 'hosts') {
+            this.form.hostid = paramid
+            this.onSelectHostId(paramid)
+          } else if (name === 'pools') {
+            this.form.poolid = paramid
+            this.onSelectPoolId(paramid)
           }
         }
       }
@@ -599,37 +1070,88 @@
       this.managedInstances = []
       this.managedInstancesSelectedRowKeys = []
     },
+    onSelectHypervisor (value) {
+      this.sourceHypervisor = value
+      this.sourceActions = this.AllSourceActions.filter(x => x.sourceDestHypervisors[value])
+      this.form.sourceAction = this.sourceActions[0].name || ''
+      this.selectedVmwareVcenter = undefined
+      this.onSelectSourceAction(this.form.sourceAction)
+    },
+    onSelectSourceAction (value) {
+      this.selectedSourceAction = value
+      const selectedAction = _.find(this.AllSourceActions, (option) => option.name === value)
+      this.destinationHypervisor = selectedAction.sourceDestHypervisors[this.sourceHypervisor]
+      this.wizardTitle = selectedAction.wizardTitle
+      this.wizardDescription = selectedAction.wizardDescription
+      this.form.zoneid = undefined
+      this.form.podid = undefined
+      this.form.clusterid = undefined
+      this.fetchOptions(this.params.zones, 'zones')
+      this.resetLists()
+    },
     onSelectZoneId (value) {
       this.zoneId = value
       this.podId = null
       this.clusterId = null
+      this.hostId = null
+      this.poolId = null
       this.zone = _.find(this.options.zones, (option) => option.id === value)
       this.resetLists()
       this.form.clusterid = undefined
       this.form.podid = undefined
+      this.form.poolid = undefined
       this.updateQuery('zoneid', value)
       this.fetchOptions(this.params.pods, 'pods')
     },
     onSelectPodId (value) {
       this.podId = value
+      this.pod = _.find(this.options.pods, (option) => option.id === value)
       this.resetLists()
+      this.clusterId = null
       this.form.clusterid = undefined
       this.updateQuery('podid', value)
       this.fetchOptions(this.params.clusters, 'clusters', value)
     },
     onSelectClusterId (value) {
       this.clusterId = value
+      this.cluster = _.find(this.options.clusters, (option) => option.id === value)
       this.resetLists()
       this.updateQuery('clusterid', value)
-      this.fetchInstances()
+      if (this.isUnmanaged) {
+        this.fetchInstances()
+      } else if (this.showHost) {
+        this.fetchOptions(this.params.hosts, 'hosts', value)
+      } else if (this.showPool) {
+        this.fetchOptions(this.params.pools, 'pools', value)
+      }
+    },
+    onSelectHostId (value) {
+      this.hostId = value
+      this.updateQuery('scope', 'local')
+      this.fetchOptions(this.params.pools, 'pools', value)
+    },
+    onSelectPoolId (value) {
+      this.poolId = value
+    },
+    onSelectPoolScope (value) {
+      this.poolscope = value
+      this.poolId = null
+      this.updateQuery('scope', value)
+      this.fetchOptions(this.params.pools, 'pools', value)
     },
     fetchInstances () {
-      if (this.selectedCluster.hypervisortype === 'VMware') {
-        this.fetchUnmanagedInstances()
+      this.fetchUnmanagedInstances()
+      if (this.isUnmanaged) {
         this.fetchManagedInstances()
+      } else if (this.kvmOption === 'external') {
+        this.fetchExternalInstances()
       }
     },
     fetchUnmanagedInstances (page, pageSize) {
+      if (this.isExternal) {
+        this.fetchExtKVMInstances(page, pageSize)
+        return
+      }
       const params = {
         clusterid: this.clusterId
       }
@@ -649,12 +1171,72 @@
       }
       this.unmanagedInstancesLoading = true
       this.searchParams.unmanaged = params
-      api(this.listInstancesApi.unmanaged, params).then(json => {
-        const listUnmanagedInstances = json.listunmanagedinstancesresponse.unmanagedinstance
+
+      let apiName = this.listInstancesApi.unmanaged
+      if (this.isMigrateFromVmware && this.selectedVmwareVcenter) {
+        apiName = this.listInstancesApi.migratefromvmware
+        if (this.selectedVmwareVcenter.vcenter) {
+          params.datacentername = this.selectedVmwareVcenter.datacentername
+          params.vcenter = this.selectedVmwareVcenter.vcenter
+          params.username = this.selectedVmwareVcenter.username
+          params.password = this.selectedVmwareVcenter.password
+        } else {
+          params.existingvcenterid = this.selectedVmwareVcenter.existingvcenterid
+        }
+      }
+
+      api(apiName, params).then(json => {
+        const response = this.isMigrateFromVmware ? json.listvmwaredcvmsresponse : json.listunmanagedinstancesresponse
+        const listUnmanagedInstances = response.unmanagedinstance
         if (this.arrayHasItems(listUnmanagedInstances)) {
           this.unmanagedInstances = this.unmanagedInstances.concat(listUnmanagedInstances)
         }
-        this.itemCount.unmanaged = json.listunmanagedinstancesresponse.count
+        this.itemCount.unmanaged = response.count
+      }).finally(() => {
+        this.unmanagedInstancesLoading = false
+      })
+    },
+    fetchExtKVMInstances (page, pageSize) {
+      const params = {
+        zoneid: this.zoneid
+      }
+      const query = Object.assign({}, this.$route.query)
+      this.page.unmanaged = page || parseInt(query.unmanagedpage) || this.page.unmanaged
+      this.updateQuery('unmanagedpage', this.page.unmanaged)
+      params.page = this.page.unmanaged
+      this.pageSize.unmanaged = pageSize || this.pageSize.unmanaged
+      params.pagesize = this.pageSize.unmanaged
+      this.unmanagedInstances = []
+      this.unmanagedInstancesSelectedRowKeys = []
+      if (this.searchParams.unmanaged.keyword) {
+        params.keyword = this.searchParams.unmanaged.keyword
+      }
+      this.values = toRaw(this.form)
+      this.unmanagedInstancesLoading = true
+      params.zoneid = this.zoneId
+      params.host = this.values.hostname
+      params.username = this.values.username
+      params.password = this.values.password
+      params.hypervisor = this.destinationHypervisor
+      var details = ['host', 'username', 'password']
+      for (var detail of details) {
+        if (!params[detail]) {
+          this.$notification.error({
+            message: this.$t('message.request.failed'),
+            description: this.$t('message.please.enter.valid.value') + ': ' + this.$t('label.' + detail.toLowerCase())
+          })
+          return
+        }
+      }
+      this.searchParams.unmanaged = params
+      api(this.listInstancesApi.external, params).then(json => {
+        const listUnmanagedInstances = json.listvmsforimportresponse.unmanagedinstance
+        if (this.arrayHasItems(listUnmanagedInstances)) {
+          this.unmanagedInstances = this.unmanagedInstances.concat(listUnmanagedInstances)
+        }
+        this.itemCount.unmanaged = json.listvmsforimportresponse.count
+      }).catch(error => {
+        this.$notifyError(error)
       }).finally(() => {
         this.unmanagedInstancesLoading = false
       })
@@ -715,6 +1297,9 @@
     },
     updateManageInstanceActionLoading (value) {
       this.importUnmanagedInstanceLoading = value
+      if (!value) {
+        this.fetchInstances()
+      }
     },
     onManageInstanceAction () {
       this.selectedUnmanagedInstance = {}
@@ -724,6 +1309,36 @@
         this.selectedUnmanagedInstance.ostypename = this.selectedUnmanagedInstance.osdisplayname
         this.selectedUnmanagedInstance.state = this.selectedUnmanagedInstance.powerstate
       }
+      if (this.isMigrateFromVmware && this.selectedUnmanagedInstance.state === 'PowerOn' && this.selectedUnmanagedInstance.ostypename.toLowerCase().includes('windows')) {
+        message.error({
+          content: () => 'Cannot import Running Windows VMs, please gracefully shutdown the source VM before importing',
+          style: {
+            marginTop: '20vh',
+            color: 'red'
+          }
+        })
+        this.showUnmanageForm = false
+      } else {
+        this.showUnmanageForm = true
+      }
+    },
+    onImportInstanceAction () {
+      this.selectedUnmanagedInstance = {}
+      this.values = toRaw(this.form)
+      if (!this.values.diskpath) {
+        this.$notification.error({
+          message: this.$t('message.request.failed'),
+          description: this.$t('message.please.enter.valid.value') + ': ' + this.$t('label.disk.path')
+        })
+        return
+      }
+      if (this.showPool && !this.values.poolid) {
+        this.$notification.error({
+          message: this.$t('message.request.failed'),
+          description: this.$t('message.please.enter.valid.value') + ': ' + this.$t('label.storagepool')
+        })
+        return
+      }
       this.showUnmanageForm = true
     },
     closeImportUnmanagedInstanceForm () {
@@ -751,6 +1366,21 @@
         }
       })
     },
+    trackImportJobId (details) {
+      const jobId = details[0]
+      const name = details[1]
+      this.$pollJob({
+        jobId,
+        title: this.$t('label.import.instance'),
+        description: this.$t('label.import.instance'),
+        loadingMessage: `${this.$t('label.import.instance')} ${name} ${this.$t('label.in.progress')}`,
+        catchMessage: this.$t('error.fetching.async.job.result'),
+        successMessage: this.$t('message.success.import.instance') + ' ' + name,
+        successMethod: (result) => {
+          this.fetchInstances()
+        }
+      })
+    },
     unmanageInstances () {
       for (var index of this.managedInstancesSelectedRowKeys) {
         const vm = this.managedInstances[index]
@@ -776,47 +1406,65 @@
           this.loading = false
         })
       }
+    },
+    onListUnmanagedInstancesFromVmware (obj) {
+      this.selectedVmwareVcenter = obj.params
+      this.unmanagedInstances = obj.response.unmanagedinstance
+      this.itemCount.unmanaged = obj.response.count
+      this.unmanagedInstancesLoading = false
+    },
+    updateVmwareVcenterType (type) {
+      this.vmwareVcenterType = type
     }
   }
 }
 </script>
 
 <style scoped lang="less">
-  :deep(.ant-table-small) > .ant-table-content > .ant-table-body {
-    margin: 0;
-  }
+:deep(.ant-table-small) > .ant-table-content > .ant-table-body {
+  margin: 0;
+}
 
-  .importform {
-    width: 80vw;
-  }
-  .instances-card {
-    height: 100%;
-  }
-  .instances-card-table {
-    overflow-y: auto;
-    margin-bottom: 100px;
-  }
-  .instances-card-footer {
-    height: 100px;
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    margin-left: 10px;
-    right: 0;
-    margin-right: 10px;
-  }
-  .row-element {
-    margin-top: 10px;
-    margin-bottom: 10px;
-  }
-  .action-button-left {
-    text-align: left;
-  }
-  .action-button-right {
-    text-align: right;
-  }
+.importform {
+  width: 80vw;
+}
+.instances-card {
+  height: 100%;
+}
+.source-dest-card {
+  width: 50%;
+  height: 100%;
+}
+.instances-card-table {
+  overflow-y: auto;
+  margin-bottom: 100px;
+}
+.instances-card-footer {
+  height: 100px;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  margin-left: 10px;
+  right: 0;
+  margin-right: 10px;
+}
+.row-element {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.action-button-left {
+  text-align: left;
+}
+.action-button-right {
+  text-align: right;
+}
+.fetch-instances-column {
+  width: 50%;
+  margin-left: 50%;
+  padding-left: 24px;
+}
 
-  .breadcrumb-card {
+.breadcrumb-card {
   margin-left: -24px;
   margin-right: -24px;
   margin-top: -16px;
diff --git a/ui/src/views/tools/SelectVmwareVcenter.vue b/ui/src/views/tools/SelectVmwareVcenter.vue
new file mode 100644
index 0000000..78e38f1
--- /dev/null
+++ b/ui/src/views/tools/SelectVmwareVcenter.vue
@@ -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.
+
+<template>
+  <a-form
+        :ref="formRef"
+        :model="form"
+        :rules="rules"
+        @finish="handleSubmit"
+        layout="vertical">
+    <a-col :md="24" :lg="24">
+      <div>
+        <a-form-item :label="$t('label.select.source.vcenter.datacenter')" name="vmwareopt" ref="vmwareopt">
+          <a-radio-group
+            style="text-align: center; width: 100%"
+            v-model:value="vcenterSelectedOption"
+            buttonStyle="solid"
+            @change="onVcenterTypeChange">
+            <a-radio-button value="existing" style="width: 50%; text-align: center">
+              {{ $t('label.existing') }}
+            </a-radio-button>
+            <a-radio-button value="new" style="width: 50%; text-align: center">
+              {{ $t('label.external') }}
+            </a-radio-button>
+          </a-radio-group>
+        </a-form-item>
+      </div>
+      <div v-if="vcenterSelectedOption === 'existing'">
+        <a-form-item name="sourcezoneid" ref="sourcezoneid" :label="$t('label.zoneid')">
+          <a-select
+            v-model:value="form.sourcezoneid"
+            showSearch
+            optionFilterProp="label"
+            :filterOption="(input, option) => {
+              return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+            }"
+            @change="onSelectZoneId"
+            :loading="loading"
+          >
+            <a-select-option v-for="zoneitem in zones" :key="zoneitem.id" :label="zoneitem.name">
+              <span>
+                <resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
+                <global-outlined v-else style="margin-right: 5px" />
+                {{ zoneitem.name }}
+              </span>
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <div v-if="sourcezoneid">
+          <a-form-item :label="$t('label.vcenter')" name="vmwaredatacenter" ref="vmwaredatacenter" v-if="existingvcenter.length > 0">
+            <a-select
+              v-model:value="form.vmwaredatacenter"
+              :loading="loading"
+              optionFilterProp="label"
+              :filterOption="(input, option) => {
+                return  option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              }"
+              :placeholder="$t('label.vcenter.datacenter')"
+              @change="onSelectExistingVmwareDatacenter">
+              <a-select-option v-for="opt in existingvcenter" :key="opt.id">
+                  {{ 'VC: ' + opt.vcenter + ' - DC: ' + opt.name }}
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+          <div v-else>
+            {{ $t('message.list.zone.vmware.datacenter.empty') }}
+          </div>
+        </div>
+      </div>
+      <div v-else-if="vcenterSelectedOption === 'new'">
+        <a-form-item ref="vcenter" name="vcenter">
+          <template #label>
+            <tooltip-label :title="$t('label.vcenter')" :tooltip="apiParams.vcenter.description"/>
+          </template>
+          <a-input
+            v-model:value="vcenter"
+            :placeholder="apiParams.vcenter.description"
+          />
+        </a-form-item>
+        <a-form-item ref="datacenter" name="datacenter">
+          <template #label>
+            <tooltip-label :title="$t('label.vcenter.datacenter')" :tooltip="apiParams.datacentername.description"/>
+          </template>
+          <a-input
+            v-model:value="datacenter"
+            :placeholder="apiParams.datacentername.description"
+          />
+        </a-form-item>
+        <a-form-item ref="username" name="username">
+          <template #label>
+            <tooltip-label :title="$t('label.vcenter.username')" :tooltip="apiParams.username.description"/>
+          </template>
+          <a-input
+            v-model:value="username"
+            :placeholder="apiParams.username.description"
+          />
+        </a-form-item>
+        <a-form-item ref="password" name="password">
+          <template #label>
+            <tooltip-label :title="$t('label.vcenter.password')" :tooltip="apiParams.password.description"/>
+          </template>
+          <a-input-password
+            v-model:value="password"
+            :placeholder="apiParams.password.description"
+          />
+        </a-form-item>
+      </div>
+      <div class="card-footer">
+        <a-button
+          v-if="vcenterSelectedOption == 'existing' || vcenterSelectedOption == 'new'"
+          :disabled="(vcenterSelectedOption === 'new' && (vcenter === '' || datacentername === '' || username === '' || password === '')) ||
+            (vcenterSelectedOption === 'existing' && selectedExistingVcenterId === '')"
+          :loading="loading"
+          type="primary"
+          @click="listVmwareDatacenterVms">{{ $t('label.list.vmware.vcenter.vms') }}</a-button>
+      </div>
+    </a-col>
+  </a-form>
+</template>
+
+<script>
+import { api } from '@/api'
+import { ref, reactive } from 'vue'
+import TooltipLabel from '@/components/widgets/TooltipLabel'
+import Status from '@/components/widgets/Status'
+
+export default {
+  name: 'SelectVmwareVcenter',
+  components: {
+    TooltipLabel,
+    Status
+  },
+  data () {
+    return {
+      vcenter: '',
+      datacenter: '',
+      username: '',
+      password: '',
+      loading: false,
+      zones: {},
+      vcenterSelectedOption: '',
+      existingvcenter: [],
+      selectedExistingVcenterId: '',
+      selectedPoweredOnVm: false,
+      vmwareDcVms: [],
+      vmwareDcVmSelectedRows: [],
+      vmwareDcVmsColumns: [
+        {
+          title: this.$t('label.hostname'),
+          dataIndex: 'hostname'
+        },
+        {
+          title: this.$t('label.cluster'),
+          dataIndex: 'clustername'
+        },
+        {
+          title: this.$t('label.virtualmachinename'),
+          dataIndex: 'virtualmachinename'
+        },
+        {
+          title: this.$t('label.powerstate'),
+          key: 'powerstate',
+          dataIndex: 'powerstate'
+        }
+      ]
+    }
+  },
+  computed: {
+    vmwareDcVmsSelection () {
+      return {
+        type: 'radio',
+        selectedRowKeys: this.vmwareDcVmSelectedRows || [],
+        onChange: this.onVmwareDcVmSelectRow
+      }
+    }
+  },
+  beforeCreate () {
+    this.apiParams = this.$getApiParams('listVmwareDcVms')
+  },
+  created () {
+    this.initForm()
+    this.fetchZones()
+  },
+  methods: {
+    initForm () {
+      this.formRef = ref()
+      this.form = reactive({
+        vcenter: '',
+        username: '',
+        password: ''
+      })
+      this.rules = reactive({})
+    },
+    listVmwareDatacenterVms () {
+      this.loading = true
+      this.$emit('loadingVmwareUnmanagedInstances')
+      const params = {}
+      if (this.vcenterSelectedOption === 'new') {
+        params.datacentername = this.datacenter
+        params.vcenter = this.vcenter
+        params.username = this.username
+        params.password = this.password
+      } else {
+        params.existingvcenterid = this.selectedExistingVcenterId
+      }
+      api('listVmwareDcVms', params).then(json => {
+        const obj = {
+          params: params,
+          response: json.listvmwaredcvmsresponse
+        }
+        this.$emit('listedVmwareUnmanagedInstances', obj)
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    fetchZones () {
+      this.loading = true
+      api('listZones', { showicon: true }).then(response => {
+        this.zones = response.listzonesresponse.zone || []
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    onSelectZoneId (value) {
+      this.sourcezoneid = value
+      this.listZoneVmwareDcs()
+    },
+    listZoneVmwareDcs () {
+      this.loading = true
+      api('listVmwareDcs', { zoneid: this.sourcezoneid }).then(response => {
+        if (response.listvmwaredcsresponse.VMwareDC && response.listvmwaredcsresponse.VMwareDC.length > 0) {
+          this.existingvcenter = response.listvmwaredcsresponse.VMwareDC
+        }
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    onSelectExistingVmwareDatacenter (value) {
+      this.selectedExistingVcenterId = value
+    },
+    onVcenterTypeChange () {
+      this.$emit('onVcenterTypeChanged', this.vcenterSelectedOption)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.card-footer {
+  text-align: right;
+}
+
+.card-footer button {
+  width: 50%;
+  text-align: center;
+}
+</style>
diff --git a/ui/tests/mockData/ActionButton.mock.json b/ui/tests/mockData/ActionButton.mock.json
index 85ff8f1..7ec4deb 100644
--- a/ui/tests/mockData/ActionButton.mock.json
+++ b/ui/tests/mockData/ActionButton.mock.json
@@ -33,4 +33,4 @@
       }
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/ui/tests/mockData/AutogenView.mock.json b/ui/tests/mockData/AutogenView.mock.json
index 8d932e0..56807cc 100644
--- a/ui/tests/mockData/AutogenView.mock.json
+++ b/ui/tests/mockData/AutogenView.mock.json
@@ -458,7 +458,8 @@
       "meta": {
         "title": "label.title",
         "icon": "play-circle-outlined",
-        "permission": ["testApiNameCase1"]
+        "permission": ["testApiNameCase1"],
+        "columns": ["column1", "column2", "column3"]
       },
       "component": {},
       "children": [{
@@ -487,7 +488,8 @@
       "meta": {
         "title": "label.title",
         "icon": "play-circle-outlined",
-        "permission": ["testApiNameCase1"]
+        "permission": ["testApiNameCase1"],
+        "columns": ["column1"]
       },
       "component": {}
     },
diff --git a/ui/tests/mockData/MigrateWizard.mock.json b/ui/tests/mockData/MigrateWizard.mock.json
index 30f61ca..746492b 100644
--- a/ui/tests/mockData/MigrateWizard.mock.json
+++ b/ui/tests/mockData/MigrateWizard.mock.json
@@ -52,6 +52,7 @@
       "path": "/test-router-1",
       "meta": {
         "name": "vm",
+        "resourceType": "UserVm",
         "icon": "play-circle-outlined"
       },
       "component": {}
@@ -61,6 +62,7 @@
       "path": "/test-router-2",
       "meta": {
         "name": "vm",
+        "resourceType": "UserVm",
         "icon": "play-circle-outlined"
       },
       "component": {}
@@ -70,6 +72,7 @@
       "path": "/test-router-3",
       "meta": {
         "name": "test-router-3",
+        "resourceType": "SystemVm",
         "icon": "play-circle-outlined"
       },
       "component": {}
@@ -79,6 +82,7 @@
       "path": "/test-router-4",
       "meta": {
         "name": "vm",
+        "resourceType": "UserVm",
         "icon": "play-circle-outlined"
       },
       "component": {}
@@ -88,6 +92,7 @@
       "path": "/test-router-5",
       "meta": {
         "name": "vm",
+        "resourceType": "UserVm",
         "icon": "play-circle-outlined"
       },
       "component": {}
@@ -97,9 +102,10 @@
       "path": "/test-router-6",
       "meta": {
         "name": "vm",
+        "resourceType": "UserVm",
         "icon": "play-circle-outlined"
       },
       "component": {}
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/ui/tests/unit/components/view/ActionButton.spec.js b/ui/tests/unit/components/view/ActionButton.spec.js
index 29ade02..7e41f0b 100644
--- a/ui/tests/unit/components/view/ActionButton.spec.js
+++ b/ui/tests/unit/components/view/ActionButton.spec.js
@@ -51,6 +51,7 @@
 describe('Components > View > ActionButton.vue', () => {
   beforeEach(() => {
     jest.clearAllMocks()
+    jest.spyOn(console, 'warn').mockImplementation(() => {})
   })
 
   describe('Template', () => {
diff --git a/ui/tests/unit/views/AutogenView.spec.js b/ui/tests/unit/views/AutogenView.spec.js
index fe890ea..eb0352f 100644
--- a/ui/tests/unit/views/AutogenView.spec.js
+++ b/ui/tests/unit/views/AutogenView.spec.js
@@ -16,6 +16,7 @@
 // under the License.
 
 import { flushPromises } from '@vue/test-utils'
+import { ref } from 'vue'
 
 import mockAxios from '../../mock/mockAxios'
 import AutogenView from '@/views/AutogenView'
@@ -180,6 +181,7 @@
 describe('Views > AutogenView.vue', () => {
   beforeEach(async () => {
     jest.clearAllMocks()
+    jest.spyOn(console, 'warn').mockImplementation(() => {})
 
     delete window.ResizeObserver
     delete window.ls
@@ -253,6 +255,9 @@
           case 'RefValidateFields':
             wrapper.vm.formRef.value.validateFields = originalFunc[key]
             break
+          case 'formRef':
+            wrapper.vm.formRef = originalFunc[key]
+            break
           case 'switchProject':
             wrapper.vm.switchProject = originalFunc[key]
             break
@@ -549,10 +554,13 @@
         await flushPromises()
 
         expect(wrapper.vm.columns.length).toEqual(2)
+        expect(wrapper.vm.columns[0].key).toEqual('name')
         expect(wrapper.vm.columns[0].title).toEqual('name-en')
         expect(wrapper.vm.columns[0].dataIndex).toEqual('name')
-        expect(wrapper.vm.columns[0].slots).toEqual({ customRender: 'name' })
         expect(typeof wrapper.vm.columns[0].sorter).toBe('function')
+        expect(wrapper.vm.columns[1].key).toEqual('filtercolumn')
+        expect(wrapper.vm.columns[1].dataIndex).toEqual('filtercolumn')
+        expect(wrapper.vm.columns[1].customFilterDropdown).toBeTruthy()
         done(0)
       })
 
@@ -623,7 +631,7 @@
       //   })
       //   await router.push({ name: 'testRouter12', params: { id: 'test-id' } })
       //   await flushPromises()
-      //
+
       //   expect(mockAxios).toHaveBeenCalled()
       //   expect(mockAxios).toHaveBeenLastCalledWith({
       //     url: '/',
@@ -2287,12 +2295,21 @@
       it('formRef makes validation calls when handleSubmit() is called in list view', async (done) => {
         originalFunc.callGroupApi = wrapper.vm.callGroupApi
         originalFunc.fetchData = wrapper.vm.fetchData
+        originalFunc.formRef = wrapper.vm.formRef
         wrapper.vm.fetchData = jest.fn()
         wrapper.vm.callGroupApi = jest.fn((params, resourceName) => {
           return new Promise(resolve => {
             resolve()
           })
         })
+        if (!wrapper.vm.formRef) {
+          wrapper.vm.formRef = ref()
+        }
+        wrapper.vm.formRef.value.validate = jest.fn((params, resourceName) => {
+          return new Promise(resolve => {
+            resolve()
+          })
+        })
 
         const fetchData = jest.spyOn(wrapper.vm, 'fetchData')
         const callGroupApi = jest.spyOn(wrapper.vm, 'callGroupApi')
@@ -2311,6 +2328,11 @@
           items: [{
             id: 'test-id-value-1',
             name: 'test-name-value-1'
+          }],
+          columns: [{
+            key: 'column1',
+            dataIndex: 'column1',
+            title: 'column1'
           }]
         })
         await wrapper.vm.handleSubmit(event)
diff --git a/ui/tests/unit/views/compute/MigrateWizard.spec.js b/ui/tests/unit/views/compute/MigrateWizard.spec.js
index ab44b74..f352b2d 100644
--- a/ui/tests/unit/views/compute/MigrateWizard.spec.js
+++ b/ui/tests/unit/views/compute/MigrateWizard.spec.js
@@ -93,12 +93,12 @@
 describe('Views > compute > MigrateWizard.vue', () => {
   beforeEach(() => {
     jest.clearAllMocks()
+    jest.spyOn(console, 'warn').mockImplementation(() => {})
 
     if (!wrapper) {
       mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
       wrapper = factory({
-        props: { resource: {} },
-        data: { columns: [] }
+        props: { resource: {} }
       })
     }
 
@@ -107,7 +107,6 @@
 
   afterEach(() => {
     if (wrapper) {
-      wrapper.vm.columns = []
       wrapper.vm.searchQuery = ''
       wrapper.vm.page = 1
       wrapper.vm.pageSize = 10
@@ -132,7 +131,6 @@
       it('API should be called with resource is empty and searchQuery is empty', async (done) => {
         await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
         await wrapper.setProps({ resource: {} })
-        await wrapper.setData({ columns: [] })
         await wrapper.vm.fetchData()
         await flushPromises()
 
@@ -156,7 +154,6 @@
       it('API should be called with resource.id is empty and searchQuery is empty', async (done) => {
         await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
         await wrapper.setProps({ resource: { id: null } })
-        await wrapper.setData({ columns: [] })
         await wrapper.vm.fetchData()
         await flushPromises()
 
@@ -180,7 +177,6 @@
       it('API should be called with resource.id is not empty and searchQuery is empty', async (done) => {
         await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
         await wrapper.setProps({ resource: { id: 'test-id-value' } })
-        await wrapper.setData({ columns: [] })
         await wrapper.vm.fetchData()
         await flushPromises()
 
@@ -204,7 +200,7 @@
       it('API should be called with resource.id is not empty and searchQuery is not empty', async (done) => {
         await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
         await wrapper.setProps({ resource: { id: 'test-id-value' } })
-        await wrapper.setData({ searchQuery: 'test-query-value', columns: [] })
+        await wrapper.setData({ searchQuery: 'test-query-value' })
         await wrapper.vm.fetchData()
         await flushPromises()
 
@@ -231,8 +227,7 @@
         await wrapper.setData({
           searchQuery: 'test-query-value',
           page: 2,
-          pageSize: 20,
-          columns: []
+          pageSize: 20
         })
         await wrapper.vm.fetchData()
         await flushPromises()
diff --git a/ui/vue.config.js b/ui/vue.config.js
index 09f0423..c74218b 100644
--- a/ui/vue.config.js
+++ b/ui/vue.config.js
@@ -127,6 +127,7 @@
         modifyVars: {
           // https://ant.design/docs/spec/colors
           // https://vue.ant.design/docs/vue/customize-theme/
+          'root-entry-name': 'default'
         },
         javascriptEnabled: true
       }
diff --git a/usage/pom.xml b/usage/pom.xml
index defb7fa..044b353 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
@@ -59,8 +59,8 @@
             <artifactId>commons-daemon</artifactId>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java
index aa20f52..823d04c 100644
--- a/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java
+++ b/usage/src/main/java/com/cloud/usage/UsageAlertManagerImpl.java
@@ -83,7 +83,7 @@
         if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM)
                 && (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY)
                 && (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC)
-                && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE)) {
+                && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE)) {
             alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId);
         }
         if (alert == null) {
diff --git a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java
index dd838f2..a001ffe 100644
--- a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java
+++ b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java
@@ -16,6 +16,8 @@
 // under the License.
 package com.cloud.usage;
 
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
 import java.net.InetAddress;
 import java.sql.SQLException;
 import java.util.ArrayList;
@@ -34,16 +36,16 @@
 import javax.naming.ConfigurationException;
 import javax.persistence.EntityExistsException;
 
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.cloudstack.quota.QuotaAlertManager;
 import org.apache.cloudstack.quota.QuotaManager;
 import org.apache.cloudstack.quota.QuotaStatement;
+import org.apache.cloudstack.usage.UsageTypes;
 import org.apache.cloudstack.utils.usage.UsageUtils;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.managed.context.ManagedContextRunnable;
-import org.apache.cloudstack.usage.UsageTypes;
 
 import com.cloud.alert.AlertManager;
 import com.cloud.event.EventTypes;
@@ -51,6 +53,8 @@
 import com.cloud.event.UsageEventVO;
 import com.cloud.event.dao.UsageEventDao;
 import com.cloud.event.dao.UsageEventDetailsDao;
+import com.cloud.usage.dao.BucketStatisticsDao;
+import com.cloud.usage.dao.UsageBackupDao;
 import com.cloud.usage.dao.UsageDao;
 import com.cloud.usage.dao.UsageIPAddressDao;
 import com.cloud.usage.dao.UsageJobDao;
@@ -59,14 +63,15 @@
 import com.cloud.usage.dao.UsageNetworkOfferingDao;
 import com.cloud.usage.dao.UsagePortForwardingRuleDao;
 import com.cloud.usage.dao.UsageSecurityGroupDao;
-import com.cloud.usage.dao.UsageBackupDao;
-import com.cloud.usage.dao.UsageVMSnapshotOnPrimaryDao;
 import com.cloud.usage.dao.UsageStorageDao;
 import com.cloud.usage.dao.UsageVMInstanceDao;
 import com.cloud.usage.dao.UsageVMSnapshotDao;
+import com.cloud.usage.dao.UsageVMSnapshotOnPrimaryDao;
 import com.cloud.usage.dao.UsageVPNUserDao;
 import com.cloud.usage.dao.UsageVmDiskDao;
 import com.cloud.usage.dao.UsageVolumeDao;
+import com.cloud.usage.parser.BackupUsageParser;
+import com.cloud.usage.parser.BucketUsageParser;
 import com.cloud.usage.parser.IPAddressUsageParser;
 import com.cloud.usage.parser.LoadBalancerUsageParser;
 import com.cloud.usage.parser.NetworkOfferingUsageParser;
@@ -74,7 +79,6 @@
 import com.cloud.usage.parser.PortForwardingUsageParser;
 import com.cloud.usage.parser.SecurityGroupUsageParser;
 import com.cloud.usage.parser.StorageUsageParser;
-import com.cloud.usage.parser.BackupUsageParser;
 import com.cloud.usage.parser.VMInstanceUsageParser;
 import com.cloud.usage.parser.VMSnapshotOnPrimaryParser;
 import com.cloud.usage.parser.VMSnapshotUsageParser;
@@ -98,8 +102,6 @@
 import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.utils.exception.CloudRuntimeException;
 
-import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
-
 @Component
 public class UsageManagerImpl extends ManagerBase implements UsageManager, Runnable {
     public static final Logger s_logger = Logger.getLogger(UsageManagerImpl.class.getName());
@@ -165,6 +167,9 @@
     @Inject
     private QuotaStatement _quotaStatement;
 
+    @Inject
+    private BucketStatisticsDao _bucketStatisticsDao;
+
     private String _version = null;
     private final Calendar _jobExecTime = Calendar.getInstance();
     private int _aggregationDuration = 0;
@@ -497,6 +502,7 @@
             Map<String, UsageNetworkVO> networkStats = null;
             List<VmDiskStatisticsVO> vmDiskStats = null;
             Map<String, UsageVmDiskVO> vmDiskUsages = null;
+            List<BucketStatisticsVO> bucketStats = null;
             TransactionLegacy userTxn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
             try {
                 Long limit = Long.valueOf(500);
@@ -627,6 +633,46 @@
                     offset = new Long(offset.longValue() + limit.longValue());
                 } while ((vmDiskStats != null) && !vmDiskStats.isEmpty());
 
+                // reset offset
+                offset = Long.valueOf(0);
+
+                // get all the user stats to create usage records for the bucket usage
+                Long lastBucketStatsId = _usageDao.getLastBucketStatsId();
+                if (lastBucketStatsId == null) {
+                    lastBucketStatsId = Long.valueOf(0);
+                }
+
+                SearchCriteria<BucketStatisticsVO> sc5 = _bucketStatisticsDao.createSearchCriteria();
+                sc5.addAnd("id", SearchCriteria.Op.LTEQ, lastBucketStatsId);
+                do {
+                    Filter filter = new Filter(BucketStatisticsVO.class, "id", true, offset, limit);
+
+                    bucketStats = _bucketStatisticsDao.search(sc5, filter);
+
+                    if ((bucketStats != null) && !bucketStats.isEmpty()) {
+                        // now copy the accounts to cloud_usage db
+                        _usageDao.updateBucketStats(bucketStats);
+                    }
+                    offset = new Long(offset.longValue() + limit.longValue());
+                } while ((bucketStats != null) && !bucketStats.isEmpty());
+
+                // reset offset
+                offset = Long.valueOf(0);
+
+                sc5 = _bucketStatisticsDao.createSearchCriteria();
+                sc5.addAnd("id", SearchCriteria.Op.GT, lastBucketStatsId);
+                do {
+                    Filter filter = new Filter(BucketStatisticsVO.class, "id", true, offset, limit);
+
+                    bucketStats = _bucketStatisticsDao.search(sc5, filter);
+
+                    if ((bucketStats != null) && !bucketStats.isEmpty()) {
+                        // now copy the accounts to cloud_usage db
+                        _usageDao.saveBucketStats(bucketStats);
+                    }
+                    offset = new Long(offset.longValue() + limit.longValue());
+                } while ((bucketStats != null) && !bucketStats.isEmpty());
+
             } finally {
                 userTxn.close();
             }
@@ -979,6 +1025,12 @@
                 s_logger.debug("VM Backup usage successfully parsed? " + parsed + " (for account: " + account.getAccountName() + ", id: " + account.getId() + ")");
             }
         }
+        parsed = BucketUsageParser.parse(account, currentStartDate, currentEndDate);
+        if (s_logger.isDebugEnabled()) {
+            if (!parsed) {
+                s_logger.debug("Bucket usage successfully parsed? " + parsed + " (for account: " + account.getAccountName() + ", id: " + account.getId() + ")");
+            }
+        }
         return parsed;
     }
 
@@ -1854,7 +1906,7 @@
         if (usageVpnUsers.size() > 0) {
             s_logger.debug(String.format("We do not need to create the usage VPN user [%s] assigned to account [%s] because it already exists.", userId, accountId));
         } else {
-            s_logger.debug(String.format("Creating VPN user user [%s] assigned to account [%s] domain [%s], zone [%s], and created at [%s]", userId, accountId, domainId, zoneId,
+            s_logger.debug(String.format("Creating VPN user [%s] assigned to account [%s] domain [%s], zone [%s], and created at [%s]", userId, accountId, domainId, zoneId,
                     event.getCreateDate()));
             UsageVPNUserVO vpnUser = new UsageVPNUserVO(zoneId, accountId, domainId, userId, event.getResourceName(), event.getCreateDate(), null);
             _usageVPNUserDao.persist(vpnUser);
diff --git a/usage/src/main/java/com/cloud/usage/parser/BucketUsageParser.java b/usage/src/main/java/com/cloud/usage/parser/BucketUsageParser.java
new file mode 100644
index 0000000..1223c79
--- /dev/null
+++ b/usage/src/main/java/com/cloud/usage/parser/BucketUsageParser.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 com.cloud.usage.parser;
+
+import com.cloud.usage.BucketStatisticsVO;
+import com.cloud.usage.UsageVO;
+import com.cloud.usage.dao.BucketStatisticsDao;
+import com.cloud.usage.dao.UsageDao;
+import com.cloud.user.AccountVO;
+import org.apache.cloudstack.usage.UsageTypes;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public class BucketUsageParser {
+    public static final Logger s_logger = Logger.getLogger(BucketUsageParser.class.getName());
+
+    private static UsageDao s_usageDao;
+    private static BucketStatisticsDao s_bucketStatisticsDao;
+
+    @Inject
+    private UsageDao _usageDao;
+    @Inject
+    private BucketStatisticsDao _bucketStatisticsDao;
+
+    @PostConstruct
+    void init() {
+        s_usageDao = _usageDao;
+        s_bucketStatisticsDao = _bucketStatisticsDao;
+    }
+
+    public static boolean parse(AccountVO account, Date startDate, Date endDate) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Parsing all Bucket usage events for account: " + account.getId());
+        }
+
+        if ((endDate == null) || endDate.after(new Date())) {
+            endDate = new Date();
+        }
+
+        List<BucketStatisticsVO> BucketStatisticsVOs = s_bucketStatisticsDao.listBy(account.getId());
+
+        List<UsageVO> usageRecords = new ArrayList<>();
+        for (BucketStatisticsVO bucketStatistics : BucketStatisticsVOs) {
+            long bucketSize = bucketStatistics.getSize();
+            if(bucketSize > 0) {
+                UsageVO usageRecord =
+                        new UsageVO(1L, account.getId(), account.getDomainId(), "Bucket Size", bucketSize + " bytes",
+                                UsageTypes.BUCKET, new Double(bucketSize), bucketStatistics.getBucketId(), null, null, startDate, endDate);
+                usageRecords.add(usageRecord);
+            }
+        }
+
+        s_usageDao.saveUsageRecords(usageRecords);
+
+        return true;
+    }
+}
diff --git a/usage/src/test/java/com/cloud/usage/UsageSanityCheckerIT.java b/usage/src/test/java/com/cloud/usage/UsageSanityCheckerIT.java
index d8a6580..3e57c07 100644
--- a/usage/src/test/java/com/cloud/usage/UsageSanityCheckerIT.java
+++ b/usage/src/test/java/com/cloud/usage/UsageSanityCheckerIT.java
@@ -41,6 +41,7 @@
 import org.mockito.Mockito;
 
 import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.db.TransactionLegacy;
 
 @RunWith(Parameterized.class)
 public class UsageSanityCheckerIT{
@@ -106,7 +107,7 @@
     protected Connection createConnection(String dbSchema) throws SQLException {
         String cloudDbUrl = "jdbc:mysql://"+properties.getProperty("db."+dbSchema+".host") +
                 ":" + properties.getProperty("db."+dbSchema+".port") + "/" +
-                properties.getProperty("db."+dbSchema+".name");
+                properties.getProperty("db."+dbSchema+".name") + "?" + TransactionLegacy.CONNECTION_PARAMS;
         return DriverManager.getConnection(cloudDbUrl, properties.getProperty("db."+dbSchema+".username"),
                 properties.getProperty("db."+dbSchema+".password"));
     }
diff --git a/usage/src/test/resources/cloud1.xml b/usage/src/test/resources/cloud1.xml
index 773283a..cf79f95 100644
--- a/usage/src/test/resources/cloud1.xml
+++ b/usage/src/test/resources/cloud1.xml
@@ -11,7 +11,7 @@
 <dataset>
     <configuration name="usage.stats.job.aggregation.range" value="600" instance="test"/>
 
-    <vm_instance type="User" id="8" account_id="1" domain_id="1" name="test" instance_name="test" state="destoyed" guest_os_id="1" service_offering_id="1" data_center_id="1" vnc_password="xyz" vm_type="User" created="2019-01-01 00:00:01" removed="2018-01-01 00:00:01" />
+    <vm_instance type="User" id="8" account_id="1" domain_id="1" name="test" instance_name="test" state="destroyed" guest_os_id="1" service_offering_id="1" data_center_id="1" vnc_password="xyz" vm_type="User" created="2019-01-01 00:00:01" removed="2018-01-01 00:00:01" />
 
     <volumes id="16" account_id="1" domain_id="1" size="1" data_center_id="1" volume_type="root" disk_offering_id="1" removed="2018-01-01 00:00:01"/>
     <volumes id="17" account_id="1" domain_id="1" size="1" data_center_id="1" volume_type="root" disk_offering_id="1" removed="2019-01-01 00:00:01"/>
diff --git a/usage/src/test/resources/cloud2.xml b/usage/src/test/resources/cloud2.xml
index 099dde5..e8190b1 100644
--- a/usage/src/test/resources/cloud2.xml
+++ b/usage/src/test/resources/cloud2.xml
@@ -11,7 +11,7 @@
 <dataset>
 <configuration name="usage.stats.job.aggregation.range" value="600" instance="test" />
 
-    <vm_instance type="User" id="8" account_id="1" domain_id="1" name="test" instance_name="test" state="destoyed" guest_os_id="1" service_offering_id="1" data_center_id="1" vnc_password="xyz"  vm_type="User" created="2019-01-01 00:00:01" removed="2018-01-01 00:00:01" />
+    <vm_instance type="User" id="8" account_id="1" domain_id="1" name="test" instance_name="test" state="destroyed" guest_os_id="1" service_offering_id="1" data_center_id="1" vnc_password="xyz"  vm_type="User" created="2019-01-01 00:00:01" removed="2018-01-01 00:00:01" />
 
     <volumes id="16" account_id="1" domain_id="1" size="1" data_center_id="1" volume_type="root" disk_offering_id="1" removed="2018-01-01 00:00:01"/>
     <volumes id="17" account_id="1" domain_id="1" size="1" data_center_id="1" volume_type="root" disk_offering_id="1" removed="2018-01-01 00:00:01"/>
diff --git a/utils/pom.xml b/utils/pom.xml
index 193ec3e..d52903a 100755
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <dependencies>
@@ -118,10 +118,16 @@
             <artifactId>javax.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.cronutils</groupId>
+            <artifactId>cron-utils</artifactId>
+            <version>${cs.cron-utils.version}</version>
+        </dependency>
         <!-- Test dependency in mysql for db tests -->
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <version>${cs.mysql.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -256,6 +262,7 @@
                             <goal>shade</goal>
                         </goals>
                         <configuration>
+                            <finalName>${project.artifactId}-${project.version}-bundled</finalName>
                             <createDependencyReducedPom>false</createDependencyReducedPom>
                             <artifactSet>
                                 <includes>
diff --git a/utils/src/main/java/com/cloud/utils/DateUtil.java b/utils/src/main/java/com/cloud/utils/DateUtil.java
index 4d0157f..39ea60d 100644
--- a/utils/src/main/java/com/cloud/utils/DateUtil.java
+++ b/utils/src/main/java/com/cloud/utils/DateUtil.java
@@ -22,6 +22,10 @@
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.YearMonth;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.TimeZone;
@@ -31,6 +35,13 @@
 import java.time.OffsetDateTime;
 
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.cronutils.descriptor.CronDescriptor;
+import com.cronutils.model.CronType;
+import com.cronutils.model.definition.CronDefinition;
+import com.cronutils.model.definition.CronDefinitionBuilder;
+import com.cronutils.parser.CronParser;
+import org.springframework.scheduling.support.CronExpression;
+
 
 public class DateUtil {
     public static final int HOURS_IN_A_MONTH = 30 * 24;
@@ -52,7 +63,7 @@
     };
 
     public static Date currentGMTTime() {
-        // Date object always stores miliseconds offset based on GMT internally
+        // Date object always stores milliseconds offset based on GMT internally
         return new Date();
     }
 
@@ -295,4 +306,39 @@
         return (dateCalendar1.getTimeInMillis() - dateCalendar2.getTimeInMillis() )/1000;
 
     }
+
+    public static CronExpression parseSchedule(String schedule) {
+        if (schedule != null) {
+            // CronExpression's granularity is in seconds. Prepending "0 " to change the granularity to minutes.
+            return CronExpression.parse(String.format("0 %s", schedule));
+        } else {
+            return null;
+        }
+    }
+
+    public static String getHumanReadableSchedule(CronExpression schedule) {
+        CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING);
+        CronParser parser = new CronParser(cronDefinition);
+        CronDescriptor descriptor = CronDescriptor.instance();
+        return descriptor.describe(parser.parse(schedule.toString()));
+    }
+
+    public static ZonedDateTime getZoneDateTime(Date date, ZoneId tzId) {
+        if (date == null) {
+            return null;
+        }
+        ZonedDateTime zonedDate = ZonedDateTime.ofInstant(date.toInstant(), tzId);
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), TimeZone.getDefault().toZoneId());
+        zonedDate = zonedDate.withYear(localDateTime.getYear())
+                .withMonth(localDateTime.getMonthValue())
+                .withDayOfMonth(localDateTime.getDayOfMonth())
+                .withHour(localDateTime.getHour())
+                .withMinute(localDateTime.getMinute())
+                .withSecond(localDateTime.getSecond());
+        return zonedDate;
+    }
+
+    public static int getHoursInCurrentMonth(Date date) {
+        return YearMonth.of(date.getYear(), date.getMonth() + 1).lengthOfMonth() * 24;
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/FileUtil.java b/utils/src/main/java/com/cloud/utils/FileUtil.java
index d9bf081..3521453 100644
--- a/utils/src/main/java/com/cloud/utils/FileUtil.java
+++ b/utils/src/main/java/com/cloud/utils/FileUtil.java
@@ -21,9 +21,18 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.ssh.SshHelper;
@@ -57,4 +66,28 @@
         }
         throw new CloudRuntimeException(finalErrMsg);
     }
+
+    public static List<String> getFilesPathsUnderResourceDirectory(String resourceDirectory) {
+        s_logger.info(String.format("Searching for files under resource directory [%s].", resourceDirectory));
+
+        URL resourceDirectoryUrl = Thread.currentThread().getContextClassLoader().getResource(resourceDirectory);
+        if (resourceDirectoryUrl == null) {
+            throw new CloudRuntimeException(String.format("Resource directory [%s] does not exist or is empty.", resourceDirectory));
+        }
+
+        URI resourceDirectoryUri;
+        try {
+            resourceDirectoryUri = resourceDirectoryUrl.toURI();
+        } catch (URISyntaxException e) {
+            throw new CloudRuntimeException(String.format("Unable to get URI from URL [%s].", resourceDirectoryUrl), e);
+        }
+
+        try (FileSystem fs = FileSystems.newFileSystem(resourceDirectoryUri, Collections.emptyMap());
+                Stream<Path> paths = Files.walk(fs.getPath(resourceDirectory))) {
+            return paths.filter(Files::isRegularFile).map(Path::toString).collect(Collectors.toList());
+        } catch (IOException e) {
+            throw new CloudRuntimeException(String.format("Error while trying to list files under resource directory [%s].", resourceDirectoryUri), e);
+        }
+    }
+
 }
diff --git a/utils/src/main/java/com/cloud/utils/LogUtils.java b/utils/src/main/java/com/cloud/utils/LogUtils.java
index edfb53f..a458be7 100644
--- a/utils/src/main/java/com/cloud/utils/LogUtils.java
+++ b/utils/src/main/java/com/cloud/utils/LogUtils.java
@@ -28,6 +28,7 @@
 
 import org.apache.log4j.Appender;
 import org.apache.log4j.FileAppender;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.log4j.Logger;
 import org.apache.log4j.xml.DOMConfigurator;
 
@@ -78,21 +79,29 @@
         return fileNames;
     }
 
+    /**
+     * Tries to convert message parameters to JSON format and use them in the message.
+     * @param formatMessage message to format.
+     * @param objects objects to convert to JSON. A null object will be defaulted to the String "null";
+     * if it is not possible to convert an object to JSON, the object's 'toString' will be used instead.
+     * @return the formatted message.
+     */
     public static String logGsonWithoutException(String formatMessage, Object ... objects) {
         List<String> gsons = new ArrayList<>();
         for (Object object : objects) {
             try {
                 gsons.add(GSON.toJson(object));
             } catch (Exception e) {
-                LOGGER.debug(String.format("Failed to log object [%s] using GSON.", object != null ? object.getClass().getSimpleName() : "null"));
-                gsons.add("error to decode");
+                Object errObj = ObjectUtils.defaultIfNull(object, "null");
+                LOGGER.trace(String.format("Failed to log object [%s] using GSON.", errObj.getClass().getSimpleName()));
+                gsons.add("error decoding " + errObj);
             }
         }
         try {
             return String.format(formatMessage, gsons.toArray());
         } catch (Exception e) {
             String errorMsg = String.format("Failed to log objects using GSON due to: [%s].", e.getMessage());
-            LOGGER.error(errorMsg, e);
+            LOGGER.trace(errorMsg, e);
             return errorMsg;
         }
     }
diff --git a/utils/src/main/java/com/cloud/utils/NumbersUtil.java b/utils/src/main/java/com/cloud/utils/NumbersUtil.java
index 3d48344..f1a2e86 100644
--- a/utils/src/main/java/com/cloud/utils/NumbersUtil.java
+++ b/utils/src/main/java/com/cloud/utils/NumbersUtil.java
@@ -19,7 +19,9 @@
 
 package com.cloud.utils;
 
+import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.text.NumberFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -147,6 +149,26 @@
         }
     }
 
+    /**
+     * Formats a BigDecimal with the number formatter.
+     * @return
+     * <li>null if value is null;</li>
+     * <li>value.toString() if the numberFormat is null;</li>
+     * <li>the value formatted if both parameters are not null;</li>
+     */
+    public static String formatBigDecimalAccordingToNumberFormat(BigDecimal value, NumberFormat numberFormat) {
+        if (value == null) {
+            return null;
+        }
+
+        if (numberFormat == null) {
+            return value.toString();
+        }
+
+        return numberFormat.format(value);
+    }
+
+
     public static int hash(long value) {
         return (int)(value ^ (value >>> 32));
     }
diff --git a/utils/src/main/java/com/cloud/utils/SerialVersionUID.java b/utils/src/main/java/com/cloud/utils/SerialVersionUID.java
index 306a678..3084495 100644
--- a/utils/src/main/java/com/cloud/utils/SerialVersionUID.java
+++ b/utils/src/main/java/com/cloud/utils/SerialVersionUID.java
@@ -23,7 +23,7 @@
  * purposes.  This is purely on an honor system though.  You should always
  **/
 public interface SerialVersionUID {
-    public static final long Base = 0x564D4F70 << 32;  // 100 brownie points if you guess what this is and tell me.
+    public static final long Base = 0x564D4F70L << 32;  // 100 brownie points if you guess what this is and tell me.
 
     public static final long UUID = Base | 0x1;
     public static final long CloudRuntimeException = Base | 0x2;
diff --git a/utils/src/main/java/com/cloud/utils/StringUtils.java b/utils/src/main/java/com/cloud/utils/StringUtils.java
index 93e66ec..01b6f83 100644
--- a/utils/src/main/java/com/cloud/utils/StringUtils.java
+++ b/utils/src/main/java/com/cloud/utils/StringUtils.java
@@ -19,6 +19,10 @@
 
 package com.cloud.utils;
 
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -282,4 +286,22 @@
         final String value = keyValuePair.substring(index + 1);
         return new Pair<>(key.trim(), value.trim());
     }
+
+    public static Map<String, String> parseJsonToMap(String jsonString) {
+        ObjectMapper objectMapper = new ObjectMapper();
+        Map<String, String> mapResult = new HashMap<>();
+
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(jsonString)) {
+            try {
+                JsonNode jsonNode = objectMapper.readTree(jsonString);
+                jsonNode.fields().forEachRemaining(entry -> {
+                    mapResult.put(entry.getKey(), entry.getValue().asText());
+                });
+            } catch (Exception e) {
+                throw new CloudRuntimeException("Error while parsing json to convert it to map " + e.getMessage());
+            }
+        }
+
+        return mapResult;
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/concurrency/SynchronizationEvent.java b/utils/src/main/java/com/cloud/utils/concurrency/SynchronizationEvent.java
index 5207ad4..5871c0c 100644
--- a/utils/src/main/java/com/cloud/utils/concurrency/SynchronizationEvent.java
+++ b/utils/src/main/java/com/cloud/utils/concurrency/SynchronizationEvent.java
@@ -65,13 +65,13 @@
         }
     }
 
-    public boolean waitEvent(long timeOutMiliseconds) throws InterruptedException {
+    public boolean waitEvent(long timeOutMilliseconds) throws InterruptedException {
         synchronized (this) {
             if (signalled)
                 return true;
 
             try {
-                wait(timeOutMiliseconds);
+                wait(timeOutMilliseconds);
                 return signalled;
             } catch (InterruptedException e) {
                 // TODO, we don't honor time out semantics when the waiting thread is interrupted
diff --git a/utils/src/main/java/com/cloud/utils/events/SubscriptionMgr.java b/utils/src/main/java/com/cloud/utils/events/SubscriptionMgr.java
index 254c27d..ae129b2 100644
--- a/utils/src/main/java/com/cloud/utils/events/SubscriptionMgr.java
+++ b/utils/src/main/java/com/cloud/utils/events/SubscriptionMgr.java
@@ -25,6 +25,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.log4j.Logger;
 
@@ -160,5 +161,10 @@
             }
             return false;
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(this.clazz, this.subscriber, this.methodName, this.method);
+        }
     }
 }
diff --git a/utils/src/main/java/com/cloud/utils/net/NetUtils.java b/utils/src/main/java/com/cloud/utils/net/NetUtils.java
index 137cebb..018912a 100644
--- a/utils/src/main/java/com/cloud/utils/net/NetUtils.java
+++ b/utils/src/main/java/com/cloud/utils/net/NetUtils.java
@@ -80,6 +80,7 @@
     public static final String ICMP6_PROTO = "icmp6";
     public final static String ALL_PROTO = "all";
     public final static String HTTP_PROTO = "http";
+    public final static String HTTPS_PROTO = "https";
     public final static String SSL_PROTO = "ssl";
 
     public final static String ALL_IP4_CIDRS = "0.0.0.0/0";
diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java b/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java
index 47ac06af..52039a9 100644
--- a/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java
+++ b/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java
@@ -22,6 +22,7 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -47,7 +48,7 @@
     private String path;
     private Optional<String> jsonPayload = ABSENT;
     private final Map<String, String> parameters = new HashMap<String, String>();
-    private final Map<String, String> methodParameters = new HashMap<String, String>();
+    private final Map<String, String> methodParameters = new LinkedHashMap<String, String>();
 
     private HttpUriRequestBuilder() {
 
diff --git a/utils/src/main/java/com/cloud/utils/script/OutputInterpreter.java b/utils/src/main/java/com/cloud/utils/script/OutputInterpreter.java
index 654f87e..d54d411 100644
--- a/utils/src/main/java/com/cloud/utils/script/OutputInterpreter.java
+++ b/utils/src/main/java/com/cloud/utils/script/OutputInterpreter.java
@@ -22,6 +22,7 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
 /**
@@ -136,6 +137,38 @@
         public String getLines() {
             return allLines;
         }
+
+        @Override
+        public boolean drain() {
+            return true;
+        }
     }
 
+    public static class LineByLineOutputLogger extends OutputInterpreter {
+        private Logger logger;
+        private String logPrefix;
+
+        public LineByLineOutputLogger(Logger logger) {
+            this.logger = logger;
+        }
+
+        public LineByLineOutputLogger(Logger logger, String logPrefix) {
+            this.logger = logger;
+            this.logPrefix = logPrefix;
+        }
+
+        @Override
+        public boolean drain() {
+            return true;
+        }
+
+        @Override
+        public String interpret(BufferedReader reader) throws IOException {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                logger.info(StringUtils.isNotBlank(logPrefix) ? String.format("(%s) %s", logPrefix, line) : line);
+            }
+            return null;
+        }
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java b/utils/src/main/java/com/cloud/utils/script/Script.java
index fb57f25..cdab31f 100644
--- a/utils/src/main/java/com/cloud/utils/script/Script.java
+++ b/utils/src/main/java/com/cloud/utils/script/Script.java
@@ -56,6 +56,7 @@
     private volatile boolean _isTimeOut = false;
 
     private boolean _passwordCommand = false;
+    private boolean avoidLoggingCommand = false;
 
     private static final ScheduledExecutorService s_executors = Executors.newScheduledThreadPool(10, new NamedThreadFactory("Script"));
 
@@ -73,6 +74,10 @@
         return _process.exitValue();
     }
 
+    public void setAvoidLoggingCommand(boolean avoid) {
+        avoidLoggingCommand = avoid;
+    }
+
     public Script(String command, Duration timeout, Logger logger) {
         this(command, timeout.getMillis(), logger);
     }
@@ -204,9 +209,132 @@
 
     public String execute(OutputInterpreter interpreter) {
         String[] command = _command.toArray(new String[_command.size()]);
+        String commandLine = buildCommandLine(command);
+        if (_logger.isDebugEnabled() && !avoidLoggingCommand) {
+            _logger.debug(String.format("Executing command [%s].", commandLine.split(KeyStoreUtils.KS_FILENAME)[0]));
+        }
+
+        try {
+            _logger.trace(String.format("Creating process for command [%s].", commandLine));
+            ProcessBuilder pb = new ProcessBuilder(command);
+            pb.redirectErrorStream(true);
+            if (_workDir != null)
+                pb.directory(new File(_workDir));
+
+            _logger.trace(String.format("Starting process for command [%s].", commandLine));
+            _process = pb.start();
+            if (_process == null) {
+                _logger.warn(String.format("Unable to execute command [%s] because no process was created.", commandLine));
+                return "Unable to execute the command: " + command[0];
+            }
+
+            BufferedReader ir = new BufferedReader(new InputStreamReader(_process.getInputStream()));
+
+            _thread = Thread.currentThread();
+            ScheduledFuture<String> future = null;
+            if (_timeout > 0) {
+                _logger.trace(String.format("Scheduling the execution of command [%s] with a timeout of [%s] milliseconds.", commandLine, _timeout));
+                future = s_executors.schedule(this, _timeout, TimeUnit.MILLISECONDS);
+            }
+
+            long processPid = _process.pid();
+            Task task = null;
+            if (interpreter != null && interpreter.drain()) {
+                _logger.trace(String.format("Executing interpreting task of process [%s] for command [%s].", processPid, commandLine));
+                task = new Task(interpreter, ir);
+                s_executors.execute(task);
+            }
+
+            while (true) {
+                _logger.trace(String.format("Attempting process [%s] execution for command [%s] with timeout [%s].", processPid, commandLine, _timeout));
+                try {
+                    if (_process.waitFor(_timeout, TimeUnit.MILLISECONDS)) {
+                        _logger.trace(String.format("Process [%s] execution for command [%s] completed within timeout period [%s].", processPid, commandLine,
+                                _timeout));
+                        if (_process.exitValue() == 0) {
+                            _logger.debug(String.format("Successfully executed process [%s] for command [%s].", processPid, commandLine));
+                            if (interpreter != null) {
+                                if (interpreter.drain()) {
+                                    _logger.trace(String.format("Returning task result of process [%s] for command [%s].", processPid, commandLine));
+                                    return task.getResult();
+                                }
+                                _logger.trace(String.format("Returning interpretation of process [%s] for command [%s].", processPid, commandLine));
+                                return interpreter.interpret(ir);
+                            } else {
+                                // null return exitValue apparently
+                                _logger.trace(String.format("Process [%s] for command [%s] exited with value [%s].", processPid, commandLine,
+                                        _process.exitValue()));
+                                return String.valueOf(_process.exitValue());
+                            }
+                        } else {
+                            _logger.warn(String.format("Execution of process [%s] for command [%s] failed.", processPid, commandLine));
+                            break;
+                        }
+                    }
+                } catch (InterruptedException e) {
+                    if (!_isTimeOut) {
+                        _logger.debug(String.format("Exception [%s] occurred; however, it was not a timeout. Therefore, proceeding with the execution of process [%s] for command "
+                                + "[%s].", e.getMessage(), processPid, commandLine), e);
+                        continue;
+                    }
+                } finally {
+                    if (future != null) {
+                        future.cancel(false);
+                    }
+                    Thread.interrupted();
+                }
+
+                TimedOutLogger log = new TimedOutLogger(_process);
+                Task timedoutTask = new Task(log, ir);
+
+                _logger.trace(String.format("Running timed out task of process [%s] for command [%s].", processPid, commandLine));
+                timedoutTask.run();
+                if (!_passwordCommand) {
+                    _logger.warn(String.format("Process [%s] for command [%s] timed out. Output is [%s].", processPid, commandLine, timedoutTask.getResult()));
+                } else {
+                    _logger.warn(String.format("Process [%s] for command [%s] timed out.", processPid, commandLine));
+                }
+
+                return ERR_TIMEOUT;
+            }
+
+            _logger.debug(String.format("Exit value of process [%s] for command [%s] is [%s].", processPid, commandLine, _process.exitValue()));
+
+            BufferedReader reader = new BufferedReader(new InputStreamReader(_process.getInputStream()), 128);
+
+            String error;
+            if (interpreter != null) {
+                error = interpreter.processError(reader);
+            } else {
+                error = String.valueOf(_process.exitValue());
+            }
+
+            _logger.warn(String.format("Process [%s] for command [%s] encountered the error: [%s].", processPid, commandLine, error));
+
+            return error;
+        } catch (SecurityException ex) {
+            _logger.warn(String.format("Exception [%s] occurred. This may be due to an attempt of executing command [%s] as non root.", ex.getMessage(), commandLine),
+                    ex);
+            return stackTraceAsString(ex);
+        } catch (Exception ex) {
+            _logger.warn(String.format("Exception [%s] occurred when attempting to run command [%s].", ex.getMessage(), commandLine), ex);
+            return stackTraceAsString(ex);
+        } finally {
+            if (_process != null) {
+                _logger.trace(String.format("Destroying process [%s] for command [%s].", _process.pid(), commandLine));
+                IOUtils.closeQuietly(_process.getErrorStream());
+                IOUtils.closeQuietly(_process.getOutputStream());
+                IOUtils.closeQuietly(_process.getInputStream());
+                _process.destroyForcibly();
+            }
+        }
+    }
+
+    public String executeIgnoreExitValue(OutputInterpreter interpreter, int exitValue) {
+        String[] command = _command.toArray(new String[_command.size()]);
 
         if (_logger.isDebugEnabled()) {
-            _logger.debug("Executing: " + buildCommandLine(command).split(KeyStoreUtils.KS_FILENAME)[0]);
+            _logger.debug(String.format("Executing: %s", buildCommandLine(command).split(KeyStoreUtils.KS_FILENAME)[0]));
         }
 
         try {
@@ -217,8 +345,8 @@
 
             _process = pb.start();
             if (_process == null) {
-                _logger.warn("Unable to execute: " + buildCommandLine(command));
-                return "Unable to execute the command: " + command[0];
+                _logger.warn(String.format("Unable to execute: %s", buildCommandLine(command)));
+                return String.format("Unable to execute the command: %s", command[0]);
             }
 
             BufferedReader ir = new BufferedReader(new InputStreamReader(_process.getInputStream()));
@@ -236,12 +364,12 @@
             }
 
             while (true) {
-                _logger.debug("Executing while with timeout : " + _timeout);
+                _logger.debug(String.format("Executing while with timeout : %d", _timeout));
                 try {
                     //process execution completed within timeout period
                     if (_process.waitFor(_timeout, TimeUnit.MILLISECONDS)) {
                         //process completed successfully
-                        if (_process.exitValue() == 0) {
+                        if (_process.exitValue() == 0 || _process.exitValue() == exitValue) {
                             _logger.debug("Execution is successful.");
                             if (interpreter != null) {
                                 return interpreter.drain() ? task.getResult() : interpreter.interpret(ir);
@@ -275,15 +403,15 @@
 
                 timedoutTask.run();
                 if (!_passwordCommand) {
-                    _logger.warn("Timed out: " + buildCommandLine(command) + ".  Output is: " + timedoutTask.getResult());
+                    _logger.warn(String.format("Timed out: %s.  Output is: %s", buildCommandLine(command), timedoutTask.getResult()));
                 } else {
-                    _logger.warn("Timed out: " + buildCommandLine(command));
+                    _logger.warn(String.format("Timed out: %s", buildCommandLine(command)));
                 }
 
                 return ERR_TIMEOUT;
             }
 
-            _logger.debug("Exit value is " + _process.exitValue());
+            _logger.debug(String.format("Exit value is %d", _process.exitValue()));
 
             BufferedReader reader = new BufferedReader(new InputStreamReader(_process.getInputStream()), 128);
 
@@ -302,7 +430,7 @@
             _logger.warn("Security Exception....not running as root?", ex);
             return stackTraceAsString(ex);
         } catch (Exception ex) {
-            _logger.warn("Exception: " + buildCommandLine(command), ex);
+            _logger.warn(String.format("Exception: %s", buildCommandLine(command)), ex);
             return stackTraceAsString(ex);
         } finally {
             if (_process != null) {
@@ -508,14 +636,32 @@
     }
 
     public static int runSimpleBashScriptForExitValue(String command) {
-        return runSimpleBashScriptForExitValue(command, 0);
+        return runSimpleBashScriptForExitValue(command, 0, false);
     }
 
-    public static int runSimpleBashScriptForExitValue(String command, int timeout) {
+    public static int runSimpleBashScriptForExitValueAvoidLogging(String command) {
+        return runSimpleBashScriptForExitValue(command, 0, true);
+    }
+
+    /**
+     * Executes a bash script and returns the exit value of the script.
+     *
+     * @param command
+     *         The bash command to be executed.
+     * @param timeout
+     *         The maximum time (in milliseconds) that the script is allowed to run before it is forcibly terminated.
+     * @param avoidLogging
+     *         If set to true, some logging is avoided.
+     *
+     * @return The exit value of the script. Returns -1 if the result is null or empty, or if it cannot be parsed into
+     *         an integer which can happen in case of a timeout.
+     */
+    public static int runSimpleBashScriptForExitValue(String command, int timeout, boolean avoidLogging) {
 
         Script s = new Script("/bin/bash", timeout);
         s.add("-c");
         s.add(command);
+        s.setAvoidLoggingCommand(avoidLogging);
 
         String result = s.execute(null);
         if (result == null || result.trim().isEmpty())
@@ -529,4 +675,24 @@
         }
     }
 
+    public static String runBashScriptIgnoreExitValue(String command, int exitValue) {
+        return runBashScriptIgnoreExitValue(command, exitValue, 0);
+    }
+
+    public static String runBashScriptIgnoreExitValue(String command, int exitValue, int timeout) {
+
+        Script s = new Script("/bin/bash", timeout);
+        s.add("-c");
+        s.add(command);
+
+        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
+        s.executeIgnoreExitValue(parser, exitValue);
+
+        String result = parser.getLines();
+        if (result == null || result.trim().isEmpty()) {
+            return null;
+        } else {
+            return result.trim();
+        }
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java
index 7a73e86..6a2aa82 100644
--- a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java
+++ b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java
@@ -65,6 +65,10 @@
     }
 
     public static void scpFrom(String host, int port, String user, File permKeyFile, String localTargetDirectory, String remoteTargetFile) throws Exception {
+        scpFrom(host, port, user, permKeyFile, null,  localTargetDirectory, remoteTargetFile);
+    }
+
+    public static void scpFrom(String host, int port, String user, File permKeyFile, String password, String localTargetDirectory, String remoteTargetFile) throws Exception {
         com.trilead.ssh2.Connection conn = null;
         com.trilead.ssh2.SCPClient scpClient = null;
 
@@ -72,11 +76,20 @@
             conn = new com.trilead.ssh2.Connection(host, port);
             conn.connect(null, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT);
 
-            if (!conn.authenticateWithPublicKey(user, permKeyFile, null)) {
-                String msg = "Failed to authentication SSH user " + user + " on host " + host;
-                s_logger.error(msg);
-                throw new Exception(msg);
+            if (permKeyFile == null) {
+                if (!conn.authenticateWithPassword(user, password)) {
+                    String msg = "Failed to authentication SSH user " + user + " on host " + host;
+                    s_logger.error(msg);
+                    throw new Exception(msg);
+                }
+            } else {
+                if (!conn.authenticateWithPublicKey(user, permKeyFile, password)) {
+                    String msg = "Failed to authentication SSH user " + user + " on host " + host;
+                    s_logger.error(msg);
+                    throw new Exception(msg);
+                }
             }
+
             scpClient = conn.createSCPClient();
 
             scpClient.get(remoteTargetFile, localTargetDirectory);
diff --git a/utils/src/main/java/com/cloud/utils/storage/QCOW2Utils.java b/utils/src/main/java/com/cloud/utils/storage/QCOW2Utils.java
index 300fc4d..a36b011 100644
--- a/utils/src/main/java/com/cloud/utils/storage/QCOW2Utils.java
+++ b/utils/src/main/java/com/cloud/utils/storage/QCOW2Utils.java
@@ -33,6 +33,7 @@
 import org.apache.log4j.Logger;
 
 import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.UriUtils;
 
 public final class QCOW2Utils {
     public static final Logger LOGGER = Logger.getLogger(QCOW2Utils.class.getName());
@@ -118,7 +119,7 @@
             URI url = new URI(urlStr);
             httpConn = (HttpURLConnection)url.toURL().openConnection();
             httpConn.setInstanceFollowRedirects(followRedirects);
-            return getVirtualSizeFromInputStream(httpConn.getInputStream());
+            return getVirtualSize(httpConn.getInputStream(), UriUtils.isUrlForCompressedFile(urlStr));
         } catch (URISyntaxException e) {
             LOGGER.warn("Failed to validate for qcow2, malformed URL: " + urlStr + ", error: " + e.getMessage());
             throw new IllegalArgumentException("Invalid URL: " + urlStr);
diff --git a/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreter.java b/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreter.java
index 03ad3b4..b15bd31 100644
--- a/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreter.java
+++ b/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreter.java
@@ -78,7 +78,7 @@
     }
 
     /**
-     * Adds the parameters to a Map that will be converted to JS variables right before executing the scripts..
+     * Adds the parameters to a Map that will be converted to JS variables right before executing the script.
      * @param key The name of the variable.
      * @param value The value of the variable.
      */
@@ -88,6 +88,21 @@
     }
 
     /**
+     * Adds the parameter, surrounded by double quotes, to a Map that will be converted to a JS variable right before executing the script.
+     * @param key The name of the variable.
+     * @param value The value of the variable.
+     */
+    public void injectStringVariable(String key, String value) {
+        if (value == null) {
+            logger.trace(String.format("Not injecting [%s] because its value is null.", key));
+            return;
+        }
+        value = String.format("\"%s\"", value);
+        logger.trace(String.format(injectingLogMessage, key, value));
+        variables.put(key, value);
+    }
+
+    /**
      * Injects the variables to the script and execute it.
      * @param script Code to be executed.
      * @return The result of the executed script.
diff --git a/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/TagAsRuleHelper.java b/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/TagAsRuleHelper.java
new file mode 100644
index 0000000..114818a
--- /dev/null
+++ b/utils/src/main/java/org/apache/cloudstack/utils/jsinterpreter/TagAsRuleHelper.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.utils.jsinterpreter;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.log4j.Logger;
+
+import java.io.IOException;
+
+public class TagAsRuleHelper {
+
+    private static final Logger LOGGER = Logger.getLogger(TagAsRuleHelper.class);
+
+    private static final String PARSE_TAGS = "tags = tags ? tags.split(',') : [];";
+
+
+    public static boolean interpretTagAsRule(String rule, String tags, long timeout) {
+        String script = PARSE_TAGS + rule;
+        tags = String.format("'%s'", StringEscapeUtils.escapeEcmaScript(tags));
+        try (JsInterpreter jsInterpreter = new JsInterpreter(timeout)) {
+            jsInterpreter.injectVariable("tags", tags);
+            Object scriptReturn = jsInterpreter.executeScript(script);
+            if (scriptReturn instanceof Boolean) {
+                return (Boolean)scriptReturn;
+            }
+        } catch (IOException ex) {
+            String message = String.format("Error while executing script [%s].", script);
+            LOGGER.error(message, ex);
+            throw new CloudRuntimeException(message, ex);
+        }
+
+        LOGGER.debug(String.format("Result of tag rule [%s] was not a boolean, returning false.", script));
+        return false;
+    }
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/FileUtilTest.java b/utils/src/test/java/com/cloud/utils/FileUtilTest.java
index 70ad39c..87e1a18 100644
--- a/utils/src/test/java/com/cloud/utils/FileUtilTest.java
+++ b/utils/src/test/java/com/cloud/utils/FileUtilTest.java
@@ -20,49 +20,52 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.File;
 
 import static org.mockito.ArgumentMatchers.nullable;
 
-@PrepareForTest(value = {SshHelper.class})
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.*", "org.w3c.dom.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class FileUtilTest {
 
     @Test
     public void successfulScpTest() throws Exception {
-        PowerMockito.mockStatic(SshHelper.class);
+        MockedStatic<SshHelper> sshHelperMocked = Mockito.mockStatic(SshHelper.class);
         String basePath = "/tmp";
         String[] files = new String[] { "file1.txt" };
         int sshPort = 3922;
         String controlIp = "10.0.0.10";
         String destPath = "/home/cloud/";
         File pemFile = new File("/root/.ssh/id_rsa");
-        PowerMockito.doNothing().when(SshHelper.class, "scpTo", Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString());
+        sshHelperMocked.when(() ->
+                SshHelper.scpTo(
+                        Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString()
+                )).then(invocation -> null);
         FileUtil.scpPatchFiles(controlIp, destPath, sshPort, pemFile, files, basePath);
+        sshHelperMocked.close();
     }
 
     @Test
     public void FailingScpFilesTest() throws Exception {
-        PowerMockito.mockStatic(SshHelper.class);
         String basePath = "/tmp";
         String[] files = new String[] { "file1.txt" };
         int sshPort = 3922;
         String controlIp = "10.0.0.10";
         String destPath = "/home/cloud/";
         File pemFile = new File("/root/.ssh/id_rsa");
-        PowerMockito.doThrow(new Exception()).when(SshHelper.class, "scpTo", Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), Mockito.anyString(), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString());
+        MockedStatic<SshHelper> sshHelperMocked = Mockito.mockStatic(SshHelper.class);
+        sshHelperMocked.when(() ->
+                SshHelper.scpTo(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), Mockito.anyString(), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString()
+                )).thenThrow(new Exception());
         try {
             FileUtil.scpPatchFiles(controlIp, destPath, sshPort, pemFile, files, basePath);
         } catch (Exception e) {
             Assert.assertEquals("Failed to scp files to system VM", e.getMessage());
         }
+        sshHelperMocked.close();
 
     }
 
diff --git a/utils/src/test/java/com/cloud/utils/ScriptTest.java b/utils/src/test/java/com/cloud/utils/ScriptTest.java
index e6ed64a..e624ffc 100644
--- a/utils/src/test/java/com/cloud/utils/ScriptTest.java
+++ b/utils/src/test/java/com/cloud/utils/ScriptTest.java
@@ -112,6 +112,20 @@
     }
 
     @Test
+    public void executeWithOutputInterpreterAllLinesParserLargeOutput() {
+        Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
+        OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
+        Script script = new Script("seq");
+        script.add("-f");
+        script.add("my text to test cloudstack %g");
+        script.add("4096"); // AllLinesParser doesn't work with that amount of data
+        String value = script.execute(parser);
+        // it is a stack trace in this case as string
+        Assert.assertNull(value);
+        Assert.assertEquals(129965, parser.getLines().length());
+    }
+
+    @Test
     public void runSimpleBashScriptNotExisting() {
         Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
         String output = Script.runSimpleBashScript("/not/existing/scripts/"
@@ -130,6 +144,6 @@
     public void testFindScript() {
         Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
         String script = Script.findScript("/bin", "pwd");
-        Assert.assertNotNull("/bin/pwd shoud be there on linux", script);
+        Assert.assertNotNull("/bin/pwd should be there on linux", script);
     }
 }
diff --git a/utils/src/test/java/com/cloud/utils/SwiftUtilTest.java b/utils/src/test/java/com/cloud/utils/SwiftUtilTest.java
index 6dc2cc7..d517683 100644
--- a/utils/src/test/java/com/cloud/utils/SwiftUtilTest.java
+++ b/utils/src/test/java/com/cloud/utils/SwiftUtilTest.java
@@ -99,12 +99,7 @@
 
     @Test
     public void testGetSwiftCmd() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn(null);
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg(null);
 
         String cmd = SwiftUtil.getSwiftCmd(cfg, "swift", "stat");
 
@@ -114,12 +109,7 @@
 
     @Test
     public void testGetSwiftObjectCmd() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn(null);
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg(null);
 
         String objectCmd = SwiftUtil.getSwiftObjectCmd(cfg, "swift", "delete", "T-123", "template.vhd");
 
@@ -129,12 +119,7 @@
 
     @Test
     public void testGetSwiftContainerCmd() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn(null);
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg(null);
 
         String containerCmd = SwiftUtil.getSwiftContainerCmd(cfg, "swift", "list", "T-123");
 
@@ -144,27 +129,16 @@
 
     @Test
     public void testGetUploadCmd() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn(null);
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg(null);
 
         String uploadCmd = SwiftUtil.getUploadObjectCommand(cfg, "swift", "T-1", "template.vhd", 1024);
-
         String expected = "/usr/bin/python swift -A swift.endpoint -U cs:sec-storage -K mypassword upload T-1 template.vhd";
         assertThat(uploadCmd, is(equalTo(expected)));
     }
 
     @Test
     public void testGetUploadCmdWithSegmentsBecauseOfSize() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn(null);
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg(null);
 
         String uploadCmd = SwiftUtil.getUploadObjectCommand(cfg, "swift", "T-1", "template.vhd", 5368709121L);
 
@@ -174,12 +148,7 @@
 
     @Test
     public void testGetUploadCmdWithStoragePolicy() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn("policy1");
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg("policy1");
 
         String uploadCmd = SwiftUtil.getUploadObjectCommand(cfg, "swift", "T-1", "template.vhd", 1024L);
         String expected = "/usr/bin/python swift -A swift.endpoint -U cs:sec-storage -K mypassword upload T-1 template.vhd --storage-policy \"policy1\"";
@@ -188,12 +157,7 @@
 
     @Test
     public void testGetUploadCmdWithSegmentsAndStoragePolicy() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn("policy1");
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg("policy1");
         String uploadCmd = SwiftUtil.getUploadObjectCommand(cfg, "swift", "T-1", "template.vhd", 5368709121L);
         String expected = "/usr/bin/python swift -A swift.endpoint -U cs:sec-storage -K mypassword upload T-1 template.vhd --storage-policy \"policy1\" -S 5368709120";
         assertThat(uploadCmd, is(equalTo(expected)));
@@ -201,12 +165,7 @@
 
     @Test
     public void testListContainerCmdWithStoragePolicyButNotSupportedByOperation() {
-        SwiftClientCfg cfg = mock(SwiftClientCfg.class);
-        given(cfg.getEndPoint()).willReturn("swift.endpoint");
-        given(cfg.getAccount()).willReturn("cs");
-        given(cfg.getUserName()).willReturn("sec-storage");
-        given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn("policy1");
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg("policy1");
 
         String uploadCmd = SwiftUtil.getSwiftContainerCmd(cfg, "swift", "list", "T-1");
         String expected = "/usr/bin/python swift -A swift.endpoint -U cs:sec-storage -K mypassword list T-1";
@@ -215,15 +174,20 @@
 
     @Test
     public void testListContainerCmdWithoutStoragePolicy() {
+        SwiftClientCfg cfg = CreateMockSwiftClientCfg(null);
+
+        String uploadCmd = SwiftUtil.getSwiftContainerCmd(cfg, "swift", "list", "T-1");
+        String expected = "/usr/bin/python swift -A swift.endpoint -U cs:sec-storage -K mypassword list T-1";
+        assertThat(uploadCmd, is(equalTo(expected)));
+    }
+
+    private SwiftClientCfg CreateMockSwiftClientCfg(String policy){
         SwiftClientCfg cfg = mock(SwiftClientCfg.class);
         given(cfg.getEndPoint()).willReturn("swift.endpoint");
         given(cfg.getAccount()).willReturn("cs");
         given(cfg.getUserName()).willReturn("sec-storage");
         given(cfg.getKey()).willReturn("mypassword");
-        given(cfg.getStoragePolicy()).willReturn(null);
-
-        String uploadCmd = SwiftUtil.getSwiftContainerCmd(cfg, "swift", "list", "T-1");
-        String expected = "/usr/bin/python swift -A swift.endpoint -U cs:sec-storage -K mypassword list T-1";
-        assertThat(uploadCmd, is(equalTo(expected)));
+        given(cfg.getStoragePolicy()).willReturn(policy);
+        return cfg;
     }
 }
diff --git a/utils/src/test/java/com/cloud/utils/TestProfiler.java b/utils/src/test/java/com/cloud/utils/TestProfiler.java
index 1520963..1d2d5f7 100644
--- a/utils/src/test/java/com/cloud/utils/TestProfiler.java
+++ b/utils/src/test/java/com/cloud/utils/TestProfiler.java
@@ -23,16 +23,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.cloud.utils.testcase.Log4jEnabledTestCase;
+import org.mockito.junit.MockitoJUnitRunner;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({ "javax.management.*", "com.sun.org.apache.xerces.*", "javax.xml.*",
-        "org.xml.*", "org.w3c.dom.*", "com.sun.org.apache.xalan.*", "javax.activation.*" })
-@PrepareForTest(Profiler.class)
+@RunWith(MockitoJUnitRunner.class)
 public class TestProfiler extends Log4jEnabledTestCase {
 
     private static final long SLEEP_TIME_NANO = 1000000000L;
diff --git a/utils/src/test/java/com/cloud/utils/UriUtilsParametrizedTest.java b/utils/src/test/java/com/cloud/utils/UriUtilsParametrizedTest.java
index 68479f5..3f1e707 100644
--- a/utils/src/test/java/com/cloud/utils/UriUtilsParametrizedTest.java
+++ b/utils/src/test/java/com/cloud/utils/UriUtilsParametrizedTest.java
@@ -31,18 +31,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.modules.junit4.PowerMockRunnerDelegate;
 
 import com.google.common.collect.ImmutableSet;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockRunnerDelegate(Parameterized.class)
-@PowerMockIgnore({"javax.xml.*", "org.apache.xerces.*", "org.xml.*", "org.w3c.*"})
+@RunWith(Parameterized.class)
 public class UriUtilsParametrizedTest {
     @FunctionalInterface
     public interface ThrowingBlock<E extends Exception> {
@@ -149,14 +143,11 @@
     }
 
     @Test
-    @PrepareForTest({UriUtils.class})
-
     public void validateUrl() throws Exception {
 
+        MockedStatic<InetAddress> inetAddressMocked = Mockito.mockStatic(InetAddress.class);
         InetAddress inetAddressMock = Mockito.mock(InetAddress.class);
-
-        PowerMockito.mockStatic(InetAddress.class);
-        PowerMockito.when(InetAddress.getByName(Mockito.anyString())).thenReturn(inetAddressMock);
+        inetAddressMocked.when(() -> InetAddress.getByName(Mockito.anyString())).thenReturn(inetAddressMock);
 
         if (expectSuccess) {
             UriUtils.validateUrl(format, url);
@@ -164,14 +155,14 @@
             assertThrows(() -> UriUtils.validateUrl(format, url), IllegalArgumentException.class);
         }
 
-        PowerMockito.verifyStatic(InetAddress.class);
-        InetAddress.getByName(Mockito.anyString());
+        inetAddressMocked.verify(() -> InetAddress.getByName(Mockito.anyString()));
 
         Mockito.verify(inetAddressMock).isAnyLocalAddress();
         Mockito.verify(inetAddressMock).isLinkLocalAddress();
         Mockito.verify(inetAddressMock).isLoopbackAddress();
         Mockito.verify(inetAddressMock).isMulticastAddress();
 
+        inetAddressMocked.close();
     }
 
     @Test
diff --git a/utils/src/test/java/com/cloud/utils/net/Ip4AddressTest.java b/utils/src/test/java/com/cloud/utils/net/Ip4AddressTest.java
index 79ff238..63a5216 100644
--- a/utils/src/test/java/com/cloud/utils/net/Ip4AddressTest.java
+++ b/utils/src/test/java/com/cloud/utils/net/Ip4AddressTest.java
@@ -33,8 +33,8 @@
 
     @Test
     public void testIsSameAddressAs() {
-        Assert.assertTrue("1 and one should be considdered the same address", new Ip4Address(1L, 5L).isSameAddressAs("0.0.0.1"));
-        Assert.assertFalse("zero and 0L should be considdered the same address but a Long won't be accepted", new Ip4Address("0.0.0.0", "00:00:00:00:00:08").isSameAddressAs(0L));
+        Assert.assertTrue("1 and one should be considered the same address", new Ip4Address(1L, 5L).isSameAddressAs("0.0.0.1"));
+        Assert.assertFalse("zero and 0L should be considered the same address but a Long won't be accepted", new Ip4Address("0.0.0.0", "00:00:00:00:00:08").isSameAddressAs(0L));
     }
 
 }
diff --git a/utils/src/test/java/com/cloud/utils/net/IpTest.java b/utils/src/test/java/com/cloud/utils/net/IpTest.java
index 89608f1..3ff7293 100644
--- a/utils/src/test/java/com/cloud/utils/net/IpTest.java
+++ b/utils/src/test/java/com/cloud/utils/net/IpTest.java
@@ -56,8 +56,8 @@
 
     @Test
     public void testIsSameAddressAs() {
-        Assert.assertTrue("1 and one should be considdered the same address", new Ip(1L).isSameAddressAs("0.0.0.1"));
-        Assert.assertTrue("zero and 0L should be considdered the same address", new Ip("0.0.0.0").isSameAddressAs(0L));
+        Assert.assertTrue("1 and one should be considered the same address", new Ip(1L).isSameAddressAs("0.0.0.1"));
+        Assert.assertTrue("zero and 0L should be considered the same address", new Ip("0.0.0.0").isSameAddressAs(0L));
     }
 
 }
diff --git a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
index 53e2e3a..defb440 100644
--- a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
+++ b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java
@@ -52,15 +52,12 @@
 import com.googlecode.ipv6.IPv6Network;
 
 import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.runners.MockitoJUnitRunner;
 
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"jdk.xml.internal.*", "javax.xml.parsers.*", "org.xml.sax.*", "org.w3c.dom.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class NetUtilsTest {
     private static final String WIDE_SHARED_NET_CIDR_IP = "10.20.0.0";
     private static final List<String> WIDE_SHARED_NET_USED_IPS = List.of("10.20.0.22", "10.20.1.22", "10.20.2.22");
@@ -719,6 +716,7 @@
         NetUtils.isIpv4("2001:db8:300::/64");
     }
 
+    @Test
     public void testAllIpsOfDefaultNic() {
         final String defaultHostIp = NetUtils.getDefaultHostIp();
         if (defaultHostIp != null) {
@@ -815,34 +813,34 @@
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void getNetworkInterfaceTestReturnNullWhenGetByNameReturnsNull() throws SocketException {
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getByName(Mockito.anyString())).thenReturn(null);
+        MockedStatic<NetworkInterface> networkInterfaceMocked = Mockito.mockStatic(NetworkInterface.class);
+        Mockito.when(NetworkInterface.getByName(Mockito.anyString())).thenReturn(null);
         NetworkInterface result = NetUtils.getNetworkInterface("  test   ");
 
         Assert.assertNull(result);
+        networkInterfaceMocked.close();
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void getNetworkInterfaceTestReturnNullWhenGetByNameThrowsException() throws SocketException {
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getByName(Mockito.anyString())).thenThrow(SocketException.class);
+        MockedStatic<NetworkInterface> networkInterfaceMocked = Mockito.mockStatic(NetworkInterface.class);
+        Mockito.when(NetworkInterface.getByName(Mockito.anyString())).thenThrow(SocketException.class);
         NetworkInterface result = NetUtils.getNetworkInterface("  test   ");
 
         Assert.assertNull(result);
+        networkInterfaceMocked.close();
     }
 
     @Test
-    @PrepareForTest(NetUtils.class)
     public void getNetworkInterfaceTestReturnInterfaceReturnedByGetByName() throws SocketException {
-        NetworkInterface expected = PowerMockito.mock(NetworkInterface.class);
-        PowerMockito.mockStatic(NetworkInterface.class);
-        PowerMockito.when(NetworkInterface.getByName(Mockito.anyString())).thenReturn(expected);
+        MockedStatic<NetworkInterface> networkInterfaceMocked = Mockito.mockStatic(NetworkInterface.class);
+        NetworkInterface expected = Mockito.mock(NetworkInterface.class);
+        Mockito.when(NetworkInterface.getByName(Mockito.anyString())).thenReturn(expected);
 
         NetworkInterface result = NetUtils.getNetworkInterface("  test   ");
 
         Assert.assertEquals(expected, result);
+        networkInterfaceMocked.close();
     }
 }
diff --git a/utils/src/test/java/com/cloud/utils/ssh/SshHelperTest.java b/utils/src/test/java/com/cloud/utils/ssh/SshHelperTest.java
index 37bd089..04d8443 100644
--- a/utils/src/test/java/com/cloud/utils/ssh/SshHelperTest.java
+++ b/utils/src/test/java/com/cloud/utils/ssh/SshHelperTest.java
@@ -25,38 +25,31 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.runners.MockitoJUnitRunner;
 
 import com.trilead.ssh2.ChannelCondition;
 import com.trilead.ssh2.Connection;
 import com.trilead.ssh2.Session;
 
-@PrepareForTest({ Thread.class, SshHelper.class })
-@PowerMockIgnore({ "javax.management.*", "com.sun.org.apache.xerces.*", "javax.xml.*",
-        "org.xml.*", "org.w3c.dom.*", "com.sun.org.apache.xalan.*", "javax.activation.*" })
-@RunWith(PowerMockRunner.class)
+@RunWith(MockitoJUnitRunner.class)
 public class SshHelperTest {
 
     @Test
     public void canEndTheSshConnectionTest() throws Exception {
-        PowerMockito.spy(SshHelper.class);
+        MockedStatic<SshHelper> sshHelperMocked = Mockito.mockStatic(SshHelper.class, Mockito.CALLS_REAL_METHODS);
         Session mockedSession = Mockito.mock(Session.class);
 
-        PowerMockito.doReturn(true).when(SshHelper.class, "isChannelConditionEof", Mockito.anyInt());
-        Mockito.when(mockedSession.waitForCondition(ChannelCondition.EXIT_STATUS, 1l)).thenReturn(0);
-        PowerMockito.doNothing().when(SshHelper.class, "throwSshExceptionIfConditionsTimeout", Mockito.anyInt());
+        Mockito.when(SshHelper.isChannelConditionEof(Mockito.anyInt())).thenReturn(true);
+        Mockito.when(mockedSession.waitForCondition(ChannelCondition.EXIT_STATUS, 1L)).thenReturn(0);
 
         SshHelper.canEndTheSshConnection(1, mockedSession, 0);
-
-        PowerMockito.verifyStatic(SshHelper.class);
-        SshHelper.isChannelConditionEof(Mockito.anyInt());
-        SshHelper.throwSshExceptionIfConditionsTimeout(Mockito.anyInt());
+        sshHelperMocked.verify(() -> SshHelper.isChannelConditionEof(Mockito.anyInt()), Mockito.times(1));
+        sshHelperMocked.verify(() -> SshHelper.throwSshExceptionIfConditionsTimeout(Mockito.anyInt()));
 
         Mockito.verify(mockedSession).waitForCondition(ChannelCondition.EXIT_STATUS, 1l);
+        sshHelperMocked.close();
     }
 
     @Test(expected = SshException.class)
@@ -143,7 +136,6 @@
     @Test
     public void openConnectionSessionTest() throws IOException, InterruptedException {
         Connection conn = Mockito.mock(Connection.class);
-        PowerMockito.mockStatic(Thread.class);
         SshHelper.openConnectionSession(conn);
 
         Mockito.verify(conn).openSession();
diff --git a/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java b/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java
index 08cc389..2b99275 100644
--- a/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java
+++ b/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java
@@ -20,37 +20,35 @@
 import org.apache.cloudstack.utils.security.DigestHelper;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
+
 
 import java.io.File;
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-@PrepareForTest(value = {Script.class, DigestHelper.class})
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.*", "org.w3c.dom.*", "org.xml.*"})
+@RunWith(MockitoJUnitRunner.class)
 public class ChecksumUtilTest {
 
     @Test
     public void invalidFileForCheckSumValidationTest() {
-        PowerMockito.mockStatic(Script.class);
+        MockedStatic<Script> scriptMocked = Mockito.mockStatic(Script.class);
         Mockito.when(Script.findScript(Mockito.anyString(), Mockito.anyString())).thenReturn(null);
         try {
             ChecksumUtil.calculateCurrentChecksum(Mockito.anyString(), Mockito.anyString());
         } catch (Exception e) {
             assertTrue(e.getMessage().contains("Unable to find cloudScripts path, cannot update SystemVM"));
         }
+        scriptMocked.close();
     }
 
     @Test
     public void generateChecksumTest() {
-        PowerMockito.mockStatic(Script.class);
-        PowerMockito.mockStatic(DigestHelper.class);
+        MockedStatic<Script> scriptMocked = Mockito.mockStatic(Script.class);
+        MockedStatic<DigestHelper> digestHelperMocked = Mockito.mockStatic(DigestHelper.class);
         Mockito.when(Script.findScript(Mockito.anyString(), Mockito.anyString())).thenReturn("/dummyPath");
         Mockito.when(DigestHelper.calculateChecksum(Mockito.any(File.class))).thenReturn("dummy-checksum");
         try {
@@ -58,5 +56,7 @@
         } catch (Exception e) {
             fail("Failed to generate checksum");
         }
+        scriptMocked.close();
+        digestHelperMocked.close();
     }
 }
diff --git a/utils/src/test/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreterTest.java b/utils/src/test/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreterTest.java
index 1567d89..a8c2e6e 100644
--- a/utils/src/test/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreterTest.java
+++ b/utils/src/test/java/org/apache/cloudstack/utils/jsinterpreter/JsInterpreterTest.java
@@ -35,16 +35,12 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
 
 import javax.script.ScriptEngine;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"javax.xml.*", "org.apache.xerces.*", "org.xml.*", "org.w3c.*"})
-@PrepareForTest(JsInterpreter.class)
+@RunWith(MockitoJUnitRunner.class)
 public class JsInterpreterTest {
     private long timeout = 2000;
 
@@ -95,7 +91,6 @@
     public void executeScriptTestReturnResultOfScriptExecution() {
         String script = "5";
         Object expected = new Object();
-        Mockito.doReturn(script).when(jsInterpreterSpy).addVariablesToScript(Mockito.anyString());
         Mockito.doReturn(expected).when(jsInterpreterSpy).executeScript(Mockito.anyString());
 
         Object result = jsInterpreterSpy.executeScript(script);
@@ -173,9 +168,8 @@
     }
 
     @Test
-    @PrepareForTest(NashornScriptEngineFactory.class)
     public void setScriptEngineDisablingJavaLanguageTest() {
-        NashornScriptEngineFactory nashornScriptEngineFactoryMock = Mockito.mock(NashornScriptEngineFactory.class);
+        NashornScriptEngineFactory nashornScriptEngineFactoryMock = Mockito.spy(NashornScriptEngineFactory.class);
         ScriptEngine scriptEngineMock = Mockito.mock(ScriptEngine.class);
 
         Mockito.doReturn(scriptEngineMock).when(nashornScriptEngineFactoryMock).getScriptEngine(Mockito.anyString());
@@ -185,4 +179,22 @@
         Assert.assertEquals(scriptEngineMock, jsInterpreterSpy.interpreter);
         Mockito.verify(nashornScriptEngineFactoryMock).getScriptEngine("--no-java");
     }
+
+    @Test
+    public void injectStringVariableTestNullValueDoNothing() {
+        jsInterpreterSpy.variables = new LinkedHashMap<>();
+
+        jsInterpreterSpy.injectStringVariable("a", null);
+
+        Assert.assertTrue(jsInterpreterSpy.variables.isEmpty());
+    }
+
+    @Test
+    public void injectStringVariableTestNotNullValueSurroundWithDoubleQuotes() {
+        jsInterpreterSpy.variables = new LinkedHashMap<>();
+
+        jsInterpreterSpy.injectStringVariable("a", "b");
+
+        Assert.assertEquals(jsInterpreterSpy.variables.get("a"), "\"b\"");
+    }
 }
diff --git a/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java b/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java
index 674700b..58dfbce 100644
--- a/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java
+++ b/utils/src/test/java/org/apache/cloudstack/utils/redfish/RedfishClientTest.java
@@ -87,7 +87,7 @@
     public void buildRequestUrlTestHttpsGetSystemId() {
         RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, true, false, REDFISHT_REQUEST_RETRIES);
         String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetSystemId, systemId);
-        String expected = String.format("https://%s/redfish/v1/Systems/", oobAddress, systemId);
+        String expected = String.format("https://%s/redfish/v1/Systems/", oobAddress);
         Assert.assertEquals(expected, result);
     }
 
@@ -95,7 +95,7 @@
     public void buildRequestUrlTestGetSystemId() {
         RedfishClient redfishclient = new RedfishClient(USERNAME, PASSWORD, false, false, REDFISHT_REQUEST_RETRIES);
         String result = redfishclient.buildRequestUrl(oobAddress, RedfishClient.RedfishCmdType.GetSystemId, systemId);
-        String expected = String.format("http://%s/redfish/v1/Systems/", oobAddress, systemId);
+        String expected = String.format("http://%s/redfish/v1/Systems/", oobAddress);
         Assert.assertEquals(expected, result);
     }
 
diff --git a/utils/src/test/java/org/apache/cloudstack/utils/reflectiontostringbuilderutils/ReflectionToStringBuilderUtilsTest.java b/utils/src/test/java/org/apache/cloudstack/utils/reflectiontostringbuilderutils/ReflectionToStringBuilderUtilsTest.java
index 4a2a2a6..48bb972 100644
--- a/utils/src/test/java/org/apache/cloudstack/utils/reflectiontostringbuilderutils/ReflectionToStringBuilderUtilsTest.java
+++ b/utils/src/test/java/org/apache/cloudstack/utils/reflectiontostringbuilderutils/ReflectionToStringBuilderUtilsTest.java
@@ -33,16 +33,12 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.reflections.ReflectionUtils;
 
-@RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"org.w3c.*", "javax.xml.*", "org.xml.*"})
-@PrepareForTest(ReflectionToStringBuilderUtils.class)
+@RunWith(MockitoJUnitRunner.class)
 public class ReflectionToStringBuilderUtilsTest extends TestCase {
 
     private static final Set<ToStringStyle> TO_STRING_STYLES = new HashSet<>(Arrays.asList(ToStringStyle.DEFAULT_STYLE, ToStringStyle.JSON_STYLE, ToStringStyle.MULTI_LINE_STYLE,
@@ -95,60 +91,63 @@
 
     @Test
     public void validateGetObjectClassInvalidObjectMustReturnNull(){
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(false);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(false);
+            Class<?> result = ReflectionToStringBuilderUtils.getObjectClass("test");
 
-        Class<?> result = ReflectionToStringBuilderUtils.getObjectClass("test");
-
-        Assert.assertNull(result);
+            Assert.assertNull(result);
+        }
     }
 
     @Test
     public void validateGetObjectClassObjectIsNotACollectionMustReturnObjectClass(){
         Class<?> expectedResult = classToReflect;
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(false);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(false);
 
-        Class<?> result = ReflectionToStringBuilderUtils.getObjectClass("test");
+            Class<?> result = ReflectionToStringBuilderUtils.getObjectClass("test");
 
-        Assert.assertEquals(expectedResult, result);
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
     @Test
     public void validateGetObjectClassObjectIsAnEmptyCollectionMustReturnNull(){
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
 
-        Class<?> result = ReflectionToStringBuilderUtils.getObjectClass(new ArrayList<String>());
+            Class<?> result = ReflectionToStringBuilderUtils.getObjectClass(new ArrayList<String>());
 
-        Assert.assertNull(result);
+            Assert.assertNull(result);
+        }
     }
 
     @Test
     public void validateGetObjectClassObjectIsACollectionWithOnlyNullValuesMustReturnNull(){
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
 
-        Class<?> result = ReflectionToStringBuilderUtils.getObjectClass(new ArrayList<String>(Arrays.asList(null, null)));
+            Class<?> result = ReflectionToStringBuilderUtils.getObjectClass(new ArrayList<String>(Arrays.asList(null, null)));
 
-        Assert.assertNull(result);
+            Assert.assertNull(result);
+        }
     }
 
     @Test
     public void validateGetObjectClassObjectIsACollectionWithAtLeastOneObjectsMustReturnObjectClass(){
         Class<?> expectedResult = classToReflect;
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isValidObject(Mockito.any())).thenReturn(true);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
+            Class<?> result = ReflectionToStringBuilderUtils.getObjectClass(new ArrayList<>(Arrays.asList(null, "test1")));
 
-        Class<?> result = ReflectionToStringBuilderUtils.getObjectClass(new ArrayList<>(Arrays.asList(null, "test1")));
-
-        Assert.assertEquals(expectedResult, result);
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
     @Test
@@ -163,12 +162,13 @@
 
     @Test
     public void validateGetNonSelectedFieldsNullObjectClassMustReturnNull(){
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.getObjectClass(Mockito.any())).thenReturn(null);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.getObjectClass(Mockito.any())).thenReturn(null);
 
-        String[] result = ReflectionToStringBuilderUtils.getNonSelectedFields(null, "test1", "test2");
+            String[] result = ReflectionToStringBuilderUtils.getNonSelectedFields(null, "test1", "test2");
 
-        Assert.assertNull(result);
+            Assert.assertNull(result);
+        }
     }
 
     @Test
@@ -250,26 +250,28 @@
 
     @Test
     public void validateReflectOnlySelectedFieldsNullNonSelectedFieldsMustReturnNull() throws Exception{
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(null);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(null);
 
-        TO_STRING_STYLES.forEach(style -> {
-            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(null, style, "-");
-            Assert.assertNull(result);
-        });
+            TO_STRING_STYLES.forEach(style -> {
+                String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(null, style, "-");
+                Assert.assertNull(result);
+            });
+        }
     }
 
     @Test
     public void validateReflectOnlySelectedFieldsEmptyNonSelectedFieldsMustReturnEmptyString() throws Exception{
         String expectedResult = "";
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(new String[0]);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(new String[0]);
 
-        TO_STRING_STYLES.forEach(style -> {
-            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(null, style, "-");
-            Assert.assertEquals(expectedResult, result);
-        });
+            TO_STRING_STYLES.forEach(style -> {
+                String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(null, style, "-");
+                Assert.assertEquals(expectedResult, result);
+            });
+        }
     }
 
     @Test
@@ -277,28 +279,30 @@
         String fieldToRemove = classToReflectRemovedField;
         String expectedResult = "test";
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(classToReflectFieldsNamesArray);
-        PowerMockito.when(ReflectionToStringBuilderUtils.reflectCollection(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(expectedResult);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(classToReflectFieldsNamesArray);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.reflectCollection(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(expectedResult);
 
-        TO_STRING_STYLES.forEach(style -> {
-            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object(), style, "-", fieldToRemove);
-            Assert.assertEquals(expectedResult, result);
-        });
+            TO_STRING_STYLES.forEach(style -> {
+                String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object(), style, "-", fieldToRemove);
+                Assert.assertEquals(expectedResult, result);
+            });
+        }
     }
 
     @Test
     public void validateReflectOnlySelectedFieldsObjectIsNotACollectionMustReflectObject() throws Exception{
         String expectedResult = "test";
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(classToReflectFieldsNamesArray);
-        PowerMockito.when(ReflectionToStringBuilderUtils.reflectCollection(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(null);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.getNonSelectedFields(Mockito.any(), Mockito.any())).thenReturn(classToReflectFieldsNamesArray);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.reflectCollection(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(null);
 
-        for (ToStringStyle style : TO_STRING_STYLES){
-            PowerMockito.doReturn(expectedResult).when(ReflectionToStringBuilderUtils.class, "getReflectedObject", Mockito.any(), Mockito.any(), Mockito.any());
-            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(expectedResult, style, "-", classToReflectFieldsNamesArray);
-            Assert.assertEquals(expectedResult, result);
+            for (ToStringStyle style : TO_STRING_STYLES) {
+                reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.getReflectedObject(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(expectedResult);
+                String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(expectedResult, style, "-", classToReflectFieldsNamesArray);
+                Assert.assertEquals(expectedResult, result);
+            }
         }
     }
 
@@ -306,51 +310,54 @@
     public void validateReflectOnlySelectedFieldsDefaultStyleReflectionNullMustReturnNull(){
         String expectedResult = null;
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.reflectOnlySelectedFields(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(null);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.reflectOnlySelectedFields(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(null);
 
-        String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object(), (String[]) null);
-        Assert.assertEquals(expectedResult, result);
+            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object(), (String[]) null);
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
     @Test
     public void validateReflectOnlySelectedFieldsDefaultStyleReflectCollectionMustReturnValue(){
         String expectedResult = "[test]";
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.reflectOnlySelectedFields(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn("test");
-        PowerMockito.when(ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.reflectOnlySelectedFields(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn("test");
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(true);
 
-        String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object());
-        Assert.assertEquals(expectedResult, result);
+            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object());
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
     @Test
     public void validateReflectOnlySelectedFieldsDefaultStyleReflectMustReturnValue(){
         String expectedResult = "test";
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.reflectOnlySelectedFields(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(expectedResult);
-        PowerMockito.when(ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(false);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.reflectOnlySelectedFields(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(expectedResult);
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.isCollection(Mockito.any())).thenReturn(false);
 
-        String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object());
-        Assert.assertEquals(expectedResult, result);
+            String result = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(new Object());
+            Assert.assertEquals(expectedResult, result);
+        }
     }
 
     @Test
     public void reflectCollectionTestCallBaseReflectCollectionMethodWithDefaultParameters() {
         String expected = "test";
 
-        PowerMockito.spy(ReflectionToStringBuilderUtils.class);
-        PowerMockito.when(ReflectionToStringBuilderUtils.reflectCollection(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(expected);
+        try (MockedStatic<ReflectionToStringBuilderUtils> reflectionToStringBuilderUtilsMocked = Mockito.mockStatic(ReflectionToStringBuilderUtils.class, Mockito.CALLS_REAL_METHODS)) {
+            reflectionToStringBuilderUtilsMocked.when(() -> ReflectionToStringBuilderUtils.reflectCollection(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any())).thenReturn(expected);
 
-        Object object = Mockito.mock(Object.class);
-        String result = ReflectionToStringBuilderUtils.reflectCollection(object);
+            Object object = Mockito.mock(Object.class);
+            String result = ReflectionToStringBuilderUtils.reflectCollection(object);
 
-        Assert.assertEquals(expected, result);
+            Assert.assertEquals(expected, result);
 
-        PowerMockito.verifyStatic(ReflectionToStringBuilderUtils.class);
-        String[] excludeFields = null;
-        ReflectionToStringBuilderUtils.reflectCollection(object, DEFAULT_STYLE, DEFAULT_MULTIPLE_VALUES_SEPARATOR, excludeFields);
+            String[] excludeFields = null;
+            reflectionToStringBuilderUtilsMocked.verify(() -> ReflectionToStringBuilderUtils.reflectCollection(object, DEFAULT_STYLE, DEFAULT_MULTIPLE_VALUES_SEPARATOR, excludeFields));
+        }
     }
 }
diff --git a/utils/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/utils/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/utils/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/vmware-base/pom.xml b/vmware-base/pom.xml
index 527231f..24d2c6f 100644
--- a/vmware-base/pom.xml
+++ b/vmware-base/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache.cloudstack</groupId>
         <artifactId>cloudstack</artifactId>
-        <version>4.18.3.0-SNAPSHOT</version>
+        <version>4.19.1.0-SNAPSHOT</version>
     </parent>
     <dependencies>
         <dependency>
@@ -78,5 +78,11 @@
             <artifactId>maven-artifact</artifactId>
             <version>3.6.3</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cloudstack</groupId>
+            <artifactId>cloud-core</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java
index 430eb6d..153efe2 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java
@@ -114,7 +114,7 @@
                 key = field.getKey();
             } catch (Exception e) {
                 // assuming the exception is caused by concurrent operation from other places
-                // so we retieve the key again
+                // so we retrieve the key again
                 key = getCustomFieldKey(fieldName);
             }
         }
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
index 095b20c..e78c843 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java
@@ -69,7 +69,7 @@
 //
 public class ClusterMO extends BaseMO implements VmwareHypervisorHost {
     private static final Logger s_logger = Logger.getLogger(ClusterMO.class);
-    private ManagedObjectReference _environmentBrowser = null;
+    protected ManagedObjectReference _environmentBrowser = null;
 
     public ClusterMO(VmwareContext context, ManagedObjectReference morCluster) {
         super(context, morCluster);
@@ -740,4 +740,22 @@
             throw new CloudRuntimeException(msg);
         }
     }
+
+    @Override
+    public GuestOsDescriptor getGuestOsDescriptor(String guestOsId) throws Exception {
+        VirtualMachineConfigOption vmConfigOption = _context.getService().queryConfigOption(getEnvironmentBrowser(), null, null);
+        List<GuestOsDescriptor> guestDescriptors = vmConfigOption.getGuestOSDescriptor();
+        for (GuestOsDescriptor descriptor : guestDescriptors) {
+            if (guestOsId != null && guestOsId.equalsIgnoreCase(descriptor.getId())) {
+                return descriptor;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<GuestOsDescriptor> getGuestOsDescriptors() throws Exception {
+        VirtualMachineConfigOption vmConfigOption = _context.getService().queryConfigOption(getEnvironmentBrowser(), null, null);
+        return vmConfigOption.getGuestOSDescriptor();
+    }
 }
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java
index ade8227..d8c7e8a 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java
@@ -21,6 +21,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import com.cloud.hypervisor.vmware.util.VmwareHelper;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
@@ -159,14 +161,24 @@
         return null;
     }
 
-    public List<Pair<ManagedObjectReference, String>> getAllVmsOnDatacenter() throws Exception {
-        List<Pair<ManagedObjectReference, String>> vms = new ArrayList<Pair<ManagedObjectReference, String>>();
-
+    public List<UnmanagedInstanceTO> getAllVmsOnDatacenter() throws Exception {
+        List<UnmanagedInstanceTO> vms = new ArrayList<>();
         List<ObjectContent> ocs = getVmPropertiesOnDatacenterVmFolder(new String[] {"name"});
         if (ocs != null) {
             for (ObjectContent oc : ocs) {
-                String vmName = oc.getPropSet().get(0).getVal().toString();
-                vms.add(new Pair<ManagedObjectReference, String>(oc.getObj(), vmName));
+                ManagedObjectReference vmMor = oc.getObj();
+                if (vmMor != null) {
+                    VirtualMachineMO vmMo = new VirtualMachineMO(_context, vmMor);
+                    try {
+                        if (!vmMo.isTemplate()) {
+                            HostMO hostMO = vmMo.getRunningHost();
+                            UnmanagedInstanceTO unmanagedInstance = VmwareHelper.getUnmanagedInstance(hostMO, vmMo);
+                            vms.add(unmanagedInstance);
+                        }
+                    } catch (Exception e) {
+                        s_logger.debug(String.format("Unexpected error checking unmanaged instance %s, excluding it: %s", vmMo.getVmName(), e.getMessage()), e);
+                    }
+                }
             }
         }
 
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java
index a76e7d9..7e9021a 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatastoreMO.java
@@ -21,6 +21,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import com.vmware.vim25.FolderFileInfo;
 import org.apache.log4j.Logger;
 
 import com.cloud.exception.CloudException;
@@ -281,7 +282,7 @@
                 return false;
             }
         } catch (Exception e) {
-            s_logger.error(String.format("Cannot move file to destination datastore due to file %s due to exeception %s", srcFullPath, e.getMessage()));
+            s_logger.error(String.format("Cannot move file to destination datastore due to file %s due to exception %s", srcFullPath, e.getMessage()));
             return false;
         }
 
@@ -307,7 +308,7 @@
         String url = getContext().composeDatastoreBrowseUrl(dcPair.second(), fullPath);
 
         // TODO, VMware currently does not have a formal API to list Datastore directory content,
-        // folloing hacking may have performance hit if datastore has a large number of files
+        // following hacking may have performance hit if datastore has a large number of files
         return _context.listDatastoreDirContent(url);
     }
 
@@ -321,7 +322,7 @@
         HostDatastoreBrowserSearchResults results = browserMo.searchDatastore(dirFile.getPath(), file.getFileName(), true);
         if (results != null) {
             List<FileInfo> info = results.getFile();
-            if (info != null && info.size() > 0) {
+            if (info != null && info.size() == 1 && !(info.get(0) instanceof FolderFileInfo)) {
                 s_logger.info("File " + fileFullPath + " exists on datastore");
                 return true;
             }
@@ -368,7 +369,7 @@
         HostDatastoreBrowserSearchResults results = browserMo.searchDatastore(folderParentDatastorePath, folderName, true);
         if (results != null) {
             List<FileInfo> info = results.getFile();
-            if (info != null && info.size() > 0) {
+            if (info != null && info.size() == 1 && info.get(0) instanceof FolderFileInfo) {
                 s_logger.info("Folder " + folderName + " exists on datastore");
                 return true;
             }
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
index 15d80fc..3b96e7e 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java
@@ -36,6 +36,7 @@
 import com.vmware.vim25.CustomFieldStringValue;
 import com.vmware.vim25.DatastoreSummary;
 import com.vmware.vim25.DynamicProperty;
+import com.vmware.vim25.GuestOsDescriptor;
 import com.vmware.vim25.HostConfigManager;
 import com.vmware.vim25.HostConnectInfo;
 import com.vmware.vim25.HostFirewallInfo;
@@ -1088,7 +1089,7 @@
 
         HostListSummaryQuickStats stats = getHostQuickStats();
         if (stats.getOverallCpuUsage() == null || stats.getOverallMemoryUsage() == null)
-            throw new Exception("Unable to get valid overal CPU/Memory usage data, host may be disconnected");
+            throw new Exception("Unable to get valid overall CPU/Memory usage data, host may be disconnected");
 
         resourceSummary.setEffectiveCpu(totalCpu - stats.getOverallCpuUsage());
 
@@ -1165,6 +1166,26 @@
         return null;
     }
 
+    @Override
+    public GuestOsDescriptor getGuestOsDescriptor(String guestOsId) throws Exception {
+        ManagedObjectReference morParent = getParentMor();
+        if (morParent.getType().equals("ClusterComputeResource")) {
+            ClusterMO clusterMo = new ClusterMO(_context, morParent);
+            return clusterMo.getGuestOsDescriptor(guestOsId);
+        }
+        return null;
+    }
+
+    @Override
+    public List<GuestOsDescriptor> getGuestOsDescriptors() throws Exception {
+        ManagedObjectReference morParent = getParentMor();
+        if (morParent.getType().equals("ClusterComputeResource")) {
+            ClusterMO clusterMo = new ClusterMO(_context, morParent);
+            return clusterMo.getGuestOsDescriptors();
+        }
+        return null;
+    }
+
     public String getHostManagementIp(String managementPortGroup) throws Exception {
         HostNetworkInfo netInfo = getHostNetworkInfo();
 
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HttpNfcLeaseMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HttpNfcLeaseMO.java
index 6e4980c..367a21b 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HttpNfcLeaseMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HttpNfcLeaseMO.java
@@ -159,11 +159,11 @@
         public void run() {
             while (!_done) {
                 try {
-                    Thread.sleep(1000);            // update progess every 1 second
+                    Thread.sleep(1000);            // update progress every 1 second
                     updateLeaseProgress(_percent);
                 } catch (InterruptedException e) {
                     if (s_logger.isInfoEnabled())
-                        s_logger.info("ProgressReporter is interrupted, quiting");
+                        s_logger.info("ProgressReporter is interrupted, quitting");
                     break;
                 } catch (Exception e) {
                     s_logger.warn("Unexpected exception ", e);
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
index 5cc8899..12ef462 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java
@@ -1866,7 +1866,7 @@
 
     /**
      * deploys a new VM from a ovf spec. It ignores network, defaults locale to 'US'
-     * @throws Exception shoud be a VmwareResourceException
+     * @throws Exception should be a VmwareResourceException
      */
     public static void importVmFromOVF(VmwareHypervisorHost host, String ovfFilePath, String vmName, DatastoreMO dsMo, String diskOption, ManagedObjectReference morRp,
                                        ManagedObjectReference morHost, String configurationId) throws CloudRuntimeException, IOException {
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
index f2e247c..9b520ce 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java
@@ -1881,7 +1881,7 @@
 
                         // tar files into OVA
                         if (packToOva) {
-                            // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature)
+                            // Important! we need to sync file system before we can safely use tar to work around a linux kernel bug(or feature)
                             s_logger.info("Sync file system before we package OVA...");
 
                             Script commandSync = new Script(true, "sync", 0, s_logger);
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
index ce2f178..2177b06 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java
@@ -20,6 +20,7 @@
 
 import com.vmware.vim25.ClusterDasConfigInfo;
 import com.vmware.vim25.ComputeResourceSummary;
+import com.vmware.vim25.GuestOsDescriptor;
 import com.vmware.vim25.ManagedObjectReference;
 import com.vmware.vim25.ObjectContent;
 import com.vmware.vim25.VirtualMachineConfigSpec;
@@ -91,5 +92,10 @@
     ComputeResourceSummary getHyperHostHardwareSummary() throws Exception;
 
     LicenseAssignmentManagerMO getLicenseAssignmentManager() throws Exception;
+
     String getRecommendedDiskController(String guestOsId) throws Exception;
+
+    GuestOsDescriptor getGuestOsDescriptor(String guestOsId) throws Exception;
+
+    List<GuestOsDescriptor> getGuestOsDescriptors() throws Exception;
 }
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java
index 1599408..9da2ee3 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java
@@ -550,7 +550,7 @@
      * Url for the query
      *     https://vsphere-1.lab.vmops.com/folder/Fedora-clone-test?dcPath=cupertino&dsName=NFS+datastore
      *
-     * Returned conent from vSphere
+     * Returned content from vSphere
      *
         <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
         <html>
diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java
index 841d914..3a8aa08 100644
--- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java
+++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java
@@ -26,8 +26,11 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
 import java.util.UUID;
@@ -37,6 +40,31 @@
 import javax.xml.datatype.DatatypeFactory;
 import javax.xml.datatype.XMLGregorianCalendar;
 
+import com.cloud.hypervisor.vmware.mo.ClusterMO;
+import com.cloud.hypervisor.vmware.mo.DatastoreFile;
+import com.cloud.hypervisor.vmware.mo.DistributedVirtualSwitchMO;
+import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper;
+import com.cloud.serializer.GsonHelper;
+import com.cloud.utils.net.NetUtils;
+import com.vmware.vim25.DatastoreInfo;
+import com.vmware.vim25.DistributedVirtualPort;
+import com.vmware.vim25.DistributedVirtualSwitchPortCriteria;
+import com.vmware.vim25.GuestInfo;
+import com.vmware.vim25.GuestNicInfo;
+import com.vmware.vim25.HostPortGroupSpec;
+import com.vmware.vim25.NasDatastoreInfo;
+import com.vmware.vim25.VMwareDVSPortSetting;
+import com.vmware.vim25.VirtualDeviceFileBackingInfo;
+import com.vmware.vim25.VirtualIDEController;
+import com.vmware.vim25.VirtualMachineConfigSummary;
+import com.vmware.vim25.VirtualMachineGuestOsIdentifier;
+import com.vmware.vim25.VirtualMachineToolsStatus;
+import com.vmware.vim25.VirtualSCSIController;
+import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec;
+import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec;
+import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 
@@ -763,4 +791,271 @@
         }
         return host;
     }
+
+    public static UnmanagedInstanceTO getUnmanagedInstance(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
+        UnmanagedInstanceTO instance = null;
+        try {
+            instance = new UnmanagedInstanceTO();
+            instance.setName(vmMo.getVmName());
+            instance.setInternalCSName(vmMo.getInternalCSName());
+            instance.setCpuCoresPerSocket(vmMo.getCoresPerSocket());
+            instance.setOperatingSystemId(vmMo.getVmGuestInfo().getGuestId());
+            VirtualMachineConfigSummary configSummary = vmMo.getConfigSummary();
+            if (configSummary != null) {
+                instance.setCpuCores(configSummary.getNumCpu());
+                instance.setCpuSpeed(configSummary.getCpuReservation());
+                instance.setMemory(configSummary.getMemorySizeMB());
+            }
+
+            ClusterMO clusterMo = new ClusterMO(hyperHost.getContext(), hyperHost.getHyperHostCluster());
+            instance.setClusterName(clusterMo.getName());
+            instance.setHostName(hyperHost.getHyperHostName());
+
+            if (StringUtils.isEmpty(instance.getOperatingSystemId()) && configSummary != null) {
+                instance.setOperatingSystemId(configSummary.getGuestId());
+            }
+            VirtualMachineGuestOsIdentifier osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST;
+            try {
+                osIdentifier = VirtualMachineGuestOsIdentifier.fromValue(instance.getOperatingSystemId());
+            } catch (IllegalArgumentException iae) {
+                if (StringUtils.isNotEmpty(instance.getOperatingSystemId()) && instance.getOperatingSystemId().contains("64")) {
+                    osIdentifier = VirtualMachineGuestOsIdentifier.OTHER_GUEST_64;
+                }
+            }
+            instance.setOperatingSystem(vmMo.getGuestInfo().getGuestFullName());
+            if (StringUtils.isEmpty(instance.getOperatingSystem()) && configSummary != null) {
+                instance.setOperatingSystem(configSummary.getGuestFullName());
+            }
+            UnmanagedInstanceTO.PowerState powerState = UnmanagedInstanceTO.PowerState.PowerUnknown;
+            if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_ON")) {
+                powerState = UnmanagedInstanceTO.PowerState.PowerOn;
+                instance.setCpuSpeed(vmMo.getRuntimeInfo().getMaxCpuUsage() / instance.getCpuCores());
+            }
+            if (vmMo.getPowerState().toString().equalsIgnoreCase("POWERED_OFF")) {
+                powerState = UnmanagedInstanceTO.PowerState.PowerOff;
+            }
+            instance.setPowerState(powerState);
+            instance.setDisks(getUnmanageInstanceDisks(vmMo));
+            instance.setNics(getUnmanageInstanceNics(hyperHost, vmMo));
+        } catch (Exception e) {
+            s_logger.info("Unable to retrieve unmanaged instance info. " + e.getMessage());
+        }
+        return instance;
+    }
+
+    protected static List<UnmanagedInstanceTO.Disk> getUnmanageInstanceDisks(VirtualMachineMO vmMo) {
+        List<UnmanagedInstanceTO.Disk> instanceDisks = new ArrayList<>();
+        VirtualDisk[] disks = null;
+        try {
+            disks = vmMo.getAllDiskDevice();
+        } catch (Exception e) {
+            s_logger.info("Unable to retrieve unmanaged instance disks. " + e.getMessage());
+        }
+        if (disks != null) {
+            for (VirtualDevice diskDevice : disks) {
+                try {
+                    if (diskDevice instanceof VirtualDisk) {
+                        UnmanagedInstanceTO.Disk instanceDisk = new UnmanagedInstanceTO.Disk();
+                        VirtualDisk disk = (VirtualDisk) diskDevice;
+                        instanceDisk.setDiskId(disk.getDiskObjectId());
+                        instanceDisk.setLabel(disk.getDeviceInfo() != null ? disk.getDeviceInfo().getLabel() : "");
+                        instanceDisk.setFileBaseName(vmMo.getVmdkFileBaseName(disk));
+                        instanceDisk.setImagePath(getAbsoluteVmdkFile(disk));
+                        instanceDisk.setCapacity(disk.getCapacityInBytes());
+                        instanceDisk.setPosition(diskDevice.getUnitNumber());
+                        DatastoreFile file = new DatastoreFile(getAbsoluteVmdkFile(disk));
+                        if (StringUtils.isNoneEmpty(file.getFileBaseName(), file.getDatastoreName())) {
+                            VirtualMachineDiskInfo diskInfo = vmMo.getDiskInfoBuilder().getDiskInfoByBackingFileBaseName(file.getFileBaseName(), file.getDatastoreName());
+                            instanceDisk.setChainInfo(GsonHelper.getGsonLogger().toJson(diskInfo));
+                        }
+                        for (VirtualDevice device : vmMo.getAllDeviceList()) {
+                            if (diskDevice.getControllerKey() == device.getKey()) {
+                                if (device instanceof VirtualIDEController) {
+                                    instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
+                                    instanceDisk.setControllerUnit(((VirtualIDEController) device).getBusNumber());
+                                } else if (device instanceof VirtualSCSIController) {
+                                    instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString());
+                                    instanceDisk.setControllerUnit(((VirtualSCSIController) device).getBusNumber());
+                                } else {
+                                    instanceDisk.setController(DiskControllerType.none.toString());
+                                }
+                                break;
+                            }
+                        }
+                        if (disk.getBacking() instanceof VirtualDeviceFileBackingInfo) {
+                            VirtualDeviceFileBackingInfo diskBacking = (VirtualDeviceFileBackingInfo) disk.getBacking();
+                            ManagedObjectReference morDs = diskBacking.getDatastore();
+                            DatastoreInfo info = (DatastoreInfo)vmMo.getContext().getVimClient().getDynamicProperty(diskBacking.getDatastore(), "info");
+                            if (info instanceof NasDatastoreInfo) {
+                                NasDatastoreInfo dsInfo = (NasDatastoreInfo) info;
+                                instanceDisk.setDatastoreName(dsInfo.getName());
+                                if (dsInfo.getNas() != null) {
+                                    instanceDisk.setDatastoreHost(dsInfo.getNas().getRemoteHost());
+                                    instanceDisk.setDatastorePath(dsInfo.getNas().getRemotePath());
+                                    instanceDisk.setDatastoreType(dsInfo.getNas().getType());
+                                }
+                            } else {
+                                instanceDisk.setDatastoreName(info.getName());
+                            }
+                        }
+                        s_logger.info(vmMo.getName() + " " + disk.getDeviceInfo().getLabel() + " " + disk.getDeviceInfo().getSummary() + " " + disk.getDiskObjectId() + " " + disk.getCapacityInKB() + " " + instanceDisk.getController());
+                        instanceDisks.add(instanceDisk);
+                    }
+                } catch (Exception e) {
+                    s_logger.info("Unable to retrieve unmanaged instance disk info. " + e.getMessage());
+                }
+            }
+            Collections.sort(instanceDisks, new Comparator<UnmanagedInstanceTO.Disk>() {
+                @Override
+                public int compare(final UnmanagedInstanceTO.Disk disk1, final UnmanagedInstanceTO.Disk disk2) {
+                    return extractInt(disk1) - extractInt(disk2);
+                }
+
+                int extractInt(UnmanagedInstanceTO.Disk disk) {
+                    String num = disk.getLabel().replaceAll("\\D", "");
+                    // return 0 if no digits found
+                    return num.isEmpty() ? 0 : Integer.parseInt(num);
+                }
+            });
+        }
+        return instanceDisks;
+    }
+
+    private static List<UnmanagedInstanceTO.Nic> getUnmanageInstanceNics(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo) {
+        List<UnmanagedInstanceTO.Nic> instanceNics = new ArrayList<>();
+
+        HashMap<String, List<String>> guestNicMacIPAddressMap = new HashMap<>();
+        try {
+            GuestInfo guestInfo = vmMo.getGuestInfo();
+            if (guestInfo.getToolsStatus() == VirtualMachineToolsStatus.TOOLS_OK) {
+                for (GuestNicInfo nicInfo: guestInfo.getNet()) {
+                    if (CollectionUtils.isNotEmpty(nicInfo.getIpAddress())) {
+                        List<String> ipAddresses = new ArrayList<>();
+                        for (String ipAddress : nicInfo.getIpAddress()) {
+                            if (NetUtils.isValidIp4(ipAddress)) {
+                                ipAddresses.add(ipAddress);
+                            }
+                        }
+                        guestNicMacIPAddressMap.put(nicInfo.getMacAddress(), ipAddresses);
+                    }
+                }
+            } else {
+                s_logger.info(String.format("Unable to retrieve guest nics for instance: %s from VMware tools as tools status: %s", vmMo.getName(), guestInfo.getToolsStatus().toString()));
+            }
+        } catch (Exception e) {
+            s_logger.info("Unable to retrieve guest nics for instance from VMware tools. " + e.getMessage());
+        }
+        VirtualDevice[] nics = null;
+        try {
+            nics = vmMo.getNicDevices();
+        } catch (Exception e) {
+            s_logger.info("Unable to retrieve unmanaged instance nics. " + e.getMessage());
+        }
+        if (nics != null) {
+            for (VirtualDevice nic : nics) {
+                try {
+                    VirtualEthernetCard ethCardDevice = (VirtualEthernetCard) nic;
+                    s_logger.error(nic.getClass().getCanonicalName() + " " + nic.getBacking().getClass().getCanonicalName() + " " + ethCardDevice.getMacAddress());
+                    UnmanagedInstanceTO.Nic instanceNic = new UnmanagedInstanceTO.Nic();
+                    instanceNic.setNicId(ethCardDevice.getDeviceInfo().getLabel());
+                    if (ethCardDevice instanceof VirtualPCNet32) {
+                        instanceNic.setAdapterType(VirtualEthernetCardType.PCNet32.toString());
+                    } else if (ethCardDevice instanceof VirtualVmxnet2) {
+                        instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet2.toString());
+                    } else if (ethCardDevice instanceof VirtualVmxnet3) {
+                        instanceNic.setAdapterType(VirtualEthernetCardType.Vmxnet3.toString());
+                    } else {
+                        instanceNic.setAdapterType(VirtualEthernetCardType.E1000.toString());
+                    }
+                    instanceNic.setMacAddress(ethCardDevice.getMacAddress());
+                    if (guestNicMacIPAddressMap.containsKey(instanceNic.getMacAddress())) {
+                        instanceNic.setIpAddress(guestNicMacIPAddressMap.get(instanceNic.getMacAddress()));
+                    }
+                    if (ethCardDevice.getSlotInfo() != null) {
+                        instanceNic.setPciSlot(ethCardDevice.getSlotInfo().toString());
+                    }
+                    VirtualDeviceBackingInfo backing = ethCardDevice.getBacking();
+                    if (backing instanceof VirtualEthernetCardDistributedVirtualPortBackingInfo) {
+                        VirtualEthernetCardDistributedVirtualPortBackingInfo backingInfo = (VirtualEthernetCardDistributedVirtualPortBackingInfo) backing;
+                        DistributedVirtualSwitchPortConnection port = backingInfo.getPort();
+                        String portKey = port.getPortKey();
+                        String portGroupKey = port.getPortgroupKey();
+                        String dvSwitchUuid = port.getSwitchUuid();
+
+                        s_logger.debug("NIC " + nic.toString() + " is connected to dvSwitch " + dvSwitchUuid + " pg " + portGroupKey + " port " + portKey);
+
+                        ManagedObjectReference dvSwitchManager = vmMo.getContext().getVimClient().getServiceContent().getDvSwitchManager();
+                        ManagedObjectReference dvSwitch = vmMo.getContext().getVimClient().getService().queryDvsByUuid(dvSwitchManager, dvSwitchUuid);
+
+                        // Get all ports
+                        DistributedVirtualSwitchPortCriteria criteria = new DistributedVirtualSwitchPortCriteria();
+                        criteria.setInside(true);
+                        criteria.getPortgroupKey().add(portGroupKey);
+                        List<DistributedVirtualPort> dvPorts = vmMo.getContext().getVimClient().getService().fetchDVPorts(dvSwitch, criteria);
+
+                        for (DistributedVirtualPort dvPort : dvPorts) {
+                            // Find the port for this NIC by portkey
+                            if (portKey.equals(dvPort.getKey())) {
+                                VMwareDVSPortSetting settings = (VMwareDVSPortSetting) dvPort.getConfig().getSetting();
+                                if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchVlanIdSpec) {
+                                    VmwareDistributedVirtualSwitchVlanIdSpec vlanId = (VmwareDistributedVirtualSwitchVlanIdSpec) settings.getVlan();
+                                    s_logger.trace("Found port " + dvPort.getKey() + " with vlan " + vlanId.getVlanId());
+                                    if (vlanId.getVlanId() > 0 && vlanId.getVlanId() < 4095) {
+                                        instanceNic.setVlan(vlanId.getVlanId());
+                                    }
+                                } else if (settings.getVlan() instanceof VmwareDistributedVirtualSwitchPvlanSpec) {
+                                    VmwareDistributedVirtualSwitchPvlanSpec pvlanSpec = (VmwareDistributedVirtualSwitchPvlanSpec) settings.getVlan();
+                                    s_logger.trace("Found port " + dvPort.getKey() + " with pvlan " + pvlanSpec.getPvlanId());
+                                    if (pvlanSpec.getPvlanId() > 0 && pvlanSpec.getPvlanId() < 4095) {
+                                        DistributedVirtualSwitchMO dvSwitchMo = new DistributedVirtualSwitchMO(vmMo.getContext(), dvSwitch);
+                                        Pair<Integer, HypervisorHostHelper.PvlanType> vlanDetails = dvSwitchMo.retrieveVlanFromPvlan(pvlanSpec.getPvlanId(), dvSwitch);
+                                        if (vlanDetails != null && vlanDetails.first() != null && vlanDetails.second() != null) {
+                                            instanceNic.setVlan(vlanDetails.first());
+                                            instanceNic.setPvlan(pvlanSpec.getPvlanId());
+                                            instanceNic.setPvlanType(vlanDetails.second().toString());
+                                        }
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    } else if (backing instanceof VirtualEthernetCardNetworkBackingInfo) {
+                        VirtualEthernetCardNetworkBackingInfo backingInfo = (VirtualEthernetCardNetworkBackingInfo) backing;
+                        instanceNic.setNetwork(backingInfo.getDeviceName());
+                        if (hyperHost instanceof HostMO) {
+                            HostMO hostMo = (HostMO) hyperHost;
+                            HostPortGroupSpec portGroupSpec = hostMo.getHostPortGroupSpec(backingInfo.getDeviceName());
+                            instanceNic.setVlan(portGroupSpec.getVlanId());
+                        }
+                    }
+                    instanceNics.add(instanceNic);
+                } catch (Exception e) {
+                    s_logger.info("Unable to retrieve unmanaged instance nic info. " + e.getMessage());
+                }
+            }
+            Collections.sort(instanceNics, new Comparator<UnmanagedInstanceTO.Nic>() {
+                @Override
+                public int compare(final UnmanagedInstanceTO.Nic nic1, final UnmanagedInstanceTO.Nic nic2) {
+                    return extractInt(nic1) - extractInt(nic2);
+                }
+
+                int extractInt(UnmanagedInstanceTO.Nic nic) {
+                    String num = nic.getNicId().replaceAll("\\D", "");
+                    // return 0 if no digits found
+                    return num.isEmpty() ? 0 : Integer.parseInt(num);
+                }
+            });
+        }
+        return  instanceNics;
+    }
+
+    public static String getAbsoluteVmdkFile(VirtualDisk disk) {
+        String vmdkAbsFile = null;
+        VirtualDeviceBackingInfo backingInfo = disk.getBacking();
+        if (backingInfo instanceof VirtualDiskFlatVer2BackingInfo) {
+            VirtualDiskFlatVer2BackingInfo diskBackingInfo = (VirtualDiskFlatVer2BackingInfo) backingInfo;
+            vmdkAbsFile = diskBackingInfo.getFileName();
+        }
+        return vmdkAbsFile;
+    }
 }
diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java
index cf16979..6e1793a 100644
--- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java
+++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java
@@ -22,28 +22,24 @@
 import com.vmware.vim25.FileInfo;
 import com.vmware.vim25.HostDatastoreBrowserSearchResults;
 import com.vmware.vim25.ManagedObjectReference;
-import com.vmware.vim25.VimPortType;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
-/**
- * Created by sudharma_jain on 6/13/17.
- */
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(DatastoreMO.class)
+
+@RunWith(MockitoJUnitRunner.class)
 public class DatastoreMOTest {
     @Mock
     VmwareContext _context ;
@@ -51,22 +47,20 @@
     VmwareClient _client;
     @Mock
     ManagedObjectReference _mor;
-    @Mock
-    HostDatastoreBrowserMO browserMo;
-    @Mock
-    VimPortType vimPortType;
 
     DatastoreMO datastoreMO ;
     String fileName = "ROOT-5.vmdk";
 
+    MockedConstruction<HostDatastoreBrowserMO> hostDataStoreBrowserMoConstruction;
+
 
     @Before
     public void setUp() throws Exception {
 
         datastoreMO = new DatastoreMO(_context, _mor);
-        PowerMockito.whenNew(HostDatastoreBrowserMO.class).withAnyArguments().thenReturn(browserMo);
         when(_context.getVimClient()).thenReturn(_client);
-        when(_client.getDynamicProperty(any(ManagedObjectReference.class), eq("name"))).thenReturn("252d36c96cfb32f48ce7756ccb79ae37");
+        when(_client.getDynamicProperty(any(ManagedObjectReference.class), eq("name"))).thenReturn(
+                "252d36c96cfb32f48ce7756ccb79ae37");
 
         ArrayList<HostDatastoreBrowserSearchResults> results = new ArrayList<>();
 
@@ -92,23 +86,28 @@
         results.add(r2);
         results.add(r3);
 
-        when(browserMo.searchDatastore(any(String.class), any(String.class), eq(true))).thenReturn(null);
-        when(browserMo.searchDatastoreSubFolders(any(String.class),any(String.class), any(Boolean.class) )).thenReturn(results);
+        hostDataStoreBrowserMoConstruction = Mockito.mockConstruction(HostDatastoreBrowserMO.class, (mock, context) -> {
+            when(mock.searchDatastore(any(String.class), any(String.class), eq(true))).thenReturn(null);
+            when(mock.searchDatastoreSubFolders(any(String.class),any(String.class), any(Boolean.class) )).thenReturn(results);
+        });
     }
 
     @After
     public void tearDown() throws Exception {
-
+        hostDataStoreBrowserMoConstruction.close();
     }
 
     @Test
     public void testSearchFileInSubFolders() throws Exception {
-        assertEquals("Unexpected Behavior: search should exclude .snapshot folder", "[252d36c96cfb32f48ce7756ccb79ae37] i-2-5-VM/ROOT-5.vmdk", datastoreMO.searchFileInSubFolders(fileName, false, ".snapshot") );
+        assertEquals("Unexpected Behavior: search should exclude .snapshot folder",
+                "[252d36c96cfb32f48ce7756ccb79ae37] i-2-5-VM/ROOT-5.vmdk",
+                datastoreMO.searchFileInSubFolders(fileName, false, ".snapshot"));
     }
 
     @Test
     public void testSearchFileInSubFoldersWithExcludeMultipleFolders() throws Exception {
-        assertEquals("Unexpected Behavior: search should exclude folders", datastoreMO.searchFileInSubFolders(fileName, false, "i-2-5-VM, .snapshot/hourly.2017-02-23_1705"), "[252d36c96cfb32f48ce7756ccb79ae37] .snapshot/hourly.2017-02-23_1605/i-2-5-VM/ROOT-5.vmdk" );
+        assertEquals("Unexpected Behavior: search should exclude folders",
+                "[252d36c96cfb32f48ce7756ccb79ae37] .snapshot/hourly.2017-02-23_1605/i-2-5-VM/ROOT-5.vmdk",
+                datastoreMO.searchFileInSubFolders(fileName, false, "i-2-5-VM, .snapshot/hourly.2017-02-23_1705"));
     }
-
 }
diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HostMOTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HostMOTest.java
new file mode 100644
index 0000000..5314f8f
--- /dev/null
+++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HostMOTest.java
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package com.cloud.hypervisor.vmware.mo;
+
+import com.cloud.hypervisor.vmware.util.VmwareClient;
+import com.cloud.hypervisor.vmware.util.VmwareContext;
+import com.vmware.vim25.GuestOsDescriptor;
+import com.vmware.vim25.ManagedObjectReference;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HostMOTest {
+
+    @Mock
+    VmwareContext _context ;
+    @Mock
+    VmwareClient _client;
+    @Mock
+    ManagedObjectReference _mor;
+    @Mock
+    ManagedObjectReference _environmentBrowser;
+
+    @Mock
+    GuestOsDescriptor guestOsDescriptor;
+
+    HostMO hostMO ;
+    ClusterMO clusterMO ;
+
+    AutoCloseable closeable;
+
+    @Before
+    public void setUp() throws Exception {
+        closeable = MockitoAnnotations.openMocks(this);
+        hostMO = new HostMO(_context, _mor);
+        clusterMO = new ClusterMO(_context, _mor);
+        clusterMO._environmentBrowser = _environmentBrowser;
+        when(_context.getVimClient()).thenReturn(_client);
+        when(_client.getDynamicProperty(any(ManagedObjectReference.class), eq("parent"))).thenReturn(_mor);
+        when(_mor.getType()).thenReturn("ClusterComputeResource");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    public void testGetGuestOsDescriptors() throws Exception {
+        List<GuestOsDescriptor> guestOsDescriptors = new ArrayList<>();
+        guestOsDescriptors.add(guestOsDescriptor);
+        try (MockedConstruction<ClusterMO> ignored = Mockito.mockConstruction(ClusterMO.class,
+                (mock, context) -> when(mock.getGuestOsDescriptors()).thenReturn(guestOsDescriptors))) {
+            List<GuestOsDescriptor> result = hostMO.getGuestOsDescriptors();
+            Assert.assertEquals(guestOsDescriptor, result.get(0));
+        }
+    }
+
+    @Test
+    public void testGetGuestOsDescriptor() throws Exception {
+        try (MockedConstruction<ClusterMO> ignored = Mockito.mockConstruction(ClusterMO.class,
+                (mock, context) -> when(mock.getGuestOsDescriptor(any(String.class))).thenReturn(guestOsDescriptor))) {
+            GuestOsDescriptor result = hostMO.getGuestOsDescriptor("1");
+            Assert.assertEquals(guestOsDescriptor, result);
+        }
+    }
+}
diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java
index ff4169d..1c888a0 100644
--- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java
+++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java
@@ -20,10 +20,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.HashMap;
@@ -85,9 +85,11 @@
     String prefix;
     String svlanId = null;
 
+    AutoCloseable closeable;
+
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         when(context.getServiceContent()).thenReturn(serviceContent);
         when(serviceContent.getAbout()).thenReturn(aboutInfo);
         when(clusterMO.getClusterConfigInfo()).thenReturn(clusterConfigInfo);
@@ -102,12 +104,9 @@
     public static void tearDownAfterClass() throws Exception {
     }
 
-    @Before
-    public void setUp() throws Exception {
-    }
-
     @After
     public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
@@ -129,8 +128,8 @@
     @Test
     public void testGetVcenterApiVersionWithNullContextObject() throws Exception {
         assertNull(HypervisorHostHelper.getVcenterApiVersion(null));
-        verifyZeroInteractions(aboutInfo);
-        verifyZeroInteractions(serviceContent);
+        verifyNoInteractions(aboutInfo);
+        verifyNoInteractions(serviceContent);
     }
 
     @Test
diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java
index 7d07a90..570f2a7 100644
--- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java
+++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java
@@ -35,13 +35,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -56,12 +56,14 @@
 
     VirtualMachineMO vmMo;
 
+    AutoCloseable closeable;
+
     private List<VirtualDevice> getVirtualScSiDeviceList(Class<?> cls) {
 
         List<VirtualDevice> deviceList = new ArrayList<>();
         try {
 
-            VirtualSCSIController scsiController = (VirtualSCSIController)cls.newInstance();
+            VirtualSCSIController scsiController = (VirtualSCSIController)cls.getDeclaredConstructor().newInstance();
             scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING);
             scsiController.setBusNumber(0);
             scsiController.setKey(1);
@@ -76,7 +78,7 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        closeable = MockitoAnnotations.openMocks(this);
         vmMo = new VirtualMachineMO(context, mor);
         when(context.getVimClient()).thenReturn(client);
     }
@@ -89,12 +91,9 @@
     public static void tearDownAfterClass() throws Exception {
     }
 
-    @Before
-    public void setUp() throws Exception {
-    }
-
     @After
     public void tearDown() throws Exception {
+        closeable.close();
     }
 
     @Test
diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareContextTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareContextTest.java
index 732d45c..6e48806 100644
--- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareContextTest.java
+++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareContextTest.java
@@ -26,7 +26,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
 public class VmwareContextTest {
@@ -42,6 +42,9 @@
         Mockito.doReturn(conn).when(vmwareContext).getHTTPConnection("http://example.com", "PUT");
         //This method should not throw any exception. Ref: CLOUDSTACK-8669
         vmwareContext.uploadResourceContent("http://example.com", "content".getBytes());
+        Mockito.verify(vmwareContext, Mockito.times(1)).getHTTPConnection("http://example.com", "PUT");
+        Mockito.verify(conn, Mockito.times(1)).getOutputStream();
+        Mockito.verify(conn, Mockito.times(1)).getInputStream();
     }
 
 }
diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java
index 4417748..3908f8b 100644
--- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java
+++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java
@@ -18,7 +18,15 @@
 package com.cloud.hypervisor.vmware.util;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
+import com.vmware.vim25.DatastoreInfo;
+import com.vmware.vim25.Description;
+import com.vmware.vim25.ManagedObjectReference;
+import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
+import org.apache.cloudstack.vm.UnmanagedInstanceTO;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -28,16 +36,50 @@
 import com.cloud.hypervisor.vmware.mo.VirtualMachineMO;
 import com.vmware.vim25.VirtualDisk;
 
+import java.util.List;
+
 @RunWith(MockitoJUnitRunner.class)
 public class VmwareHelperTest {
     @Mock
     private VirtualMachineMO virtualMachineMO;
 
+    private static final String diskLabel = "disk1";
+    private static final String diskFileBaseName = "xyz.vmdk";
+    private static final String dataStoreName = "Datastore";
+    private static final String vmName = "VM1";
+
+    @Before
+    public void setUp() throws Exception {
+        VirtualDiskFlatVer2BackingInfo backingInfo = Mockito.mock(VirtualDiskFlatVer2BackingInfo.class);
+        Mockito.when(backingInfo.getFileName()).thenReturn("abc");
+        Mockito.when(backingInfo.getDatastore()).thenReturn(Mockito.mock(ManagedObjectReference.class));
+        VirtualDisk disk = Mockito.mock(VirtualDisk.class);
+        VirtualDisk[] disks = new VirtualDisk[1];
+        disks[0] = disk;
+        Description description = Mockito.mock(Description.class);
+        Mockito.when(description.getLabel()).thenReturn(diskLabel);
+        Mockito.when(description.getSummary()).thenReturn("");
+        Mockito.when(disk.getBacking()).thenReturn(backingInfo);
+        Mockito.when(disk.getDeviceInfo()).thenReturn(description);
+        Mockito.when(virtualMachineMO.getAllDiskDevice()).thenReturn(disks);
+        Mockito.when(virtualMachineMO.getVmdkFileBaseName(disk)).thenReturn(diskFileBaseName);
+
+        DatastoreInfo datastoreInfo = Mockito.mock(DatastoreInfo.class);
+        Mockito.when(datastoreInfo.getName()).thenReturn(dataStoreName);
+        VmwareClient client = Mockito.mock(VmwareClient.class);
+        Mockito.when(client.getDynamicProperty(Mockito.any(ManagedObjectReference.class), Mockito.anyString()))
+                .thenReturn(datastoreInfo);
+        VmwareContext context = Mockito.mock(VmwareContext.class);
+        Mockito.when(context.getVimClient()).thenReturn(client);
+        Mockito.when(virtualMachineMO.getContext()).thenReturn(context);
+        Mockito.when(virtualMachineMO.getName()).thenReturn(vmName);
+    }
+
     @Test
     public void prepareDiskDeviceTestNotLimitingIOPS() throws Exception {
         Mockito.when(virtualMachineMO.getIDEDeviceControllerKey()).thenReturn(1);
         VirtualDisk virtualDisk = (VirtualDisk) VmwareHelper.prepareDiskDevice(virtualMachineMO, null, -1, new String[1], null, 0, 0, null);
-        assertEquals(null, virtualDisk.getStorageIOAllocation());
+        assertNull(virtualDisk.getStorageIOAllocation());
     }
 
     @Test
@@ -51,6 +93,16 @@
     public void prepareDiskDeviceTestLimitingIOPSToZero() throws Exception {
         Mockito.when(virtualMachineMO.getIDEDeviceControllerKey()).thenReturn(1);
         VirtualDisk virtualDisk = (VirtualDisk) VmwareHelper.prepareDiskDevice(virtualMachineMO, null, -1, new String[1], null, 0, 0, Long.valueOf(0));
-        assertEquals(null, virtualDisk.getStorageIOAllocation());
+        assertNull(virtualDisk.getStorageIOAllocation());
+    }
+
+    @Test
+    public void testGetUnmanageInstanceDisks() {
+        List<UnmanagedInstanceTO.Disk> disks = VmwareHelper.getUnmanageInstanceDisks(virtualMachineMO);
+        Assert.assertEquals(1, disks.size());
+        UnmanagedInstanceTO.Disk disk = disks.get(0);
+        Assert.assertEquals(diskLabel, disk.getLabel());
+        Assert.assertEquals(diskFileBaseName, disk.getFileBaseName());
+        Assert.assertEquals(dataStoreName, disk.getDatastoreName());
     }
 }
diff --git a/vmware-base/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/vmware-base/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d4
--- /dev/null
+++ b/vmware-base/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline